# Phase 12 — Patient records & visit notes are fake demo data **Blocker:** blockers.md § "Patient records & visit notes." **Depends on:** nothing to start, but items #3/#4 below need a product decision made before you flip the mock flag — don't skip straight to "turn off the mock," it will break on day one exactly as blockers.md warns. --- **Root cause.** `client/src/services/patientRecords/constants.ts:13` — `USE_PATIENT_RECORDS_MOCK = true`, with a stale comment claiming "no backend at all." The backend is fully built and every route the client calls already exists (`PatientCareRecordsController.cs`) — confirmed 1:1 against `archive/docs/integration/domains/patient-records.md`. ## Six distinct shape/behavior mismatches, ranked by severity 1. **Silent data-wipe (fix first — this is the dangerous one).** `UpsertCarePlanCommand.Handler.cs:27-45` always **fully replaces** all three lists (medications/routine/tasks) from the request — no merge with what's stored. The client's `EditableTabs.save()` (`patients/[id]/record/page.tsx:173-198`) **saves one tab at a time**, sending only that tab's field and omitting the other two from the JSON entirely. Today this "works" only because the **mock** does a partial merge (`mockApi.ts:183-191`) — against the real endpoint, saving one medication would silently erase every routine item and task for that patient on the next save. Structurally identical to the address-edit bug (phase 03). *Fix (pick one, it's a design choice more than a bug fix):* (a) client always sends the full current plan on every save — smaller, safer, matches "PUT = full replace" semantics the integration doc already asserts — or (b) server only replaces a list when its field is non-null, preserving the rest. Lean toward (a). 2. **Id types.** Client ids are `string` (`'m1'`, `newTempId()` = `` `new-${Date.now()}` ``); wire ids are `long`. Stringify/parse at the boundary; **omit** the id for new items rather than inventing one. 3. **Medication fields don't match.** Client has structured `doseAmount`/`doseUnit` + `frequencyCode`/`frequencyText` + `timeOfDay: TimeOfDayCode[]` (added post-hoc in a later UI pass); server `MedicationDto` only has one free-text `Dosage`, one required `Frequency` string, and **no** `timeOfDay` field at all. **Flag — genuine product decision, not inferable from code:** either build the richer schema server-side to match the UI, or simplify the UI back to the server's simpler shape. The richer client UI was built after the backend shipped, targeting a table that was never added to match. 4. **RoutineItem `timeOfDay` is multi-select client-side, single `string?` server-side.** Same decision as #3 — array support needs a backend field, or the UI needs to drop to single-select. 5. **`taskResults` is a stale mapper bug, not a contract gap.** The real `CareRecordDto` already serves `TaskResults: [{label, done}]` — an exact structural match to the client's own type — but the client's `toVisitNote` (`clientApi.ts`) hardcodes `taskResults: []` on read, and on write it folds the checklist into a free-text summary line instead of sending the wire's native `taskResults` array (`WritePatientCareRecordCommand.cs:12-16` already has a first-class `TaskResultsJson` column for this). *Fix:* stop discarding on read, send the array directly on write. Pure bug, not a design choice. 6. **`RecordAccess.deniedReason` enum mismatch.** Client union is `'no_access' | 'not_found'`; server actually sends `'not_authorized'` for the denied-not-found-owner case (`PatientAccess.cs:19-20`). Fix the client union to match the real value (the archived integration doc is also stale here — trust the code). **Fix order:** #1 first (safety), then #2/#6 (mechanical), then resolve #3/#4 as a product decision before flipping the mock flag, then #5 (independent bug fix, can land anytime).