plan: Flavor B — 🔮 forgotten gems pulls from Discover too

The suggestion panel now folds enriched-but-unimported Discover entries
into the same Hecate-driven pool as library recipes. Pinning a discover
suggestion auto-imports it to the household's Mealie library and adds
the resulting slug to cauldron_meal_picks.

- db: list_discover_eligible_for_group(mealie_group_id, limit=80) — joins
  cauldron_discovered_recipes against cauldron_discover_imports to find
  enriched rows no household in the caller's group has imported yet
- forge.suggest_recipes: accepts a per-entry `source` field; when any
  entry is source='discover' the prompt gains an explicit hint so Hecate
  treats discover entries as "something new to try" with a soft cap on
  proportion (~half max unless library exhausted)
- /api/plan/suggest: builds a unified pool with prefixed IDs (lib:<slug>
  vs disc:<id>) so library and discover entries can coexist in Sonnet's
  validation map; decorates each suggestion with kind+image+meta_summary+
  hecate_quip; discover entries also carry source_url and source_host
- /api/plan/suggest/pin: new dispatch on body.kind:
  - kind=library (default — back-compat with existing Flavor A callers):
    same as before
  - kind=discover: looks up the discover row, short-circuits to cached
    Mealie slug if THIS household already imported it, else
    mealie.import_from_url() + record_discover_import() + add to picks.
    Returns {kind, mealie_slug, imported, added_to_picks}
- plan.html: card render is kind-aware. Discover entries get a
  "📬 from Discover · <host>" footer instead of last-planned recall, an
  "import + pin" button label, and use data-* attributes for the click
  payload so the JS doesn't need to template-interpolate slugs into
  onclick handlers
This commit is contained in:
Kayos 2026-05-01 20:57:44 -07:00
parent b41c93e559
commit 8752fcd340
4 changed files with 285 additions and 35 deletions

View file

@ -983,11 +983,19 @@ class Forge:
if isinstance(fit, dict) and fit:
fit_str = ",".join(f"{n}:{s}" for n, s in fit.items())
extras.append(f"fit:{fit_str}")
# Source hint — Flavor B passes "library" or "discover" so Hecate
# can reason about whether an entry is already in the household's
# Mealie library (free pick) vs scraped from the open web (would
# need to be imported on pin).
src = r.get("source")
if src:
extras.append(f"source:{src}")
line = f"- {slug} | {name}"
if extras:
line += f" [{' · '.join(extras)}]"
pool_lines.append(line)
pool_block = "\n".join(pool_lines)
has_discover = any(r.get("source") == "discover" for r in eligible_pool)
# Picker profile block (same shape as planner uses, abbreviated)
profile_block = ""
@ -1025,13 +1033,25 @@ class Forge:
if in_plan_slugs:
already_block = f"\n(For context: this week's plan already has {len(in_plan_slugs)} recipe(s). They're NOT in the pool above.)\n"
discover_hint = (
"\nNOTE: some entries below have `source:discover` — those are "
"RECIPES NEW TO THIS HOUSEHOLD, scraped from the open web and "
"enriched but not yet imported to the family's Mealie library. "
"Treat them as 'something new to try' — bias toward them when "
"the family's history is heavy on the same handful of cuisines/"
"proteins, but don't overdo it (max ~half the picks should be "
"discover-source unless the library is exhausted). Pinning a "
"discover entry will trigger an automatic import.\n"
if has_discover else ""
)
prompt = (
"You are Hecate, a Greek-mythology witch goddess of crossroads, "
"herbs, and magic — and the family's meal planner. The household "
"asked you to look through their recipe library and surface "
"'forgotten gems': recipes that fit who they are but haven't "
"been served recently (or ever).\n\n"
f"This is for the week of {week_start}.\n\n"
f"This is for the week of {week_start}.\n"
f"{discover_hint}\n"
f"ELIGIBLE POOL (already filtered: nothing seen in the last 90 days, "
f"nothing already on this week's plan):\n{pool_block}\n"
f"{profile_block}"