Care Plans & Care Provider Tasks
Part of the Anaya Care product wiki. See 00-overview.md.
Purpose
This module covers two related but separate "care plan" concepts and the task execution pipeline built on top of them:
ClientCarePlan— the narrative/clinical care plan for a client (ADL/IADL approaches, goals, safety adaptations, memory care, health monitoring, escalation protocol). Authored manually by admins or generated by an AI agent pipeline. Versioned, exportable as PDF.CareProviderTasks— the operational task list ("care plan document" in much of the code/UI) attached to one client schedule. Holds embedded recurring tasks (rrule-based) that caregivers must perform during shifts. Has a DRAFT → PUBLISHED → ARCHIVED lifecycle with version groups.- Task execution — per-shift expansion of published tasks, caregiver submissions on mobile (
TaskSubmission), threshold warnings, geofence validation, and family/representative review with comments and escalatable concerns (TaskSubmissionComment).
Backend code lives in apps/backend/src/clients/ (entities, services, controllers); shared AI-generation constants live in packages/shared/src/care-plan/care-plan-generation.ts.
Entities & Data Model
ClientCarePlan — collection client_care_plans
Defined in apps/backend/src/clients/entities/client-care-plan.entity.ts.
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | Required, indexed (line 238–244) |
client | ObjectId → Client | Required (line 246) |
plan | CarePlanPlan (embedded) | escalation, emergencyProtocol, memoryCareSupport?, adl, iadl, adaptationsSafety, healthMonitoring? (lines 196–218). Each domain item carries assessmentScore?, assessmentNotes?, approach, instructions[], goals[], clientNotes[] (lines 19–38) |
author | ObjectId → User | Required (line 252) |
version | number | Required; monotonically increasing per client (line 255) |
scheduleIds | ObjectId[] | Schedules the plan was generated against (line 258) |
lastUpdated | Date | null | (line 261) |
isCompleted | boolean | Default false; AI generation saves with true (ai/agents/care-plan/care-plan-orchestrator.service.ts:353–359) |
There is no status field; "the latest care plan (highest version) is considered the approved/published version" (client-care-plans.service.ts:173–176). Updates never mutate — every update/revert creates a new version document (client-care-plans.service.ts:268–329, 357–439).
CareProviderTasks — collection care_provider_tasks
Defined in apps/backend/src/clients/entities/care-provider-tasks.entity.ts (lines 188–259).
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | Required, indexed |
client | ObjectId → Client | Required |
schedule | ObjectId → ClientSchedule | Required — one document per client schedule |
tasks | CareProviderTask[] (embedded) | See below |
author | ObjectId → User | Required |
status | CareProviderTasksStatus | DRAFT | PUBLISHED | ARCHIVED (packages/shared/src/enums/domain-status.ts:416–420) |
version | number | Default 1 |
publishedAt | Date? | Set on publish |
previousVersionId | ObjectId → CareProviderTasks | Links draft to its source |
versionGroupId | ObjectId → CareProviderTasks | Groups all versions of a plan; set to own _id on first create |
isLatestPublished | boolean | Exactly one per version group should be true |
metadata | object? | AI generation metadata |
Indexes: {business, createdAt}, {business, client, status}, {client, status} (lines 256–258).
CareProviderTask (embedded in tasks[])
Same file, lines 44–152.
| Field | Type | Notes |
|---|---|---|
taskId | string | UUID-style app-level ID (not _id; subdocs have _id: false) |
templateType | string | Joins to TaskTemplate.type via virtual template (lines 145–150) |
taskMode | TaskMode | scheduled (once per occurrence per shift) or tracking (multiple submissions) (domain-status.ts:425–428) |
tzid | string | Required IANA timezone |
dtstart / rrule / exdate | Date / string / Date[] | RFC 5545 recurrence; null for tracking tasks |
instructionSteps | {id, text, isOptional?}[] | Checklist shown to caregivers |
isRequired | boolean | Used by shift completion check (shifts/shifts.service.ts:4187–4195) |
priority | number | min 1; AI generation clamps 1–5 (client-care-provider-tasks.service.ts:235) |
metadata | object? | e.g. aiGenerated, mealId, populated meal/engagement at read time |
files | ObjectId[] → LockCare | Attachments |
excusedOccurrences | {occurrenceDate, reason, appointmentId?, excusedBy?, excusedAt}[] | Written when a doctor appointment collides with a task occurrence (client-doctors-appointments.service.ts:1276–1330; reason enum TaskExcusalReason.DoctorAppointment, packages/shared/src/types/appointment.ts:195–197) |
TaskTemplate — collection task_templates
Defined in apps/backend/src/clients/entities/task-template.entity.ts (lines 167–210).
| Field | Type | Notes |
|---|---|---|
category | string | e.g. "Vital Signs" |
type | string | Unique; referenced by CareProviderTask.templateType and computed medication tasks (MEDICATION_REMINDER) |
label, description | string | Display |
taskMode | 'scheduled' | 'tracking' | Default mode for tasks of this type |
fields | Map<string, TemplateField> | Dynamic form schema: number/text/textarea/select/radio/multiselect/time/date/file, with min/max/required/warningThreshold (low/high/critical), conditional display (conditionalOn/showWhen) (lines 76–131) |
validationRules | {rule, message}[] | Cross-field rules, e.g. diastolic < systolic (lines 18–25) |
Templates are global (no business field) and seeded from apps/backend/src/clients/data/task-templates.json via TaskTemplatesService.populateTaskTemplates() (task-templates.service.ts:68–103); rePopulateTaskTemplates() wipes and reseeds (lines 122–126).
TaskSubmission — collection task_submissions
Defined in apps/backend/src/clients/entities/task-submission.entity.ts.
| Field | Type | Notes |
|---|---|---|
business, client | ObjectId | Required |
careProviderTasks | ObjectId → CareProviderTasks | Parent task document |
shiftAssignment | ObjectId → ShiftAssignment | Required — submissions are scoped to a shift (lines 67–72) |
taskId | string | Embedded task's taskId, or virtual medication ID medgrp_<taskGroupId>_<scheduleId> (common/utils/compute-medication-tasks.util.ts:16) |
template | ObjectId → TaskTemplate | Required |
caregiver | ObjectId → User | Required |
taskMode | TaskMode | scheduled/tracking |
submissionNumber | number? | Auto-incremented for tracking tasks (client-task-submissions.service.ts:334–342) |
responses | TaskResponseField[] | Field answers |
status | TaskResponseStatus | DRAFT | IN_PROGRESS | SUBMITTED (domain-status.ts:398–402) |
startedAt / completedAt | Date? | Two-step flow timestamps |
wasAutoCompleted | boolean | Set by shift clock-out / stale cron (see workflows) |
location + isLocationValidated | GeoLocation / boolean | Geofence check at submit (client-task-submissions.service.ts:82–94) |
files | ObjectId[] → LockCare | |
stepCompletions | {stepId, completed, completedAt?}[] | Schema exists (lines 128–138); written only by the test-data script scripts/complete-shifts-with-ai.ts, not the production services |
thresholdWarnings | {fieldName, level, value, threshold, message}[] | Non-blocking warnings (lines 140–147) |
Unique partial index unique_scheduled_task_submission_per_shift on {careProviderTasks, taskId, caregiver, shiftAssignment, taskMode} filtered to taskMode: SCHEDULED — enforces one submission per scheduled task per shift per caregiver (lines 160–173).
TaskSubmissionComment — collection task_submission_comments
Defined in apps/backend/src/clients/entities/task-submission-comment.entity.ts.
| Field | Type | Notes |
|---|---|---|
business, taskSubmission, client, author | ObjectId | Required |
content | string | max 2000 chars |
isConcern | boolean | Flags comment as a concern |
concernStatus | ConcernStatus | null | open → acknowledged → resolved (domain-status.ts:407–411) |
acknowledgedBy/At, resolvedBy/At, resolutionNote | audit fields | |
attachments | ObjectId[] → LockCare | |
isDeleted, deletedAt, deletedBy | soft delete | Schema only — no delete endpoint exists in task-submission-comments.controller.ts |
Workflows & State Machines
How a ClientCarePlan comes into existence
Two paths (both via ClientCarePlansService, routes in clients.controller.ts:1026–1170):
- Manual —
POST /clients/:clientId/care-planswith aCarePlanPlanDtoandscheduleIds; the service normalizes (utils/care-plan-normalization.ts) and saves version = latest+1 (client-care-plans.service.ts:212–266).PUT .../:carePlanIddoes not edit in place — it creates a new version copyingscheduleIdsfrom the original (lines 268–329). - AI generation —
generateCarePlan()enqueues acare-plan-generationBullMQ job (one active job per client; lines 441–546).CarePlanProcessor(processors/care-plan.processor.ts:35–83) delegates toCarePlanOrchestratorService(ai/agents/care-plan/), an agentic loop (validation → context builder → content generator → quality evaluator). The context builder pulls the client narrative, medications, and completed assessments (ai/agents/care-plan/agents/context-builder.agent.ts:111–120). Generation modesfast|standard|thoroughand focus areas come frompackages/shared/src/care-plan/care-plan-generation.ts(GENERATION_MODE_PRESETS,CarePlanFocusArea, provider map for OpenAI/Anthropic). On success it callscreateCarePlan(..., isCompleted=true)(care-plan-orchestrator.service.ts:353–359) and notifies the requesting user over the notification WebSocket (CARE_PLAN_GENERATION_COMPLETED).
Revert: revertCarePlanToVersion hard-deletes all newer versions and re-saves the target content as a new highest version (client-care-plans.service.ts:357–439).
There is no approval state machine on ClientCarePlan — version order is the only lifecycle.
How CareProviderTasks (the task document) is created and published
- Empty drafts:
POST /clients/:clientId/care-provider-tasks/draftcreates one DRAFT document per scheduleId, all-or-nothing, rejecting schedules that already have a document (client-care-provider-tasks.service.ts:1031–1104). Emitscare-provider-tasks.created. - AI generation:
POST .../generaterequires the client's latestClientCarePlanto exist and to havescheduleIds(client-care-provider-tasks.service.ts:1404–1434). It auto-targets schedules that don't yet have task documents (or user-selected ones), passes summaries of existing tasks for context, and enqueues acare-provider-tasks-generationjob (lines 1452–1586). The processor (processors/care-provider-tasks.processor.ts+ai/agents/care-provider-tasks/) generates tasks, which are saved as DRAFT v1 documents — tracking duplicates deduped, scheduled tasks sorted chronologically,versionGroupId= own_id,metadata.aiGenerated: true(saveCareProviderTasksFromGeneration, lines 182–261). - Manual task CRUD (admin web): add task (
addTaskToCareProviderTasks, lines 331–385 — validatestemplateTypeexists), update task (lines 904–956), delete task with modesall/this(addsexdate) /thisAndFollowing(rewritesUNTIL=in the rrule) (lines 387–545). - Publish: only DRAFT documents can publish; previously-published versions in the group are atomically archived and the new one gets
isLatestPublished: true+publishedAt(lines 1192–1254). Emitscare-provider-tasks.published, whichClientsService.onCareProviderTasksPublishedlistens to and auto-activates the client on first publish (clients.service.ts:721–743). - New draft from published: copies tasks, increments
version, linkspreviousVersionId(lines 1256–1300). - Revert to a published version: sets it
isLatestPublishedand hard-deletes all newer non-draft versions (lines 1308–1374). - Archive: sets ARCHIVED +
isLatestPublished: false(lines 1379–1402).
How tasks reach a caregiver's shift (per-shift expansion)
Tasks are not stored per shift or per day — they are stored once per schedule with rrule recurrence, and expanded at read time:
GET /clients/:clientId/care-provider-tasks/document/shift/:shiftAssignmentId (client-care-provider-tasks.service.ts:755–891):
- Loads the shift assignment, computes the shift window from
dtstart+durationMinutesintzid. - Finds the single
PUBLISHED+isLatestPublisheddocument for the shift's schedule. If none exists, returns an empty task list (no 404) (lines 798–813). - Filters embedded tasks into the shift window using
filterTasksForShiftWindow(common/utils/shift-task-filter.util.ts), which expandsdtstart/rrule/exdatewith a "fake UTC" workaround for the rrule library's timezone/DST bug. - Excludes any stored legacy
MEDICATION_REMINDERtasks; medication tasks are instead computed on the fly from activeClientMedicationschedules (computeMedicationTasks, virtualtaskIdprefixmedgrp_) (lines 826–862). - Injects per-shift meal data (
ShiftMealAssignment→metadata.mealforMEAL_PREPARATIONtasks) and engagement data (ShiftEngagementAssignment→metadata.engagementforENGAGEMENTtasks) (lines 2152–2358). - Attaches this shift's existing
TaskSubmissions per task with signed file URLs (lines 2384–2483).
Task submission state machine
- Scheduled tasks:
startTaskcreates anIN_PROGRESSsubmission (duplicate blocked by the unique index → 400,client-task-submissions.service.ts:559–568);completeTaskvalidates responses against the template (TaskFieldValidator), checks warning thresholds against client health baselines (TaskThresholdValidator,validators/task-threshold.validator.ts— client-specific baseline overrides template default), validates files, setsSUBMITTED(lines 611–838). - Tracking tasks:
submitTrackingTaskdoes everything in one step with auto-incrementedsubmissionNumber(lines 157–464). - Virtual medication tasks resolve via
resolveTask()— amedgrp_-prefixed taskId maps to aClientMedicationtask group and uses theMEDICATION_REMINDERtemplate (lines 127–150). Completing one with amedicationChecklistemitsMEDICATION_TASK_COMPLETED_EVENTfor stock decrementing/notifications (lines 1368–1397). - Threshold breaches at
high/criticalemitTHRESHOLD_BREACH_EVENTto the notification module (lines 96–120). Warnings never block submission. - Geofence:
isLocationValidatedis computed against the client's address withinGEOFENCE.TASK_SUBMISSION_RADIUS; failure does not block submission (lines 82–94). - Auto-completion: at shift clock-out, all
IN_PROGRESSsubmissions for that shift are force-SUBMITTEDwithwasAutoCompleted: true(shifts/shifts.service.ts:4203–4231); a cron also force-submits anyDRAFT/IN_PROGRESSsubmissions older than the stale threshold every 30 minutes across all tenants (shifts/services/auto-complete-tasks.service.ts:25–53).
Review: comments and concerns
There is no approve/reject status on submissions. Instead, post-hoc review happens via comments (TaskSubmissionCommentsService):
- Anyone with
TASK_COMMENTS_CREATEcan comment on a submission; settingisConcern: trueopens a concern (concernStatus: open) and notifies all active Admin/Owner users of the business (task-submission-comments.service.ts:55–97, 249–279). Plain comments notify the submitting caregiver and the client's active representatives (lines 281–319). - Concerns are acknowledged (
open → acknowledged) and resolved (open|acknowledged → resolvedwith optional note) by users withTASK_CONCERNS_MANAGE; the author is notified at each step (lines 135–205). GET /task-submission-concernslists/filter concerns business-wide (lines 207–247).
Family/representative read model
RepresentativeTasksService (representative-tasks.service.ts) gates everything on an active RepresentativeProfile link to the client (lines 58–75) and provides: linked clients, daily tasks (all tasks of all latest-published documents + that day's submissions, marked not_submitted/status, lines 90–185), submission history, submission detail, and stats. Stats use a rough estimate expectedTasks = scheduledTaskCount × dayCount — rrule expansion is explicitly skipped (lines 310–323).
Business Rules & Constraints
- One
CareProviderTasksdocument per schedule — enforced on draft create, draft update, and schedule reassignment (client-care-provider-tasks.service.ts:997–1008, 1051–1063, 1151–1163). - Only
DRAFTdocuments can be edited or published; onlyPUBLISHED+isLatestPublishedcan spawn a new draft (client-care-provider-tasks.service.ts:1128–1130, 1205–1207, 1271–1281). - Exactly one scheduled-task submission per (task, caregiver, shift) — DB-level partial unique index (
task-submission.entity.ts:160–173); tracking tasks allow unlimited submissions withsubmissionNumber. - Caregivers may only complete/update/delete their own submissions (
client-task-submissions.service.ts:625–629, 966–971, 1069–1073); the shift assignment must belong to the submitting caregiver, client, and business (lines 204–211). - AI task generation requires an existing latest
ClientCarePlanwithscheduleIds(client-care-provider-tasks.service.ts:1413–1434). - Adding a task validates the
templateTypeagainsttask_templates(client-care-provider-tasks.service.ts:354–360, 1933–1964). - Multi-tenancy: most queries filter
business; SuperAdmin (businessIdnull) bypasses the filter (getBusinessFilter,client-care-provider-tasks.service.ts:134–140). Exception:ClientCarePlanreads/writes filter only byclientId, not business (client-care-plans.service.ts:149–210). - Deleting a schedule cascades: deletes all
ClientCarePlans referencing it inscheduleIdsand allCareProviderTasksdocuments for it (client-schedules.service.ts:766–769→deleteCarePlansByScheduleId/deleteCareProviderTasksByScheduleId). - Per-route permissions (
BusinessPermission):CARE_TASKS_VIEW_ASSIGNED(reads),CARE_TASKS_CREATE/EDIT/DELETE,CARE_TASKS_GENERATE,CARE_TASKS_PUBLISH,CARE_TASKS_SUBMIT(start/complete/submit),TASK_COMMENTS_CREATE,TASK_CONCERNS_MANAGE(client-care-provider-tasks.controller.ts,client-task-submissions.controller.ts,task-submission-comments.controller.ts). - One in-flight generation job per client for both queues; cancellation via
CancellationRegistry+ job-data flag (client-care-plans.service.ts:459–488, 740–898;client-care-provider-tasks.service.ts:1521–1546, 1671–1802). - Threshold warnings prefer client-specific
healthBaselines[templateType][fieldName]over template defaults (validators/task-threshold.validator.ts:39–46). - Care-plan create/update/delete emits platform activity logs and bumps the client's
updatedAt(client-care-plans.service.ts:58–80).
The change-of-conditions cascade (critical pain point)
What exists today, traced from this module's side:
- APIs that can update an existing care plan: only the manual endpoints —
PUT /clients/:clientId/care-plans/:carePlanId(creates a new version),POST .../revert, and re-running AI generation (POST .../generate-care-planflow viagenerateCarePlan). All are user-initiated (clients.controller.ts:1050–1105,client-care-plans.service.ts). - Nothing updates tasks when the plan changes. There is no listener, hook, or job that reacts to a new
ClientCarePlanversion by updatingCareProviderTasks.createCarePlan/updateCarePlanemit only platform-log events (client-care-plans.service.ts:252–257, 315–320). The only event consumers touching this module are:care-provider-tasks.published→ client auto-activation (clients.service.ts:721), andclient-meal.rejected/client-meal.unassigned→ clearingmetadata.mealIdfrom MEAL_PREPARATION tasks (client-care-provider-tasks.service.ts:2526–2561). - Change-of-conditions observations don't reach the plan either. The health-observations module emits
health-observation.submitted(sole listener:shift-handovers/listeners/handover-trigger.listener.ts:31, which triggers a handover — not a plan update) andhealth-observation.reviewed, which has no listener anywhere in the backend (health-observations.service.ts:192, 225; repo-wide grep finds no@OnEvent('health-observation.reviewed')). The web "Change of Conditions" page (apps/web/app/(app)/(admin)/dashboard/clients/[id]/edit/change-of-conditions/) is review-only; its review dialog contains no care-plan action. - Assessments don't cascade.
ClientAssessmentsServiceemits only platform-log events (client-assessments.service.ts:70–91). Assessments influence care plans only when an admin manually triggers AI regeneration (the context builder reads completed assessments at generation time,ai/agents/care-plan/agents/context-builder.agent.ts:111–120). - Net effect — the sync is fully manual: condition changes → someone must (1) regenerate or edit the
ClientCarePlan(new version), then (2) separately create a newCareProviderTasksdraft, edit tasks, and publish. Worse,generateCareProviderTasksby default skips schedules that already have task documents ("Care provider tasks already exist for all schedules in this care plan",client-care-provider-tasks.service.ts:1480–1489), so post-change regeneration requires explicitly selecting schedules, and the generated output is a brand-new version group rather than an update of the existing one. - Exceptions that do auto-propagate (because they're resolved at read time, not stored): medication tasks are computed per shift from current
ClientMedicationdata (client-care-provider-tasks.service.ts:843–861), and meal/engagement details are populated per shift. Medication changes therefore flow to caregivers without touching the task document.
Flagged: plan→task synchronization is absent; see Open Questions #1–3.
Relationship to the approved care proposal
The care proposal (care-proposals/entities/care-proposal.entity.ts:181–203) embeds its own carePlan object with a completely different shape (cover letter, memoryCare[], physicalHealthAndSafety[], etc. as flat id/value lists). Repo-wide search finds no code in the care-proposals module that reads or writes ClientCarePlan — proposal approval does not snapshot into a ClientCarePlan. The clinical care plan is created independently from client data/assessments/schedules. The naming collision is historical. See 03-care-proposals.md.
Relationship to shifts/schedules
Tasks are per-schedule, expanded per shift: the published CareProviderTasks document for a shift's ClientSchedule is filtered into the shift's time window via rrule at read time, and submissions are keyed to the ShiftAssignment (task-submission.entity.ts:67–72). Shift completion checks that all required tasks in the window have submissions (shifts/shifts.service.ts:4187–4195), and clock-out auto-submits in-progress tasks. Doctor-appointment collisions write excusedOccurrences onto tasks. See 05-scheduling-and-shifts.md.
Surfaces (Web & Mobile)
Web (admins / care managers)
- Care plan:
apps/web/app/(app)/(admin)/dashboard/clients/[id]/edit/care-plan/page.tsx— loads the latest plan, edits via a form whose save calls the update endpoint (creating a new version),GenerateCarePlanDialog(mode/focus/provider options from@anaya/shared), generation status indicator (WebSocket + polling), version history atcare-plan/[version], compare dialog, and PDF view (_components/view-care-plan-pdf.tsx→ backendpdf-streamroute,clients.controller.ts:1107–1170). - Care provider tasks:
apps/web/app/(app)/(admin)/dashboard/clients/[id]/edit/care-provider-tasks/page.tsx— document list per schedule,GenerateAITasksDialog,CreateManualTasksDialog(empty drafts), per-document page[careProviderTasksId]/page.tsxwith calendar/timeline views, add/edit/view task dialogs with rrule recurrence fields and instruction-step editor, version management (versions/,_components/care-provider-tasks-versions.tsx). - Change of conditions (read-only review of caregiver health observations):
.../edit/change-of-conditions/page.tsx. - Frontend-only rule: the care-plan page form validates against
carePlanPlanSchemaclient-side before save (care-plan/page.tsx:448–456).
Mobile (care providers)
- Daily tasks per shift:
apps/mobile/app/(client)/[id]/daily-tasks/index.tsxusesuseCareProviderTasksByShift(hooks/use-care-provider-tasks.ts:40–44) →GET /clients/:clientId/care-provider-tasks/document/shift/:shiftAssignmentId(lib/care-provider-tasks-api.ts:45); alsodaily-tasks/shift-calendar.tsx. - Task completion:
components/care-provider-tasks/task-details-bottom-sheet.tsxdrives the full flow —startTask(line 288),completeTask(line 328, shows threshold warnings in the success toast, lines 401–405), andsubmitTrackingTask(line 454, "tracking tasks can always be submitted again", line 656). File fields upload viaPOST .../tasks/:taskId/upload-file. - Care plan (read-only):
apps/mobile/app/(client)/[id]/care-plan/index.tsxrenders the latestClientCarePlanviauseLatestCarePlan.
Mobile (family / representatives)
- Care tab:
apps/mobile/app/(app)/(care)/index.tsx+components/care/care-tasks-tab.tsxuseuseRepresentativeDailyTasks→GET /representative/clients/:clientId/tasks/daily(representative-tasks.controller.ts:21); submission detail incomponents/care/task-submission-detail-sheet.tsxwithtask-comment-section.tsxrendering comments and concern status badges (open/acknowledged/resolved).
Cross-Module Dependencies
- Scheduling & shifts (05-scheduling-and-shifts.md):
ShiftAssignmentscopes per-shift task expansion and every submission; shift completion/clock-out reads and auto-submits task submissions (shifts/shifts.service.ts:4187–4231); schedule deletion cascades into both plan collections (client-schedules.service.ts:766–769). - Care proposals (03-care-proposals.md): naming overlap only — no data flow into
ClientCarePlan(see above). - Medications: virtual
medgrp_tasks computed fromClientMedication(compute-medication-tasks.util.ts); completion emitsMEDICATION_TASK_COMPLETED_EVENTconsumed bynotification/listeners/medication-stock-notification.listener.ts:50. - Meals & engagement:
ShiftMealAssignment/ShiftEngagementAssignmentpopulate task metadata per shift;client-meal.rejected|unassignedevents clear task meal refs (client-care-provider-tasks.service.ts:2526–2561). - AI module:
care-plan-generationandcare-provider-tasks-generationBullMQ queues → orchestrators underai/agents/care-plan/andai/agents/care-provider-tasks/; shared presets inpackages/shared/src/care-plan/care-plan-generation.ts. - Appointments: doctor-appointment collisions write
excusedOccurrences(client-doctors-appointments.service.ts:1276–1330). - Notifications: threshold breaches (
THRESHOLD_BREACH_EVENT), comment/concern notifications (NotificationTaskCommentType.*), care-plan generation completion viaNotificationGateway. - Files (LockCare/S3): task attachments and submission files with presigned URLs (
lock-care.service,files.service). - Clients lifecycle: first task publish auto-activates the client (
clients.service.ts:721–743); plan activity feeds platform logs (platform-logs). - PDF:
PdfService.generateCarePlanPdf(client-care-plans.service.ts:551–623). - Shift summaries / reports: shift summary and tracking-summary services read submissions for change-of-condition narratives (
shift-summary.service.ts,client-tracking-summary.service.ts) — read-only consumers.
Open Questions & Gaps
- No change-of-conditions cascade. A reviewed health observation (
health-observation.reviewed) has no listener anywhere; nothing connects observations, assessments, or client condition changes toClientCarePlanorCareProviderTasksupdates. The entire plan-and-task refresh after a condition change is manual, multi-step, and easy to skip. Whether automation was intended cannot be determined from code (the event is emitted with anapprovedflag that nothing consumes —health-observations.service.ts:225–228). - No plan→task sync. Publishing a new
ClientCarePlanversion does not invalidate, flag, or regenerate theCareProviderTasksdocuments that were generated from an older plan version.CareProviderTasksdoesn't even record which care-plan version it was generated from (only generic AImetadata), so staleness is undetectable. - Regeneration friction after changes:
generateCareProviderTasksdefaults to schedules without existing task documents and errors with "Care provider tasks already exist for all schedules" otherwise (client-care-provider-tasks.service.ts:1484–1488); regenerating after a condition change requires explicitly passing scheduleIds, and produces a new version group disconnected from the old one. - Phantom
isActivefield:createDraftandsaveCareProviderTasksFromGenerationsetisActive: true(client-care-provider-tasks.service.ts:245, 1074) andgenerateCareProviderTasksqueries{ isActive: true }(line 1456), butCareProviderTaskshas noisActiveschema prop — the value is never persisted and the query filter matches on a nonexistent field. Effect depends on MongoosestrictQueryconfig; cannot determine intended behavior from code. TaskResponseStatus.DRAFTis never set by any production code path (only the stale-task cron queries for it,auto-complete-tasks.service.ts:35);stepCompletionsis written only by the dev scriptscripts/complete-shifts-with-ai.ts. Dead-or-future schema.ClientCarePlanreads are not business-scoped —getCarePlansList/getCarePlanById/getLatestCarePlanfilter only byclientId(client-care-plans.service.ts:149–210), and the:clientId/care-plans*routes inclients.controller.tsrely on the surrounding guard stack; whether cross-tenant clientId probing is blocked upstream cannot be determined from this module.- Destructive version reverts: both
revertCarePlanToVersionandrevertToPublishedVersionpermanentlydeleteManynewer versions (client-care-plans.service.ts:380–394;client-care-provider-tasks.service.ts:1344–1354) — no audit trail of the deleted versions remains. updateTasksilently drops fields: it always overwritesmetadatawithdata.metadata(evenundefined) and cannot changetaskMode,priority,isRequired, ortemplateType(client-care-provider-tasks.service.ts:933–945).- Representative daily tasks ignore recurrence:
getDailyTasksreturns every task in every latest-published document regardless of whether it occurs on the requested date, andgetStatsadmits its expected-task count is a roughtasks × daysestimate (representative-tasks.service.ts:100–185, 317–319). Family-facing completion numbers can be misleading. - Naming collision: code and UI call both
ClientCarePlanandCareProviderTasks"care plan" (e.g. error string "Care plan already exists for schedule" refers toCareProviderTasks,client-care-provider-tasks.service.ts:1006), which is a standing source of confusion. - No review state on submissions: submissions terminate at
SUBMITTED; supervisor review exists only informally via comments/concerns.updateTaskSubmissionhas a TODO for supervisor privileges that was never implemented (client-task-submissions.service.ts:965–971). - Caregivers can delete their own submissions with no audit trail or time limit (
client-task-submissions.service.ts:1054–1078) — compliance-sensitive. medicationTemplateCacheis cached forever (per process) after first read (client-care-provider-tasks.service.ts:118–132); template repopulation won't be reflected until restart.deleteCarePlansByScheduleIddeletes any plan whosescheduleIdsarray contains the schedule (client-care-plans.service.ts:715–735) — a plan spanning multiple schedules is deleted entirely when only one of its schedules is removed.GET .../care-plans/calendaris identical to the list endpoint (clients.controller.ts:1032–1035) — purpose unclear from code.- Comment soft delete is schema-only:
isDeleted/deletedAt/deletedByexist onTaskSubmissionCommentbut no controller/service path deletes comments.