Anaya Care Docs

Clients (Core)

Part of the Anaya Care product wiki. See 00-overview.md.

Purpose

The Clients module is the system of record for the people receiving care ("clients"/"residents"). It owns:

  • The core client profile (demographics, address, timezone, narrative, AI summary, health baselines, DNR/advance-directive flags) — apps/backend/src/clients/entities/client.entity.ts
  • The client lifecycle status state machine (Draft → Active → OnHold → Closed) — packages/shared/src/client-status-transitions.ts
  • Satellite documents created with every client: service information, external care providers (home-health orgs and pharmacies), and the medical record — apps/backend/src/clients/services/clients.service.ts:211-314
  • The care-team link between clients and internal CareProvider users (CaregiverAssignment) — apps/backend/src/clients/services/client-caregiver-assignments.service.ts
  • Linking clients to Representative users (family/decision-makers) and MedicalProfessional users — apps/backend/src/clients/services/clients.service.ts:1788-2159
  • Cascade cleanup when a client is deleted — apps/backend/src/clients/services/client-cleanup.service.ts
  • Aggregate read models: tracking-summary counts for the client sidebar (client-tracking-summary.service.ts) and enriched report data for care-summary PDFs (report-data-enricher.service.ts)

Everything is tenant-scoped: every entity carries a required business ref, auto-injected into queries by the global Mongoose business-scope plugin (apps/backend/src/common/plugins/business-scope.plugin.ts:41-66).

Naming note: the Family role was renamed to Representative (apps/backend/src/migrations/025-family-to-representative-rename.ts); FamilyProfile became RepresentativeProfile (collection representative_profiles). There is no FamilyProfile entity in the codebase today.

Entities & Data Model

Client — collection clients

