cauldron/cauldron
Kayos 69e05b1f92 Step 3: foods consolidator — cluster + merge dupes via Mealie's API
New bulk job that scans Mealie's foods table for duplicate-feeling
clusters, asks Sonnet to pick the canonical survivor + flag the rest
as merge candidates, and uses Mealie's PUT /api/foods/merge to
consolidate. After each successful merge, alias_additions get pushed
onto the survivor so Mealie's CRF/NLP parser fuzzy-matches the
discarded variant names from then on.

Architecture mirrors bulk_sterilize.py:
- Migrations 018+019 add cauldron_consolidate_jobs +
  cauldron_consolidate_proposals (state machine: running → review →
  applying → done/failed/cancelled)
- New consolidate_foods.py — daemon-thread runner with cancel-respect
  and stuck-job recovery
- /api/foods/consolidate-{start,status,jobs,apply,cancel} for session
  users + /api/admin/foods/consolidate-{start,jobs,cancel} for kayos

Sonnet integration:
- forge.cluster_decision(foods) → returns {merge, canonical_id,
  canonical_name, discard_ids, alias_additions, reason}
- Conservative-by-default: when in doubt Sonnet returns merge=false
  (the "olive oil vs olive" false-positive case from the prompt)
- Alias rules in the prompt explain why we want discarded names to
  travel back to the survivor as aliases (parser future-proofing)

Mealie integration:
- mealie.merge_foods(from_id, to_id) → PUT /api/foods/merge
- mealie.update_food(food_id, body) → for pushing aliases onto the
  survivor after merges land
- Apply path catches 403/permission errors and surfaces them as the
  per-cluster apply_error (cross-household merge attempts will fail
  here, same way as the sterilize cross-household path)

Clustering:
- rapidfuzz token_set_ratio ≥ 88 (slightly stricter than Mealie's
  parser threshold of 85 to reduce false-positive clusters)
- Single-link agglomerative — O(n²) but Cobb's ~3000 foods = ~9M
  comparisons, runs in seconds
- Singleton clusters (no merge candidates) are dropped, not stored

UI:
- /consolidate — same shape as /sterilize: progress bar → review grid
  → apply button. Cards show member chips with the canonical marked
  ★, discards marked × in red, alias_additions listed in green, plus
  Sonnet's one-line reasoning. Mergeable approved by default; user
  toggles individual clusters off if they disagree.
- Linked from /me → tools section, alongside bulk sterilize.

Total: ~600 LoC across 6 files. Foundation for the "Mealie owns
canonical names" architectural rule is now actually enforceable —
cobb runs this once, his foods table gets cleaned up, and Sonnet's
catalog-aware parser (Step 1) starts matching aliases for free.
2026-04-30 12:00:20 -07:00
..
data v0.3 step 5: lean shopping list — claude on-demand foods + game strip 2026-04-29 22:02:20 -07:00
templates Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
__init__.py v0.1 — backend bones + ingredient sterilizer 2026-04-28 16:59:11 -07:00
aggregator.py Step 2: re-key cauldron's food metadata by mealie_food_id 2026-04-30 11:52:25 -07:00
bulk_sterilize.py sterilize bulk: respect external cancel mid-loop 2026-04-30 10:02:53 -07:00
config.py fix: split MEALIE_API_URL (internal) from MEALIE_PUBLIC_URL (UI link) 2026-04-28 20:26:25 -07:00
consolidate_foods.py Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
crypto.py v0.2 foundation — Authentik OIDC + sulkta-mariadb DB + Fernet crypto 2026-04-28 19:47:47 -07:00
db.py Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
foods.py Step 2: re-key cauldron's food metadata by mealie_food_id 2026-04-30 11:52:25 -07:00
forge.py Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
mealie.py Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
oidc.py v0.2 foundation — Authentik OIDC + sulkta-mariadb DB + Fernet crypto 2026-04-28 19:47:47 -07:00
recipe_index.py search: local fuzzy recipe index — way smarter than Mealie's lexical default 2026-04-28 21:37:12 -07:00
server.py Step 3: foods consolidator — cluster + merge dupes via Mealie's API 2026-04-30 12:00:20 -07:00
sterilizer.py sterilize Phase 2: pass Mealie's food catalog into Sonnet's prompt 2026-04-30 11:48:40 -07:00