Anaya Care — As-Built Product Wiki
What this is: A reverse-engineered, "as-built" documentation of the Anaya Care platform, generated from the codebase on 2026-06-11. It documents what the code actually does today — not what anyone intended. Inconsistencies, dead code, and missing behavior are recorded as-is and flagged in each page's Open Questions & Gaps section.
How to use it: Read it, correct it, and decide. The Open Questions & Gaps sections are the review agenda — each one is either (a) a verbal decision that was never written down, (b) a bug, or (c) a feature that was never finished. The team should label each item accordingly.
Conventions: Every stated rule cites the file (and often line) that enforces it. Line numbers drift as code changes; paths are the stable anchor. Collections are MongoDB; entities are Mongoose schemas with
_id → idnormalization.
Product summary
Anaya Care is a multi-tenant home care management platform. A care business (agency) is the tenant: it assesses prospective clients, sends priced care proposals to family representatives, converts accepted proposals into managed clients, plans care (care plans + care provider tasks), schedules shifts worked by care providers, fills coverage gaps via job postings, and keeps families informed through requests, chat, notifications, and shared reports. AI features (care plan/task/meal/activity generation, wound photo analysis, shift summaries, RAG-backed trainings) run throughout.
- Backend: NestJS + MongoDB (Mongoose) + Redis/BullMQ + Socket.IO —
apps/backend - Web: Next.js dashboard for agency staff (admins, managers) —
apps/web - Mobile: Expo React Native app for care providers and family representatives —
apps/mobile - Shared: enums, types, and all state-machine definitions —
packages/shared(@anaya/shared)
Roles & tenancy (the 2-minute version)
Full detail: 11-identity-and-access.md
- Roles: SuperAdmin (platform), Owner/Admin (agency management), CareProvider (caregiver), Representative (family-side; renamed from
Family/FamilyProfilein migration 025), MedicalProfessional, plus per-business Custom roles (theCareManagerbase role was removed in migration 031 and converted to custom roles). - Authorization is permission-based:
@RequirePermissions+PermissionsGuard, with per-business overrides (BusinessPermission,baseRolePermissionOverrides). - Tenancy: nearly all data carries a
businessref, auto-scoped by a CLS-based Mongoose plugin. Known systemic holes are listed below.
Module index
| # | Page | Covers (backend modules) |
|---|---|---|
| 01 | Clients (Core) | clients core: Client, status lifecycle, care team (CaregiverAssignment), medical record, service info, representative links |
| 02 | Assessments & Change of Conditions | initial-assessments, six per-client clinical assessments, health-observations (the "change of conditions" feature), care readiness quiz |
| 03 | Care Proposals | care-proposals + the shared transition map, review rounds, snapshots, e-sign flow |
| 04 | Care Plans & Care Provider Tasks | ClientCarePlan, CareProviderTasks, task templates, task submissions & family concerns |
| 05 | Scheduling & Shifts | client_schedules (RRULE), shifts, timesheets/clock-in-out, shift-handovers, shift summaries, daily-hour caps |
| 06 | Job Postings & Applications | job-postings: hiring marketplace, shift→posting conversion, applicant review |
| 07 | Medications | ClientMedication, med-pass virtual tasks, MedicationLog, stock tracking, RxTerms/OpenFDA |
| 08 | Health Monitoring | health-metrics (vitals), wound-care (+AI analysis), DNR acknowledgments, doctors' appointments |
| 09 | Incident Reports & Emergency Alerts | incident-reports, emergency-alerts (SOS) |
| 10 | Daily Living | client-meals, client-engagements, essential-needs, home-inventories, daily-motivations |
| 11 | Identity, Access & Tenancy | auth, users, profile-setup, business, base-rates, permissions & tenancy machinery |
| 12 | Communication | chat, websocket, notification (the fan-out hub), livekit calls, announcements, emails |
| 13 | Requests | requests: representative ↔ agency service tickets |
| 14 | AI Features | ai, ai-chat, ai-settings, plate-ai, knowledge-base, generation orchestrators |
| 15 | Content & Community | posts, blog, trainings (LMS), skills, ratings, feature-requests |
| 16 | Platform Operations | files/assets/avatar, lock-care vault, pdf, google-maps, statistics, platform-*, app-version, bull-board, migrations |
How the modules relate
The core care lifecycle is a chain; everything else either feeds it or fans out from it.
Key mechanics behind the arrows (each documented in the linked pages):
- 02 → 03:
InitialAssessment.convertToCareProposal()copies assessment data into the proposal. The reverse rule — "a proposal must originate from an assessment" — is not enforced in the backend (see below). - 03 → 01:
POST /clients/from-care-proposal/:idmaterializes a Client (+ service info shells, seeded routine-task inventory) and marks the proposal In Service. It does not create schedules, caregiver assignments, or aClientCarePlan. - 04 ↔ 05: Tasks live per-schedule (
CareProviderTasks, DRAFT→PUBLISHED) and are expanded per shift; clock-out is gated on required task submission; medications/meals/engagements are merged into the shift task list at read time. - 05 ↔ 06: Shifts convert to job postings; acceptance fires events that assign the caregiver to the client and the originating shift. Daily-hour caps (12h cross-client / 24h same-client) are checked at apply/accept/assign — but not during bulk shift auto-generation.
- 09/02 → 05: Incident reports and health observations feed a BullMQ queue where an AI decides whether to create a shift handover note for the next caregiver. That is currently their only downstream effect.
- 12: ~110 registry-driven notification types fan out to in-app/WebSocket, Expo push, Resend email, and Twilio SMS; nearly every module emits into it.
Cross-cutting findings (read this before the per-module gaps)
These patterns recur across many modules. They are the highest-leverage items for team review.
1. Several "known rules" exist verbally but not in code
| Rule as stated by the team | What the code actually does | Where documented |
|---|---|---|
| "Care provider must not exceed 16 hours/day" | No 16h constant exists anywhere. Implemented caps: 12h cross-client / 24h same-client (packages/shared/src/constants/common.ts), enforced at apply/accept/assign/pickup but not during bulk shift auto-generation | 05, 06 |
| "A care proposal cannot be created unless it originates from an initial assessment" | POST /care-proposals accepts direct creation and fabricates a blank linked assessment; the web UI exposes a direct "add proposal" page. Only trace of the rule is a permission label string | 03 |
| "Change of conditions updates the care plan and care provider tasks" | No cascade exists. "Change of conditions" is a UI label on HealthObservation (free-text notes). Its only automated effect is an AI shift-handover note. Plan and task updates are entirely manual, separate steps | 02, 04 |
| "Incident reports affect schedule/care plan" | Incidents trigger notifications + an AI handover note, then dead-end. Nothing touches plan, schedule, or assessments | 09 |
2. Permission enforcement is partially decorative
There is no global auth guard. PermissionsGuard, BusinessGuard, and IdleTimeoutGuard are registered globally but run before the per-controller JwtAuthGuard, and all three skip when req.user is absent — which at that point it always is. Result: @RequirePermissions is enforced only on the ~24 controllers that locally re-bind @UseGuards(JwtAuthGuard, PermissionsGuard); on ~22 others (including clients, shifts, care-proposals, chat, posts, lock-care) the decorators do nothing, and the inactive-business block and HIPAA idle-timeout never execute anywhere. Full analysis: 11-identity-and-access.md.
3. Tenancy has systemic holes
The CLS-based Mongoose plugin auto-scopes schemas that have a business field — but: LockCare, Notification, and all profile entities have no business field; aggregation pipelines skip both casting and the plugin (several $matches compare strings to ObjectIds and silently return nothing); and the plugin no-ops outside HTTP request context (cron jobs, WebSockets, migrations). Cross-tenant exposures were found in announcements fan-out, knowledge base CRUD, meals catalog, skills, and LockCare reads. See 11, 12, 14, 16.
4. Event-driven architecture with dead ends
Multiple events are emitted but have zero listeners (health-observation.reviewed, medication.updated/deleted, engagement approve/reject), and some listeners are fire-and-forget where failures silently break invariants (job acceptance side-effects, proposal snapshots). Where the team expected cascades (CoC → plan → tasks), the events simply were never wired. Flagged per-module.
5. Security items needing immediate triage
Collected from module pages, most severe first — see 16-platform-operations.md and 11-identity-and-access.md:
DevControllerendpoints are unauthenticated and registered in production (user enumeration; overwrite client medical data).- Bull Board queue dashboard (
/admin/queues) has no auth in code. GET /files/file/:keyis public; file delete/copy endpoints have no ownership checks; incident photos uploadedisPublic: true.- Several PHI-bearing endpoint groups (doctors' appointments, medications, assessments) have no permission decorators and/or missing business filters.
- Full client PHI narratives are sent to OpenAI/Anthropic/Gemini/Qdrant with no redaction layer (14).
6. Naming history that confuses readers
FamilyProfile→RepresentativeProfile(migration 025). UI says "Family"; code says "Representative".CareManagerbase role removed (migration 031) → per-business custom roles.ClientCareProvidersis not the care team — it stores external provider organizations. The care team isCaregiverAssignment. (01)- "Change of conditions" in the UI =
HealthObservationin code. (02) plate-aiis the rich-text editor AI, not meal-photo analysis. (14)
Suggested review order
- Product/clinical leads: 01 → 02 → 03 → 04 → 05 → 06 (the lifecycle), focusing on each Open Questions section — most items there are "decide what the rule should be" questions.
- Engineering leads: 11 and 16 first (cross-cutting permission/tenancy/security findings), then the lifecycle pages.
- Everyone: treat every Open Questions item as a ticket candidate: label it decision needed / bug / unfinished feature.
Generated from the codebase as of commit 4b321bf8 (main). Each page is independently editable Markdown — paste into ClickUp or internal docs as needed.