apps/backend/src/clients/entities/client.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed (client.entity.ts:61-67)
patientIdstring6-char id from a UUID, generated server-side (clients.service.ts:465-467); no uniqueness constraint
firstName / lastName / preferredNamestring
imagestringavatar URL auto-generated if missing (clients.service.ts:221-225)
dateOfBirthDate
sexenum ClientSex
addressAddress subdocincludes lat/lng, used for timezone + caregiver distance
timezonestringresolved from address via Google Maps (clients.service.ts:212-215)
benefitsInquiryBenefitsInquiry subdoc
weight / height{ value, unit }
havePets / petsDescriptionboolean / string
primaryLanguage, maritalStatusstring / enum
careProposalObjectId → CareProposalset when created from a proposal (clients.service.ts:359)
serviceObjectId → ClientService1:1 satellite doc
careProvidersObjectId → ClientCareProviders1:1 satellite doc (external orgs, not caregivers)
medicalRecordObjectId → ClientMedicalRecord1:1 satellite doc
medicalProfessionals[{ user: ObjectId → User }]embedded array (client.entity.ts:143-144)
statusHistory[{ status, notes, timestamp, author }]newest entry at index 0; current status = statusHistory[0].status (clients.service.ts:568-569)
narrativestringpersonal-history narrative (AI-assisted)
aiSummary / aiSummaryUpdatedAtstring / Date
healthBaselinesobject (HealthBaselines)client.entity.ts:158-159
hasDnrOrderboolean, default falselegacy — actively forced to false by sync (see Gaps #7)
hasAdvanceDirectiveboolean, default falsesynced from Care Lock documents (dnr-sync.listener.ts:24-47)
authorObjectId → Usercreator

Indexes: { business: 1, createdAt: -1 } (client.entity.ts:172). toJSON normalizes _idid (client.entity.ts:48-56) — true for all entities below as well.

ClientService — collection client_services

apps/backend/src/clients/entities/client-service.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired
typeenum ClientServiceTypeIn-Home Care, Hospice Care, Respite Care, Placement (client-service.entity.ts:5-10); defaults to In-Home Care on manual create (clients.service.ts:259), copied from proposal otherwise (clients.service.ts:375)
startDateDaterequired
endDateDate?nullable

ClientCareProviders — collection client_care_providers

apps/backend/src/clients/entities/client-care-providers.entity.ts. Despite the name, this is not the care team — it stores external provider organizations.

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired
homeHealthProviders[HomeHealthProvider]{ type, name, phone, email, fax, address } (client-care-providers.entity.ts:6-25)
pharmacies[Pharmacy]{ name, primaryContact, phone, email, fax, address } (client-care-providers.entity.ts:30-49)

A lazy self-healing migration creates this doc if missing on read (clients.service.ts:1276-1305, 2254-2301).

ClientMedicalRecord — collection client_medical_records

apps/backend/src/clients/entities/client-medical-record.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired
medicalEquipments[{ name, quantity }]
allergies[Allergy]enum allergen (Pollen…Other) + severity (Mild/Moderate/Severe) (client-medical-record.entity.ts:6-22)
hospitalizations[Hospitalization]facility + dates
surgeries[{ name, date, notes }]
diagnosesAndConditions[DiagnosisAndCondition]status: Active/Inactive/Resolved/Chronic (client-medical-record.entity.ts:24-29)

Self-heals: findMedicalRecord creates an empty record if the ref is dangling (clients.service.ts:1361-1383).

CaregiverAssignment — collection caregiver_assignments

apps/backend/src/clients/entities/caregiver-assignment.entity.ts. The care team link: one row per (client, caregiver) pair.

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired, indexed
caregiverObjectId → User (CareProvider)required, indexed
invitedByObjectId → Userrequired; may be a system actor for job-posting auto-assigns
invitedAtDaterequired
currentStatusenum CaregiverInviteStatusInvited, Accepted, Declined, Inactive, Assigned (packages/shared/src/enums/domain-status.ts:197-203)
statusHistory[{ status, timestamp, updatedBy, notes }]appended on each change (push to end, unlike Client which unshifts)
notesstring?
prioritynumber | nullcare-team ordering; appended at end on invite (client-caregiver-assignments.service.ts:163-177), reorderable in bulk (:530-558)

Indexes: unique { client: 1, caregiver: 1 }, plus { caregiver, currentStatus }, { client, currentStatus }, { client, priority } (caregiver-assignment.entity.ts:99-105).

ClientCareProviders vs CaregiverAssignment: ClientCareProviders = external organizations (home health agencies, pharmacies) stored as contact-info subdocuments. CaregiverAssignment = internal CareProvider users assigned to the client's care team, with status/priority. They are unrelated despite similar names (see Gaps #5).

RepresentativeProfile — collection representative_profiles

apps/backend/src/users/entities/representative-profile.entity.ts (users module; documented here because it is the client↔family link).

FieldTypeNotes
userObjectId → Userrequired
clientObjectId → Clientrequired — every representative link points at exactly one client
relationshipTypeenum RelationshipTyperequired
relationshipDescription / notesstring?
decisionAuthorities[DecisionAuthority]default []
isActiveboolean, default trueremoval is a soft-delete (clients.service.ts:2094-2097)

Indexes: { user, isActive }, { client, isActive }, and unique { user, client } partial-filtered on isActive: true (representative-profile.entity.ts:63-68). Note: no business field, so this collection is not auto-scoped by the business plugin.

How a Representative (family) user is linked to a client: an admin either creates a new Representative user + profile (clients.service.ts:1994-2036) or links an existing Representative-role user (clients.service.ts:2038-2084). A Representative can then only read clients they have an active profile for — enforced in ClientsService.findOne (clients.service.ts:1109-1124).

CareProviderProfile — collection care_provider_profiles

apps/backend/src/users/entities/care-provider-profile.entity.ts. The caregiver's résumé profile (userId, about, workExperiences, education, licenses, skills, averageRating, isAcceptingJobs). It does not link caregivers to clients — that is CaregiverAssignment's job. It is read by caregiver matching (client-caregiver-assignments.service.ts:593-664).

DnrAcknowledgment — collection dnr_acknowledgments

apps/backend/src/clients/entities/dnr-acknowledgment.entity.ts — lives in this module and is cascade-deleted with the client (client-cleanup.service.ts:82), with endpoints on the clients controller (clients.controller.ts:1418-1454). Full behavior is covered in 08-health-monitoring.md.

Workflows & State Machines

Client status state machine

States: Draft, Active, OnHold, Closed (packages/shared/src/enums/domain-status.ts:165-170). Current status is statusHistory[0].status; new entries are unshifted to position 0 with optimistic concurrency (clients.service.ts:595-625).

Who can trigger what (packages/shared/src/client-status-transitions.ts:19-54):

  • SuperAdmin / Owner: full admin transition map, bypassing permission checks (client-status-transitions.ts:47-49).
  • Any role holding BusinessPermission.CLIENTS_CHANGE_STATUS (typically Admin/CareManager): same admin map (client-status-transitions.ts:50-52).
  • Everyone else: empty map — no manual transitions (client-status-transitions.ts:53).
  • Draft → Active is deliberately absent from the manual map (client-status-transitions.ts:20-23). It is written only by autoActivateOnFirstPublish, triggered by the care-provider-tasks.published event when the first care plan is published (clients.service.ts:652-743; emit site client-care-provider-tasks.service.ts:1246-1251). The transition is recorded with the sentinel note CLIENT_AUTO_ACTIVATION_NOTE (client-status-transitions.ts:116-117) and skipped entirely if the publish event has no triggeredByUserId (clients.service.ts:728-732).
  • Closed is terminal — no outgoing transitions (client-status-transitions.ts:32), with a dedicated error message (client-status-transitions.ts:84-89).

Side-effect guard: OnHold and Closed are "blocked operation" statuses (client-status-transitions.ts:103-108) — see Business Rules.

Client creation (three paths)

All three end in Draft with the three satellite docs created and back-referenced:

  1. Manual (web "Add Client"): POST /clientscreateClient builds the Client (timezone from Google Maps, avatar if no image), then sequentially creates ClientService (In-Home Care, startDate=now), ClientCareProviders (empty), ClientMedicalRecord (empty), and emits a CREATED platform log (clients.service.ts:211-314). Roles: any authenticated user — there is no permission decorator on this endpoint (clients.controller.ts:126-129).
  2. From a care proposal: POST /clients/from-care-proposal/:careProposalId → requires the proposal to be complete (clients.service.ts:322-326), copies clientInfo, seeds the Simplified Routine Task Inventory assessment from the proposal (clients.service.ts:423-427), then best-effort marks the proposal InService linking back to the new client (clients.service.ts:432-446). See 03-care-proposals.md.
  3. Duplicate: POST /clients/:clientId/duplicate (requires CLIENTS_MANAGE, clients.controller.ts:131-135) — copies personal info, satellite docs, all assessments, and medications (without files); fresh patientId and Draft status (clients.service.ts:1651-1786).

Caregiver assignment lifecycle

Roles: Admin/CareManager act on the client's care team (web); CareProvider responds (mobile).

  1. Assign: POST /clients/:clientId/caregiver-assignments/:caregiverIdinviteCaregiver verifies client status is not OnHold/Closed (client-caregiver-assignments.service.ts:135-141), target user has CareProvider role (:144-147), and no non-Inactive assignment exists (:150-160). The assignment is created directly in Assigned status ("always direct assign", :168-186) with priority appended at the end. Emits caregiver.invitation-sent notification event and a platform log (:191-210).
  2. Auto-assign from job postings: the job-application.accepted-for-client event triggers the same inviteCaregiver with a note, wrapped in CLS so business scoping works outside HTTP (listeners/caregiver-auto-assign.listener.ts:25-65). ConflictException (already assigned) is swallowed.
  3. Status change: PUT /clients/caregiver-assignments/:assignmentId/statusupdateCaregiverStatus writes any CaregiverInviteStatus with no transition validation and no role/permission restriction (client-caregiver-assignments.service.ts:215-267, controller clients.controller.ts:1341-1352).
  4. Accept / decline (mobile caregiver): POST /clients/:clientId/caregiver-assignments/invitation/accept|decline — only matches assignments in Invited status (client-caregiver-assignments.service.ts:269-313). Since step 1 always creates Assigned, these only fire for assignments put into Invited some other way (see Gaps #4).
  5. Remove / delete: DELETE /clients/:clientId/caregiver-assignments/:caregiverId hard-deletes the row and emits caregiver.removed (client-caregiver-assignments.service.ts:487-528; removeCaregiver at :315-359 is the non-Inactive variant).
  6. Reorder: PUT /clients/:clientId/caregiver-assignments/reorder (requires CLIENTS_MANAGE) bulk-writes priorities after verifying every assignment belongs to the client (client-caregiver-assignments.service.ts:530-558).
  7. Matching: GET /clients/:clientId/caregiver-assignments/available ranks unassigned CareProviders by Haversine distance from the client's address, rating, and skill match; optionally enriches with shift-conflict and daily-hour-limit checks via ShiftsService (client-caregiver-assignments.service.ts:560-723).

Representative (family) linking

Admin-side only (web): create-new (clients.service.ts:1994-2036), select-existing (must already hold Representative role, :2038-2084), update (:2108-2142), soft-remove by isActive=false (:2086-2106), send invite (:2144-2159). Email/phone uniqueness is checked platform-wide with same-business hinting (clients.service.ts:745-796). Representative users have no self-service linking flow in this module.

Client deletion (cascade)

DELETE /clients/:clientId (requires CLIENTS_ARCHIVE, clients.controller.ts:646-659) is a hard delete (clients.service.ts:1568-1649):

  1. Optionally deletes the linked care proposal (+ optionally its initial assessment), otherwise just clears the proposal's InService status (:1582-1595).
  2. Deletes the three satellite docs (:1597-1619).
  3. Deletes all assessments and meal assignments (:1621-1628).
  4. ClientCleanupService.deleteAllForClient deletes 17 collections' rows referencing the client: caregiver assignments, schedules, care-provider tasks, care plans, task submissions (+comments), shift summaries, DNR acknowledgments, engagement activities, medication logs, doctors appointments, appointment-history summaries, shift assignments, care-readiness assessments, health observations, shift engagement assignments, incident reports (client-cleanup.service.ts:73-130).
  5. A SuperAdmin-only orphan sweep endpoint exists for pre-cascade data: POST /clients/admin/cleanup-orphans?dryRun=false (clients.controller.ts:117-124, client-cleanup.service.ts:142-216).

Note: RepresentativeProfile rows are not in the cascade list (see Gaps #9).

Business Rules & Constraints

  • Current client status is always statusHistory[0].status, defaulting to Draft when history is empty (clients.service.ts:568-569).
  • Manual status changes must pass the shared role/permission state machine; invalid transitions throw 403 with the explanatory message (clients.service.ts:577-593).
  • Status writes use optimistic concurrency on statusHistory.0.status — a concurrent change yields 409 (clients.service.ts:595-625); auto-activation silently no-ops on the same race (clients.service.ts:674-701).
  • Auto-activation only runs from Draft (idempotent across re-publishes) and never when the publish event lacks a triggering user (clients.service.ts:664-671, :728-732).
  • OnHold/Closed clients block: new caregiver assignments (client-caregiver-assignments.service.ts:135-141), new schedules (client-schedules.service.ts:117), and new shifts (shifts.service.ts:179). Draft is allowed so care teams can be set up during onboarding (packages/shared/src/client-status-transitions.ts:97-104). The shared comment also claims care proposals are blocked, but no usage exists in the care-proposals module (see Gaps #10).
  • Closed is terminal; re-admission requires a new client record (packages/shared/src/client-status-transitions.ts:16-17, :84-89).
  • One assignment per (client, caregiver) pair — unique index (caregiver-assignment.entity.ts:102) plus an application-level conflict check on non-Inactive assignments (client-caregiver-assignments.service.ts:150-160).
  • Only users with role CareProvider can be assigned as caregivers (client-caregiver-assignments.service.ts:144-147).
  • CareProvider access to a client requires an Accepted or Assigned assignment (validateCaregiverAccess, client-caregiver-assignments.service.ts:431-452); admins bypass via hasAdminPrivileges.
  • Representative users can only read clients they hold an active RepresentativeProfile for (clients.service.ts:1109-1124) — enforced in findOne only, not in findAll.
  • A user being created as a representative/medical professional must have a platform-unique email and phone; same-business duplicates get a "Select Existing" hint (clients.service.ts:745-796).
  • An existing user can only be linked as representative if their role is Representative, and double-linking an active profile is rejected (clients.service.ts:2050-2062).
  • Representative removal is a soft-delete (isActive=false), preserving history (clients.service.ts:2094-2097).
  • Client timezone is recomputed whenever the address coordinates change on update (clients.service.ts:497-507).
  • Update uses Object.assign from the DTO, but the global ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }) strips unknown fields, so statusHistory etc. cannot be mass-assigned (clients.service.ts:485, apps/backend/src/main.ts:41-44).
  • hasAdvanceDirective is derived data: whenever a Care Lock document in the ADVANCE_DIRECTIVE folder changes, the flag is recomputed from the document count and hasDnrOrder is forced to false (listeners/dnr-sync.listener.ts:24-47; intent per apps/backend/src/scripts/migrate-dnr-orders-to-advance-directives.ts:1-10).
  • All queries are tenant-scoped automatically via the business-scope plugin reading CLS; callers may opt out with _bypassBusinessScope (used by the platform-wide email/phone check, clients.service.ts:750-764).
  • PHI access to client detail and medical record is audit-logged via @AuditPhiAccess (clients.controller.ts:199, :228).
  • List view enriches each client with caregiver assignments and the next active shift (in-progress shifts ranked first, lookback window 24h) (clients.service.ts:955-1079).

Surfaces (Web & Mobile)

Web (Next.js — admins / care managers)

  • Client listapps/web/app/(app)/(admin)/dashboard/clients/page.tsx: paginated list calling GET /clients with search/status/sort params and per-status counts; "Add Client" is permission-gated client-side via usePermissions (page.tsx:26-50).
  • Add clientapps/web/app/(app)/(admin)/dashboard/clients/add/page.tsx.
  • Client workspaceapps/web/app/(app)/(admin)/dashboard/clients/[id]/edit/* with ~40 sub-pages: personal-details, service-information, care-providers (external orgs), medical-record, medications, medical-professionals, representatives, narrative-personal-history, health-baselines, care-team (+ care-team/find for the matching/ranking UI), schedules, shifts, calendar, care-plan, care-provider-tasks, assessments/*, care-summary, tracking pages (wound-care, health-metrics, incident-reports, essential-needs, home-inventory, lock-care, change-of-conditions, doctors-appointments), and more. Sidebar badges are fed by GET /clients/:id/tracking-summary (client-tracking-summary.service.ts:58-113).
  • Status change dialogapps/web/components/clients/change-client-status-dialog.tsx: computes the allowed next statuses with the shared getValidClientNextStatuses from the user's session role + permissions (:66-69) and hides the control without CLIENTS_CHANGE_STATUS (:71-73). The same machine is re-validated server-side (clients.service.ts:577-593), so this is defense-in-depth, not frontend-only.

Mobile (Expo — care providers, representatives)

  • Client workspaceapps/mobile/app/(client)/[id]/*: dashboard (index.tsx), resident-profile, care-plan, daily-tasks (+ shift calendar), medications.tsx, meals, appointments, doctor-files, engagement-activities, essential-needs, health, health-observations, home-inventory, incident-reports, wound-care, lockare (document vault), calendar.tsx. The layout only gates on "fully verified" session, not role (apps/mobile/app/(client)/_layout.tsx); which client ids a caregiver can open comes from their assignments.
  • Assignments & invitationsapps/mobile/lib/caregiver-assignment-api.ts wraps /clients/caregiver-assignments/my-assignments, /invitations, /my-invitations/count, and accept/decline endpoints; surfaced in apps/mobile/app/(app)/(tabs)/invitations/ and apps/mobile/app/(shared)/invitations/. Note the backend currently creates assignments directly as Assigned, so the accept/decline screens have no reachable Invited rows from the standard flow (Gaps #4).
  • Server-side, caregiver-facing reads are limited to the caller's own rows (getMyAssignments/getMyInvitations return [] for non-CareProvider roles, client-caregiver-assignments.service.ts:397-429).

Rules enforced only in frontend code

  • The web list page hides the "Add Client" button by permission, but POST /clients has no @RequirePermissions decorator (clients.controller.ts:126-129) — creation is effectively open to any authenticated user in the business.
  • More broadly, most @RequirePermissions decorators on this controller appear not to be enforced at runtime due to guard ordering (see Gaps #1) — making the web UI's usePermissions gating the de facto enforcement layer for several actions.

Cross-Module Dependencies

  • clients → care-proposals: forwardRef injection of CareProposalsService; reads proposal completeness, marks InService on client creation, removes/unlinks on client delete (clients.service.ts:125-126, :316-463, :1582-1595). See 03-care-proposals.md.
  • care-plans/tasks → clients: listens to care-provider-tasks.published (EventEmitter2) to auto-activate Draft clients (clients.service.ts:721-743; emitter in client-care-provider-tasks.service.ts:1246-1251). See 04-care-plans.md.
  • clients → shifts: injects ShiftsService (forwardRef) for conflict/daily-hour checks in caregiver matching (client-caregiver-assignments.service.ts:54-56, :689-720); reads ShiftAssignment for next-shift enrichment (clients.service.ts:969-1036); shifts.service.ts:179 consumes isClientOperationBlocked. See 05-schedules-shifts.md.
  • clients → notifications: emits caregiver.invitation-sent, caregiver.status-updated, caregiver.removed events consumed by the notification module (client-caregiver-assignments.service.ts:191-202, :244-256, :341-350).
  • clients → platform-logs: emits PLATFORM_LOG_ACTIVITY_EVENT for create/update/status-change/delete/duplicate and every sub-resource edit (clients.service.ts:301-311, :526-543, :627-638); PHI reads audited via @AuditPhiAccess (clients.controller.ts:199).
  • job-postings → clients: job-application.accepted-for-client event auto-assigns the hired caregiver (listeners/caregiver-auto-assign.listener.ts:25-65).
  • lock-care ↔ clients: file uploads create LockCare records (clients.service.ts:2161-2202); lockcare.document.changed events sync hasAdvanceDirective (listeners/dnr-sync.listener.ts).
  • clients → google-maps / avatar / pdf / AI: timezone resolution (clients.service.ts:212-215), avatar generation (:221-225), narrative generation via AIClientNarrativeReportService (:1534-1539), AI summary service (clients.controller.ts:422-435).
  • clients ← assessments / medications (in-module services): client creation seeds the Simplified RTI from the proposal; duplicate copies assessments and medications (clients.service.ts:423-427, :1745-1771). See 02-assessments.md and 07-medications.md.
  • tracking summary reads (read-only, parallel counts): wound-care, health-metrics, health-observations, incident-reports, essential-needs, home-inventories, lock-care, shifts, doctors-appointments, shift summaries (client-tracking-summary.service.ts:33-56). See 08-health-monitoring.md.
  • report enrichment reads: incident-reports, health-observations, business, users to build care-summary report sections (report-data-enricher.service.ts:47-89).
  • cascade deletes write into: shifts, health-observations, client-engagements, incident-reports, plus 13 in-module collections (client-cleanup.service.ts:73-108).
  • users module: creates/updates Representative and MedicalProfessional users and sends invites via UsersService (clients.service.ts:1834-1836, :2014, :2158).

Open Questions & Gaps

  1. @RequirePermissions on ClientsController appears unenforced. PermissionsGuard is registered only as a global APP_GUARD (apps/backend/src/app.module.ts:292-294) and explicitly defers (returns true) when req.user is not yet populated (apps/backend/src/auth/guards/permissions.guard.ts:67-72). Global guards run before the controller-level JwtAuthGuard (clients.controller.ts:92), and ClientsController does not bind PermissionsGuard at controller level — unlike e.g. statistics.controller.ts:9 and home-inventories.controller.ts:27, which follow the documented @UseGuards(JwtAuthGuard, PermissionsGuard) pattern. Consequence: CLIENTS_CHANGE_STATUS, CLIENTS_MANAGE, CLIENTS_VIEW_ASSIGNED, CLIENTS_ARCHIVE, and SHIFTS_EXECUTE checks on this controller are likely no-ops at the HTTP layer (the status change still re-validates permissions in the service, clients.service.ts:577-593, but delete/duplicate/list do not). Needs runtime confirmation and likely a one-line fix.
  2. "View assigned" scoping isn't implemented in findAll. GET /clients requires CLIENTS_VIEW_ASSIGNED (clients.controller.ts:186-187) but the query never filters by the caller's assignments or representative links — any caller passing the guard sees every client in the business (clients.service.ts:858-1100). The per-caller restriction exists only in findOne and only for Representatives (clients.service.ts:1109-1124). Cannot determine from code whether CareProviders/Representatives are meant to call this list endpoint at all.
  3. updateCaregiverStatus has no state machine and no authorization. Any authenticated user can set any CaregiverInviteStatus on any assignment in their business (client-caregiver-assignments.service.ts:215-267; route clients.controller.ts:1341-1352). Contrast with the carefully role-gated client status machine.
  4. The Invited caregiver flow looks dead. inviteCaregiver always creates assignments as Assigned ("always direct assign", client-caregiver-assignments.service.ts:168-175), yet accept/decline endpoints only match Invited (:273-277, :296-300) and the mobile app ships full invitation screens (apps/mobile/app/(app)/(tabs)/invitations/). Either the invite-then-accept flow was removed verbally and the consumer screens are leftovers, or Invited rows are produced somewhere not found in this module. Needs a decision: remove the dead path or restore opt-in invitations.
  5. Naming collision: ClientCareProviders (external orgs/pharmacies, client_care_providers) vs CaregiverAssignment (internal care team) vs the web route care-providers vs the role CareProvider. Repeated source of confusion; consider renaming the entity (e.g. ClientExternalProviders).
  6. Dead fields written on create: createClient/createClientFromCareProposal/duplicateClient set caregivers: [] and families: [] (clients.service.ts:247-249, :360-364, :1702), but neither path exists on the Client schema anymore (families removed in migration 023-profile-entity-rename-and-family-cleanup.ts). Mongoose silently drops them — should be deleted from the service code.
  7. hasDnrOrder is a zombie flag. The sync listener unconditionally forces it to false (dnr-sync.listener.ts:40-43) per the DNR→advance-directive migration (scripts/migrate-dnr-orders-to-advance-directives.ts), yet the field remains on the schema and dnr-acknowledgment.service.ts:56-62 still ORs it into "acknowledgment required" logic. Intended end-state (keep or drop the field) cannot be determined from code.
  8. Report metric bug: taskCompletionRate is inflated 100×. buildCareDeliveryMetrics computes taskCompletionRate already as a 0–100 percentage (report-data-enricher.service.ts:138-142) and then returns Math.round(taskCompletionRate * 100) (:186) — a 50% coverage rate is reported as 5000. coveragePercent (:189-193) computes the same ratio correctly, suggesting taskCompletionRate was meant to be 0–1.
  9. RepresentativeProfile rows are not cascade-deleted with the client. The cleanup list (client-cleanup.service.ts:73-108) covers 17 collections but not representative_profiles, leaving active profiles pointing at deleted clients (they would also dodge the orphan sweep, which only scans cascade-tracked collections). Representative findOne access checks would simply fail, but list/UI joins may surface dangling links.
  10. Hard-block scope mismatch with its own documentation. CLIENT_BLOCKED_OPERATION_STATUSES is documented as blocking "new shifts, schedules, and care proposals" (client-status-transitions.ts:97-101), but isClientOperationBlocked is only consumed by schedules, shifts, and caregiver assignments — no usage in the care-proposals module. Either the comment overstates or a proposal-side guard is missing.
  11. patientId collisions are possible: 6 hex chars from a UUID with no uniqueness check or index (clients.service.ts:465-467), and it is searchable as an identifier (clients.service.ts:837-839). ~16.7M space; collisions become plausible at scale within a business.
  12. Unprotected utility/migration endpoints: POST /clients/migrate/care-providers, GET /clients/migrate/care-providers/stats, POST /clients/clear-background-jobs (clients.controller.ts:1174, :1251-1264) have no role/permission decorators beyond JWT. getRecentUpdatedClients and findSnippet are likewise unrestricted (clients.controller.ts:137-140, :204-207) — findSnippet uses an aggregate matching only _id; business scoping then depends entirely on the aggregate hook of the business-scope plugin.
  13. Sequential multi-save creation is not transactional. All three creation paths save the Client, then each satellite doc, with three intermediate client.save() calls (clients.service.ts:254-299); a crash mid-way leaves partially-wired clients. The self-healing reads (:1276-1305, :1361-1383) paper over this for two of three satellite docs, but service has no self-heal.
  14. deleteClient permission is named CLIENTS_ARCHIVE but performs an irreversible hard delete across 20+ collections (clients.controller.ts:646-659, clients.service.ts:1568-1649). Given Closed was designed as the terminal audit-preserving state, whether hard delete should remain admin-reachable looks like a product decision made verbally.

On this page