diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index f040359..bbcfaae 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -29,6 +29,7 @@ The project's reason to exist — the meal-planner generation algorithm — is * - Email backend (`backend/app/services/email.py`): Protocol + `ConsoleEmailBackend` (writes JSONL to `backend/var/email_outbox.jsonl`) + `SendGridEmailBackend` stub that raises `NotImplementedError`. Selected via `EMAIL_BACKEND` env (default `console`). - 401 from Swiftly raises `SwiftlyAuthError` carrying the verbatim message `"SWIFTLY_BEARER_TOKEN expired — request a fresh token from the user (capture from luckysupermarkets.com network tab on a /search/api/v1 request)"`. The bg runner catches it and writes `ScrapeLog.error_message` so it surfaces via the admin logs endpoint. - Thin Phase 4: ingredient + recipe CRUD endpoints with admin gating; NeverSuggest CRUD (covers both ingredient blocklist and recipe blocklist via the existing schema); ingredient↔grocery_item match layer (rapidfuzz top-3 ranking with confidence threshold 0.75, manual override via /api/admin/ingredients/{id}/matches and /api/admin/ingredient-matches/{id}); 50 canonical ingredients seeded with aliases enriching pre-existing rows from migration 0002; 30 starter recipes spanning chicken/beef/turkey/pork/fish/vegetarian with varied cuisines, all under 45 min for 28/30. Match job runs after each successful scrape; matcher failures don't flip the scrape to FAILED. +- Phase 9: meal-plan generation. POST /api/admin/meal-plans/generate runs the full filter→score→set-select pipeline against seeded recipes and produces a persisted MealPlan with up to 3 MealPlanItem dinners. Regenerate endpoint accepts relaxed constraint overrides (`relax_time_max_minutes`, `relax_calorie_pct`, `relax_max_meal_cost`) and deletes any prior plan for the same `(family, week_start_date)` before re-running. Per-meal cost matched against ingredient_grocery_match using the top-confidence grocery row. 88 tests green. ### Database - Postgres 15. Seven migrations: `0001_initial_migration`, `0002_seed_data`, `0003_grocery_item_description`, `0004_family_profile_calorie_target`, `0005_grocery_item_external_id`, `0006_thin_phase4` (ingredient.aliases, recipe.calories_per_serving, ingredient_grocery_match), `0007_seed_canonical_ingredients` (50 ingredients + 30 recipes). @@ -71,12 +72,6 @@ The project's reason to exist — the meal-planner generation algorithm — is * - `feedback` table exists with `rating`, `denial_reason`, free-text. No frontend page reads or writes it. No `/api/feedback` router (folded into `meals.py`?). - The "learn from feedback" loop into Phase 9 is unscoped. -### Phase 9 — **Meal-planner generation algorithm (not started)** -- This is the core of the project. -- Inputs: `family_profile` (size, budget, calorie target), `family_member` preferences (mushroom etc.), `never_suggest` filter, `home_pantry` items, `grocery_item` sales for the week, `recipe` library, prior `feedback` (downweight low-rated, never-suggest-flagged). -- Output: 7 `MealPlanItem` rows (or 21 if 3 meals/day) with chosen `recipe_id`, day, meal_type. -- Constraints from spec: variety (max 2 same protein/sauce per week), respect mushroom rule (3 of 4 don't like → recipes containing mushrooms heavily downweighted unless flagged "kid-only"), prefer pantry items, prefer sale items, hit budget per meal. - ### Phase 10 — Images (not started) - Recipe images: scrape from source sites first, AI fallback (`AI_IMAGE_ENABLED=false` flag exists, no implementation). @@ -110,6 +105,14 @@ The project's reason to exist — the meal-planner generation algorithm — is * 10. **Frontend doesn't have a login UI.** Until you build one, the family-facing flows can't actually be exercised by a real user — only by tests. The Dashboard/Pantry/etc. pages assume the cookie is already set. +11. `regenerate.exclude_recipe_ids` accepted by the API for forward compat but not yet honored by the orchestrator — only NeverSuggest blocklist applies. ~30-line follow-up. + +12. `GET /api/meal-plans/{id}` returns persisted items but with `score=0`, `components={}`, and zeroed debug — those are only available in the immediate `generate` response. Acceptable for the email-approval flow which uses the generate response directly. To persist them, add columns to MealPlanItem. + +13. `family_profile.calorie_target` is treated as per-serving by the planner filter (matches spec §2.1 wording). The family-setup UI/API should clarify per-serving vs per-day to avoid confusion. Test families use ~500 cal/serving for a 4-person household. + +14. Cost estimation treats `qty` as dimensionless (no unit conversion). Produces a biased-but-monotonic ranking signal; sufficient for current use, revisit if real-dollar accuracy is needed (`docs/specs/2026-05-05-meal-planner-algorithm-design.md` §7). + --- ## Verification commands diff --git a/docs/ORIENTATION.md b/docs/ORIENTATION.md index 110fdea..9271f29 100644 --- a/docs/ORIENTATION.md +++ b/docs/ORIENTATION.md @@ -43,7 +43,7 @@ web ─► nginx :80/:443 ─► React/Vite ────┼─► FastAPI ─ | 6 | SendGrid email integration (proposal/reminder/confirmation) | Stub only — `app/services/email.py::SendGridEmailBackend` raises NotImplementedError | | 7 | Web UI core (Dashboard / Meal Detail / Pantry / Shopping List) | **Complete** (no auth UI yet) | | 8 | Web UI feedback portal | Not started | -| 9 | **Meal-planner generation algorithm** | Not started — *the core of the project* | +| 9 | Meal-planner generation algorithm | **Complete** — POST /api/admin/meal-plans/generate produces 3-dinner plans against seeded recipes + matched grocery prices. Filter (6 hard constraints), score (5 signals), top-K=20 set enumeration with diversity penalty. | | 10 | Image strategy (scraped + AI fallback) | Not started | | 11 | Polish (variety analysis, budget tracking, APScheduler) | Not started | @@ -149,4 +149,4 @@ A `.env.test` template lives in the repo root (gitignored) for local stack runs. --- -Last updated: 2026-05-06 — after thin Phase 4 (ingredient + recipe CRUD, match layer, 50 ingredient + 30 recipe seed). 59/59 pytest green. +Last updated: 2026-05-06 — Phase 9 complete (meal-plan generation algorithm: filter→score→set-select pipeline; persisted MealPlan + items via POST /api/admin/meal-plans/generate). 88/88 pytest green.