From db4b01337e13df92f87f08d6f863fbc24cc57e15 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Wed, 6 May 2026 06:21:54 -0700 Subject: [PATCH] feat: run matcher after successful scrape; failures don't flip scrape status --- backend/app/services/scraper_service.py | 6 ++ backend/tests/test_match_hook.py | 79 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 backend/tests/test_match_hook.py diff --git a/backend/app/services/scraper_service.py b/backend/app/services/scraper_service.py index 177a4c8..7656c01 100644 --- a/backend/app/services/scraper_service.py +++ b/backend/app/services/scraper_service.py @@ -86,6 +86,12 @@ def _run_scrape_in_background( log.duration_seconds = int( (log.completed_at - log.started_at).total_seconds() ) + try: + from app.services.matcher import run_match_job + run_match_job(db, source_filter="lucky_california") + except Exception as e: # matcher failure must not flip scrape to FAILED + import logging + logging.exception("matcher failed after successful scrape: %s", e) db.commit() logger.info( "Background scrape %s complete: %s/%s items saved", diff --git a/backend/tests/test_match_hook.py b/backend/tests/test_match_hook.py new file mode 100644 index 0000000..e97fe20 --- /dev/null +++ b/backend/tests/test_match_hook.py @@ -0,0 +1,79 @@ +import pytest + +pytestmark = pytest.mark.requires_postgres + + +def test_run_match_job_persists_top_matches(monkeypatch): + """End-to-end: seed Ingredient + GroceryItem rows, run the matcher, + verify ingredient_grocery_match rows exist with confidence >= 0.75. + """ + from datetime import datetime, timezone + from decimal import Decimal + from uuid import uuid4 + + from app.database import SessionLocal + from app.models import ( + GroceryItem, + Ingredient, + IngredientGroceryMatch, + IngredientMatchSource, + ) + from app.services.matcher import run_match_job + + setup = SessionLocal() + ing_id = uuid4() + grocery_id = uuid4() + try: + setup.add( + Ingredient( + id=ing_id, + name="Zorblax Fizzberry Test", + name_lower="zorblax fizzberry test", + aliases=["zorblax fizzberry"], + aisle="meat", + unit="lb", + ) + ) + setup.add( + GroceryItem( + id=grocery_id, + name="Zorblax Fizzberry Family Pack", + source="lucky_california", + external_id="ext-test-1", + current_price=Decimal("3.99"), + regular_price=Decimal("5.49"), + is_on_sale=True, + scraped_at=datetime.now(timezone.utc), + ) + ) + setup.commit() + finally: + setup.close() + + work = SessionLocal() + try: + written = run_match_job(work) + assert written >= 1 + rows = ( + work.query(IngredientGroceryMatch) + .filter(IngredientGroceryMatch.ingredient_id == ing_id) + .all() + ) + assert any( + r.grocery_item_id == grocery_id + and r.confidence >= Decimal("0.750") + and r.source == IngredientMatchSource.AUTO + for r in rows + ) + finally: + cleanup = SessionLocal() + try: + cleanup.query(IngredientGroceryMatch).filter( + IngredientGroceryMatch.ingredient_id == ing_id + ).delete() + cleanup.query(GroceryItem).filter(GroceryItem.id == grocery_id).delete() + cleanup.query(Ingredient).filter(Ingredient.id == ing_id).delete() + cleanup.commit() + finally: + cleanup.close() + work.close()