Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #110.
This commit is contained in:
+78
-3
@@ -98,7 +98,7 @@ Columns ending in `_ct` hold AES-256-GCM ciphertext (via `secretbox`).
|
||||
| `photos` | source_url, local_path, width, height, sha256, fetched_at. |
|
||||
| `dinners` | The meal record and its lifecycle: name, normalized_name, status (`suggested` / `quest_log` / `tried` / `regular` / `archived`), origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, recipe_url (a pasted recipe link awaiting grounding), photo_id, grounding_status (`pending` / `grounded` / `failed` / `manual`), grounding_error (why grounding failed), grounding_revision (raised whenever a dinner is renamed, so only the latest `ground_dinner` job may write it and no candidate offered before the rename grounds it), updated_by. |
|
||||
| `suggestion_batches` | trigger, status, ai_run_id. |
|
||||
| `suggestions` | dinner_id, batch_id, reason, cited_regular_ids (JSON), status, decided_at. |
|
||||
| `suggestions` | dinner_id, batch_id, name (the name the dish was suggested under, kept whatever its dinner is later renamed, so a Pass stays an immutable record every later batch dedupes against), reason, cited_regular_ids (JSON), status, decided_at. |
|
||||
| `week_plans` | week_start (unique), status (`draft` / `synced` / `changed`), ai_note, ai_run_id, calendar_synced_at. |
|
||||
| `plan_days` | plan_id, date (unique per plan), dinner_id, is_new, calendar_event_id. |
|
||||
| `rotation_reviews` | dinner_id + plan_date (unique), status, decision (`keep` / `drop` / `not_made`), decided_at, updated_by. |
|
||||
@@ -127,7 +127,7 @@ suggested (deck) ──Try it──▶ quest_log ──planned date passes──
|
||||
│ └──────── explicit decision ─────┤
|
||||
│ ├──▶ regular
|
||||
│ └──▶ archived
|
||||
└──Pass──▶ archived (name kept for dedupe)
|
||||
└──Pass──▶ archived (suggested name kept for dedupe)
|
||||
```
|
||||
|
||||
From `tried`, an explicit decision moves the dinner to `regular`, to
|
||||
@@ -194,7 +194,9 @@ automatic** (D-021).
|
||||
`GET /api/v1/dinners?status=regular|quest_log` returns
|
||||
`{dinners: [...]}` ordered by normalized name, each with `photo_url`
|
||||
(`/media/photos/{photo_id}-400.jpg`, or null) and `grounding_status`;
|
||||
any other status is 400. `PATCH /api/v1/dinners/{id}` edits `name`
|
||||
any other status is 400. The Quest log (`quest_log`) is the dinners
|
||||
accepted and not yet scheduled: one on the plan for the household's
|
||||
current week (by its timezone) or a later week is left out. `PATCH /api/v1/dinners/{id}` edits `name`
|
||||
(deduplicated against the other unarchived dinners), `method`
|
||||
(`stovetop` / `oven` / `crockpot` / `grill` / `other`), `minutes_total`
|
||||
(1–1440) and `difficulty` (`easy` / `medium` / `hard`); invalid values
|
||||
@@ -271,6 +273,67 @@ automatic** (D-021).
|
||||
dinner, is 500, logged with a fixed reason and the dinner id only —
|
||||
never the link, which can carry a personal token, or dependency error
|
||||
text.
|
||||
- Side quest suggestions (`suggest.Service`). `POST
|
||||
/api/v1/suggestions/batches` creates a `manual` batch and enqueues its
|
||||
`suggestion_batch` job, answering 202 `{batch_id, job_id, trigger,
|
||||
status}` without waiting; while a batch is `pending` or `running` it
|
||||
answers 202 with that batch instead, so at most one runs at a time (the
|
||||
check, the batch and its job share one write transaction). The job reads the
|
||||
household context, the deck's names and every declined name, runs
|
||||
`suggest_side_quests` for 5 Side quests (scope: the batch id), then
|
||||
grounds the survivors in the model's order with `Grounder.GroundMany`,
|
||||
as many as are still wanted at a time, until 5 are kept or none are
|
||||
left. A Side quest no recipe grounds is dropped; each round's grounded
|
||||
ones are stored before the next — a dinner with status `suggested`,
|
||||
origin `side_quest`, grounding_status `grounded` with the recipe's
|
||||
recipe_id, photo_id, method, minutes_total and difficulty and the
|
||||
model's cuisine, and a `pending` suggestion with the name it is
|
||||
suggested under, the reason and `cited_regular_ids` — unless, since the
|
||||
job read its input, a dinner it repeats was added, a suggestion it
|
||||
repeats passed, or a Regular it cites renamed or archived (checked in
|
||||
the storing transaction). The job publishes
|
||||
`{batch_id, stage, suggestion_ids, dropped}` as it goes (`suggesting`,
|
||||
`grounding`, `done`). It fails with `no_regulars`, `ai_failed` (the
|
||||
skill or `normalize_recipe` failed; no Side quest is made up instead,
|
||||
D-017), `search_not_configured` or `search_failed`, and the batch is set
|
||||
`failed`; Side quests stored before the failure stay in the deck. If
|
||||
recording the batch's end fails, its job fails; the job runner retries
|
||||
a job's final status write with backoff until it lands, each attempt
|
||||
bounded at 5 seconds; at shutdown an attempt that fails or times out is
|
||||
not retried, and the next start fails the job as interrupted. Recording
|
||||
the batch's end is bounded the same way (one attempt, 5 seconds, made
|
||||
even during shutdown), so a busy writer cannot hold a stopping worker;
|
||||
a batch whose end timed out is left `running` with its job. Each time a
|
||||
batch is started, before the active batch is selected, every batch
|
||||
still `pending` or `running` with no job queued or running (the process
|
||||
stopped, or recording its end failed) is set `failed`, so it does not
|
||||
block the next.
|
||||
`GET /api/v1/suggestions/deck` returns `{suggestions: [{id, dinner_id,
|
||||
name, photo_url, difficulty, method, minutes_total, cuisine, reason,
|
||||
cited_regular_ids, status, dinner_status, decided_at, created_at}],
|
||||
active_batch, failed_batch}`: the `pending` suggestions whose dinner is
|
||||
still `suggested`, oldest first; the batch `pending` or `running`
|
||||
(`{batch_id, job_id, trigger, status}`, or null); and the newest batch
|
||||
when it `failed` (same shape, or null; a later batch, done or under
|
||||
way, supersedes the failure), read in one snapshot, so a client that
|
||||
did not start the batch can follow its job and learns of a failure it
|
||||
was not following. At most one of the two is set. `POST /api/v1/suggestions/{id}/accept` (Try it) sets the
|
||||
suggestion `accepted` and moves its dinner to `quest_log`; `POST
|
||||
/api/v1/suggestions/{id}/decline` (Pass) sets it `declined` and archives
|
||||
the dinner; every later batch, and the household context's passed list,
|
||||
reads the suggestion's own name as declined, so renaming the archived
|
||||
dinner changes nothing. Both record
|
||||
the session's subject as the dinner's `updated_by` and answer 200
|
||||
`{suggestion, replenish_job_id}`. Repeating a decision changes nothing
|
||||
and answers 200; the opposite decision is 409 `suggestion_decided`, a
|
||||
suggestion whose dinner left the deck some other way 409
|
||||
`dinner_not_suggested`, and an unknown id 404. A decision that leaves
|
||||
fewer than 5 in the deck starts a `replenish` batch, or joins the batch
|
||||
already pending or running, and returns that batch's job id either way
|
||||
(`replenish_job_id` is null when the deck keeps 5 or more). The batch and its job are written in
|
||||
the decision's own transaction, so a decision is never recorded without
|
||||
the replenish it calls for; when they cannot be written, neither is the
|
||||
decision, and a retry decides again.
|
||||
- `/health` — liveness.
|
||||
- Everything else serves the embedded PWA with SPA fallback.
|
||||
|
||||
@@ -333,6 +396,18 @@ automatic** (D-021).
|
||||
dinner when the token-set (Jaccard) similarity of their words is at least
|
||||
0.85. A `Deduper` over the Regulars, Quest log, pending suggestions and
|
||||
passed names rejects a suggestion that repeats any of them.
|
||||
- **Side quest suggestions** (`skills.SuggestSideQuests`, skill
|
||||
`suggest_side_quests` v1): asks for twice the Side quests needed, at the
|
||||
household temperature, with the household context plus the pending Side
|
||||
quests. Malformed replies (bad JSON, a missing field, a bad enum, an
|
||||
empty name) get the one repair turn. Well-formed Side quests that break a
|
||||
rule are dropped, not repaired, and listed in `Dropped` with the rule:
|
||||
one that repeats a Regular, Quest log dinner, pending Side quest, any
|
||||
passed name (the whole history, not only the 60 in the context) or an
|
||||
earlier Side quest of the batch; one citing no Regular or a name that is
|
||||
not a Regular; and one whose reason is over 180 characters or names none
|
||||
of its cited Regulars. Cited names are rewritten as the household writes
|
||||
them. Grounding each survivor in a recipe is the caller's next step.
|
||||
|
||||
## Recipe grounding
|
||||
|
||||
|
||||
Reference in New Issue
Block a user