Business cases · use cases · flows · AI communication · implementation · July 2026

Use cases & flows

The end-to-end reference for what emealia does, for whom, and how it is wired. Business model and personas, an actor/use-case map, every product flow with a diagram and its endpoints, the full AI communication pipeline, and the cross-cutting compliance and quality concerns — with implementation pointers into emealia-app.

One line. Meal tracking made easy and guilt-free. You type what you ate in plain language; emealia estimates nutrition as honest ranges, shows the day as calm colored bands (no numbers, no red), and offers one small forward-looking tip. Intuition over arithmetic.

01 · Business model & value

emealia is an AI nutrition companion sold as a mobile-first PWA (no App Store) in Germany (primary) and the USA (simultaneous), all copy in EN + DE, GDPR-first. The base product is one version, one price per market — a free trial converting to a single paid subscription. There is no freemium tier and the core loop is never gated.

Model

Free trial → one paid subscription. No freemium, no tiers, no micro-transactions (Stripe's fixed fee kills €1 economics). An optional emealia Plus add-on may come later.

Markets

DE (primary launch) + US (simultaneous). EN + DE copy everywhere. Locale drives units, reference values (DGE/USDA), currency, and AI output language.

Break-even

≈ 13–15 paying users cover the ~$56–63/mo fixed infra + AI at 100 users. Beta infra runs ~$30–35/mo.

Pricing (Stripe price ids resolved server-side per market/interval via BillingCatalogs — never hardcoded in the frontend; no GET /config/prices endpoint exists, contrary to earlier drafts of this doc)

MarketTrialMonthlyAnnualSave
Germany30 free AI estimates · all features · no card€5.99€39.99−44%
USA30 free AI estimates · all features · no card$6.99$44.99−46%

Annual is the checkout default with a secondary monthly toggle. Price is constant (founder decision 2026-07-03): one price per market, no retention/win-back discount, no lower price on the cancel screen. Trial-CTA subline is always "All features, one plan, forever."

Trial mechanics (corrected 2026-07-07 — supersedes the 168h clock model)

The trial is credit-based, not time-based: usage_accounts.trial_credits_granted defaults to 30 (admin-configurable, admin.toml [usage] trial_credits), decremented one-per-AI-estimate in trial_credits_used. Enforcement lives in the LogMeal use case via UsageMeter.check_entitlement()/check_and_reserve() before any AI call → EntitlementError → HTTP 402 once credits are exhausted and no subscription is active. Read access (history, stats, profile) stays open after exhaustion; only new AI estimation is blocked. A per-user daily cap (20/day) and a global daily cost cap (13,333/day) apply on top, both admin-configurable. Note: the boundary docstring (UsageMeter) and the CLAUDE.md root context still describe the earlier 168-hour clock-based trial (trial_started_at + 168h) — that model is superseded and not what the shipped SqlAlchemyUsageMeter implements; treat the credit model above as authoritative.

KPIs & targets

D7 retention ≥25% (min) / ≥30% / ≥38% (great). Trial→paid ≥10% / ≥12% / ≥15%. Premium-candidate usage is instrumented invisibly from day 1 via UsageMeter.

Kill conditions

D7 <20% after 200 users → stop acquisition, redesign the core loop. AI cost >€0.08/user/month → audit every AI call; something is leaking. Paid ads only once D30 ≥ 15%.

emealia Plus (post-launch, gated on D7 ≥ 30%). A recurring add-on layer — recipe suggestions, photo logging, deep micros, advanced stats + export, "level up a meal". It may only ever add new surfaces and must never gate the core loop or nutrient data. Nothing about Plus is built before that retention gate clears.

02 · The 6 non-negotiables

Every use case, flow and PR is checked against these. They are the product, not preferences.

#RuleHow it shows up in the flows below
1No red in nutrition feedback. Ever.Band color = category, never judgment. Failed estimates become a calm needs-confirmation state, not an error.
2No numbers on the main daily view.Today shows bands + qualitative labels only; ranges/numbers live one tap away in All-details and on the meal detail.
3Free trial, not freemium.The whole loop is in the base product; Plus can only add surfaces later.
4AI free-text logging is the only primary input.One sentence in; no barcode, no weighing, no database hunting.
5Value before registration.Pre-signup demo = hardcoded data, zero AI calls, zero cost.
6No nutrition state may look like a failure.Technical status can be shown transparently ("still estimating"); a light-eating day is never framed as bad.

03 · Actors & personas

Visitor pre-signup

Curious, unregistered. Sees the demo day to understand the product before committing. Zero AI cost, zero abuse surface. Real usage begins only after registration.

Trial user 30 free estimates

Registered, has picked goals and (optionally) body data. Logs real meals; the credit-based trial and AI cost meter run. Wants to see whether daily bands become a habit.

Subscriber paid

Converted. Full loop: log, review, history, statistics, coaching. The retention target audience — D7/D30 measured here.

System / scheduler non-human

The async worker draining AI jobs, the timing engine deciding when a tip or review may fire, and retention/cost instrumentation. Acts on behalf of the loop, never spams the user (silence is default).

Voice

A warm companion in du-form (German). Never judges, always forward-looking; honest about estimates ("roughly 550–650, close enough to place it", not "612 kcal"). The companion voice is set in Newsreader italic; ordinary UI labels stay in Hanken Grotesk.

04 · Use-case map

Who acts, what emealia does for them, and the external systems reached only through boundaries. The SDK/driver for each system lives solely inside its integration module — swapping a provider is a one-line change in wiring.py.

Actors — visitor, trial user, subscriber, scheduler — connected to grouped use cases and to external systems Anthropic, Auth0, Stripe, Postgres, email, Clock and UsageMeter.
Actor → use case → system map. Every external dependency sits behind a boundary (21 shipped — see architecture.html). This diagram is stale — it draws "Auth0" as the identity system; the shipped `AuthBoundary` integration is direct Google OAuth (`GoogleAuthBoundary`), no Auth0. Listed for re-render in the doc-agent report.
actor use case external system (behind a boundary)

05 · Use-case catalog

The canonical list. Each row links a business goal to its primary actor, trigger, and the endpoints that serve it. Detailed flows follow in §06–§15.

IDUse caseActorTriggerPrimary endpoints
UC-01Understand the product before signupVisitorOpens demo?demo=1 (frontend fixtures, no API)
UC-02Register & set goals + optional body dataVisitor→TrialTaps "start"GET /auth/google/loginGET /auth/google/callback, POST /consents/body-health-data
UC-03Log a meal from free textTrial/SubTypes a sentencePOST /meals → poll → /save
UC-04Review the glass-box estimateTrial/SubDraft readyGET /meals/{id}/draft
UC-05Refine / re-estimate a draft onceTrial/SubWants to adjustPOST /meals/{id}/reestimate
UC-06Reuse a confirmed dish (0 AI)Trial/SubRepeats a mealPOST /meals (memory hit), GET /meals/suggestions
UC-07Read the day as bandsTrial/SubOpens TodayGET /daily
UC-08See numbers & transparencyTrial/SubTaps into detailsGET /daily/{day}/details
UC-09Add day / activity contextTrial/SubLogs how the day feltPUT /daily/{day}/context, /activity-context
UC-10Browse history & favouritesTrial/SubOpens HistoryGET /meals?date=
UC-11Receive a gentle tip / reviewSystem→UserTiming gate firescoaching (UsageMeter-capped AI)
UC-12See trends over timeTrial/SubOpens Statisticsperiod summaries
UC-13Start trial & subscribeTrial→SubTrial expiry / upgradeStripe · UsageMeter · HTTP 402
UC-14Install & use offlineTrial/SubAdds to home screenPWA manifest · SW · offline queue
UC-15Export / delete my dataTrial/SubPrivacy requestGDPR erasure / export (Epic 15)

06 · Core loop — log a meal UC-03 · UC-04

The signature interaction and the only primary input. It is async from day 1: the estimator never blocks the HTTP response. The contract is stable across the phase-1 in-process worker and the phase-2 SQS + Lambda worker — the frontend is never touched by that swap.

Sequence: client posts free text, API runs consent, backdate and entitlement checks via LogMeal, a draft and AI job are created, a 202 returns immediately, a background worker calls Claude and writes the draft, the client polls the draft, then saves it into daily summaries.
Async meal logging: POST /meals → 202 draft + job → worker → poll → POST /meals/{id}/save.

Main success flow

  1. Free text in. The user types one sentence ("a big bowl of lentil soup and two slices of toast") and picks a meal_type (never AI-derived; defaults to other). POST /meals {text, meal_type, locale, log_date?}.
  2. Pre-flight checks. LogMeal verifies body-health consent, validates the backdate window (≤7 days, no future), and calls UsageMeter.check_entitlement() — the trial/subscription gate.
  3. Memory shortcut? If the text matches a confirmed dish (≥3 prior confirmations), the draft is created complete from memory: no AI call, no reservation, response {status:"completed", reused:true} — the client skips polling.
  4. Reserve & queue. Otherwise check_and_reserve() meters one AI call, a draft meal + an ai_jobs row are written, and the API returns 202 {meal_id, status:"processing"} immediately.
  5. Worker estimates. A background task (phase 1) or SQS worker (phase 2) claims the job and calls the AiEstimator boundary. See §07 for the full AI pipeline. Provider latency/failure becomes retry state, never user-facing red.
  6. Poll the draft. The client polls GET /meals/{id}/draft until completed, failed, or needs_user_confirmation. The pre-save "meal log detail" screen shows the estimate with an explicit "nothing saved yet".
  7. Save. POST /meals/{id}/save {meal_type} flips draft → logged, rebuilds daily_summaries, and records the confirmed dish for reuse. Only now does the meal count toward the day and appear in History.

States & alternates

processingcompletedneeds_user_confirmationfailedreused=true

Implementation. backend/app/features/log_meal.py, save_meal.py, reestimate_draft.py; router backend/app/api/meals.py; core core/log_meal.py, core/save_meal.py, core/backdate.py. Frontend feature frontend/src/features/log-meal/ and meal-detail/.

07 · AI communication the estimator seam

The product promise depends on free text staying natural and estimates staying honest. The AI estimates; the application validates, maps, formats and interprets. The meal text is always data, never instructions. Prompts are versioned data (a snippet library), not hardcoded strings — changing a prompt means editing a data file and bumping its version.

Pipeline: versioned prompt snippets are assembled with injected constants; the meal text is wrapped as untrusted data; a forced tool-use call returns schema-constrained JSON; validation layer 1 maps and drops cosmetic rule breaks without retrying, retries once on structural failure; validation layer 2 checks plausibility and language; the outcome is a confident estimate or a calm soft fallback after two failures.
Prompt assembly → forced tool use → two-layer validation → confident estimate or calm fallback.

1 · Prompt assembly (versioned data)

The system prompt is composed per call from a snippet library, with constants (allowed units, portion descriptors, title/tag caps) injected at build time:

build_parse_meal_prompt(locale)  →  parse_meal.md
                                  +  voice_tone.{en|de}.md
                                  +  title_tag.{en|de}.md
                                  +  user_context.md
prompt_version = "meal-estimate-v1"

The user message carries prompt_version, locale, and the HTML-stripped, length-capped meal text inside an <meal_input>…</meal_input> wrapper the model is instructed to treat as data — the injection-defense seam.

2 · Forced tool use

Estimation is a single Anthropic Tool Use call through the AiEstimator boundary: temperature=0, max_tokens=1200, tool_choice pinned to the meal_estimate tool, and the SDK's own retries disabled (max_retries=0) because retries are our concern, not the transport's. The tool schema constrains the JSON:

FieldMeaning
portion_sizeA structured Amount for the whole meal — {number, attribute|null, unit} against an enum-constrained unit vocabulary (g, ml, cup, bowl, portion, handful, …) plus an optional size attribute (small/large/…). Never a free-text string; a size word is used only if the user gave one.
ingredients[]The single merged ingredient list the estimate depends on — always ≥1, unlimited. {text_en, text_de, amount: Amount|null, assumed, dominating}; assumed=false only for what the user stated explicitly (name and amount). ≤5 marked dominating. Replaces the earlier separate products[]/assumptions[] split (redesign 18.11) — ingredients and assumptions were always one concept.
rangesA required object keyed by every canonical nutrient (core/nutrients.py, all 32) — {min, max} per key, both nullable. Making the full key set mandatory (rather than a variable-length list) closes the "sugar appears then vanishes between calls" drift (redesign increment A, PR #208).
title / tagCosmetic labels, capped at 60 / 24 chars.
confidence_levelAI-supplied high · medium · low; the domain adds a fourth state, pending, before a job resolves — never returned by the tool call itself. Drives whether the meal contributes to the day's ranges.

3 · Validation layer 1 — structure & cosmetics

In ai_estimate_mapping.py, two field classes get two policies:

4 · Validation layer 2 — meaning

The parsed estimate passes through validate_meal_plausibility and enforce_assumption_language — assumptions must read as natural German for de* and natural English for en*, regardless of the meal's input language. A language failure also triggers the single retry.

5 · Outcome

Confident estimate

Ranges + ingredients written to the draft. On the day, midpoints sum linearly and uncertainties combine by root-sum-square (§08). This quadratic aggregation is the honesty promise, not a bug.

Soft fallback

After two validation failures, soft_fallback_after_validation_failures returns needs_user_confirmation at low confidence with a calm, editable message — "This one is best checked once. You can confirm it or adjust the details." Never a red error.

Cost & safety. Meal parsing and bounded coaching are the only routine paid calls. Every call is schema-validated, idempotent, metered and capped per user/month via UsageMeter. Cosmetic-rule violations are counted and alerted. Memory reuse (≥3 confirmations) removes the AI cost entirely for repeat dishes. The Anthropic monthly spend cap must be set in the Console before any prod key is used.

Implementation. integrations/anthropic_estimator.py (tool use + retry), integrations/fake_ai_estimator.py (default/tests), integrations/ai_estimate_mapping.py + ai_language.py (validation), core/meal_estimation.py (status + soft fallback), core/meal.py (Ingredient, MealEstimate), core/amount.py (Amount, unit/attribute enums), prompts/ (snippet library + builder). Boundary: boundaries/ai_estimator.py. Skill: ai-prompt-engineering.

In-flight. The structured-amounts redesign (Epic 18) landed the AI-seam and DB-native shapes above (18.10–18.12, merged) behind a write/read adapter that keeps the POST/GET /meals… wire contract and OpenAPI byte-identical for now. Story 18.13 (open PR) removes that adapter so portion_size/ingredients flow to the frontend natively — the endpoint table in §17 and the wire shapes implied by §09 still describe the pre-18.13 contract until it merges.

08 · Daily view & bands UC-07 · UC-08

The signature surface. A band gives a feel for the day, not a sum. Color says which nutrient, never good/bad; status is carried by the text label only. No numbers on Today (non-negotiable #2); numbers live one tap away in All-details.

Pipeline: logged meal ranges aggregate with root-sum-square uncertainty into persisted daily summaries, compared against stored targets to produce objective band facts, mapped by a versioned rule table to an opaque state key, and rendered as localized copy; a derived day level feeds history dots and statistics.
Meal ranges → RSS aggregation → daily_summaries → band facts → versioned state mapping → localized label.

How a band is computed

  1. Aggregate. aggregate_daily_meals sums logged meals for the day. Drafts are excluded; pending / low-confidence meals contribute zero to the ranges and instead increment a calm "still estimating" count (non-negotiable #6).
  2. Combine honestly. Per nutrient, midpoints sum linearly and half-width uncertainties combine as σ = √Σ(halfwidth²) (root-sum-square) — not naive addition.
  3. Persist. The result is upserted into daily_summaries on every save and compared to the persisted user_targets ideal zone [min, max] (DGE/USDA reference; §10).
  4. Measure geometry. BandFacts reduces the band to objective booleans: no_data, end_below_zone, start_above_line, after_pivot (before/after 15:00).
  5. Classify. classify_band_state runs a versioned, ordered rule table (first match wins) over those facts and returns an opaque state key. The evaluator knows the rule shape, never what a key means.
  6. Render. The key maps to localized copy in the frontend. Retuning a threshold or relabeling a state is a data edit — the classifier code never changes.

Band states

emptybelow_zone_earlybelow_zone_latewithin_zoneabove_zone

Category colors (which nutrient, not good/bad)

energy protein carbs fat fiber personalized micro/vitamin target-zone marker

Default 5 bands (energy, protein, carbs, fat, fiber); the user can add up to 10 from the full nutrient/vitamin/mineral set — this is how non-weight goals (e.g. "watch iron") become visible, without new schema. The range boundary marker is drawn above the fill (z-index) so the ideal zone is never occluded when an estimate overshoots.

Day level (derived, never stored, never red)

very lightlightbalancedheartyrich

A pure function of the day's energy-band position, used only for History calendar dots and statistics distribution — never as a fill-color judgment.

Implementation. core/daily_summary.py (RSS), core/band_states.py (facts + rule table), core/day_level.py, features/build_daily_view.py, integrations/sqlalchemy_daily_summary.py. Frontend features/today/ + all-details/. Research: docs/research/band-label-mapping.md. Skill: design-system-tokens.

09 · Meal detail & refine UC-04 · UC-05

The glass box. A logged or draft meal shows exactly what the estimate depends on so the user can trust or adjust it. Bands describe a day; numbers describe a meal — the meal detail may show honest ranges, but never a day-budget band.

Implementation. features/reestimate_draft.py, refine_meal.py; core/reestimate_meal.py, refine_meal.py, meal_plausibility.py. Frontend features/meal-detail/. Research: docs/research/meal-log-detail-bands.md.

10 · Onboarding & monetization UC-01 · UC-02 · UC-13

Value before registration, then a low-friction path to goals, targets, a 30-estimate credit-based trial, and one paid plan.

Funnel: zero-cost demo, registration with goals and optional body data, BuildTargets computation, a 168-hour trial that starts at the first real meal log, a soft paywall to one paid plan, and a later optional Plus add-on.
Discover (zero AI) → register → personalize (targets) → trial → convert. Plus arrives only post-launch. This diagram is stale — it still draws a 168-hour clock trial; the shipped model is 30 free AI estimates (credit-based). Listed for re-render in the doc-agent report.

Goals are a plain list, not flags

The profile carries goals: list[Goal] (+ optional free text). The canonical set: understand eating habits · improve nutrient intake · healthier lifestyle · more energy · build muscle · lose weight · gain weight · watch specific nutrients · medically motivated. No "maintain weight" (implicit when neither lose nor gain is chosen); lose × gain is the only mutual exclusion. The internal goal_focus / goal_energy_direction flags were explicitly rejected — no unnecessary backend complexity.

Targets are persisted, never computed on the fly

BuildTargets (pure) runs on every profile change: Mifflin-St Jeor TDEE, a selected-goals energy band (lose→deficit, gain→surplus, else maintenance), missing body params substituted from versioned regional averages, and activity from a movement×sport PAL grid (two onboarding radios). It upserts user_targets; the frontend renders ideal-zone bars only from the stored numbers.

Pre-signup demo

Hardcoded data, animated logo loader, pre-computed bands + one hardcoded tip. Zero AI call, zero cost, zero abuse surface. Demo data is a signed local session, purged after 30 days of inactivity, and is not migrated into a real account.

Implementation. Frontend features/onboarding/, features/demo/demoData.ts; stores onboardingProfile.ts, todayNutrients.ts. Backend features/body_health_consent.py. Targets spec: docs/nutrition/build-targets-spec.md. Skills: gdpr-compliance, i18n-localization.

11 · History, favourites & memory UC-06 · UC-10

History

GET /meals?date= lists a day's logged meals oldest-first; History calendar dots use the derived day level. Day/activity context (PUT /daily/{day}/context, /activity-context) adds optional calm context — mood, hunger, energy, rough activity — never judgment.

Favourites

Currently a client-side localStorage stopgap (emealia.favourite-meals) until a backend favourite field/endpoint lands. Named as a stopgap in code.

Meal history context & 0-AI reuse

Every confirmed dish is stored in the user's own words (meal_memory). A keyword match surfaces "From history" chips on the add-meal screen (GET /meals/suggestions). At ≥3 confirmations the dish re-logs instantly with zero AI cost — the draft lands complete and the client skips polling. Bounded recent history may inform coaching, but the product never promises the AI "learns over time".

Implementation. features/log_meal.py (memory shortcut), boundaries/meal_memory_store.py, integrations/sqlalchemy_meal_memory_store.py. Frontend features/history/; stores mealConfirmations.ts, favourites.ts.

12 · Coaching & suggestions UC-11

Bounded, consented, forward-looking. Silence is the default — emealia never sends "you haven't logged in X days" messages. Coaching sends only selected goals, cooking level, nutrition-knowledge level, preferences/free-text context, visible/starred band states, and bounded logged-meal history. Calls are schema-validated, idempotent, metered and capped.

TriggerTiming ruleGuards
Daily tipFires when cumulative kcal today > 50% of the 7-day rolling median (floor 300 kcal)Not before 10:00 local · hard cutoff 23:00 · 1/day · min. 3 logging days all-time
Every-other-day reviewAfter every second active logging day (≤1 calendar-day gap)Min. 2 calendar days between reviews

Delivery piggybacks on natural app opens. The voice is the Newsreader-italic companion: observe, don't judge; always one small step that builds on what the user already does.

Implementation. Epic 07 (implementation-plan/07-coaching-suggestions.md). Research: docs/research/suggestion-timing-and-context.md. AI cost model shares the AiEstimator/UsageMeter seams as §07/§13.

13 · Trial & payments UC-13

Implementation. boundaries/usage_meter.py, payment_service.py; core/usage.py; integrations/sqlalchemy_usage_meter.py. Tables usage_accounts, usage_user_days, usage_global_days. Epic 09. Skill: security-hardening.

14 · Statistics & reviews UC-12

Period summaries and a range-capable statistics chart — the honest ranges carry through to trends, so a period never collapses to a single fake-precise number. Distribution uses the derived day level, never red. The frontend statistics screen is currently a placeholder over recent backend groundwork.

Implementation. Epic 10 (implementation-plan/10-reviews-statistics.md). Frontend features/statistics/. Aggregation reuses the RSS math from §08.

15 · PWA & offline UC-14

Mobile-first PWA, no App Store. Installable via a hardened web app manifest (story 12.1), a service worker, an offline meal queue, and a performance budget. A meal logged offline queues locally and auto-commits to logged on reconnect — no error wall, ever.

Implementation. frontend/src/pwa/manifest.ts, service worker + Vite PWA config. Epic 11 (implementation-plan/11-pwa-offline.md). Skill: frontend-vue-pwa.

16 · Compliance & quality (cross-cutting) launch blockers

GDPR & legal — Epic 15

DPA with Anthropic, consent flows (append/revoke ledger in consents, one active per type), EU data residency (RDS Frankfurt), export, erasure (cascades on user), retention. Free-text meals carry an Art. 9 posture; body/health data is erased when consent is revoked. Impressum §5 DDG required for DE.

Security — Epic 16

Headers/CSP, CORS, CSRF, rate limits (api/rate_limit.py, security_headers.py), tenancy isolation (every read scoped to the resolved user), input validation, secrets hygiene (OIDC, no long-lived keys), SAST, pen test. Anything touching identity, money, quota, AI spend, consent or protected data lives on the backend — frontend storage is never a trust boundary.

Observability — Epic 17

Structured PII-safe logs, Sentry (scrubbed), 5 product metrics, consent-gated PostHog analytics (no PII without consent), backups, accessibility (WCAG AA), runbooks, launch sign-off. AI cosmetic-rule violations are counted and alerted.

Localization — Epic 12

Full string externalization (locales/en.json, de.json); locale drives units, reference values (DGE/USDA, salt), currency and AI output language. Market behavior is admin-configured (admin.toml), not embedded in a screen. DE voice QA gate.

Launch blockers. Epics 15 (GDPR), 16 (Security), 17 (Observability/QA) run in parallel from week 1 and must all be green before shipping. Two founder-only human gates: G1 band comprehension (≥3/5 testers unaided) before Epic 03 UI is finalized; G2 AI calibration (30 real meals DE+US, correction rate <40%) before scaling acquisition.

17 · Endpoint reference

The current API surface (backend app/api/). Meal logging is async from day 1; the frontend consumes a typed client generated from the backend OpenAPI export.

Method & pathUse caseNotes
GET /healthLivenessInfra probe.
POST /mealsLog a meal (UC-03)202 {meal_id, status:"processing"}; reused:true on memory hit. Runs consent + backdate + entitlement.
GET /meals?date=History (UC-10)Logged meals for one local day, oldest first. Read, always open, tenancy-scoped.
GET /meals/{id}Meal detail (UC-04)One logged meal (glass-box read).
GET /meals/{id}/draftPoll draft (UC-04)Pre-save estimate; completed | pending | needs_user_confirmation | failed.
GET /meals/suggestionsReuse (UC-06)≤5 "from history" chips for a meal_type, de-duplicated.
POST /meals/{id}/saveSave (UC-03)draft → logged; rebuilds daily summary; records reuse.
POST /meals/{id}/reestimateRefine (UC-05)One metered re-estimate; optimistic expected_version.
GET /dailyToday bands (UC-07)Currently a mock route under MOCK_MODE; the real builder replaces it.
GET /daily/{day}/detailsAll-details (UC-08)Numeric transparency for one day.
GET · PUT /daily/{day}/contextDay context (UC-09)Mood/hunger/energy tags.
GET · PUT /daily/{day}/activity-contextActivity context (UC-09)Rough activity turnover ranges.
GET /statistics/nutritionStatistics (UC-12)Min/max range chart per nutrient over today · week · month; percent-of-target with gap-filling, has_reference:false instead of a fabricated 0% when a nutrient has no target.
GET · DELETE /body-health-dataProfile / erasure (UC-02, UC-15)Body/health data + consent-linked deletion.
POST /demo-sessionsDemo (UC-01)Signed local demo session; no AI, no cost.

18 · Data model reference

Macros and micros are stored as individual columns (not JSONB) with CHECK constraints, so statistics can aggregate any nutrient. One storage tier, columns only. daily_summaries is pre-computed. Meals follow a draft → logged lifecycle.

TableOwns
usersIdentity anchor: id, timezone, timestamps. Day boundary via Clock.local_date.
consentsGDPR consent ledger — append/revoke, one active per user/type; cascades with erasure.
mealsRaw text, AI/display metadata, status/lifecycle/version, structured portion_size (JSONB Amount) and a merged ingredients list (JSONB, replacing the old separate products/assumptions columns as of 18.12), macro/micro/detail nutrient ranges with plausibility checks.
meal_memoryUser-scoped canonical text + confirmed nutrient/product/assumption shape for 0-AI reuse.
ai_jobsAsync estimation queue: pending/running/succeeded/failed, attempts, next-attempt time, error metadata.
daily_summariesPer-user/day aggregate ranges + pending count. Rebuilt on every save/change.
user_targetsPersisted BuildTargets output — ideal zones the bands render against.
usage_accounts · usage_user_days · usage_global_daysTrial credits, subscription status, per-user + global daily AI reservations.
daily_context_tags · activity_contextOptional day context tags and rough activity turnover ranges.

Implementation. ORM rows in integrations/models.py; migrations in backend/alembic/versions/. Never put ORM in core/. Nutrient DDL + display rules: docs/nutrition/nutrient-reference.md. Skill: database-migrations.

19 · Feature status matrix

Current implementation state (July 2026). See the developer onboarding for the live mock/real map and the app repo's implementation-plan/ for story-level acceptance criteria.

Feature / use caseStatusNote
Meal logging (UC-03) & saveReal async contractDraft → poll → save is real DB-backed; estimator is the fake unless ALLOW_REAL_AI is set.
AI communication pipeline (§07)BuiltTool use, two-layer validation, retry, soft fallback, violation counting all present. All-32 required ranges object + merged ingredients/structured Amount shipped in the AI seam and DB (18.10–18.12); wire-contract cutover (18.13) still an open PR.
Memory reuse (UC-06)Built0-AI re-log path in LogMeal; suggestions endpoint live.
Meal detail / re-estimate (UC-04/05)Real read + refineDemo fixtures via ?demo=1 for comprehension tests.
Today bands (UC-07)Mock-backedGET /daily exists only under MOCK_MODE; real builder pending. Band-state classifier is real (core/band_states.py).
All-details (UC-08)Mock-backedNumeric transparency from the same mock day variant.
Day / activity context (UC-09)Backend-backedReal stores, wired outside mock-only daily summary.
History / favourites (UC-10)HybridReads real meals; favourites is a localStorage stopgap.
Coaching (UC-11)PlannedEpic 07 — timing rules specified; shares AI/meter seams.
Statistics (UC-12)BuiltReal GET /statistics/nutrition + a min/max range chart (today/week/month) on the Statistics tab (story 10.1).
Trial & payments (UC-13)Boundaries firstUsageMeter/entitlement seams present; Stripe rollout env-gated.
PWA / offline (UC-14)In progressManifest hardened (story 12.1); SW + offline queue underway.
GDPR / Security / ObservabilityLaunch blockersEpics 15/16/17 — parallel, must be green before shipping.
real / built mock-backed hybrid / planned launch blocker

Sources of truth. On any conflict: emealia-mgmt/ wins for concept/product; implementation-plan/ wins for what gets built. This page mirrors both for humans; story state and acceptance criteria live in the app repo.