8 Commits
Author SHA1 Message Date
admin 97b84a7cd8 feat(scripts): Sprint 15 round 2 — seed_recipes_round2.py (50 gap-filling queries)
Round 1 (commit a3c89bf) imported 18 recipes before hitting
the 50-pt/day Spoonacular free-tier cap. User direction
(2026-06-07): 'please add more meals to the potential list' /
'Pull in more recipes so we have a larger sample to generate
from.'

This is a fresh-quota run with a different query list focused
on cuisines and meal types the round 1 list didn't cover:
Indian (8) + Thai (6) + Chinese regional (6) + Soups & stews
(6) + Salads (6) + Sandwiches/wraps (5) + Breakfast (5) +
German/European (4) + French (4) = 50 queries.

Same shape as scripts/seed_recipes.py: hits Spoonacular's
complexSearch directly (avoids the broken backend route and
the backend's quota counter), POSTs top hits to the backend's
/api/recipes/import. Idempotent (409 on duplicate), 1.5 sec
sleep, stops on 402.

Result: 18 imported today. 12 queries returned no hits from
Spoonacular's free-tier index (e.g. 'chana masala', 'thai
basil chicken', 'dan dan noodles' — these are absent or
premium-only). 1 query ('wedge salad') hit 402 mid-import.

DB went 49 -> 67 total recipes (37 Spoonacular + 30 manual).
LLM test (Sprint 13 endpoint, week 2026-07-20, prompt
'variety, mix of cuisines, family-friendly, no repeats'):
  {picked_count: 0, filled_count: 21, failed_count: 0}
The library now covers all 21 slots of a week (was 19/21 +
2 failed in round 1). 4 weeks of planning now has a real
library to pick from with 1.25x rotation.

Re-running scripts/seed_recipes.py (round 1) tomorrow will
add 30+ more — its query list has gaps the round 1 cap didn't
reach (American + Mediterranean cuisines).
2026-06-07 16:20:20 -07:00
admin a3c89bf6a2 feat(backend): Sprint 15 — seed 50 recipes + fix Sprint 12 latent-bug (main.py mount order)
Two changes:

1. Sprint 12 latent-bug fix: backend/app/main.py mount order.
   The pre-existing WIP backend/app/api/recipes.py:212 registers
   GET /{recipe_id} (UUID-typed) under /api/recipes. Sprint 12's
   recipe_search_api.router also mounts under /api/recipes. FastAPI
   matches routes in registration order, so the WIP's /{recipe_id}
   was catching /api/recipes/search and treating 'search' as a UUID,
   returning 422. This was a latent bug: Sprint 12 hasn't been
   deployed yet so the user hasn't seen the failure, but the
   frontend's 'Search the web' feature would 422 on every query.

   Fix: moved the recipe_search_api.router import to line 39 (with
   the other api imports) and the include_router call to BEFORE
   recipes_api.public_router. 3-line comment explains the why.

   Verified live: GET /api/recipes/search?q=chicken+parmesan&limit=2
   returns 200 with 2 hits. The WIP's GET /api/recipes/{uuid} still
   works (it just no longer shadows the /search and /import routes).

2. Sprint 15 content op: scripts/seed_recipes.py (NEW, ~150 lines).
   User direction (2026-06-05): 'Lets build out recipes for the
   coming 4 weeks in advance. In order to do this, lets add more
   recipes to the list of available ones.'

   The script seeds family-friendly recipes from Spoonacular into
   the local library. 50 queries (5 cuisines x 10 each: Italian,
   Mexican, Asian, American, Mediterranean/Middle Eastern).

   For each query: hit Spoonacular's complexSearch directly (avoids
   the broken backend route and the backend's quota counter), take
   the top hit, POST to the local backend's /api/recipes/import
   (which does the 1-pt /information call + idempotent ingredient
   upserts + Recipe insert). Idempotent: 409 from the import
   endpoint is logged and skipped. 1.5 sec sleep between queries.
   Stops cleanly on Spoonacular 402 (quota exhausted).

   Result: 18 recipes imported today. Spoonacular's free tier is
   50 pts/day (not 150 as I assumed; the _DAILY_LIMIT=140 in
   recipe_search.py:48 should drop to 45 — follow-up ticket).
   At 28 queries the script hit the cap. Re-running tomorrow will
   yield ~30 more (after the 18 already imported count toward 50).

   DB went 31 -> 49 total recipes. 19 Spoonacular + 30 manual.
   LLM test (Sprint 13 endpoint, week 2026-07-06):
     {picked_count: 0, filled_count: 19, failed_count: 2}
   The library fill covered 19/21 slots. The LLM (kimi-k2.6:cloud)
   returned 0 picks. Sprint 13 tolerance worked as designed.

No pre-existing WIP files touched (recipes.py, schemas/recipe.py,
nginx.conf unchanged). Only main.py was reordered (one-line + 3-line
comment). scripts/seed_recipes.py is a new file in the existing
scripts/ directory.

