Document grounding selection and pipeline order
CI / ci (push) Successful in 1m51s
CI / ci (pull_request) Successful in 1m50s

The architecture of record still described the first stored search
result being returned before ranking. It now says ranking decides among
stored and new candidates, a stored source_url being scored on its stored
recipe without refetching its page.

State the exact per-candidate order in the docs and in ground: the photo
download and validation (DownloadPhoto) precedes normalize_recipe, and
CachePhoto records the photo in the same transaction as the recipe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 15:49:20 -04:00
co-authored by Claude Opus 5.5
parent 24d91b5bf6
commit 1cba099f40
2 changed files with 19 additions and 6 deletions
+14 -6
View File
@@ -299,12 +299,20 @@ automatic** (D-021).
recorded twice. Files written for a transaction that rolls back are
removed by the caller (`RemovePhotoFiles`).
- Grounding (`recipes.Grounder`) ties these together.
`Ground(ctx, dishName, query)` searches for the top 5 candidates; if one
is already a stored `source_url`, the first such is returned without
fetching anything. Otherwise it ranks them and tries each ranked recipe
best first: download its photo, run `normalize_recipe`, then store the
photo, recipe, ingredients and steps in **one transaction** (an existing
`source_url` is updated in place). It returns `Grounded{recipe_id,
`Ground(ctx, dishName, query)` searches for the top 5 candidates and ranks
them all, stored and new alike: a candidate whose `source_url` is already
stored is scored on its stored recipe (title, ingredients, steps, times,
photo source) and its page is never refetched; every other candidate is
fetched and extracted. Ranking alone decides between them. It then tries
each ranked candidate best first. A stored one is returned as it is, so a
stored recipe is reused only when it ranks above every new candidate that
grounds. A new one is grounded in this order: (1) `DownloadPhoto` —
the download and validation step of photo caching — (2)
`normalize_recipe`, then (3) one **transaction** that runs `CachePhoto`
(scales and writes the photo files and records the `photos` row) and
stores the recipe, ingredients and steps (an existing `source_url` is
updated in place); files written for a rolled-back transaction are
removed. It returns `Grounded{recipe_id,
photo_id, method, minutes_total, difficulty, source_url, site_name}`. A
candidate whose photo fails or whose model output is unusable
(`llm.ErrBadOutput`) is passed over; when none grounds, the error is a
+5
View File
@@ -319,6 +319,11 @@ func (g *Grounder) storedRecipe(ctx context.Context, sourceURL string) (storedRe
// ground downloads r's photo, normalizes it and stores it as sourceURL's
// recipe. A problem with this recipe is returned as reason, so the caller
// can try another; err is a failure that is not the recipe's.
//
// The order is fixed: the photo is downloaded and validated first, so a
// recipe without a usable photo never costs a normalize_recipe call; then
// normalize_recipe runs; then save records the photo (CachePhoto) and the
// recipe in one transaction.
func (g *Grounder) ground(ctx context.Context, sourceURL string, r Recipe) (gr Grounded, reason, err error) {
photo, err := g.photos.DownloadPhoto(ctx, r.ImageURL)
if err != nil {