feat: orchestrator step_scrape with retry + stale-data fallback

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 06:27:27 -07:00
co-authored by Claude Sonnet 4.6
parent 0135b74c03
commit b2cbdd1533
2 changed files with 99 additions and 0 deletions
@@ -0,0 +1,62 @@
"""
Orchestrator step functions.
Each step receives a WeeklyRun row and an open Session. Steps are idempotent:
a non-null timestamp column causes an immediate return. Steps commit their own
changes before returning.
All service imports are at module level so tests can monkeypatch via
`app.services.orchestrator.steps.<name>`.
"""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import TYPE_CHECKING
from app.config import settings
from app.models import FamilyMember, FamilyProfile, MealPlan, MealPlanItemStatus
from app.services.approval import issue_token
from app.services.email import get_email_backend
from app.services.orchestrator.alerts import send_admin_alert
from app.services.planner.generate import generate_meal_plan
from app.services.scraper_service import ScraperService
if TYPE_CHECKING:
from sqlalchemy.orm import Session
from app.models import WeeklyRun
logger = logging.getLogger(__name__)
def step_scrape(run: "WeeklyRun", db: "Session") -> None:
if run.scraped_at is not None:
logger.info("step_scrape: already done for %s, skipping", run.week_start_date)
return
service = ScraperService(db)
last_result: dict = {}
for attempt in (1, 2):
last_result = service.run_scrape()
if last_result["status"] == "success":
run.scraped_at = datetime.now(timezone.utc)
run.used_stale_data = False
db.commit()
logger.info("step_scrape: success on attempt %d", attempt)
return
logger.warning(
"step_scrape: attempt %d failed: %s", attempt, last_result.get("error")
)
# Both attempts failed — mark stale and continue the cycle
run.scraped_at = datetime.now(timezone.utc)
run.used_stale_data = True
db.commit()
send_admin_alert(
subject=f"Scrape failed week {run.week_start_date}",
body=(
f"Both scrape attempts failed. Proceeding with stale grocery data.\n"
f"Error: {last_result.get('error')}"
),
)
logger.warning("step_scrape: both attempts failed; marked stale")