Anaya Care Docs

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_PREPARATION tasks 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 ENGAGEMENT tasks 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

FieldTypeNotes
businessObjectId → Businessrequired, indexed (line 161–167)
name, descriptionstringrequired
ingredientsMealIngredient[]required; each has id, name, quantity, unit (MeasurementUnit enum), category (IngredientCategory), isOptional, substitutions[], required nutritionPer100 (calories/protein/carbs/fat/fiber/sodium/sugar/cholesterol/saturatedFat)
instructionsMealInstruction[] ({id, text})required
mealTypesMealType[] (Breakfast/Lunch/Dinner/Snack)required (interfaces/client-meal.interface.ts:14-19)
dietaryConsiderationstringrequired
imageUrl, imagePromptstringnullable; AI image support
authorObjectId → Userrequired
generationStatusGenerationStatus (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[]miscoptional

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.

FieldTypeNotes
businessObjectId → Businessrequired, indexed
mealObjectId → ClientMealrequired; unique with client (line 82)
clientObjectId → Clientrequired
approvalStatusApprovalStatus (pending/approved/rejected)default Pending (packages/shared/src/enums/domain-status.ts:570)
approvedBy, approvedAt, approvalNotesUser/Date/stringset on approve/reject
assignedByObjectId → Userrequired

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.

FieldTypeNotes
business, client, shiftAssignmentObjectId refsrequired, indexed
taskIdstringthe care-plan task id; unique per shift ({shiftAssignment, taskId} unique index, lines 81–84)
mealObjectId → ClientMealrequired
mealType`'Breakfast''Lunch'
assignedByObjectId → Userrequired

ClientEngagementActivity — collection client_engagement_activities

apps/backend/src/clients/entities/client-engagement-activity.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired — single-client owned, unlike meals (line 132)
title, subtitle, overview, closingStatementstringrequired
materialsEngagementActivityMaterial[] ({id, name, quantity?, unit?, isOptional})required
instructionsEngagementActivityInstruction[] ({id, stepNumber, title, description, duration?, tips?})required
therapeuticObjectivesTherapeuticObjective[] ({objective, specificGoals?, measurableOutcome?})required
themeNature/Holiday/Memory/Sensory/Cookingrequired (lines 7–13)
typeCraft/Cooking/Movement/Memoryrequired (lines 15–20)
difficultyLevelEasy/Medium/Harddefault Medium
duration'15-30min'/'30-45min'/'45+min' (DurationRange)default Medium
imageUrl, imagePromptstringoptional
authorObjectId → Userrequired
approvalStatusApprovalStatusdefault Pending, stored on the activity itself (no junction table) (lines 193–199)
approvedBy, approvedAt, approvalNotesset 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

FieldTypeNotes
business, clientObjectId refsrequired, indexed
namestringrequired
itemTypeMedicalSupply | Groceryrequired (packages/shared/src/enums/domain-status.ts:793)
categoryEssentialNeedCategory (medical: WoundCare…NutritionalSupplements; grocery: FreshProduce…HouseholdItems; shared: Other)required (domain-status.ts:804)
quantitynumberdefault 1
unitPiece/Pack/Box/Bottle/Bag/Roll/Pair/Set/Otheroptional
priorityUrgent/High/Normal/Lowdefault Normal
statusNeeded | Purchaseddefault Needed (domain-status.ts:871)
estimatedCost, actualCostnumberoptional
isRecurring, recurringInterval (Weekly/Biweekly/Monthly)stored, no automation found (see Gaps)
receiptPhotosObjectId[] → LockCarefile vault refs, tagged ESSENTIAL_NEED (essential-needs.service.ts:92)
requestedBy, purchasedBy, purchasedAtaudit fields

Index: {client, status, itemType} (line 128).

HomeInventory — collection client_home_inventories

apps/backend/src/home-inventories/entities/home-inventory.entity.ts

FieldTypeNotes
business, clientObjectId refsrequired, indexed
versionnumberdefault 1; monotonically increasing per client (home-inventories.service.ts:192)
statusDraft/PendingApproval/Approved/Signeddefault Draft (packages/shared/src/enums/domain-status.ts:738)
itemsembedded 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 Under50Over5000), serialNumber?, notes?, photos[] → LockCare
signatureembeddedsignerName, signerRelationship, signatureData, signedAt required; optional witness name/signature
notes, rejectionNotesstringapproval/rejection annotations
approvedBy, approvedAtset on approve
previousVersionIdObjectId → HomeInventorylinks revisions
createdByObjectId → Userrequired

Indexes: {client, status}, {client, version} (lines 186–187).

DailyMotivation — collection daily_motivations

apps/backend/src/daily-motivations/entities/daily-motivation.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
textstringrequired, max 1000
authorstringrequired, max 200 — attribution text, not a User ref
themestring | nullmax 100
quotedDateDaterequired; unique per business per day ({business, quotedDate} unique index, line 49)

ER Diagram

Workflows & State Machines

AI Meal Generation (BullMQ queue client-meals)

  1. 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 a generate-meals job with attempts: 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 (7 MealFocusArea values, packages/shared/src/meal-generation/meal-generation.ts:5-12), additionalPrompt (max 500 chars), generateImages (dto/generate-client-meals.dto.ts).
  2. ClientMealsProcessor (concurrency 3, lock 900s — processors/client-meals.processor.ts:17-22) delegates generate-meals to MealOrchestratorService.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 with mealCount: 0 and a reason (lines 198–216); context building (10–20%) — client narrative + dietary profile; agentic loop (20–75%) — plan → generate → (optionally) evaluate quality, iterating until confidenceThreshold or maxIterations; image generation (75–90%) if generateImages; saving (90–100%) via ClientMealsService.saveMealsFromGeneration, which inserts meals with generationStatus: Completed and creates a Pending ClientMealAssignment per meal for the requesting client (services/client-meals.service.ts:387-426).
  3. 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) or claude-sonnet-4-6 (anthropic) (lines 26–30).
  4. 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).
  5. Cancellation: DELETE /clients/:clientId/meals/cancel-generation sets a Redis-backed CancellationRegistry flag and a cancelled flag on job data; waiting jobs are removed outright (services/client-meals.service.ts:301-345). Status polling via GET .../meals/generation-status inspects all BullMQ job states (lines 146–219).
  6. Variations: POST /meals/:id/variations enqueues generate-variations; the processor generates N variations one at a time via AIClientMealsService.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).
  7. Image (re)generation: POST /meals/:id/generate-image requires an existing imagePrompt (services/client-meals.service.ts:612-637).

Meal Approval (per client)

  • Approval is per (meal, client) pair on ClientMealAssignment (services/client-meal-assignment.service.ts:385-439). There is no transition guard — the endpoint overwrites status each call.
  • Emits client-meal.approved or client-meal.rejected events plus a platform log entry.
  • Bulk approve: PATCH /clients/:clientId/meals/bulk-approve sets Approved on 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/:taskId upserts the unique (shiftAssignment, taskId) row (services/shift-meal-assignment.service.ts:153-201). Unassign deletes it.
  • Bulk: POST .../bulk-assign queues a bulk-assign-meals job on the shift-meal-assignments queue (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 by mealType, and round-robins them onto MEAL_PREPARATION tasks falling within each shift window; existing assignments are skipped unless overwriteExisting (services/shift-meal-assignment.service.ts:421-618). Throws if the client has no approved meals (lines 480–484). Tasks without metadata.mealPrepData.mealType default to 'Breakfast' (line 558). Progress/completion via SHIFT_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) and GET .../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/generate with required theme + type, optional difficultyLevel (default Medium), duration (default Medium), numberOfActivities (1–5, default 1), mode (default standard), provider (default anthropic), 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 with attempts: 1 (line 304).
  • ClientEngagementActivitiesProcessor stages: validating (0–10) → building_context (10–25, uses clientsService.getFormattedNarrative) → generating (25–75/90, one AI call per activity via AIClientEngagementActivityService.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 via create() with status Pending. Completion/failure emit CLIENT_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) and PATCH .../bulk-approve (client-engagement-activities.service.ts:445-553). Emits engagement-activity.approved|rejected events.
  • Shift assignment mirrors meals: upsert per (shiftAssignment, taskId) against ENGAGEMENT tasks, calendar items, and a bulk-assign-engagements job on queue shift-engagement-assignments that 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/:template page 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). Purchased items are immutable through the API (no un-purchase endpoint).
  • Purchase appends a "Purchase note" to notes and stores receipt photos as LockCare files with signed URLs added at read time (lines 277–331).
  • GET .../summary aggregates estimated/actual cost and counts per itemType (lines 361–404). List endpoint returns per-status counts alongside pagination (lines 179–230).

Home Inventory

  • Editing (PATCH) is allowed only in Draft or PendingApproval (home-inventories.service.ts:299-305). Signed/Approved documents are immutable; revise clones items into a new Draft with previousVersionId set, 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 requires Approved status 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 (count 1–60, optional theme max 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/today returns the quote whose quotedDate falls 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 deleteAllForClient on 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 Needed items 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 revise from Signed, 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 @RequirePermissions decorators: meals use MEALS_VIEW_ASSIGNED/MEALS_MANAGE/MEALS_APPROVE; engagements use ENGAGEMENTS_*; essential needs ESSENTIAL_NEEDS_*; home inventory HOME_INVENTORY_MANAGE/APPROVE/SIGN; daily motivations reuse ANNOUNCEMENTS_VIEW (read) and ANNOUNCEMENTS_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 full HOME_INVENTORY_MANAGE and ESSENTIAL_NEEDS_MANAGE (lines 213–221); Representative (family) gets MEALS_APPROVE, ENGAGEMENTS_APPROVE, and HOME_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)

PageWhat it doesSource
/dashboard/mealsBusiness-wide meal catalog with assigned-client avatars (GET /mealsgetAllMealsWithClients)apps/web/app/(app)/(admin)/dashboard/meals/page.tsx
/dashboard/clients/[id]/edit/mealsPer-client meal management (tabs UI, checks BusinessPermission client-side).../edit/meals/page.tsx
/dashboard/clients/[id]/edit/meals-bankMeal bank/browse for the client.../edit/meals-bank/page.tsx
/dashboard/clients/[id]/edit/meal-assignmentsShift-meal assignment calendar (bulk assign, per-task assign).../edit/meal-assignments/page.tsx
/dashboard/clients/[id]/edit/engagement-activities, engagement-bank, engagement-assignments, montessori-activitiesEngagement creation/generation, bank, and shift assignmentcorresponding page.tsx files
/dashboard/clients/[id]/edit/essential-needsEssential needs list/create/purchase.../edit/essential-needs/page.tsx, API in apps/web/lib/api/client/essential-needs.ts
/dashboard/clients/[id]/edit/home-inventoryFull 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-motivationsAdmin quote management + AI generate dialogapps/web/app/(app)/(admin)/dashboard/daily-motivations/page.tsx
/print/engagement-activities/[template]Print layout consumed by the backend Puppeteer exportapps/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)

ScreenWhat it doesSource
(client)/[id]/meals/index, [mealId], calendarBrowse client meals, meal detail, meal-prep calendarapps/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/approvepending.tsx:351, apps/mobile/lib/meals-api.ts:60-71
(client)/[id]/engagement-activities/index, [engagementActivityId], calendarEngagement browsing + calendarapps/mobile/app/(client)/[id]/engagement-activities/
(client)/[id]/essential-needs/index, create, [itemId]Full needs lifecycle incl. purchase with receipt photos (usePurchaseEssentialNeedItemPATCH .../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 mutationsapps/mobile/hooks/use-home-inventory.ts
Home screensToday's motivation card (GET /daily-motivations/today) on care-provider and admin homeapps/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 CareProviderTasks and filters MEAL_PREPARATION / ENGAGEMENT template tasks into shift windows via filterTasksForShiftWindow / 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.ts injects full meal data into task metadata.meal (populateTaskMeals, line 2152) and engagement data via populateTaskEngagements (line ~865) when serving shift tasks to care providers. Verified client-meal.rejected listener: client-care-provider-tasks.service.ts:2526-2527 listens for client-meal.rejected and client-meal.unassigned and $unsets the legacy tasks.metadata.mealId from care plan documents; it does not touch shift_meal_assignments rows.
  • daily-living → 05-scheduling-and-shifts.md: ShiftAssignment is the anchor for per-shift meal/engagement assignment, calendar items exclude ShiftStatus.CANCELLED shifts (shift-meal-assignment.service.ts:336), and deleteAssignmentsForShift exists as a cascade hook on both services.
  • daily-living → 01-clients.md: client deletion cascades through ClientMealAssignmentService.deleteAllForClient and ShiftMealAssignmentService.deleteAllForClient (clients/services/clients.service.ts:1622-1632); AI generation consumes clientsService.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 ClientMealAssessment entity itself is documented in 02 (cross-link only).
  • daily-living → notifications module: BullMQ processors emit progress/completed/failed over NotificationGateway socket 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 shared CancellationRegistry.
  • 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 /print route rendered by Puppeteer.

Open Questions & Gaps

  1. Manual meal creation likely fails Mongoose validation. ClientMealsService.create/createBulk insert meals without setting the required business field (the DTO doesn't carry it and the global ValidationPipe is whitelist + forbidNonWhitelisted, main.ts:41-44), and set an originalClient field that does not exist on the schema (silently dropped under strict mode) (client-meals.service.ts:61-65, 110-115 vs client-meal.entity.ts:161-167). The web client does call POST /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.
  2. GET /meals is not business-scoped. getAllMealsWithClients builds mealQuery without a business filter (client-meal-assignment.service.ts:500-543), so the admin meal catalog appears to return meals across all tenants. getAllMealTags is business-scoped (lines 572–607), making the omission look unintentional.
  3. Unauthenticated engagement endpoints. GET /clients/:clientId/engagement-activities/:activityId and POST .../:activityId/export are @Public() (clients/controllers/client-engagement-activities.controller.ts:141-143, 192-193), and DELETE .../:activityId has no @RequirePermissions decorator (JWT only, line 168). Whether the public read/export are intentional (e.g. for the print pipeline) cannot be determined from code.
  4. Rejecting a meal does not remove its shift assignments. The client-meal.rejected listener only unsets legacy tasks.metadata.mealId (client-care-provider-tasks.service.ts:2526-2562); existing shift_meal_assignments rows referencing the now-rejected meal remain and will still be populated into shift tasks via populateTaskMeals.
  5. 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 emitted engagement-activity.approved|rejected events have no listeners in the backend (grep over src/), unlike meals. The purpose of the engagement approval workflow beyond UI display is unclear.
  6. ClientMealsService.findAll is dead/broken code: it filters on client and approvalStatus fields that don't exist on the ClientMeal schema (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).
  7. 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.
  8. Essential need recurring fields are inert: isRecurring/recurringInterval are stored and validated but no scheduler or job re-creates recurring items (no references outside CRUD).
  9. Mobile cannot complete the home-inventory signature flowuse-home-inventory.ts only implements list/get/create, yet HOME_INVENTORY_SIGN is 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.
  10. 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 assignMealToShiftTask does not verify the meal is approved (or even assigned) for that client (shift-meal-assignment.service.ts:153-201).
  11. Pending-meals endpoint truncates: GET /clients/:clientId/meals/pending delegates to findByClient with the default limit: '10' (client-meals.service.ts:552-557), while the unused getPendingMealsForClient uses limit 100 — a family reviewing >10 pending meals will not see all of them in one call unless the client paginates.
  12. generationError and GenerationStatus.Pending/Processing/Failed are never written — only Completed is set on save (client-meals.service.ts:397); failed generations leave no meal documents, making the enum values mostly vestigial.
  13. ClientScheduleModel is injected but unused in both ShiftMealAssignmentService and ShiftEngagementAssignmentService (constructor-only references).
  14. 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.

On this page