49 lines
3.9 KiB
Markdown
49 lines
3.9 KiB
Markdown
# 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).
|