Files
baya-monorepo/mvp/blocker-phases/12-patient-records.md
T
2026-08-02 20:37:14 +03:30

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:13USE_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).