# Swiss nutrient-reference dashboards

Use this note when adding an official nutrient-reference comparison to a private health dashboard.

## Reference-data contract

1. Download the official tables once and commit an explicitly dated local snapshot. Record entry URL, table URL, retrieval date, and per-file SHA-256 hashes. Runtime requests must not depend on the external site.
2. Keep raw values for parsing, but expose a cleaned human-readable reference string. Preserve units and semantic qualifiers; remove only citation markers and presentation artefacts.
3. Treat locale decimals before numeric parsing (`9,1` → `9.1`). Do not infer ranges from every number in a cell: pregnancy, lactation, phytate classes, age splits, and trimester supplements are context-dependent alternatives.
4. Maintain an allowlisted mapping from internal nutrient keys to official row names. Unsupported internal nutrients remain visible with “no matching local reference”; do not silently substitute a related nutrient.
5. Store the source snapshot separately from the normalization/comparison code so raw provenance remains inspectable.

## Personal-group resolution

- Resolve a group only from explicitly documented fields such as birth date, sex, life stage, body weight, activity, trimester, lactation month, or other factors required by that exact reference.
- Missing profile fields produce `missing_required`, a null personal target, and a visible list of missing fields. Never insert guessed defaults.
- Pregnancy and lactation tables often contain **additions to** a baseline rather than total targets. Do not display `+500 kcal` as a 500-kcal total. A total is not determinable until the baseline and required stage are documented.
- Weight-related protein references can be calculated only with a plausible documented body weight. Apply trimester/lactation additions only when the relevant stage field is present.
- If source and logged units are not semantically equivalent (for example total sugar vs free sugar, or plain µg vs DFE/NE), show the source but suppress percentage comparison.

## Missingness and calculation rules

- Unknown nutrient amount is `null`, never zero.
- If some foods/days lack a nutrient value, mark the result as partial/unknown and suppress the percentage unless completeness can be proven.
- Do not cap percentages at 100. A documented 137% remains 137%; status wording can remain neutral (“above reference”, not a diagnosis).
- Handle exact targets, minimums, maximums, and ranges separately. For ranges, display both percentage bounds rather than inventing a midpoint target.
- Upper safety limits are a distinct field. Do not derive an upper limit from a recommended intake or infer toxicity from exceeding a reference.
- For 7-/30-day periods, compare per-documented-day means and state documented versus unknown days. Aggregate food contributions consistently (for example, mean per documented day) and label that basis.

## UI and medical language

- Keep every contracted nutrient visible, grouped by macro/factor, vitamin, mineral, and other.
- Provide amount, unit, reference, percentage/range, completeness badge, source/date, cautious function note, food contributions, and a 7-/30-day trend or an explicit no-data message.
- Preferred status vocabulary: “Referenz erreicht”, “unter Referenz”, “über Referenz”, “Ziel nicht bestimmbar”, and “Daten unvollständig”. Avoid red alarm styling unless an independently sourced upper safety limit exists.
- Detail dialogs must remain keyboard accessible; mobile targets should be at least 44 px and reduced-motion mode must remove nonessential transitions.

## Secure write-path lessons

For mapping and symptom actions:

1. Obtain a fresh one-time CSRF token immediately before each mutation; do not reuse a CSRF token embedded in a long-lived page.
2. Require all of: authenticated browser session, allowed Host, matching Origin, CSRF body/cookie pair, content-type and body-size validation.
3. Ensure the authenticated session cookie path actually includes the mutation route. A cookie scoped to `/api/v1` cannot authenticate `/health-actions/...`; either issue a dedicated action cookie or use a root path while retaining HttpOnly, SameSite=Strict, expiry, and server-side session validation.
4. Return JSON for fetch clients and map 401/403/409/422/5xx to understandable inline messages. Never navigate the dashboard to a raw server error page.
5. Deduplicate pending writes by stable domain identity (for example mapping queue key, or symptom date plus action type), return the existing action ID, disable duplicate submits, and revalidate in the isolated worker.
6. Browser acceptance should prove unauthorized rejection, fresh-token success, duplicate-submit idempotence, worker persistence after reload, and zero unexpected page/console/request errors. A forced 403 may create one expected browser console resource error; assert and consume it explicitly.

## Release and verification discipline

- Record immutable legacy-dashboard checksum before/after and never deploy over that file.
- Run focused computation, API contract, Python/JS syntax, security, medical-language, 390-px, reduced-motion, keyboard, and persistence tests before release.
- If an old regression test fails, reproduce that exact test in a detached worktree at the required base SHA before calling it pre-existing.
- Preserve an exactly-one-commit constraint: no interim commits. Stage only after gates pass, then commit once, push, deploy the private V5 artifacts, and verify service/API/browser plus the unchanged legacy checksum.
