## Outcome: **NOT READY for Sprint-5A acceptance** ### Important findings 1. **Missing days are removed instead of rendered as gaps.** `dashboard-v5.js:289-308` constructs the chart axis only from non-null observations. A missing calendar day disappears, so `spanGaps: false` cannot prevent the line from connecting adjacent rendered points. This violates the no-interpolation requirement. 2. **Cockpit features are not implemented yet.** `dashboard-v5.js:158-175` renders only value, date, and baseline value: - no sparkline, - no measurement age, - no baseline delta, - no coverage/quality badge. 3. **No main-trend fallback.** `dashboard-v5.js:463-466` hardcodes `symptom.total`. If unavailable, the chart is empty instead of selecting the first usable registry metric and labeling the fallback. 4. **Event “lane” is only a badge list.** `dashboard-v5.js:250-255` renders textual badges without a shared date axis. Events are separate from the main dataset, which is good, but this is not yet a temporally aligned event lane. 5. **Baseline registry contract is not honored.** `data_provider.py:333-338` calls `rolling_baseline(points, metric.baseline_days)` and ignores `metric.baseline_min_observations`. Registry metrics specify a minimum of 3, while the helper defaults to 7. 6. **Series contract permits unsafe quality/value combinations and unordered data.** `contracts.py:137-150` currently accepts: - numeric `value` with `quality="incomplete"`, - null `value` with `quality="complete"` or `"observed"`, - unsorted series dates, - future measurement dates. The frontend assumes the final array element is latest, so unsorted valid bundles can display the wrong measurement. 7. **Concurrent render change introduced duplicate navigation.** Current `render.py:44` and `render.py:67` both emit the same tablist. This risks duplicate accessible tabs and Playwright strict-locator failures. ### Exact minimal contract changes No new serialized fields are required; keep `health_dashboard.bundle.v2`. Existing `today`, metric metadata, point `value/baseline/quality`, registry ordering, series, and events are sufficient. Strengthen series validation only: - dates must be strictly ascending and unique; - measurement dates must be `<= bundle.today`; - `quality == "incomplete"` requires `value is None`; - `quality in {"complete", "observed"}` requires a finite numeric value; - baseline must equal the median of prior non-null observations in `[date-baseline_days, date)`, once `baseline_min_observations` is reached; otherwise it must be null. Treat metric registry order as the deterministic cockpit fallback order, with `symptom.total` first. No extra `fallback_metric_id`, summary, age, delta, coverage, or sparkline fields should be added because those are deterministic presentation derivatives. ### Minimal implementation/test set - Pass `metric.baseline_min_observations` into `rolling_baseline`. - Build a dense calendar axis for 7/30/90/180-day views; insert explicit `null` values and retain `spanGaps: false`. - Derive measurement age from `bundle.today`, not wall-clock time. - Show neutral signed absolute delta: `value - baseline`; do not label changes as better/worse, normal/abnormal, or safe/unsafe. - Show factual coverage such as `6/30 dokumentierte Tage`, avoiding medically suggestive traffic-light thresholds. - Render one sparkline per card using the dense axis. - Select the first registry metric with usable period data for the main trend; expose its label and fallback context. - Render events on a separate but date-aligned lane; never add them to the health-value dataset. - Remove the duplicate navigation. Required regressions: - baseline starts exactly after the configured third prior observation and excludes current/future values; - contract rejects unordered/future dates and inconsistent quality/value pairs; - generated Chart.js datasets contain an explicit null for a wholly absent day and use `spanGaps: false`; - six cards expose sparkline, deterministic age, baseline delta, and exact coverage; - symptom-empty bundle falls back to the expected next registry metric; - all-empty bundle retains the explicit empty state; - an event on a measurement-gap day appears only in the event lane; - visible copy contains no diagnosis, causality, reassurance, urgency, or treatment language; - exactly one navigation tablist exists. ### Verification - Focused Python suite: **16 passed** - JavaScript syntax check: **passed** - `git diff --check`: **passed** - Browser suite not run because no test server URL was provided/started. - No files were created or modified by this audit; the working tree already contained concurrent modifications to `data_provider.py`, `render.py`, tests, and the untracked Sprint-5A document.