piper-assistant / catalog_resolver.py
indirapravianti's picture
Deploy Piper sticker restock manager Gradio app
1a65785
Raw
History Blame Contribute Delete
2.13 kB
"""Map messy PO product names to the nearest canonical sticker in the catalog."""
from __future__ import annotations
from difflib import SequenceMatcher, get_close_matches
from data import SAMPLE_CATALOG
from parser_fallback import PRODUCT_ALIASES
_CATALOG_LOWER = {p.lower(): p for p in SAMPLE_CATALOG}
def resolve_product(raw: str, catalog: list[str] | None = None) -> str:
"""Return the closest catalog name for a parsed product string."""
if not raw or not str(raw).strip():
return raw
catalog = catalog or SAMPLE_CATALOG
catalog_lower = {p.lower(): p for p in catalog}
cleaned = str(raw).strip()
key = cleaned.lower()
if key in catalog_lower:
return catalog_lower[key]
for alias, canonical in sorted(PRODUCT_ALIASES.items(), key=lambda x: -len(x[0])):
if alias in key or key in alias:
if canonical.lower() in catalog_lower:
return catalog_lower[canonical.lower()]
return canonical
for cat_lower, canonical in catalog_lower.items():
if cat_lower in key or key in cat_lower:
return canonical
matches = get_close_matches(key, catalog_lower.keys(), n=1, cutoff=0.55)
if matches:
return catalog_lower[matches[0]]
best_name, best_score = cleaned, 0.0
for cat_lower, canonical in catalog_lower.items():
score = SequenceMatcher(None, key, cat_lower).ratio()
if score > best_score:
best_score, best_name = score, canonical
if best_score >= 0.5:
return best_name
return cleaned
def resolve_items(items: list[dict], catalog: list[str] | None = None) -> list[dict]:
"""Normalize product field on each parsed item."""
out = []
for it in items:
row = dict(it)
raw = row.get("product", "")
resolved = resolve_product(raw, catalog)
if resolved != raw and not row.get("notes"):
row["notes"] = f"matched from: {raw}"
elif resolved != raw:
row["notes"] = f"{row['notes']} (matched from: {raw})"
row["product"] = resolved
out.append(row)
return out