# 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).
