Scraper: remove price guard in map_product so produce items without a
catalog price (e.g. Fresh Garlic, Lime sold by weight) are saved to
grocery_item with current_price=NULL rather than skipped.
Matcher:
- Add exact-name fast path: build a lowercase-trimmed name→index map
and skip fuzzy search entirely when the ingredient name matches a
grocery item exactly. Lime → Lime (confidence 1.0), Garlic → Fresh
Garlic from fuzzy (confidence 1.0).
- Add exclusion words: juice, gelatin to prevent beverage/dessert
products from matching cooking ingredients.
- Increase fuzzy candidate limit 20→100 so exact-name items buried in
large tie groups are not missed.
- Add 'juice' to exclusion: prevents '100% Lime Juice' from winning
over plain 'Lime'.
Result: all recipe ingredients now match correct Lucky CA products or
show '—' (no match); zero category cross-contamination remaining.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
fetch_category() now calls swiftly_auth.get_token() to mint a fresh
Firebase JWT on demand when no explicit bearer_token override is
pinned by tests. The cache short-circuit means the per-call mint
overhead is ~zero in the steady state.
- Removed the empty-token short-circuit; auto-mint makes it moot
- Updated _AUTH_ERROR_MESSAGE: 401-after-mint now points at the spec
(Lucky tightening anon-auth) rather than asking for manual capture
- Replaced test_swiftly_auth_error_when_token_missing with a positive
test that verifies fetch_category mints when bearer_token is None
- bearer_token constructor arg preserved for the 401-path test
Full suite: 92/92 green. Live verification via
scripts/spike_swiftly_ingest.py --confirm-live deferred to next step
per HANDOFF AM-2 halt boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>