Matching Strategies
Everything in Tidy Tags that asks “are these two titles the same thing?” — the duplicate scanner, the cross-source scan, the editor “did you mean?” warning — goes through a single service, so they always agree with each other. How it answers is up to you.
Set it under Settings → Plugins → Tidy Tags → Matching, or from config/tidytags.php.
The two strategies
fuzzy (default) |
strict |
|
|---|---|---|
| Case, whitespace, punctuation | ignored | ignored |
| Typos | matched, within a threshold | not matched |
Leading / trailing affixesFC, AFC, CF… |
not matched | matched |
QualifiersWomen, II, U21… |
ignored | veto the match |
Pick fuzzy if your tags are free text
Tags typed by editors, in their own words, with no controlled list. The duplicates you have are misspellings and spacing variants, and that is exactly what edit distance is for.
marketing ~ Marketing ~ marketting
javascript ~ JavaScript ~ java script
Manchester ~ Manchestor
Pick strict if your tags are a controlled vocabulary
Proper nouns from a known list — clubs, competitions, places, people, products. Edit distance is the wrong tool for these, and the reason is worth understanding before you choose.
Why edit distance fails on proper nouns
Short proper nouns sit very close together in edit space. Real club and county names are routinely one or two characters apart while being completely unrelated, so a threshold loose enough to catch a genuine typo is also loose enough to pair unrelated entities.
Meanwhile the variation that actually occurs in a controlled vocabulary is not a typo at all — it is a whole word, added or dropped at the start or end. FC Bayern Munich and Bayern Munich are three edits apart, so no sane threshold reaches them.
Measured over a real 2,633-title corpus of teams, competitions and tags, the two strategies behave like this:
fuzzy Essex ~ Sussex Durham ~ Fulham
Tampa ~ Samoa Top 10 ~ Top 14
-> every near-match was a false positive
-> and it missed all four genuine variants
strict FC Bayern Munich ~ Bayern Munich
Wrexham ~ Wrexham AFC
Real Madrid ~ Real Madrid CF
Barrow AFC ~ Barrow
-> all four found, none of the false positives
Neither strategy is better in general. Run fuzzy over free text and it earns its keep; run it over a list of county cricket sides and it pairs Essex with Sussex. That is why this is a setting rather than a change of default — upgrading changes nothing until you switch it.
Affix tokens
Under strict, these words are stripped from a title before comparing — but only from the start and end.
Position matters, and this is not a detail. Strip affixes anywhere in the string and FIFA Club World Cup collapses into FIFA World Cup, which is a real and separate competition. Because the variation you are targeting is always leading or trailing, restricting the strip to the ends costs you nothing and saves you that whole class of false positive.
FC Bayern Munich -> bayern munich (leading affix stripped)
Wrexham AFC -> wrexham (trailing affix stripped)
FIFA Club World Cup -> fifa club world cup (middle left alone)
The default list covers the club prefixes and suffixes common in association football and rugby. Replace it with whatever your own domain uses.
Qualifier tokens
Under strict, these are words that make two otherwise-matching titles different things. When one title carries one and the other does not, the pair is rejected outright:
Arsenalis not a duplicate ofArsenal WomenCounty Championshipis not a duplicate ofCounty Championship OneBayern Munichis not a duplicate ofBayern Munich II
This is the single most important setting in strict mode. Without it, affix normalisation is far too eager: women's sides, reserve sides and tiered competitions all reduce to the same core name as their senior counterpart, and merging them is data loss rather than cleanup.
How this differs from a differentiator field
Deliberately, and in the opposite direction. A differentiator field that is empty on one side means unknown, so the cluster is still surfaced for a human to review. A qualifier word present in the title is a positive statement about which thing this is, so it blocks the match outright.
Expect to curate it
The default list is football-shaped. A cricket, rugby, futsal or esports vocabulary needs different words, and a non-sport vocabulary needs entirely different ones — edition years, regions, product tiers. Treat the default as a starting point and read it against your own titles before trusting strict in anger.
Configuration
Both token lists accept a newline- or comma-separated paste on the settings screen, or a plain array in config/tidytags.php:
<?php
// config/tidytags.php
return [
// 'fuzzy' (default) or 'strict'
'matchStrategy' => 'strict',
// Both only used by the strict strategy. Omit either one
// to keep the built-in default.
'affixTokens' => ['fc', 'afc', 'cf', 'sc', 'ac'],
'qualifierTokens' => [
'women', 'ladies', 'ii', 'reserves',
'u21', 'one', 'two',
],
];
An unrecognised matchStrategy falls back to fuzzy rather than matching nothing, so a typo in your config degrades to the previous behaviour instead of silently switching the scanner off.
Notes on behaviour
- The threshold is ignored under
strict. There is no distance to threshold, so?threshold=Non the Duplicates page and thethresholdargument on the service methods have no effect. - Matches still report a distance. Under
strict, an exact match reports0and an affix-only match reports1, so anything sorting by distance keeps putting the unambiguous matches first. - Empty token lists are allowed. Clearing both reduces
strictto exact normalised matching, which is a perfectly reasonable setting if you want zero guesswork.
Calling the matcher yourself
The service is public, if you want to reuse the same logic in your own code:
use justinholtweb\tidytags\Plugin;
$matcher = Plugin::$plugin->titleMatcher;
$matcher->getStrategy(); // 'fuzzy' | 'strict'
$matcher->compare('FC Bayern Munich', 'Bayern Munich', 2); // 1, or null
$matcher->coreKey('Wrexham AFC'); // 'wrexham'
$matcher->qualifiers('Arsenal Women'); // ['women']
$matcher->normalize('Taff & Daff'); // 'taff and daff'
compare() returns the distance between the two titles, or null when they are not a match. compareFuzzy() and compareStrict() are also public if you need one specific strategy regardless of the setting.