Ground new Regulars in the background and add recipe fix-up API
CI / ci (push) Successful in 2m37s
CI / ci (pull_request) Successful in 2m18s

Creating a Regular now enqueues a ground_dinner job and returns at once
with its grounding_job_id. The job grounds the dinner's recipe_url with
GroundURL, else its name with Ground, and sets grounding_status grounded
(recipe, photo, method, minutes, difficulty) or failed with a
grounding_error reason code (migration 0009). It writes only a dinner
still pending, so a fix made meanwhile wins.

GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked
candidates {candidate_id, title, site_name, photo_url, minutes} without
downloading photos or running the model. POST /api/v1/dinners/{id}/ground
takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and
sets grounded or manual.

PhotoCache takes an ImageFetcher interface and recipestest provides fakes
so other packages can test grounding with fake search, fetch and model.
main wires the Grounder with normalize_recipe over the AI settings.

Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 16:14:31 -04:00
co-authored by Claude Opus 5.5
parent 729c5bd0fe
commit d6d82e8368
22 changed files with 2103 additions and 82 deletions
@@ -0,0 +1,7 @@
-- 0009_dinner_grounding_error: a dinner whose grounding failed keeps why,
-- as one of the reason codes package dinners defines (no_recipe_found,
-- recipe_unusable, search_not_configured, search_failed, ai_failed,
-- interrupted, grounding_error). It is NULL unless grounding_status is
-- failed.
ALTER TABLE dinners ADD COLUMN grounding_error TEXT;
+39
View File
@@ -70,3 +70,42 @@ SET is_favorite = 1 - is_favorite,
updated_at = ?
WHERE id = ?
RETURNING *;
-- name: GroundDinner :one
-- Records a dinner's grounding (grounded or manual) and clears any
-- grounding_error. An unknown id returns sql.ErrNoRows.
UPDATE dinners
SET recipe_id = ?,
photo_id = ?,
method = ?,
minutes_total = ?,
difficulty = ?,
grounding_status = ?,
grounding_error = NULL,
updated_by = ?,
updated_at = ?
WHERE id = ?
RETURNING *;
-- name: GroundPendingDinner :execrows
-- Records the background grounding of a dinner still pending; a dinner
-- grounded some other way meanwhile is left as it is (0 rows).
UPDATE dinners
SET recipe_id = ?,
photo_id = ?,
method = ?,
minutes_total = ?,
difficulty = ?,
grounding_status = 'grounded',
grounding_error = NULL,
updated_at = ?
WHERE id = ? AND grounding_status = 'pending';
-- name: FailPendingDinnerGrounding :execrows
-- Records why the background grounding of a dinner still pending failed; a
-- dinner grounded some other way meanwhile is left as it is (0 rows).
UPDATE dinners
SET grounding_status = 'failed',
grounding_error = ?,
updated_at = ?
WHERE id = ? AND grounding_status = 'pending';
+12
View File
@@ -69,3 +69,15 @@ ORDER BY position, id;
-- name: DeleteRecipeSteps :exec
DELETE FROM recipe_steps
WHERE recipe_id = ?;
-- name: CreateManualRecipe :one
-- A recipe the household typed in. Its source_url is a mealquest:manual:
-- URN rather than a page, so it never matches a fetched page.
INSERT INTO recipes (
id, source_url, title, total_minutes, raw_jsonld, method,
minutes_total, fetched_at, created_at, updated_at
) VALUES (
?, ?, ?, ?, ?, ?,
?, ?, ?, ?
)
RETURNING *;