3.9 KiB
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
- Silent data-wipe (fix first — this is the dangerous one).
UpsertCarePlanCommand.Handler.cs:27-45always fully replaces all three lists (medications/routine/tasks) from the request — no merge with what's stored. The client'sEditableTabs.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). - Id types. Client ids are
string('m1',newTempId()=`new-${Date.now()}`); wire ids arelong. Stringify/parse at the boundary; omit the id for new items rather than inventing one. - Medication fields don't match. Client has structured
doseAmount/doseUnit+frequencyCode/frequencyText+timeOfDay: TimeOfDayCode[](added post-hoc in a later UI pass); serverMedicationDtoonly has one free-textDosage, one requiredFrequencystring, and notimeOfDayfield 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. - RoutineItem
timeOfDayis multi-select client-side, singlestring?server-side. Same decision as #3 — array support needs a backend field, or the UI needs to drop to single-select. taskResultsis a stale mapper bug, not a contract gap. The realCareRecordDtoalready servesTaskResults: [{label, done}]— an exact structural match to the client's own type — but the client'stoVisitNote(clientApi.ts) hardcodestaskResults: []on read, and on write it folds the checklist into a free-text summary line instead of sending the wire's nativetaskResultsarray (WritePatientCareRecordCommand.cs:12-16already has a first-classTaskResultsJsoncolumn for this). Fix: stop discarding on read, send the array directly on write. Pure bug, not a design choice.RecordAccess.deniedReasonenum 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).