Deploy: git pull + docker compose up -d --build backend frontend.
The 18 new recipes are already in the DB. Re-run the seed script
on later days for the remaining 32 (after the cap resets).
2026-06-06 14:11:58 -07:00
adminandClaude Sonnet 4.6 b03e7f8070 feat: Spoonacular recipe enrichment — images + descriptions for 25/30 recipes
- Added SPOONACULAR_API_KEY to docker-compose.yml (backend + scheduler)
- scripts/enrich_recipes_spoonacular.py: searches Spoonacular by recipe name,
  updates image_url and description; 402 quota guard exits cleanly
- 25 of 30 null recipes now enriched; 5 remain (quota exhausted for today)
- Remaining: Caprese Pasta, Creamy Tuscan Chicken, Sheet-Pan Chicken Thighs,
  Loaded Veggie Quesadillas, Zucchini and Spinach Frittata

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 06:56:14 -07:00
admin 59a15a2c21 fix: nginx DNS resolver, port 8081, seed script with real family data 2026-05-09 20:50:20 -07:00
admin 76638016c1 feat: one-time family profile seed script 2026-05-09 12:47:25 -07:00
adminandClaude Opus 4.7 95b8e0c1b5 feat: AM-3..AM-6 strip SWIFTLY_BEARER_TOKEN env var, delete superseded script, refresh docs
AM-3: SWIFTLY_BEARER_TOKEN removed from .env.example, .env.test (local),
docker-compose.yml service env, and Settings (backend/app/config.py).
The scraper docstring is updated to reflect the auto-mint path.

AM-4: scripts/refresh_swiftly_token.py (commit ccfb38a, seleniumbase
click-through capture) deleted; superseded by swiftly_auth.py.

AM-5: docs refreshed.
- spec status header → "Implemented 2026-05-06" with live-verification
  evidence
- HANDOFF.md TL;DR + caveats #2/#3 collapsed; replaced with the
  auto-mint failure-modes caveat; "Suggested next move" rewritten
  pointing to Phase 5 orchestration; file-map and last-updated touched
- ORIENTATION.md env-var section updated (no bearer var) + footer

AM-6 verification gate (run 2026-05-06):
- pytest -q tests/ → 92/92 green (88 prior + 4 new swiftly_auth)
- POST /api/admin/scrape → status=success, items_scraped=10928 in 44s
- grocery_item rows: 9980 (after dedup-by external_id)
- ingredient_grocery_match rows: 29779 (matcher post-hook populated)
- Container env confirmed clean of SWIFTLY_BEARER_TOKEN

The system now scrapes, matches, and generates plans without any
operator-managed credential. Live JWT lifecycle: Firebase REST anon
signUp → cache for ~55min → re-mint as needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:47:15 -07:00
adminandClaude Opus 4.7 ccfb38a34e feat: scripts/refresh_swiftly_token.py - capture fresh Swiftly token via seleniumbase
Drives luckysupermarkets.com in stealth CDP mode: opens the store
locator, types the zip code, clicks the target store, then triggers a
category page navigation. A fetch + XHR interceptor (installed via JS)
captures the first Authorization header sent to a Swiftly host. The
captured JWT is validated (iss + exp), then written into the env file.

Runs on the host (not docker) since seleniumbase needs a real Chrome.
Defaults to .env.test, headless, zip 94806, store 757. Flags:
  --debug          visible Chrome window
  --restart-backend  rerun docker compose to pick up the new token
  --env-file PATH  override target env file
  --zip / --store  override location

Selectors are intentionally JS-based and tolerant of UI changes
(querySelectorAll fallthrough by attribute heuristics + textContent
substring match) so first-attempt failures degrade to clear errors
rather than silent breakage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 10:42:48 -07:00
adminandClaude Opus 4.7 8e89f793d5 feat: phase r1+r2 recovery + r3-0 swiftly api ingestion
R1 stabilization: pytest harness with transactional db fixture, smoke
+ alembic + auth + scrape + approval + swiftly tests, github actions
ci yaml. Bearer-token admin auth + signed-cookie session for family
ui mutations. Async POST /api/admin/scrape (BackgroundTasks, returns
202). Path canonicalization (no /list, /planned suffixes). DATABASE_URL
fail-fast on empty.

R2 deferred-risk spikes: live lucky california fetch (R2-A), full
email+per-voter approval click round trip with single-use enforcement
(R2-B, console email backend, sendgrid stub).

R3-0 phase 3 redesign: replaced playwright html scraper with requests
based swiftly json api client. 17 categories, ~10k products per scrape,
upsert by (source, external_id). 401 surfaces actionable token-refresh
message via ScrapeLog.error_message.

Pre-existing defects fixed: shopping_list.py syntax error blocking app
import, MealPlan.votes orphan relationship, JSONB(astext=True) invalid
kwarg, missing requests dep, calorie_target schema drift, every SQLEnum
needed values_callable, 0001 had empty downgrade(), seed had duplicate
ingredient rows.

Migrations added: 0003 grocery_item.description, 0004 family_profile.
calorie_target, 0005 grocery_item.external_id + source + composite index.

Verified: 31/31 pytest green, alembic upgrade->downgrade->upgrade clean,
frontend npm run build clean, live scrape 9,960 grocery_item rows in 36s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:08:19 -07:00