## Outcome **Recommended bounded design: additive `health.medication_action.v2`; do not reinterpret or mutate existing F1/legacy rows.** ### Key findings in current code - Unknown-status prescriptions are currently unusable for administration: - `medication_contract.py:289-297` requires trusted `active` status for every non-correction. - `dashboard-v5-record.js:819-820` exposes capture only for active prescriptions. - “Planned” versus manual administration is not explicit: - Current payload has no capture mode. - Active prescriptions can create standalone administrations without a plan. - Dose semantics remain conflated: - Only planned/actual dose value+unit exist; quantity, dosage form, and strength are not distinct. - Worker recombines actual value/unit into legacy `dose` at `health_dashboard_action_worker.py:1224-1262`. - Deviation confirmation is not based on an actual comparison: - Validation accepts either confirmation checkbox (`medication_contract.py:407-411`). - UI permits deviation confirmation even without a plan (`dashboard-v5-record.js:773-784`). - Existing `dosierung`/`anwendungsform` are free-text legacy fields and are **not safe preset sources**. - Preview failure keeps inputs, but emits only a generic global error; final submission uses form navigation and can lose the dialog state. - Public medication references are opaque HMAC tokens, not raw DB IDs. New endpoints must preserve that boundary and never serialize exception/SQL text. - Incidental defect: UI offers correction target `unknown`, while backend rejects it. ## Minimal additive design ### Action contract Add `health.medication_action.v2` with explicit: - `capture_mode`: `from_plan | historical_manual` - `historical_manual_confirmed` - Separate actual fields: - `quantity_value`, `quantity_unit` - `dosage_form` - `strength_value`, `strength_unit` - existing route, injection site, lot, and note remain separate - Equivalent immutable planned snapshot fields for meaningful comparisons. Rules: - `from_plan` - Requires a real, unconsumed `planned_event_ref`. - Referenced row must effectively be `planned`, belong to the same medication, and use an explicitly active prescription. - `historical_manual` - Must have no plan reference, be non-future, and explicitly confirmed. - Unknown-status prescriptions are selectable **only** here. - Captures `administered` only; it never updates `medikamente.prescription_status` or its provenance/revision. - Keep correction behavior outside this bounded change unless separately redesigned. ### Schema/migration Add nullable v2-only columns to `medication_administrations`: - `action_contract_version`, `capture_mode` - planned and actual quantity/form/strength columns Add a versioned append-only `medication_verified_presentations` table containing: - Medication relation - Structured quantity/form/strength/route values - Verification source and provenance - Business revision and supersession/active state Safety properties: - No parsing/backfill from `dosierung`, `anwendungsform`, legacy `dose`, names, or notes. - Presets are emitted only from complete, currently effective verified-presentation rows. - User-entered events never become presets. - v2 triggers activate only when `action_contract_version='health.medication_action.v2'`; existing structured and legacy rows remain untouched. - Reuse the existing copy-first migration, restore proof, logical digest, idempotency, integrity/FK, and managed-trigger digest gates. - Do not dual-write a combined v2 dose into legacy `dose`; that would recreate semantic conflation. ### Deviation confirmation Compute mismatch server-side from the referenced plan’s explicit structured fields: - Compare only planned dimensions that are known. - Use conservative canonicalization: trimmed text, canonical decimal representation, exact allowlisted units/enums—no medical conversions. - Exact match: deviation checkbox absent and `deviation_confirmed=true` rejected. - Real mismatch: preview returns allowlisted field labels; final submission requires confirmation. - Missing planned data is “unknown,” not a deviation. ### Errors and form preservation Use JSON preview/final requests: - `422`: allowlisted field validation code and field - `409`: stale plan, consumed plan, duplicate, stale preview - Generic `503` for unavailable service Never return raw IDs, values, SQL, paths, or exception strings. Keep the dialog and all entered values mounted on every failure; focus the failing field. Close/reset only after a successful durable receipt. ## Essential adversarial tests 1. Unknown prescription succeeds only for confirmed, non-future `historical_manual administered`. 2. Unknown prescription fails for `from_plan`, missed, future, absent confirmation, or forged mode. 3. Prescription row and status revision are byte-identical before/after historical capture. 4. `from_plan` rejects missing, non-plan, consumed, corrected-away, or cross-medication references. 5. Historical mode rejects any plan reference. 6. Quantity “2”, form “tablet”, strength “10 mg” persist separately; legacy `dose` remains unset. 7. Legacy free text never generates a preset. 8. Partial, unverified, stale, or superseded master rows never generate presets. 9. A tampered value cannot still claim `preset_applied`. 10. Exact plan match rejects deviation confirmation; one-field mismatch requires it. 11. Unknown planned dimensions create no mismatch. 12. Injection-site values require an injection-compatible route. 13. Every 422/409/503 preserves DOM values and returns only allowlisted errors without raw IDs or internals. 14. Duplicate/stale final submission keeps the dialog open. 15. Migration is idempotent, preserves all legacy logical digests, and performs no status/backfill changes. ## Verification / workspace - Reviewed repository at exact base `ca48449f`; working tree remained clean. - **No files created or modified.** - Focused pytest execution was unavailable because the active Python environment has no `pytest` module; review was therefore source-based.