# Flow — care-circle-patients > Last verified: 2026-08-02 against commit `c841bde` **Actor(s):** customer · **Status:** partial **Client:** partial · **Server:** real **Business source:** [product/business/01-actors-and-onboarding.md](../../product/business/01-actors-and-onboarding.md) · data model: [product/data-model/10-reviews-and-records.md](../../product/data-model/10-reviews-and-records.md) **Integration:** [docs/integration/domains/patients.md](../integration/domains/patients.md) · [patient-records.md](../integration/domains/patient-records.md) ## What it does A customer keeps a list of the people they arrange care for — «حلقهٔ مراقبت», renamed from «بیماران» in ui-phase-9. Each one is a first-class row, not the account holder: the payer is usually an adult child and the care recipient an elderly parent or a newborn. Tapping a person opens their care record — the family's own medication/routine/task plan, plus the read-only history of visit notes nurses wrote for them. > **The seam splits mid-flow.** The **list** screen is real end to end and was probed live. The **record** > screen is real UI on a mock service (`USE_PATIENT_RECORDS_MOCK = true`, > [`patientRecords/constants.ts:13`](../../client/src/services/patientRecords/constants.ts)) — even though > all five server endpoints exist and return `200`. Nothing you see on `/record` comes from the database. ## Screens | Step | Route | Component / notes | | --- | --- | --- | | E1 — the care circle | `/fa/patients` | [`patients/page.tsx`](../../client/src/app/%5Blocale%5D/(private-routes)/(customer)/patients/page.tsx) · `PatientCard` (uses `InitialsAvatar`) · add/edit in `FormDialogShell` + `PatientForm` · archive behind `ConfirmDialog` | | — home teaser | `/fa` | `HomeScreen.tsx:87` calls the same `usePatients()` | | E2 — care record | `/fa/patients/[id]/record` | [`record/page.tsx`](../../client/src/app/%5Blocale%5D/(private-routes)/(customer)/patients/%5Bid%5D/record/page.tsx) · 4 tabs داروها / روتین / سوابق / وظایف · `PatientHeader`, `VisitNoteCard`, per-item bottom-sheet edit · `EmptyState` access-denied card gated **before** any clinical fetch | | — nurse counterpart | `/fa/nurse/visits/[id]` | `NurseVisitNotesPanel.tsx:38-44` — same domain, append-only | Age is derived client-side by [`patients/age.ts`](../../client/src/services/patients/age.ts); the wire carries `birthDate`. ## API | Call | Endpoint | Notes | | --- | --- | --- | | `usePatients` → `patientsClientApi.list` | `GET /api/v1/patients/list` | **real** · `clientApi.ts:46` → `PatientsController.List` | | `usePatient` | `GET /patients/get/{id}` | real | | `useCreatePatient` / `useUpdatePatient` | `POST /patients/create` · `/update/{id}` | real · `relation`+`conditions` round-trip (REQ-005) | | `useArchivePatient` | `POST /patients/archive/{id}` | real · **archive, never delete** | | `useRecordAccess` | `GET /patients/{id}/record_access` | server real, **client mocked** | | `usePatientCareRecord` | `GET /patients/{id}/care_record` | server real, **client mocked** | | `useUpdateCareRecord` | `PUT /patients/{id}/care_record` | server real, **client mocked** — the only `PUT` in the API | | `usePatientHistory` | `GET /patients/{id}/care_records` | server real, **client mocked** | | `useCreateVisitNote` (nurse) | `POST /patients/{id}/care_records` | server real, **client mocked** | Handlers: `PatientsController.cs` (Features/Identity/{Commands,Queries}) and `PatientCareRecordsController.cs` → `Features/PatientCareRecords/**`. Request/response shapes live in the integration docs linked above — not here. ## Rules that must hold | Rule | Source | | --- | --- | | A patient is a **first-class row distinct from the customer**; `self` still creates its own row. Customer→patient is 1:N | [business/01](../../product/business/01-actors-and-onboarding.md) | | **`gender` is load-bearing** — it drives same-gender caregiver matching. Never defaulted or inferred | [integration/patients.md](../integration/domains/patients.md) | | **Archive, never delete.** A patient referenced by a booking must stay resolvable | [integration/patients.md](../integration/domains/patients.md) | | **The plan is family-owned; visit records are nurse-owned and append-only.** No edit, no delete endpoint exists and none should | [integration/patient-records.md](../integration/domains/patient-records.md) | | **Two-stage clinical disclosure** — clinical bodies encrypted at rest, decrypted only after the access check; nurse read is scoped by an active booking | INV-6, `server/CLAUDE.md` rule 18 | | **Tenancy mismatch is a 404, never a 403** | [api-contract.md](../integration/api-contract.md#status-codes) | | `initialMedicalNotes` is encrypted PII and is **not** the care record | [integration/patients.md](../integration/domains/patients.md) | ## How to test 1. Log in as `09120000010` (سارا محمدی, customer) — see [testing-setup.md](testing-setup.md). 2. Open `/fa/patients`. **Expect:** two cards — «فاطمه محمدی» and «حسن محمدی». Header reads «حلقهٔ مراقبت». Verified live: `GET /patients/list?page=1&pageSize=10` → `total: 2`. **Note:** both seeded rows carry `relation: null` and `conditions: []`, so the relation chip and the condition chips render nothing. That is seed data, not a UI bug. 3. Tap «افزودن» and save a new person. **Expect:** the card appears immediately (the list is invalidated on mutation) and a green «ذخیره شد». A new `GET /patients/list` now returns `total: 3` — this writes to the **shared remote DB**. 4. Tap a card's archive action and confirm. **Expect:** the card disappears, toast «بایگانی شد». The row is still resolvable server-side (`isActive: false`), so any booking that references it keeps working. 5. Tap a card body to open `/fa/patients/1/record`. **Expect:** the ownership banner «این پرونده متعلق به خانواده است…», then four tabs. **This screen is mock-served.** What you see under داروها / روتین / وظایف is `patientRecords/apis/mockApi.ts` fixtures, and سوابق shows mock notes — **not** the two real seeded care records. Every edit you make is lost on reload (the mock store is module-level). 6. To see the real data the screen is hiding, call it directly: `curl --noproxy '*' "http://localhost:5002/api/v1/patients/1/care_records?page=1&pageSize=20" -H "Authorization: Bearer $T_09120000010"`. **Expect:** `total: 2`, two Persian notes by «زهرا عزیزی» dated 2026-07-16 and 2026-07-25 — and `taskResults` entries whose `label` is `null` (see gaps). 7. Access control, verified live and **only** observable via curl today: | Caller | `record_access` for patient 1 | Result | | --- | --- | --- | | `09120000010` (owner) | `canView: true, canEdit: true, canAppendNote: false` | ✅ | | `09120000001` (nurse 1, has bookings for patient 1) | `canView: true, canEdit: false, canAppendNote: true` | ✅ | | `09120000011` (other customer) | `canView: false, deniedReason: "not_authorized"` | ✅ denies; `GET care_record` for the same caller returns `403` | In the browser the mock's denial path is only reachable via `MOCK_FOREIGN_PATIENT_ID = 8888` → `/fa/patients/8888/record`. The seeded world still supports every step of this flow — it does not depend on booking-request freshness. ## Known gaps - `USE_PATIENT_RECORDS_MOCK = true` (`client/src/services/patientRecords/constants.ts:13`) while **all five** server endpoints are live and return `200`. The customer's care record and the nurse's visit-note panel both render fixtures instead of the database. The single highest-value flag flip in this flow. - Flipping that flag today **breaks the record screen**: `getFamilyRecord` (`patientRecords/apis/clientApi.ts:68-69`) is a bare `unwrap()` with no mapper, and the wire shape does not match. Server `MedicationDto(long Id, string Name, string? Dosage, string Frequency, string? TimingNote)` vs client `Medication { id: string, doseAmount, doseUnit, frequencyCode, frequencyText, timeOfDay: TimeOfDayCode[], timingNote }` — dose/frequency/time-of-day all render blank. - Same flip **crashes the روتین tab**: server `RoutineItemDto.TimeOfDay` is a single `string?`; the client types it `TimeOfDayCode[]` and calls `.map()` on it (`record/page.tsx:616`). - Care-plan `id` type mismatch (hardening H-17, confirmed): server `long`, client `string`. `updateFamilyRecord` `PUT`s the whole record including client temp ids like `new-1754…` (`record/page.tsx:277`), so the write is unsafe against the real endpoint. - **Server defect — `taskResults` labels are lost.** `GetPatientHistoryQuery.Handler.cs:78` deserializes `TaskResultsJson` with default (case-**sensitive**) `JsonSerializer` options, but `DemoLifecycleSeeder.Social.cs:324` seeds camelCase `{"label":…,"done":…}`. Live result for patient 1: three entries of `{"label": null, "done": false}`. Any externally-written JSON in that column silently degrades to blank labels. - **Client discards a field the server does serve.** `toVisitNote` (`patientRecords/apis/clientApi.ts:37`) hardcodes `taskResults: []` although `CareRecordDto.taskResults` is on the wire. The «X از Y» task-summary chip on `VisitNoteCard` can never render on the real path. - `deniedReason` enum mismatch: the server returns `"not_authorized"` (`PatientAccess.cs:20`), while `patientRecords/types.ts:117` and `docs/integration/domains/patient-records.md` both declare `no_access | not_found`. Latent today (the UI renders a generic card), a bug the moment anyone branches on it. - **Stale code comments assert the opposite of reality.** `patientRecords/types.ts:11-13`, `constants.ts:1-12` and `apis/clientApi.ts:62-64` all say the family record and access check have "**no backend**" / are REQ-027 gaps. All three routes exist, are `[Authorize]`d, and were probed `200`. The integration doc is right and the code comments are wrong. - `patients/constants.ts:1-7` still explains the domain "demos behind the mock" because REQ-005 is missing — REQ-005 is delivered and the flag is already `false`. - **Editing a patient destroys their birth date.** `PatientForm.tsx:94` always submits `ageToBirthDate(age)` = `YYYY-01-01`. Saving seeded patient 1 (`birthDate: 1948-03-15`) with no changes rewrites it to `1948-01-01`. Age display survives; the date does not. - Seeded care plans are **empty** — `GET /patients/1/care_record` returns `{ medications: [], routine: [], tasks: [] }`, so three of the four record tabs would be blank the moment the flag flips. `DemoLifecycleSeeder` never seeds a `PatientCarePlan`. - Seeded patients have `relation: null` and `conditions: []`, so the relation/condition chips the UI is built around never appear on demo data. - REQ-057 open: `PatientDto` has no `lastVisitAt`/`visitCount` and `BookingListItemDto` has no `patientId`, so no booking card can show a care teaser. - **No `product/business/` file covers patient care records at all** — no documented rule for the append-only constraint, the encryption, or the clinical-access gate. Only `product/data-model/10-reviews-and-records.md` mentions the table. Largest product-doc hole touching this flow. - Archive is optimistic with no undo; a cross-tenant/stale id 404s and the card silently reappears with a generic «در دسترس نیست» toast (`patients/page.tsx:65-70`).