Public Access
fix: matcher exclusion words + precision floor for clean grocery matching
- Add exclusion words: soda, rotisserie, tuna/tonno/salmon/sardine/anchovy to prevent beverages, prepared poultry, and seafood-in-oil from matching raw cooking ingredients - Add min precision floor (0.45): grocery sig-word count must be ≤ 2× the ingredient's sig-word count, catching long branded products that pass the word-overlap recall check but are clearly wrong category matches (e.g. "Garlic Herb Rotisserie Chicken" precision=0.25 now rejected) Result: all previously wrong matches now show '—' (no match) rather than a wrong product; correct matches unchanged Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,34 @@ _STOP_WORDS = frozenset({
|
||||
"and", "with", "for", "the",
|
||||
})
|
||||
|
||||
# If any of these words appear in a grocery item's sig-words but NOT in the
|
||||
# ingredient's sig-words, the match is rejected outright. Prevents category
|
||||
# cross-contamination: "Garlic" must not match "Garlic Bread", "Lime" must not
|
||||
# match "Lime Margarita", etc.
|
||||
_EXCLUSION_WORDS = frozenset({
|
||||
# Baked goods / bread products
|
||||
"bread", "loaf", "rolls", "bun", "buns", "croissant",
|
||||
"cracker", "crackers", "cookie", "cookies", "cake", "cupcake", "muffin", "bagel",
|
||||
# Chips / snack foods
|
||||
"chips",
|
||||
# Pasta / noodles
|
||||
"pasta", "noodle", "noodles", "vermicelli", "spaghetti", "linguine",
|
||||
"fettuccine", "penne", "rigatoni", "macaroni", "rotini", "orzo",
|
||||
# Alcoholic / mixed beverages
|
||||
"margarita", "rita", "cocktail", "beer", "ale", "lager", "cider", "malt",
|
||||
"wine", "spirits", "liquor",
|
||||
"vodka", "tequila", "whiskey", "rum", "gin", "bourbon",
|
||||
"lemonade", "limeade", "seltzer", "soda",
|
||||
# Butter / spreads (prevents "Garlic & Herb Butter Spread" matching "Garlic")
|
||||
"butter", "spread", "margarine",
|
||||
# Prepared proteins / seafood-in-oil (prevents "Tuna in Olive Oil" matching "Olive Oil")
|
||||
"tuna", "tonno", "salmon", "sardine", "anchovy",
|
||||
# Prepared poultry (prevents "Garlic Herb Rotisserie Chicken" matching "Garlic")
|
||||
"rotisserie",
|
||||
# Baby / personal care (belt-and-suspenders after stop-word rework)
|
||||
"baby", "wipes", "diaper",
|
||||
})
|
||||
|
||||
|
||||
@dataclass
|
||||
class MatchResult:
|
||||
@@ -127,8 +155,18 @@ def run_match_job(
|
||||
# 100% recall: every ingredient sig-word must appear in the grocery name.
|
||||
if not ing_sig.issubset(gsig):
|
||||
continue
|
||||
# Category exclusion: reject if grocery has a disqualifying word
|
||||
# (e.g. "bread", "chips", "margarita") absent from the ingredient.
|
||||
bad_words = (gsig & _EXCLUSION_WORDS) - ing_sig
|
||||
if bad_words:
|
||||
continue
|
||||
# Precision penalises grocery items with many extra words.
|
||||
precision = len(ing_sig) / len(gsig)
|
||||
# Hard floor: grocery must not have >2× the sig-words of the ingredient.
|
||||
# Catches long branded products that sneak past exclusion words, e.g.
|
||||
# "Garlic Herb Rotisserie Chicken" for "Garlic".
|
||||
if precision < 0.45:
|
||||
continue
|
||||
combined = (score / 100.0) * precision
|
||||
if combined > best_combined:
|
||||
best_combined = combined
|
||||
|
||||
Reference in New Issue
Block a user