Daily Living (Meals, Engagement, Needs, Inventories, Motivations)
Part of the Anaya Care product wiki. See 00-overview.md.
Purpose
This module group covers the "daily living" support features that surround a client's care plan:
- Client Meals — an AI-assisted meal bank per business. Meals are authored manually or generated by an agentic AI pipeline, linked to clients through a junction table with a per-client approval workflow, and then attached to specific
MEAL_PREPARATIONtasks within shifts (apps/backend/src/client-meals/). - Engagement Activities — Montessori-style activities (crafts, cooking, movement, memory) generated by AI or created manually per client, approved per activity, and attached to
ENGAGEMENTtasks within shifts (apps/backend/src/clients/services/client-engagement-activities.service.ts,apps/backend/src/client-engagements/). - Essential Needs — a per-client shopping/supply list (medical supplies + groceries) with a simple Needed → Purchased lifecycle and receipt photo capture (
apps/backend/src/essential-needs/). - Home Inventory — a versioned, signable record of items in the client's home with an approval + signature workflow (
apps/backend/src/home-inventories/). - Daily Motivations — per-business, AI-generated motivational quotes assigned one per calendar day, shown to caregivers (
apps/backend/src/daily-motivations/).
All data is tenant-scoped via a required business ref on every entity.
Entities & Data Model
ClientMeal — collection client_meals
apps/backend/src/client-meals/entities/client-meal.entity.ts
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | required, indexed (line 161–167) |
name, description | string | required |
ingredients | MealIngredient[] | required; each has id, name, quantity, unit (MeasurementUnit enum), category (IngredientCategory), isOptional, substitutions[], required nutritionPer100 (calories/protein/carbs/fat/fiber/sodium/sugar/cholesterol/saturatedFat) |
instructions | MealInstruction[] ({id, text}) | required |
mealTypes | MealType[] (Breakfast/Lunch/Dinner/Snack) | required (interfaces/client-meal.interface.ts:14-19) |
dietaryConsideration | string | required |
imageUrl, imagePrompt | string | nullable; AI image support |
author | ObjectId → User | required |
generationStatus | GenerationStatus (pending/processing/completed/failed) | optional; set to Completed for AI-generated meals (services/client-meals.service.ts:397); shared enum packages/shared/src/enums/domain-status.ts:579 |
allergenWarnings[], preparationTimeMinutes, cookingTimeMinutes, servingSize, servingsPerRecipe, difficultyLevel (Easy/Medium/Hard), tags[] | misc | optional |
Indexes: {business, createdAt}, {createdAt}, {mealTypes}, {tags}, {author} (lines 241–248). No client or approvalStatus field on the meal itself — those live on assignments. nutritionalInfo is not stored; it is computed at read time from ingredient nutritionPer100 (services/client-meals.service.ts:968-1028).
ClientMealAssignment — collection client_meal_assignments
apps/backend/src/client-meals/entities/client-meal-assignment.entity.ts — many-to-many junction between meals and clients carrying the per-client approval state.
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | required, indexed |
meal | ObjectId → ClientMeal | required; unique with client (line 82) |
client | ObjectId → Client | required |
approvalStatus | ApprovalStatus (pending/approved/rejected) | default Pending (packages/shared/src/enums/domain-status.ts:570) |
approvedBy, approvedAt, approvalNotes | User/Date/string | set on approve/reject |
assignedBy | ObjectId → User | required |
ShiftMealAssignment — collection shift_meal_assignments
apps/backend/src/client-meals/entities/shift-meal-assignment.entity.ts — binds one meal to one MEAL_PREPARATION task occurrence within one shift.
| Field | Type | Notes |
|---|---|---|
business, client, shiftAssignment | ObjectId refs | required, indexed |
taskId | string | the care-plan task id; unique per shift ({shiftAssignment, taskId} unique index, lines 81–84) |
meal | ObjectId → ClientMeal | required |
mealType | `'Breakfast' | 'Lunch' |
assignedBy | ObjectId → User | required |
ClientEngagementActivity — collection client_engagement_activities
apps/backend/src/clients/entities/client-engagement-activity.entity.ts
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | required, indexed |
client | ObjectId → Client | required — single-client owned, unlike meals (line 132) |
title, subtitle, overview, closingStatement | string | required |
materials | EngagementActivityMaterial[] ({id, name, quantity?, unit?, isOptional}) | required |
instructions | EngagementActivityInstruction[] ({id, stepNumber, title, description, duration?, tips?}) | required |
therapeuticObjectives | TherapeuticObjective[] ({objective, specificGoals?, measurableOutcome?}) | required |
theme | Nature/Holiday/Memory/Sensory/Cooking | required (lines 7–13) |
type | Craft/Cooking/Movement/Memory | required (lines 15–20) |
difficultyLevel | Easy/Medium/Hard | default Medium |
duration | '15-30min'/'30-45min'/'45+min' (DurationRange) | default Medium |
imageUrl, imagePrompt | string | optional |
author | ObjectId → User | required |
approvalStatus | ApprovalStatus | default Pending, stored on the activity itself (no junction table) (lines 193–199) |
approvedBy, approvedAt, approvalNotes | — | set on approve/reject |
A MontessoriPrinciple embedded schema is defined (lines 96–106) but is not referenced by any field on the entity.
ShiftEngagementAssignment — collection shift_engagement_assignments
apps/backend/src/client-engagements/entities/shift-engagement-assignment.entity.ts — mirror of ShiftMealAssignment for ENGAGEMENT tasks: business, client, shiftAssignment, taskId, engagement (ref ClientEngagementActivity), assignedBy; unique {shiftAssignment, taskId} (lines 73–76).
EssentialNeedItem — collection client_essential_need_items
apps/backend/src/essential-needs/entities/essential-need-item.entity.ts
| Field | Type | Notes |
|---|---|---|
business, client | ObjectId refs | required, indexed |
name | string | required |
itemType | MedicalSupply | Grocery | required (packages/shared/src/enums/domain-status.ts:793) |
category | EssentialNeedCategory (medical: WoundCare…NutritionalSupplements; grocery: FreshProduce…HouseholdItems; shared: Other) | required (domain-status.ts:804) |
quantity | number | default 1 |
unit | Piece/Pack/Box/Bottle/Bag/Roll/Pair/Set/Other | optional |
priority | Urgent/High/Normal/Low | default Normal |
status | Needed | Purchased | default Needed (domain-status.ts:871) |
estimatedCost, actualCost | number | optional |
isRecurring, recurringInterval (Weekly/Biweekly/Monthly) | — | stored, no automation found (see Gaps) |
receiptPhotos | ObjectId[] → LockCare | file vault refs, tagged ESSENTIAL_NEED (essential-needs.service.ts:92) |
requestedBy, purchasedBy, purchasedAt | — | audit fields |
Index: {client, status, itemType} (line 128).
HomeInventory — collection client_home_inventories
apps/backend/src/home-inventories/entities/home-inventory.entity.ts
| Field | Type | Notes |
|---|---|---|
business, client | ObjectId refs | required, indexed |
version | number | default 1; monotonically increasing per client (home-inventories.service.ts:192) |
status | Draft/PendingApproval/Approved/Signed | default Draft (packages/shared/src/enums/domain-status.ts:738) |
items | embedded HomeInventoryItem[] (own _id) | name, description?, category (12 values incl. Electronics, Jewelry, MedicalEquipment…), condition (New/Good/Fair/Poor/Damaged), quantity (default 1), location?, estimatedValueRange? (6 brackets Under50…Over5000), serialNumber?, notes?, photos[] → LockCare |
signature | embedded | signerName, signerRelationship, signatureData, signedAt required; optional witness name/signature |
notes, rejectionNotes | string | approval/rejection annotations |
approvedBy, approvedAt | — | set on approve |
previousVersionId | ObjectId → HomeInventory | links revisions |
createdBy | ObjectId → User | required |
Indexes: {client, status}, {client, version} (lines 186–187).
DailyMotivation — collection daily_motivations
apps/backend/src/daily-motivations/entities/daily-motivation.entity.ts
| Field | Type | Notes |
|---|---|---|
business | ObjectId → Business | required, indexed |
text | string | required, max 1000 |
author | string | required, max 200 — attribution text, not a User ref |
theme | string | null | max 100 |
quotedDate | Date | required; unique per business per day ({business, quotedDate} unique index, line 49) |
ER Diagram
Workflows & State Machines
AI Meal Generation (BullMQ queue client-meals)
POST /clients/:clientId/meals/generate(controllers/client-meals.controller.ts:80) validates the client exists, dedupes against active non-cancelled jobs for the same client, and enqueues agenerate-mealsjob withattempts: 3, exponential backoff (services/client-meals.service.ts:221-299). Request shape: counts per meal type (numberOfBreakfasts/Lunches/Dinners/Snacks, min 0), preferred/avoid ingredients, restrictions,mode(fast|standard|thorough),provider(openai|anthropic),focusAreas(7MealFocusAreavalues,packages/shared/src/meal-generation/meal-generation.ts:5-12),additionalPrompt(max 500 chars),generateImages(dto/generate-client-meals.dto.ts).ClientMealsProcessor(concurrency 3, lock 900s —processors/client-meals.processor.ts:17-22) delegatesgenerate-mealstoMealOrchestratorService.orchestrate(src/ai/agents/client-meals/client-meals-orchestrator.service.ts). Stages and progress bands: validation (0–10%) — eligibility check; an ineligible client returns success withmealCount: 0and a reason (lines 198–216); context building (10–20%) — client narrative + dietary profile; agentic loop (20–75%) — plan → generate → (optionally) evaluate quality, iterating untilconfidenceThresholdormaxIterations; image generation (75–90%) ifgenerateImages; saving (90–100%) viaClientMealsService.saveMealsFromGeneration, which inserts meals withgenerationStatus: Completedand creates aPendingClientMealAssignmentper meal for the requesting client (services/client-meals.service.ts:387-426).- Mode presets come from shared
GENERATION_MODE_PRESETS(packages/shared/src/care-plan/care-plan-generation.ts:51-81): fast/standard = 1 iteration with quality evaluation skipped; thorough = up to 3 iterations with evaluation (confidence thresholds 0.7/0.8/0.85). Models:gpt-5.5(openai) orclaude-sonnet-4-6(anthropic) (lines 26–30). - Progress, completion and failure are emitted over the notification WebSocket to
user-{requesterId}(CLIENT_MEALS_GENERATION_PROGRESS/COMPLETED/FAILED) plus a push notification on completion (client-meals-orchestrator.service.ts:354-386,processors/client-meals.processor.ts:178-270). - Cancellation:
DELETE /clients/:clientId/meals/cancel-generationsets a Redis-backedCancellationRegistryflag and acancelledflag on job data; waiting jobs are removed outright (services/client-meals.service.ts:301-345). Status polling viaGET .../meals/generation-statusinspects all BullMQ job states (lines 146–219). - Variations:
POST /meals/:id/variationsenqueuesgenerate-variations; the processor generates N variations one at a time viaAIClientMealsService.generateMealVariation, tolerates per-variation failures, saves them to the first assigned client, then assigns them to the remaining clients of the base meal (processors/client-meals.processor.ts:57-176). - Image (re)generation:
POST /meals/:id/generate-imagerequires an existingimagePrompt(services/client-meals.service.ts:612-637).
Meal Approval (per client)
- Approval is per
(meal, client)pair onClientMealAssignment(services/client-meal-assignment.service.ts:385-439). There is no transition guard — the endpoint overwrites status each call. - Emits
client-meal.approvedorclient-meal.rejectedevents plus a platform log entry. - Bulk approve:
PATCH /clients/:clientId/meals/bulk-approvesetsApprovedon all listed meals for that client (bulkApproveMealsForClient, lines 641–697). - Legacy:
PATCH /meals/:id/approve(marked deprecated) applies the status to all clients assigned to the meal (controllers/client-meals.controller.ts:192-212).
Shift Meal Assignment (per-task, per-shift)
- Single:
PATCH /clients/:clientId/shift-meal-assignments/shift/:shiftId/tasks/:taskIdupserts the unique(shiftAssignment, taskId)row (services/shift-meal-assignment.service.ts:153-201). Unassign deletes it. - Bulk:
POST .../bulk-assignqueues abulk-assign-mealsjob on theshift-meal-assignmentsqueue (controllers/shift-meal-assignment.controller.ts:38-72). The job loads non-cancelled shifts in the optional date range, the latest published care plans per schedule, groups the client's Approved meals bymealType, and round-robins them ontoMEAL_PREPARATIONtasks falling within each shift window; existing assignments are skipped unlessoverwriteExisting(services/shift-meal-assignment.service.ts:421-618). Throws if the client has no approved meals (lines 480–484). Tasks withoutmetadata.mealPrepData.mealTypedefault to'Breakfast'(line 558). Progress/completion viaSHIFT_MEAL_ASSIGNMENT_*socket events (processors/shift-meal-assignment.processor.ts). - Read models:
GET .../shift/:id/meal-preparations(meal-prep tasks for one shift with assignment status) andGET .../calendar-items(one calendar item per task occurrence across shifts, defaulting to a ±3-month window) (services/shift-meal-assignment.service.ts:231-415).
Engagement Activity Generation (BullMQ queue client-engagement-activities)
POST /clients/:clientId/engagement-activities/generatewith requiredtheme+type, optionaldifficultyLevel(default Medium),duration(default Medium),numberOfActivities(1–5, default 1),mode(defaultstandard),provider(defaultanthropic),focusAreas(7 Montessori-relevant areas,packages/shared/src/engagement-activity-generation/engagement-activity-generation.ts:5-13),additionalPrompt(max 500),generateImages(default false) (clients/dto/client-engagement-activity.dto.ts:206-248,client-engagement-activities.service.ts:242-322). Same dedupe/cancellation pattern as meals, but jobs run withattempts: 1(line 304).ClientEngagementActivitiesProcessorstages: validating (0–10) → building_context (10–25, usesclientsService.getFormattedNarrative) → generating (25–75/90, one AI call per activity viaAIClientEngagementActivityService.generateActivity) → optional generating_images (75–90, non-fatal failures) → saving/completed (clients/processors/client-engagement-activities.processor.ts:85-345). Each activity is saved immediately viacreate()with statusPending. Completion/failure emitCLIENT_ENGAGEMENT_ACTIVITY_GENERATION_*socket events and unified push notifications linking to/dashboard/clients/{clientId}/edit/engagement-activities(lines 368–493).- Approval is on the activity document itself:
PATCH .../engagement-activities/:activityId/approve(Pending/Approved/Rejected, no transition guard) andPATCH .../bulk-approve(client-engagement-activities.service.ts:445-553). Emitsengagement-activity.approved|rejectedevents. - Shift assignment mirrors meals: upsert per
(shiftAssignment, taskId)againstENGAGEMENTtasks, calendar items, and abulk-assign-engagementsjob on queueshift-engagement-assignmentsthat round-robins across all of the client's activities regardless of approval status (client-engagements/services/shift-engagement-assignment.service.ts:420-576; see Gaps). - PDF export:
POST .../engagement-activities/:activityId/export?template=...renders the web app's/print/engagement-activities/:templatepage with Puppeteer (clients/controllers/client-engagement-activities.controller.ts:192+). This route is@Public().
Essential Needs
- Update, delete and purchase are only allowed while status is
Needed(essential-needs.service.ts:256-260, 290-294, 341-345).Purchaseditems are immutable through the API (no un-purchase endpoint). - Purchase appends a "Purchase note" to
notesand stores receipt photos as LockCare files with signed URLs added at read time (lines 277–331). GET .../summaryaggregates estimated/actual cost and counts peritemType(lines 361–404). List endpoint returns per-status counts alongside pagination (lines 179–230).
Home Inventory
- Editing (
PATCH) is allowed only inDraftorPendingApproval(home-inventories.service.ts:299-305). Signed/Approved documents are immutable;reviseclones items into a new Draft withpreviousVersionIdset, and is blocked if a Draft already exists for the client (lines 524–590). - Submit requires at least one item and clears
rejectionNotes(lines 367–401). Sign requiresApprovedstatus and records signer/witness data (lines 481–522). Delete only for Drafts (lines 592–618). - All transitions emit platform-log events (
HOME_INVENTORY_SUBMITTED/APPROVED/REJECTED/SIGNED).
Daily Motivations
POST /daily-motivations/generate(count1–60, optionalthememax 100 chars —dto/generate-motivations.dto.ts) asks the Anthropic "fast" provider for a JSON batch of quotes (services/ai-daily-motivation.service.ts:50-86; theme is sanitized against prompt injection, line 62) and assigns them to sequential UTC dates starting from max(tomorrow, day-after-latest-existing quote) (daily-motivations.service.ts:30-98). A duplicate-date insert surfaces as HTTP 409 via the unique index (lines 90–96).GET /daily-motivations/todayreturns the quote whosequotedDatefalls inside the current UTC day (lines 103–116). List/update/delete are admin operations scoped by business.- No state machine — documents are simple rows.
Business Rules & Constraints
- One meal per task per shift, one engagement per task per shift — unique
{shiftAssignment, taskId}indexes (shift-meal-assignment.entity.ts:81-84,shift-engagement-assignment.entity.ts:73-76); single-task assignment is an upsert. - A meal can only be assigned once to a given client — unique
{meal, client}index on assignments (client-meal-assignment.entity.ts:82); re-assignment attempts are logged and skipped, not errored (client-meal-assignment.service.ts:69-81). - Bulk shift-meal assignment uses only Approved meals, grouped by mealType, round-robin per type; errors if none approved (
shift-meal-assignment.service.ts:459-484). Cancelled shifts are excluded (line 433). - Bulk engagement assignment uses all activities of the client (no approval filter) and a single global round-robin counter (
client-engagements/services/shift-engagement-assignment.service.ts:456-460, 494, 537). - Engagement activities belong to exactly one client; shift assignment verifies the activity and shift both belong to that client (
shift-engagement-assignment.service.ts:97-116). Shift-meal assignment verifies the meal and shift exist but does not check the meal is assigned/approved for that client (shift-meal-assignment.service.ts:162-172). - Meal deletion cascades to both client-level assignments and shift-level assignments before deleting the meal (
client-meals.service.ts:639-672). Engagement deletion cascades to shift engagement assignments (client-engagement-activities.service.ts:226-235). - Client deletion cascades through
deleteAllForClienton both meal-assignment services (clients/services/clients.service.ts:1622-1632). - Per-client generation job mutual exclusion — a new meal or engagement generation request returns the existing job (
isNew: false) if one is active for that client (client-meals.service.ts:232-259,client-engagement-activities.service.ts:253-281). - Nutrition is derived, never stored: per-meal totals divide by
servingsPerRecipe || 1; unit→gram conversion uses a hardcoded table with arbitrary defaults (e.g.piece= 100 g, unknown unit = 100 g) (client-meals.service.ts:937-966, 1013). - Essential need lifecycle is one-way: only
Neededitems can be updated, purchased, or deleted (essential-needs.service.ts:256, 290, 341). - Home inventory immutability: post-approval edits are blocked; the only path is
revisefromSigned, and only one Draft may exist per client at a time (home-inventories.service.ts:299-305, 537-551). - One motivation per business per day enforced by unique index (
daily-motivation.entity.ts:49); generation never targets today or the past (daily-motivations.service.ts:38-57). - Authorization is permission-based, enforced by the global
PermissionsGuard(app.module.ts:288-296) with@RequirePermissionsdecorators: meals useMEALS_VIEW_ASSIGNED/MEALS_MANAGE/MEALS_APPROVE; engagements useENGAGEMENTS_*; essential needsESSENTIAL_NEEDS_*; home inventoryHOME_INVENTORY_MANAGE/APPROVE/SIGN; daily motivations reuseANNOUNCEMENTS_VIEW(read) andANNOUNCEMENTS_MANAGE(admin) (daily-motivations.controller.ts:33, 41). - Default role grants (
packages/shared/src/constants/default-role-permissions.ts): Admin gets manage+approve for everything (lines 90–107); CareProvider gets view-assigned for meals/engagements but fullHOME_INVENTORY_MANAGEandESSENTIAL_NEEDS_MANAGE(lines 213–221); Representative (family) getsMEALS_APPROVE,ENGAGEMENTS_APPROVE, andHOME_INVENTORY_SIGN(lines 280–289); CareManager migration role gets meals/engagements view-all + manage but not approve (lines 424–428).
Surfaces (Web & Mobile)
Web (Next.js admin dashboard — Admin/CareManager/Owner)
| Page | What it does | Source |
|---|---|---|
/dashboard/meals | Business-wide meal catalog with assigned-client avatars (GET /meals → getAllMealsWithClients) | apps/web/app/(app)/(admin)/dashboard/meals/page.tsx |
/dashboard/clients/[id]/edit/meals | Per-client meal management (tabs UI, checks BusinessPermission client-side) | .../edit/meals/page.tsx |
/dashboard/clients/[id]/edit/meals-bank | Meal bank/browse for the client | .../edit/meals-bank/page.tsx |
/dashboard/clients/[id]/edit/meal-assignments | Shift-meal assignment calendar (bulk assign, per-task assign) | .../edit/meal-assignments/page.tsx |
/dashboard/clients/[id]/edit/engagement-activities, engagement-bank, engagement-assignments, montessori-activities | Engagement creation/generation, bank, and shift assignment | corresponding page.tsx files |
/dashboard/clients/[id]/edit/essential-needs | Essential needs list/create/purchase | .../edit/essential-needs/page.tsx, API in apps/web/lib/api/client/essential-needs.ts |
/dashboard/clients/[id]/edit/home-inventory | Full inventory workflow — the web API client exposes create/update/submit/approve/reject/sign/revise/delete (apps/web/lib/api/client/home-inventories.ts:45-163) | .../edit/home-inventory/page.tsx |
/dashboard/daily-motivations | Admin quote management + AI generate dialog | apps/web/app/(app)/(admin)/dashboard/daily-motivations/page.tsx |
/print/engagement-activities/[template] | Print layout consumed by the backend Puppeteer export | apps/web/app/(print)/print/engagement-activities/[template]/page.tsx |
The full web meal API surface (generate, cancel, status polling, variations, similar meals, nutrition endpoints, tags, assignment CRUD) is in apps/web/lib/api/client/meals.ts.
Mobile (Expo — CareProvider and Representative are mobile-only roles, packages/shared/src/enums/user-role.ts:96)
| Screen | What it does | Source |
|---|---|---|
(client)/[id]/meals/index, [mealId], calendar | Browse client meals, meal detail, meal-prep calendar | apps/mobile/app/(client)/[id]/meals/ |
(client)/[id]/meals/pending | "Review and approve meals for your loved one's meal plan" — family approval flow calling PATCH /meals/:mealId/clients/:clientId/approve | pending.tsx:351, apps/mobile/lib/meals-api.ts:60-71 |
(client)/[id]/engagement-activities/index, [engagementActivityId], calendar | Engagement browsing + calendar | apps/mobile/app/(client)/[id]/engagement-activities/ |
(client)/[id]/essential-needs/index, create, [itemId] | Full needs lifecycle incl. purchase with receipt photos (usePurchaseEssentialNeedItem → PATCH .../purchase) | apps/mobile/hooks/use-essential-needs.ts:101-175 |
(client)/[id]/home-inventory/index, create, [inventoryId] | List, create, and view inventories (signature display only) — the mobile hook exposes only list/get/create, no submit/sign/approve mutations | apps/mobile/hooks/use-home-inventory.ts |
| Home screens | Today's motivation card (GET /daily-motivations/today) on care-provider and admin home | apps/mobile/components/home/daily-motivation.tsx, apps/mobile/lib/daily-motivation-api.ts:7 |
Frontend-only rules: the (client) route group gates only on session verification, not role — access control is backend permissions (apps/mobile/app/(client)/_layout.tsx:29-45). Generation-mode labels/estimated times shown in pickers are UI copy from @anaya/shared (MEAL_GENERATION_MODE_UI, ENGAGEMENT_ACTIVITY_GENERATION_MODE_UI), not enforced server-side.
Cross-Module Dependencies
- daily-living → 04-care-plans-and-tasks.md: shift meal/engagement assignment reads the latest published
CareProviderTasksand filtersMEAL_PREPARATION/ENGAGEMENTtemplate tasks into shift windows viafilterTasksForShiftWindow/resolveTaskOccurrence(shift-meal-assignment.service.ts:248-310,src/common/utils/shift-task-filter.util.ts). In the reverse direction,client-care-provider-tasks.service.tsinjects full meal data into taskmetadata.meal(populateTaskMeals, line 2152) and engagement data viapopulateTaskEngagements(line ~865) when serving shift tasks to care providers. Verifiedclient-meal.rejectedlistener:client-care-provider-tasks.service.ts:2526-2527listens forclient-meal.rejectedandclient-meal.unassignedand$unsets the legacytasks.metadata.mealIdfrom care plan documents; it does not touchshift_meal_assignmentsrows. - daily-living → 05-scheduling-and-shifts.md:
ShiftAssignmentis the anchor for per-shift meal/engagement assignment, calendar items excludeShiftStatus.CANCELLEDshifts (shift-meal-assignment.service.ts:336), anddeleteAssignmentsForShiftexists as a cascade hook on both services. - daily-living → 01-clients.md: client deletion cascades through
ClientMealAssignmentService.deleteAllForClientandShiftMealAssignmentService.deleteAllForClient(clients/services/clients.service.ts:1622-1632); AI generation consumesclientsService.getFormattedNarrative(clientId)(engagements) and the validation/context agents (meals). - daily-living → 02-assessments.md: the meal AI pipeline builds dietary context from client medical data; the
ClientMealAssessmententity itself is documented in 02 (cross-link only). - daily-living → notifications module: BullMQ processors emit progress/completed/failed over
NotificationGatewaysocket rooms (user-{id}) and send unified push notifications (processors/client-meals.processor.ts,clients/processors/client-engagement-activities.processor.ts:404-424). - daily-living → AI module (
src/ai/):MealOrchestratorService+ agent suite (validation, context-builder, meal-planner, meal-generator, quality-evaluator —src/ai/agents/client-meals/agents/),AIClientMealsService(variations, images),AIClientEngagementActivityService,LLMProviderFactory(also used by daily motivations), and the sharedCancellationRegistry. - daily-living → platform-logs: every CRUD/workflow action emits
PLATFORM_LOG_ACTIVITY_EVENT(e.g.MEAL_APPROVED,ESSENTIAL_NEED_PURCHASED,HOME_INVENTORY_SIGNED). - daily-living → lock-care/files: essential-need receipt photos and home-inventory item photos are LockCare documents with S3 signed URLs (
essential-needs.service.ts:75-119,home-inventories.service.ts:80-124). - daily-living → web frontend: engagement PDF export depends on
FRONTEND_URL+ the web/printroute rendered by Puppeteer.
Open Questions & Gaps
- Manual meal creation likely fails Mongoose validation.
ClientMealsService.create/createBulkinsert meals without setting the requiredbusinessfield (the DTO doesn't carry it and the global ValidationPipe iswhitelist + forbidNonWhitelisted,main.ts:41-44), and set anoriginalClientfield that does not exist on the schema (silently dropped under strict mode) (client-meals.service.ts:61-65, 110-115vsclient-meal.entity.ts:161-167). The web client does callPOST /clients/:clientId/meals(apps/web/lib/api/client/meals.ts:50). Whether this path works in production cannot be determined from code — unit tests mock the model so validation never runs. GET /mealsis not business-scoped.getAllMealsWithClientsbuildsmealQuerywithout abusinessfilter (client-meal-assignment.service.ts:500-543), so the admin meal catalog appears to return meals across all tenants.getAllMealTagsis business-scoped (lines 572–607), making the omission look unintentional.- Unauthenticated engagement endpoints.
GET /clients/:clientId/engagement-activities/:activityIdandPOST .../:activityId/exportare@Public()(clients/controllers/client-engagement-activities.controller.ts:141-143, 192-193), andDELETE .../:activityIdhas no@RequirePermissionsdecorator (JWT only, line 168). Whether the public read/export are intentional (e.g. for the print pipeline) cannot be determined from code. - Rejecting a meal does not remove its shift assignments. The
client-meal.rejectedlistener only unsets legacytasks.metadata.mealId(client-care-provider-tasks.service.ts:2526-2562); existingshift_meal_assignmentsrows referencing the now-rejected meal remain and will still be populated into shift tasks viapopulateTaskMeals. - Engagement approval is not enforced anywhere downstream. Bulk shift assignment and the task-dropdown listing use all activities regardless of
approvalStatus(client-engagements/services/shift-engagement-assignment.service.ts:456-460, 363-369), and the emittedengagement-activity.approved|rejectedevents have no listeners in the backend (grep oversrc/), unlike meals. The purpose of the engagement approval workflow beyond UI display is unclear. ClientMealsService.findAllis dead/broken code: it filters onclientandapprovalStatusfields that don't exist on theClientMealschema (client-meals.service.ts:454-464); the controller route with the same name actually calls the assignment service instead (client-meals.controller.ts:45-60).- Daily motivation "today" uses UTC, not the business's timezone (
daily-motivations.service.ts:103-116) — businesses west of UTC see the next day's quote in the evening. Intent cannot be determined from code. - Essential need recurring fields are inert:
isRecurring/recurringIntervalare stored and validated but no scheduler or job re-creates recurring items (no references outside CRUD). - Mobile cannot complete the home-inventory signature flow —
use-home-inventory.tsonly implements list/get/create, yetHOME_INVENTORY_SIGNis granted by default to Representative, a mobile-only role (default-role-permissions.ts:282,user-role.ts:96). Signing appears to be possible only through the web dashboard, which Representatives may not access; actual signing surface for families cannot be determined from code. - Meal approval has no state machine — approve/reject can be flipped indefinitely and approval can happen on a meal whose shift assignments already exist; similarly
assignMealToShiftTaskdoes not verify the meal is approved (or even assigned) for that client (shift-meal-assignment.service.ts:153-201). - Pending-meals endpoint truncates:
GET /clients/:clientId/meals/pendingdelegates tofindByClientwith the defaultlimit: '10'(client-meals.service.ts:552-557), while the unusedgetPendingMealsForClientuses limit 100 — a family reviewing >10 pending meals will not see all of them in one call unless the client paginates. generationErrorandGenerationStatus.Pending/Processing/Failedare never written — onlyCompletedis set on save (client-meals.service.ts:397); failed generations leave no meal documents, making the enum values mostly vestigial.ClientScheduleModelis injected but unused in bothShiftMealAssignmentServiceandShiftEngagementAssignmentService(constructor-only references).- Daily motivations have no AI failure compensation: if the AI returns fewer quotes than requested it logs a warning and inserts fewer (
daily-motivations.service.ts:63-68); there is no retry or top-up.