cauldron/cauldron
Kayos f7b30d3b65 sterilize: search-then-create + retry-on-UNIQUE-400 + don't mark errored as applied
Job 1's bulk run apply'd 184 recipes and 182 of them failed with the
same error: POST /api/foods -> 400 UNIQUE constraint failed:
ingredient_foods.name, ingredient_foods.group_id. Cause: Mealie's
name_normalized strips punctuation/whitespace/case more aggressively
than our local _build_name_index's plain .lower(), so the cache misses,
the create_food fires blindly, and Mealie's UNIQUE constraint kills
the call. Whole-recipe apply was wrapped in try/except at the bulk
runner so the recipe got marked errored — but applied_at was still
set to NOW(), making the rerun think we'd already tried. We had, but
the recipe's still unparsed.

Two fixes:

1. sterilizer._resolve_food / _resolve_unit replace the inline
   create-on-miss block. Order: local cache → Mealie search-endpoint
   tie-break → create. On any UNIQUE-flavored 400 from create, fall
   back to one more search to adopt whatever Mealie has under the
   normalized form. Mealie's search endpoint applies its own
   name_normalized internally so we don't have to mirror its rules.
   _search_for_match takes "foods" or "units" and looks for an exact
   case-insensitive match against name or pluralName, with a fallback
   to "trust Mealie's ranker" when there's exactly one hit.

2. db.mark_proposal_applied no longer sets applied_at on error. On
   success: applied_at=NOW(), apply_error=NULL. On error: applied_at
   stays NULL, apply_error gets the message. list_approved_unapplied_
   proposals keys off applied_at IS NULL, so a rerun naturally retries
   only the failed recipes.

Net effect: rerun can now successfully apply the 182 failed recipes
without re-walking them, and won't waste calls on the 2 that did go
through.
2026-04-30 06:05:19 -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 v0.3 step 6: bulk sterilizer — automate mealie's per-recipe Parse toil 2026-04-29 22:26:10 -07:00
__init__.py v0.1 — backend bones + ingredient sterilizer 2026-04-28 16:59:11 -07:00
aggregator.py v0.3 step 2: density-table aggregator engine — the killer math 2026-04-28 22:14:01 -07:00
bulk_sterilize.py v0.3 step 6: bulk sterilizer — automate mealie's per-recipe Parse toil 2026-04-29 22:26:10 -07:00
config.py fix: split MEALIE_API_URL (internal) from MEALIE_PUBLIC_URL (UI link) 2026-04-28 20:26:25 -07:00
crypto.py v0.2 foundation — Authentik OIDC + sulkta-mariadb DB + Fernet crypto 2026-04-28 19:47:47 -07:00
db.py sterilize: search-then-create + retry-on-UNIQUE-400 + don't mark errored as applied 2026-04-30 06:05:19 -07:00
foods.py v0.3 step 5: lean shopping list — claude on-demand foods + game strip 2026-04-29 22:02:20 -07:00
forge.py v0.3 step 5: lean shopping list — claude on-demand foods + game strip 2026-04-29 22:02:20 -07:00
mealie.py fix /plan 500 + bigger touch targets + sort/category chips + sticky search 2026-04-28 21:26:22 -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 v0.3 step 6: bulk sterilizer — automate mealie's per-recipe Parse toil 2026-04-29 22:26:10 -07:00
sterilizer.py sterilize: search-then-create + retry-on-UNIQUE-400 + don't mark errored as applied 2026-04-30 06:05:19 -07:00