ADR-0010: Generated Monday-to-Sunday meal plans behind a provider seam, with hard allergen exclusions and no numeric goals
- Status: Accepted; the “focus, never targets” rule is superseded by ADR-0011 (2026-09-20, later the same day)
- Date: 2026-09-20
- Scope: Planning, preferences, shopping list, worker scheduling, provider seams, privacy
Context
The product direction changed on 2026-09-20: instead of a manual planner, PlatePilot should
propose a whole week of meals from stored preferences (allergies, dislikes, dietary pattern, what
the adult is aiming for, household size, location, units), keep it editable, regenerate it every
Sunday morning, and derive a shopping list that stays in step with edits. The repository forbids real
vendors and credentials (ADR-0003, ADR-0005), forbids medical thresholds and goal warnings
(ADR-0006, content guidance), and requires plans to stay suggestions (M2 plan). Allergies are
safety-critical and a street address is Restricted data.
Decision
- Provider seam, mock only.
MealPlanProvider (generateWeek, localizeShoppingItems) in
packages/provider-contracts is the only place generation happens. The shipped adapter is
DeterministicMealPlanProvider: a synthetic recipe library, seeded by the request id so the same
request yields the same week. A real model or vendor implements the same seam later; the API,
data model, and client contract do not change.
- Weeks run Monday to Sunday. Every plan week is anchored on a Monday
(
plan_weeks.weekStart, database check on ISO weekday). GET /planning/week?start= returns the
week containing the date, and generation and shopping lists address a week by its Monday.
- Focus, never targets. Preferences carry a
focus (balanced, lighter, more_fuel,
higher_protein, more_plants) that shapes recipe choice and portion scale. No calorie or macro
goal is collected, shown, or enforced; nutrition on generated meals is a per-serving estimate
labelled as such. Age is used only as a coarse band derived from the optional birth year.
- Allergens are hard exclusions. The 14 EU allergens are the only allergen vocabulary. Every
recipe ingredient is tagged, the generator filters excluded allergens before choosing, the API
drops any meal that would still violate them, each plan item exposes the union of its
ingredients’ allergens, and copy tells adults to check labels. Dislikes are soft: avoided when a
full week is still possible.
- Location is country plus city or postal code only. It selects regional product names,
store sections, and units through the seam. A street address is never accepted or stored.
- Generated versus manual is tracked per item. Generated items are replaced by the next
generation; items the adult created or edited become
manual and are kept unless the adult
asks to replace their changes. Confirmed items are never replaced. Any manual change marks the
week edited.
- Sunday auto-generation is opt-out and only for next week. The worker checks every minute; on
Sunday from the configured local hour (
PLAN_AUTO_GENERATE_HOUR, default 07:00) in the adult’s
own time zone it generates the following Monday-to-Sunday week if no plan week row exists yet.
The current week is never touched, an existing or edited next week is skipped, and the run is
audited as system.
- The shopping list is derived, never authored. It is rebuilt from the week’s ingredients after
every generation and every manual change: one line per name and unit, quantities summed
(countable units rounded up), ordered by the first day the ingredient is needed, then store
section, then name. Tick state (
needed, have, bought) is kept by ingredient key across
rebuilds and reset to needed only when the quantity grew; ticks use optimistic versions.
- Audit.
plan.generate and plan.preferences_update join the existing plan.* actions;
shopping-list ticks are not audited.
Consequences
- Adults get a complete, editable week and a synchronized shopping list without any real AI or
data vendor in the loop; the mock’s variety is limited to its library, which is a local-only
limitation, not a contract.
- Weekly regeneration never destroys hand-made changes silently; the price is that an edited week
needs an explicit “replace my changes” to be regenerated.
- Because the generator is deterministic per request id, retries are idempotent and tests are
stable.
- Per-serving nutrition on generated items uses the same eight metrics as the diary, so
confirming a planned meal into the diary needs no conversion.
Follow-up
- Real provider evaluation (data-use terms, allergen data quality, regional coverage) under
ADR-0003’s vendor checklist before any non-mock adapter.
- Product and legal review of the allergen disclaimer wording and of whether a future “targets”
feature is acceptable at all.
- Optional per-item “keep” flag so an adult can pin a generated meal without editing it.
- Push delivery of “your new week is ready” through the notification seam once a real adapter
exists.