## Outcome Read-only audit completed at exact base `3f69e46823269c97260976270e53acbcc45f00d4`; worktree was clean before and after. No files were modified. ## Smallest safe vertical path ### 1. Medication read provider/API **Primary touchpoint:** `scripts/health/dashboard_v5/read_api.py` - Extend `_record_medications()` at **3478–3546** rather than adding a parallel endpoint. - Keep the existing response keys: - `planned` - `administered` - `missed` - `corrected` - `truncated`, `truncated_sections`, `next_cursor` - Add strict query parameters to `/api/v1/medications` dispatch at **4438–4439**: - `medication`: exact server-published value or opaque catalog key - `from`, `to` - `status`: allowlist of the four public buckets - `source`: allowlisted public source category, not raw database text - Apply all filters in SQL **before** `LIMIT`; retain per-status limits and explicit truncation. - Return enough inline detail to avoid a new identifier-bearing details endpoint: - effective date and recorded date - name, dose, route - public status - bounded note - translated/public source label - scheduled date where applicable - Add a separate top-level `current_plans`/`current_documented_plans` collection to the same response only if it can be derived conservatively. **Shared semantics that must be reconciled:** - `data_provider.medication_data()` at `scripts/health/dashboard_v5/data_provider.py:152–213` owns Today’s last-administered/next-planned calculation, cancellation suppression, future boundary, and Explorer bundle events. - `_next_planned_medications()` at `read_api.py:3721–3752` independently implements future planning. - `_capture_plans()` at `read_api.py:3794–3815` currently treats the newest historical row for every medication as an “existing plan.” - These should call one shared status/plan classifier; otherwise Today, Akte, quick capture, and report will disagree. Do **not** derive “current prescriptions” directly from `medikamente`: `database/schema.sql:251–260` has no active/inactive state, validity interval, review state, or prescription identity. Likewise, “ever administered” is not proof of a current prescription. If no authoritative active-plan signal exists, label the section **“Aktuell dokumentierte Planungen”**, not “Aktuelle Verordnungen.” ### 2. Akte → Medikamente UI **Primary touchpoint:** `scripts/health/assets/health-assets/dashboard-v5-record.js` - Expand `FILTER_KEYS.medications` at **20–26** from `from,to` to medication/time/status/source. - Replace the four static tables in `renderMedications()` at **732–742** with: 1. current documented plans; 2. one chronological history; 3. filter form; 4. expandable inline detail rows. - Preserve text-node rendering through the existing `element()`/`table()` helpers. - Add quick actions on eligible plan/history rows: - “Als verabreicht dokumentieren” - “Als ausgelassen dokumentieren” - “Korrektur erfassen” - Keep status text/icon visible independently of color. **Markup/CSS touchpoints:** - `scripts/health/dashboard_v5/render.py` - Akte shell is already rendered under the 6E feature flag. - Existing medication dialog is at **172**, generic capture dialog at **169**. - `scripts/health/assets/health-assets/dashboard-v5.css` - reuse `.record-filter-form` around **550–554** - reuse `.record-actions` around **456** - reuse `.capture-sheet` around **833–874** - add only medication workspace/detail/preview layout rules - extend print rules at **1211–1214** so action buttons, dialogs, notes not selected for the report, and private controls never print. ### 3. Preview → Confirm through the existing Action Queue The safest reuse path is the **generic capture contract**, not a third write contract: - UI: `dashboard-v5-capture.js` - HTTP: `POST /health-actions/capture` - validation: `dashboard_v5/capture_contract.py` - worker: `health_dashboard_action_worker.py::apply_capture_action` - status polling: `/api/v1/capture/status/{idempotency_key}` Why: - Existing generic medication capture already provides: - exact-plan selection; - statuses `administered|missed|corrected`; - idempotency key; - conscious `plan_value_confirmed` / `deviation_confirmed`; - append-only correction/withdrawal references; - worker-side catalog revalidation; - queue status polling. - Relevant code: - medication fields: `dashboard-v5-capture.js:48–61` - payload construction: **187–207** - contract validation: `capture_contract.py:230–265` - worker catalog/confirmation checks: `health_dashboard_action_worker.py:1035–1055` - mirror into `medication_administrations`: **1181–1192** Minimal UI changes: - Export a narrow opener such as `window.healthCaptureOpenMedication(preset, mode, sourceItem)` from `dashboard-v5-capture.js`; keep `openType()` otherwise private. - Add an actual preview step inside `#capture-entry-dialog`: - first submit builds and freezes the exact payload; - preview displays medication, status, timestamp, dose/unit/route, source plan, note, and whether plan value or deviation was confirmed; - “Zurück” edits; - “Verbindlich vormerken” sends that same frozen payload. - Akte and Today call the exported opener. - Do not use `window.confirm()` as the medication Preview→Confirm UI. Keep `/health-actions/medication-event` for compatibility, but stop routing new V5 medication buttons to it. Its current path: - form: `render.py:172` - browser confirmation: `dashboard-v5.js:1730–1734` - server validation: `health_dashboard_server.py:764–856` - worker validation/write: `health_dashboard_action_worker.py:402–406,1327–1337` lacks the generic capture path’s idempotency, plan/deviation confirmation, and correction lineage. ### 4. Integration touchpoints - **Today** - `dashboard-v5.js:651–666` renders last actual and next planned. - Route both quick action and “Medikamente in der Akte” through the shared capture opener/Akte tab. - Keep `data_provider.medication_data()` status-safe. - **Day** - `read_api._day_medications()` at **1325–1369** - `dashboard-v5-day-controller.js:131–143` - Preserve all four statuses; add detail/open-Akte actions without flattening them. - **Calendar** - medication counts: `read_api.py:2663–2677` - display/category routing: `dashboard-v5-day-controller.js:180–250` - Calendar remains count/category only; clicking opens the central day, not PHI in calendar labels. - **Explorer** - `_events()` at `read_api.py:968–1007` - It intentionally publishes only affirmative administrations as `medication_administered`. - Do not add planned, missed, or corrected rows as exposure markers. - **Doctor report** - `_doctor_report()` at `read_api.py:4032+`, medications at **4051–4057** - report UI section mapping in `dashboard-v5-record.js:772–806` - Reuse filtered four-status history, retain neutral wording and truncation disclosure. - **Worker regeneration** - `health_dashboard_action_worker.py:2181–2202` regenerates V5 with explicit `--today` and the health-record profile. - Keep this path so Today’s static bundle updates after processing. ## Hidden compatibility/privacy pitfalls 1. **No authoritative prescription model.** Neither `medication_administrations` nor `medikamente` can prove an active prescription. Avoid a false “current prescription” claim or add a separately approved schema/model sprint. 2. **Two existing write paths already overlap.** `/health-actions/medication-event` and `/health-actions/capture` both write medication administrations. Adding another route would create three incompatible semantics. 3. **Same-day uniqueness can reject valid administrations.** `database/schema.sql:248` enforces `UNIQUE(datum, medication_name, event_type)`. Two legitimate same-day administrations, or retry/mirror behavior, may conflict. This must be probed before promising multiple daily doses; do not silently overwrite. 4. **Correction lineage is lost in the legacy mirror.** `capture_entries` retains `corrects_entry_id`/`withdraws_entry_id`; `medication_administrations` only receives `event_type='corrected'`. Akte history sourced solely from the legacy table cannot truthfully identify what was corrected. 5. **Legacy rows lack safe correction IDs.** Avoid exposing numeric database IDs. For legacy history, allow a new standalone correction with explicit preview/note, or require a migration to an opaque persisted identity before claiming linked correction. 6. **Plan classifiers currently drift.** - Today suppresses cancelled plans and can derive a scheduled date from a past actual administration. - Akte only includes explicit plan event types. - capture plans include every historical medication. These must not remain independent. 7. **Do not reinterpret status aliases.** Preserve `ADMINISTERED` and existing German aliases. Unknown event types remain unknown/omitted, never coerced to administered, planned, or corrected. 8. **Explorer medical semantics are narrower by design.** Missed/corrected/planned events must not become treatment-exposure markers or enter correlation lanes as administrations. 9. **Browser-timezone bug in generic capture.** `dashboard-v5-capture.js:24–28` derives time using the browser and selected URL date. The product contract is Europe/Zurich. Quick actions need an authoritative server-provided local datetime/day, especially near midnight. 10. **Raw source values may leak internals.** Filtering and rendering should use public source categories/labels. Do not place raw source, notes, medication names, queue payloads, internal IDs, or receipt data in URLs, storage, logs, or analytics. 11. **Notes are PHI.** Render only as text nodes, collapse by default, exclude from calendar labels, and include in doctor print only when the medication section is explicitly selected. 12. **Queue acknowledgement must remain truthful.** “Vorgemerkt” means queued, not applied. Only the worker status may say processed. Preserve one-time receipt removal and `no-store`. 13. **Preview can become stale.** Freeze and submit the exact displayed payload. The worker must still revalidate medication existence and confirmation flags; client preview is not authorization. 14. **Static bundle versus live API.** Akte reads live API while Today is generated into the bundle. Verification must wait for worker regeneration before asserting cross-surface consistency. ## Maximum-six Playwright scenario plan Create one new spec, e.g. `tests/browser/dashboard_v5_sprint7c_f_medications.spec.js`, with **exactly six tests**: 1. **Akte current plans + chronology** - current documented plan wording; - chronological order; - all four statuses visibly distinct; - route/dose/source/details; - unknown/missing fields remain “Nicht dokumentiert.” 2. **Filters, URL, reload, back/forward** - medication, date interval, status, source; - URL contains only allowlisted filter values, no note/payload/internal ID; - reload and history restore; - out-of-order responses cannot replace the latest filter result. 3. **Administration Preview→Confirm→Queue→Worker** - open from a current plan; - preview exact timestamp/name/dose/route/status; - confirm once; - assert one `0600` queue action and idempotency key; - run worker against synthetic DB; - assert processed status and one administration. 4. **Omission and correction safety** - omission remains omission everywhere; - correction preview identifies the source capture when available; - append-only version/history preserved; - correction does not modify/delete original; - no omission/correction appears as administered in Explorer. 5. **Today/day/calendar/explorer/report integration** - Today last actual/next planned; - Day shows all four statuses; - Calendar medication count opens the correct day; - Explorer shows only affirmative administrations; - doctor report includes selected medication statuses and neutral disclaimer. 6. **Privacy, accessibility, mobile and print** - 390×844 and 200% text, no horizontal overflow; - 44px targets, keyboard/focus restoration, Escape; - status not color-only; - zero external requests and no console errors; - no medication data in storage/URL; - action controls hidden in print. Supporting tests: - New focused Python contract file for filtering-before-limit, alias separation, cancellation handling, future boundary, raw-source suppression, timezone, duplicate/replay, and correction lineage. - Extend synthetic fixture only; never use productive data. - Add one isolated runner group to `tests/browser/run_v5_isolated_matrix.sh`; preserve the existing temp DB/inbox, instance-ID, and localhost controls. - Rerun existing medication gates: - `tests/test_health_dashboard_v5.py` - `tests/test_dashboard_v5_sprint6e_record.py` - `tests/test_dashboard_v5_sprint6d_day_calendar.py` - `tests/test_dashboard_v5_sprint6g_b2.py` - capture/worker tests - capped six-scenario browser spec. ## Preserving V4 - Do not edit `scripts/health/health_dashboard_v4.py`; it is unchanged at the audited base. - Do not alter `/health-dashboard`, V4 rendering, V4 assets, redirect behavior, or fallback routing. - Keep all new UI behind the existing `--health-record-6e` V5 render profile. - Keep server additions V5/API-only and additive. - Release gate: - `git diff --exit-code -- scripts/health/health_dashboard_v4.py` - compare the generated V4 artifact checksum before/after - verify both V4 and V5 routes independently - retain the parity-matrix rule in `docs/dashboard_v4_v5_parity.md`: V4 cannot be retired by this sprint.