# LumisCare — Enterprise Healthcare Platform

Home health and community care management SaaS. Rebrand from VivaCareUSA. Stack: React 19, Java 21 Spring Boot, Azure.

# Rebrand Plan — VivaCareUSA → LumisCare

# Plan: LumisCare Rebrand — VivaCareUSA → LumisCare

**Architect:** Petter Graff (Team Lead)
**Date:** 2026-04-05
**Estimated execution:** 1 working day

---

## Research Summary

### Golden Commit
`c80a3637` — "ALL TESTS GREEN: Element 100%, CEO 100%, Flow PASS" (2026-01-25)
This is the autocoder Day 1 commit: full project skeleton + 500 features registered in features.db.
No VivaCareUSA-branded code built yet — cleanest possible rebrand starting point.
411 commits of autocoder feature builds come after this. We rebrand the skeleton, then rerun autocoder.

### What Needs Rebranding
- **106 files** with VCC/vivacare references
- **~238 occurrences** across .ts, .tsx, .js files
- Key areas:
  - HTTP headers: `X-VCC-Organization-Id`, `X-VCC-User-Id` → `X-LC-Organization-Id`, `X-LC-User-Id`
  - ENV vars: `VITE_VCC_ORGANIZATION_ID` → `VITE_LC_ORGANIZATION_ID`
  - Mock emails: `@vivacare.com` → `@lumiscare.com`
  - CSS comments: `VivaCareUSA Color System` → `LumisCare Color System`
  - Azure DevOps script: `azureProject: 'VCC'` → `azureProject: 'LumisCare'`
  - CSS legacy vars: `--color-vcc-*` → `--color-lc-*`

### Design Tokens — Already Correct
`index.css` already has the right colors (Jade Green, Neon Blue, Mint Green).
Only comments and legacy var names need updating.

### Infrastructure — Already Exists
Azure Container Apps, Static Web Apps, Bicep IaC all deployed.
`deploy-frontends.sh` + `deploy-demo.sh` exist and work.

### The Core Problem
10 days of localStorage migrations, BFF wiring, header standardization —
none of it was requested. The golden commit worked. We restore it, rebrand it, ship it.

---

## Objective

Branch off `c80a3637` (project skeleton, 500 features in DB, zero built code),
rebrand all skeleton files (VivaCareUSA/VCC → LumisCare/LC),
then run AutoCoder to rebuild all 500 features with LumisCare branding from day 1.
No migrations. No patching. Clean rebuild.

---

## Team Orchestration

### Team Members

| ID | Name | Role | Agent |
|----|------|------|-------|
| B1 | branch-builder | Create clean rebrand branch from golden commit | builder |
| V1 | branch-validator | Verify branch is clean and builds | validator |
| B2 | rebrand-builder | Execute mechanical find-replace rebrand | codecraft |
| V2 | rebrand-validator | Verify zero VCC references remain, app builds | validator |
| B3 | design-builder | Update CSS legacy var names + comments only | builder |
| V3 | design-validator | Verify design tokens are correct, no visual regression | validator |
| B4 | deploy-builder | Deploy to Azure dev environment | flowforge |
| V4 | deploy-validator | Playwright smoke test on deployed URL | sentinel-tester |

---

## Step-by-Step Tasks

### Phase 1: Restore Golden State

**Task 1.1 — Create rebrand branch from c80a3637 skeleton**
- Owner: B1 (builder)
- BlockedBy: none
- Command:
  ```bash
  cd ~/projects/client/lumiscare
  git checkout c80a3637 -b rebrand/lumiscare-clean
  git push origin rebrand/lumiscare-clean
  ```
- Acceptance:
  - [ ] Branch `rebrand/lumiscare-clean` exists on origin
  - [ ] `git log -1` shows commit `c80a3637`
  - [ ] Project skeleton files present (frontend/, backend/, infrastructure/)
  - [ ] features.db contains 500 features with passes=0

**Task 1.2 — Validate skeleton compiles**
- Owner: V1 (validator)
- BlockedBy: 1.1
- Commands:
  ```bash
  cd ~/projects/client/lumiscare/frontend/web && yarn install && yarn type-check
  sqlite3 features.db "SELECT COUNT(*) FROM features WHERE passes=0;"
  # Expected: 500
  ```
- Acceptance:
  - [ ] TypeScript exits 0 (or known baseline errors only)
  - [ ] 500 features in DB, all passes=0 (ready for autocoder)
  - [ ] No built feature code — only skeleton

---

### Phase 2: Mechanical Rebrand

**Task 2.1 — Execute find-replace rebrand**
- Owner: B2 (codecraft)
- BlockedBy: 1.2
- Replacements (exact, case-sensitive where needed):

  | Find | Replace | Scope |
  |------|---------|-------|
  | `VivaCareUSA` | `LumisCare` | all src files |
  | `VivaCare USA` | `LumisCare` | all src files |
  | `vivacareusa` | `lumiscare` | all src files |
  | `vivacare.com` | `lumiscare.com` | all src files |
  | `X-VCC-Organization-Id` | `X-LC-Organization-Id` | .ts/.tsx |
  | `X-VCC-User-Id` | `X-LC-User-Id` | .ts/.tsx |
  | `VITE_VCC_ORGANIZATION_ID` | `VITE_LC_ORGANIZATION_ID` | .ts/.tsx/.env |
  | `VITE_VCC_USER_ID` | `VITE_LC_USER_ID` | .ts/.tsx/.env |
  | `lumiscare-organization-id` | `lumiscare-organization-id` | NO CHANGE (already correct) |
  | `--color-vcc-` | `--color-lc-` | .css files |
  | `VCC Green` | `LC Green` | comments |
  | `VCC Blue` | `LC Blue` | comments |
  | `azureProject: 'VCC'` | `azureProject: 'LumisCare'` | scripts |

- Acceptance:
  - [ ] `grep -r "VivaCareUSA\|vivacareusa\|vivacare.com\|X-VCC-\|VITE_VCC_" frontend/web/src` returns 0 results
  - [ ] `yarn build` still passes after replacements

**Task 2.2 — Validate zero VCC references**
- Owner: V2 (validator)
- BlockedBy: 2.1
- Commands:
  ```bash
  grep -rn "VivaCareUSA\|VivaCare USA\|vivacareusa\|vivacare\.com\|X-VCC-\|VITE_VCC_" \
    ~/projects/client/lumiscare/frontend/web/src/ | wc -l
  # Must return 0
  grep -rn "--color-vcc-" ~/projects/client/lumiscare/frontend/web/src/ | wc -l
  # Must return 0
  ```
- Acceptance:
  - [ ] VCC references = 0
  - [ ] vivacare references = 0
  - [ ] Build passes

---

### Phase 3: Design Token Cleanup

**Task 3.1 — Update CSS comments and legacy var names**
- Owner: B3 (builder)
- BlockedBy: 2.2
- Files:
  - `frontend/web/src/index.css` — update comment header only
  - Any remaining `--color-vcc-*` → `--color-lc-*` (if V2 missed any)
- Acceptance:
  - [ ] `index.css` comment says `LumisCare Color System`
  - [ ] No `VCC` in any CSS file
  - [ ] Colors unchanged (Jade Green #35b276, Neon Blue #516df2, Mint #73ffd3)

**Task 3.2 — Validate design tokens**
- Owner: V3 (validator)
- BlockedBy: 3.1
- Acceptance:
  - [ ] `--color-jade-500: #35b276` present
  - [ ] `--color-blue-500: #516df2` present
  - [ ] `--color-mint-500: #73ffd3` present
  - [ ] No VCC color names remain

---

### Phase 4: Deploy

**Task 4.1 — Deploy to Azure dev environment**
- Owner: B4 (flowforge)
- BlockedBy: 3.2
- Commands:
  ```bash
  cd ~/projects/client/lumiscare
  # Push rebrand branch → triggers Azure Static Web Apps auto-deploy
  git push origin rebrand/lumiscare-clean
  # OR run manual deploy script
  bash infrastructure/deploy-frontends.sh dev
  ```
- Acceptance:
  - [ ] Frontend deployed to Azure Static Web Apps (dev)
  - [ ] HTTP 200 on all 3 portal URLs
  - [ ] No build errors in Azure DevOps pipeline

**Task 4.2 — Smoke test deployed app**
- Owner: V4 (sentinel-tester)
- BlockedBy: 4.1
- Test the 3 portals:
  - Backoffice: login, dashboard load, clients list
  - Admin: organizations page, users page
  - Family Portal: dashboard, care plan
- Acceptance:
  - [ ] All 3 portals load without JS errors
  - [ ] Login flow works (or mock auth works in dev)
  - [ ] No "VivaCare" or "VCC" text visible in UI
  - [ ] "LumisCare" branding visible throughout

---

## Validation Commands (Final)

```bash
# 1. Zero VCC references in frontend
grep -rn "VCC\|VivaCare\|vivacare" ~/projects/client/lumiscare/frontend/web/src/ \
  | grep -v "//\|node_modules" | wc -l
# Expected: 0

# 2. Build passes
cd ~/projects/client/lumiscare/frontend/web && yarn build && yarn type-check

# 3. Portal URLs return 200
curl -s -o /dev/null -w "%{http_code}" https://jolly-bay-01cfa3003.6.azurestaticapps.net
curl -s -o /dev/null -w "%{http_code}" https://zealous-hill-097d4b803.6.azurestaticapps.net
curl -s -o /dev/null -w "%{http_code}" https://white-island-07c0e4c03.4.azurestaticapps.net
```

---

## What This Plan Does NOT Do

- No localStorage migrations (P2-28 through P2-32 stay in MC for later)
- No new features
- No backend changes
- No BFF wiring changes
- No architectural refactoring

**Restore → Rebrand → Deploy. That's it.**

---

## Risk

| Risk | Mitigation |
|------|-----------|
| Azure DevOps pipeline still named "VCC" | Rename pipeline display name only — do NOT touch infra resource names (breaking change) |
| Backend services still use `X-VCC-*` headers | Frontend and backend header rename must be done in same PR, or keep old header names and only rename in comments |
| `8b0effdc` missing some later fixes | V1 task verifies build — if broken, use next clean commit after `8b0effdc` |

**Note on headers:** `X-VCC-Organization-Id` is sent by frontend AND read by backend services. If we rename on frontend only, backend breaks. Either rename both simultaneously, or leave headers as-is (they're internal API headers, not user-visible branding).

---

## Execution

Run `/build-plan` to execute with agent teams.

# Architecture — Component HLD Master

# LumisCare — Component High-Level Design Master Document

**Version:** 1.0
**Date:** 2026-04-04
**Author:** Dr. Sarah Chen, Healthcare IT Systems Architect
**Status:** Active — Reflects verified implementation state as of 2026-04-04
**Method:** Static analysis of source tree + HLD document review + gap analysis synthesis

---

## Document Purpose

This document provides the authoritative component-level HLD for all LumisCare microservices, BFF layers, and frontend features. It cross-references the documented architecture (VCC2-C4-Architecture-Diagrams.md, Services-Integration-Matrix.md, BFF-Architecture-Design.md, Service-Bus-Subscriptions-Design.md) against verified implementation on disk.

Each component section records: implementation status, controller/entity counts, BFF integration status, event bus wiring status, and known gaps.

**CRITICAL NOTE — Naming inconsistency:** HLD documents use "VCC 2.0" throughout. The BFF design switches to "iCON". The deployed product is "LumisCare". All three names refer to the same system. This document uses "LumisCare" as the canonical product name.

---

## System Architecture Summary

```
Web (React 19) ──────────────────────── Web BFF (port 8080)
                                                │
Mobile (React Native / Expo SDK 52) ──── Mobile BFF
                                                │
                               ┌────────────────┴───────────────────┐
                               │    MICROSERVICES LAYER              │
                               │  (Java 21, Spring Boot 3.4)         │
                               │                                     │
                               │  identity-service                   │
                               │  assessment-service                 │
                               │  careplan-service                   │
                               │  visits-service                     │
                               │  hr-service                         │
                               │  scheduling-service                 │
                               │  incidents-service                  │
                               │  finance-service                    │
                               │  notification-service               │
                               │  safety-service                     │
                               │  policy-service                     │
                               │  document-service                   │
                               │  fhir-adapter-service              │
                               └─────────────────────────────────────┘
                                               │
                              Azure Service Bus (domain-events topic)
                              Azure PostgreSQL (per-service databases)
                              Azure Redis (shared cache)
                              Azure Blob Storage (documents)
```

**Multi-tenancy:** Row-Level Security (RLS) on organisation_id across all service databases.
**Auth:** Azure Entra ID (MSAL) — JWT pass-through from BFF to microservices. Service-to-service via Azure Managed Identity.
**Deployment:** Azure Container Apps (AKS target). Azure Static Web Apps for frontend.

---

## Component Status Legend

| Status | Meaning |
|--------|---------|
| PRODUCTION-READY | Controllers, entities, repositories, and migrations all present and verified |
| PARTIAL | Core entities and controllers present but key integration (event bus, BFF, mobile) absent |
| SKELETON | Directory exists, minimal or no Java source files |
| UNDOCUMENTED | Implemented in code but absent from all HLD documents |
| DESIGN-ONLY | Documented in HLD but not implemented in code |

---

## Component Index

1. Identity
2. HR
3. Visits
4. Scheduling
5. Care Plans
6. Assessments
7. Incidents
8. Finance
9. Notifications
10. Safety
11. Policy (AI/RAG)
12. Document
13. FHIR Adapter
14. Web BFF
15. Mobile BFF

---

## 1. Identity

### Purpose

Manages users, organisations, service users, RBAC roles, consent records, PHI access audit, and GDPR data rights. Serves as the authentication enrichment layer for Azure Entra ID tokens — all services call identity-service to resolve user context from JWTs.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 8 — ConsentController, OrganizationController, ServiceUserController, RoleController, AuthInternalController, DataRightsController, SafetyController, UserController
- **Entities:** 11 — UserEntity, ServiceUserEntity, RoleEntity, OrganizationEntity, ConsentEntity, EmergencyContactEntity, OrganizationSettingsEntity, PhiAccessAuditEntity, PhiAnomalyAlertEntity, DataRightsRequestEntity, DnacprAuditLogEntity
- **Repositories:** 10 — UserRepository, ServiceUserRepository, RoleRepository, OrganizationRepository, ConsentRepository, EmergencyContactRepository, PhiAccessAuditRepository, PhiAnomalyAlertRepository, DataRightsRequestRepository, DnacprAuditLogRepository
- **Migrations:** 14 Flyway migrations (V8: role permissions, V12: PHI access audit and anomaly tables)
- **Key capabilities:**
  - Multi-tenant organisation isolation via RLS on organisation_id
  - PHI access audit infrastructure (PhiAccessAuditEntity, PhiAnomalyAlertEntity) — HIPAA minimum-necessary enforcement at data layer
  - DNACPR audit log repository (DnacprAuditLogEntity) — advance directive capture. NOTE: creates dual-domain ownership with assessment-service (see Gaps)
  - DataRightsController — GDPR/UK GDPR data subject access request workflow
  - ConsentController + ConsentEntity — consent lifecycle management
  - AuthInternalController — Entra ID token enrichment (JWT → VCC claims)
  - RoleController — RBAC role and permission management

### Frontend

- **Pages:** `/admin/users`, `/admin/roles`, auth pages (login, MFA, OTP)
- **Status:** Real — users, roles, and auth pages implemented in `src/features/admin/users`, `src/features/admin/roles`, `src/features/admin/organizations`

### BFF Integration

- **web-bff:** UsersController, OrganizationsController — GET /users/{id}, GET /organizations/{id}, GET /service-users/{id}
- **mobile-bff:** No dedicated controller confirmed — identity resolution happens via JWT enrichment on every request

### Event Bus

- **Publishes:** No event publisher confirmed in source
- **Subscribes:** Consumes `employee.created` and `employee.terminated` from HR (documented in Integration Matrix; HrEventPublisher exists in hr-service)
- **Status:** NOT WIRED — no identity-service event publisher found; subscription to HR events not confirmed in source despite being documented

### Gaps

- No event publisher — service user onboarding events (`serviceuser.created`) are not published, preventing other services from reacting to new intake
- DNACPR dual-domain ownership: DnacprAuditLogEntity in identity-service and DnacprOrderEntity in assessment-service have no confirmed synchronisation mechanism. For a resuscitation decision, this is clinically unacceptable — a DNACPR order cancelled in assessment-service may not be reflected in the identity audit log
- No confirmed break-glass emergency access pattern implementation (entity infrastructure exists but controller-level break-glass with audit is not confirmed)
- No event publisher means identity cannot signal account deactivation to scheduling-service for orphan appointment cleanup

---

## 2. HR

### Purpose

Manages employee records, qualifications, training certifications, availability preferences, leave management, performance reviews, and equality/diversity data. Acts as the canonical source of carer workforce data for the scheduling-service.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 12 — EmployeeController, EmployeeSearchController, EmployeeNoteController, AvailabilityPreferencesController, TrainingRecordController, TrainingCertificateUploadController, EducationTrainingController, PerformanceReviewController, LeaveManagementController, SupportingDocumentController, HealthInformationController, ClientFeedbackController
- **Entities:** 12+ — EmploymentHistory, PerformanceReview, LeaveRecord, TrainingRecord, EducationTraining, CertificateUploadTracking, ResumeUploadTracking, HealthInformation, EqualityDiversity, NextOfKin, EmployeeReference, AvailabilityPreferences
- **Repositories:** 12 — EmployeeRepository, LeaveRecordRepository, PerformanceReviewRepository, TrainingRecordRepository, EducationTrainingRepository, AvailabilityPreferencesRepository, CertificateUploadTrackingRepository, ResumeUploadTrackingRepository, HealthInformationRepository, ClientFeedbackRepository, EmployeeReferenceRepository, SupportingDocumentRepository
- **Migrations:** 24 Flyway migrations (highest in the fleet — V14: leave records, V15: performance reviews)
- **Key capabilities:**
  - HealthInformation as separate entity — appropriate PHI boundary for staff health data
  - CertificationExpiryScheduler — scheduled job for expired training certificate detection (CQC Regulation 17 compliance)
  - HrEventPublisher — publishes HR events to Azure Service Bus
  - AvailabilityPreferencesService — publishes availability changes consumed by scheduling-service
  - LeaveManagementService — publishes leave approval events
  - Bradford Factor calculation support

### Frontend

- **Pages:** `/employees` (list), `/employees/:id` (detail), HR sub-pages for DBS, Training, Bradford Factor, Supervisions
- **Feature directory:** `src/features/backOffice/hr/`
- **Status:** Real — hr feature has full component structure (api, components, constants, hooks, models, pages, schemas, services, store, utils)

### BFF Integration

- **web-bff:** EmployeeController, HrComplianceController, ResumeUploadController — GET /employees, GET /employees/{id}/availability, GET /employees/{id}/compliance
- **mobile-bff:** No dedicated HR controller — carer profile accessed via CarerScheduleController

### Event Bus

- **Publishes:** `employee.created`, `employee.terminated`, `availability.updated`, `leave.approved`, `skill.added`
- **Subscribes:** Nothing confirmed (HR is primarily a publisher; scheduling-service consumes its events)
- **Status:** WIRED — HrEventPublisher, AvailabilityPreferencesService, LeaveManagementService all confirmed in source. AvailabilityUpdatedEventListener confirmed in scheduling-service as consumer.

### Gaps

- Entity naming inconsistency: HR entities do not use the `*Entity.java` suffix convention used by other services. This does not affect runtime but complicates static analysis tooling and cross-service code navigation
- ClientFeedbackController sits in hr-service — client feedback data may belong in assessment-service or incidents-service by domain
- No BFF controller for employee DBS (Disclosure and Barring Service) record status — DBS expiry is a CQC compliance item and should be surfaced to care coordinators via a dedicated endpoint

---

## 3. Visits

### Purpose

Manages the full visit execution lifecycle: Electronic Visit Verification (EVV) check-in/check-out, care note recording, eMAR (electronic medication administration record), vital sign capture, task completion, observation recording, real-time carer location tracking, and geofence enforcement. The most clinically active service during field operations.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 12 — VisitController, VisitLifecycleController, EVVController, EmarController, MedicationController, MedicationRefusalController, TaskController, ObservationController, VitalSignController, CareNoteController, GeofenceController, TrackingController
- **Entities:** 13+ — EVVRecord, EmarRecord, MedicationSchedule, MedicationAdministration, VisitTask, VitalSignRecord, TrackingSession, Geofence, Observation, ObservationThreshold, ObservationType, TaskStatus, MedicationRefusalAlert
- **Repositories:** 12 — VisitRepository, EVVRecordRepository, EmarRecordRepository, MedicationScheduleRepository, MedicationAdministrationRepository, MedicationRefusalAlertRepository, VitalSignRecordRepository, CareNoteRepository, CarerLocationRepository, TrackingSessionRepository, VisitTaskRepository, ObservationRepository
- **Migrations:** 7 Flyway migrations (V9: observation threshold table, V13: PRN minimum interval hours)
- **Key capabilities:**
  - EVV 6-data-element compliance (21st Century Cures Act): service type, carer ID, service user ID + Medicaid ID, start/end GPS coordinates, transmission status with timestamp, manual override with reason
  - TransmissionStatus enum (PENDING/TRANSMITTED) with EVVService aggregator submission pipeline
  - MedicationAdministration with requiresWitness, witnessedBy, witnessSignatureUrl — controlled substance double-check protocol at entity level
  - V13 migration: prn_min_interval_hours — PRN medication safety interval enforcement
  - MedicationRefusalController + MedicationRefusalAlertRepository — refusal recording and alerting
  - GeofenceController — service user address boundary enforcement
  - TrackingController — real-time carer location (30-second intervals to Redis + Azure Web PubSub for dashboard)

### Frontend

- **Pages:** `/visits` (list), `/visits/:id` (detail), live tracking dashboard, geofence configuration, visit handoff
- **Feature directory:** `src/features/backOffice/visits/` with subdirectories: api, components, geofence, handoff, hooks, liveTracking, pages, services, store, types, utils
- **EVV feature:** `src/features/backOffice/evv/` — EVVPage, EVVComplianceReport, EVVRecordsList, EVVManualCheckInForm, EVVRecordDetail, EVVFilters
- **Status:** Real — full component structure confirmed

### BFF Integration

- **web-bff:** VisitsController, HandoffController, CompletedEventsController — GET /visits, GET /visits/{id}/full-details (aggregates visit + care plan tasks + service user + carer), GET /completed-events
- **mobile-bff:** CarerScheduleController (MobileVisitsClient) — GET /my-visits/today, POST /visits/{id}/check-in, POST /visits/{id}/check-out, POST /visits/{id}/tasks/{taskId}/complete, POST /tracking/location
- **CRITICAL GAP:** Mobile BFF has no eMAR controller, no EVV dedicated controller, no vital signs controller, and no care note controller. If mobile carers access these endpoints, they bypass the BFF layer entirely.

### Event Bus

- **Publishes:** `visit.checkedin`, `visit.completed`, `task.flagged`, `checkin.timeout`, `assistance.requested`, `location.updated`, `redflag.detected` (VisitEventPublisher confirmed in source)
- **Subscribes:** No subscription confirmed (documented as publisher-only in MVP Integration Matrix)
- **Status:** PARTIALLY WIRED — VisitEventPublisher exists; however, no consumer-side listeners confirmed in finance-service for `visit.completed` (the event that should trigger invoice line item creation)

### Gaps

- Only 7 Flyway migrations for a service with 12 controllers and significant clinical data scope — schema change audit trail is incomplete; later entity changes may have been applied via ORM DDL rather than versioned migrations (compliance risk for schema change governance)
- No confirmed barcode/NFC medication verification integration at the EmarController layer — entity exists but scan endpoint not confirmed
- Mobile BFF missing: eMAR recording, EVV check-in/check-out (dedicated), vital sign capture, incident reporting, and SOS — if mobile app calls these directly (bypassing BFF), JWT scope enforcement, PHI boundary logging, and API gateway rate limiting are not applied consistently (HIPAA minimum-necessary enforcement gap)
- Controlled substance witness enforcement: MedicationAdministration has the correct entity fields (requiresWitness, witnessedBy, witnessSignatureUrl) but business logic enforcement preventing "given" status without a witness signature needs to be confirmed at EmarController service layer — entity-level fields are necessary but not sufficient
- Finance-service has no confirmed listener for `visit.completed` event — invoicing cannot be triggered automatically from visit completion

---

## 4. Scheduling

### Purpose

Creates, manages, and publishes care schedules. Handles recurring appointment generation, carer assignment with skill matching, schedule approval workflow, cover arrangements, and proposed appointment review. Bridges care plan proposed visits and operational visit execution.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 9 — AppointmentController, RecurringScheduleController, ScheduleController, ScheduleApprovalController, ScheduleAlertController, ScheduleEventController, CoverController, ProposedAppointmentReviewController, FinanceController
- **Entities:** 7 — AppointmentEntity, RecurringScheduleEntity, ScheduleGenerationEntity, ScheduleAlertEntity, ScheduleEventEntity, CarePlanEventIdempotencyEntity, HrAvailabilityEventIdempotencyEntity
- **Repositories:** 7 — AppointmentRepository, RecurringScheduleRepository, ScheduleGenerationRepository, ScheduleAlertRepository, ScheduleEventRepository, CarePlanEventIdempotencyRepository, HrAvailabilityEventIdempotencyRepository
- **Migrations:** 13 Flyway migrations (V9: travel time on appointments, V13: rejected status on schedule generations)
- **Key capabilities:**
  - Two-step scheduling governance: ScheduleApprovalController + ProposedAppointmentReviewController
  - Idempotency repositories (CarePlanEventIdempotency, HrAvailabilityEventIdempotency) — duplicate event delivery from care plan or HR will not create duplicate appointments
  - AppointmentEventPublisher — outbound event publishing for schedule lifecycle events
  - HrAvailabilityUpdatedEventHandler — consumes HR events to keep schedules consistent with carer availability
  - FinanceController within scheduling-service — feeds billable hours data to finance-service
  - Travel time field (V9 migration) — routing time included in appointment planning
  - ScheduleAlertController + ScheduleAlertEntity — alert generation for scheduling conflicts

### Frontend

- **Pages:** `/scheduling` (week/month view), appointment detail, cover requests, schedule approval workflow
- **Feature directory:** `src/features/backOffice/scheduling/` with subdirectories: components, pages, services, types
- **Status:** Real — confirmed component structure

### BFF Integration

- **web-bff:** SchedulingController, CalendarController — GET /schedules, POST /schedules, PUT /schedules/{id}/publish, GET /calendar (aggregated view)
- **mobile-bff:** AppointmentsController (AppointmentsService via scheduling-service) — GET /my-appointments, GET /appointments/{id}

### Event Bus

- **Publishes:** `schedule.published`, `schedule.updated` (AppointmentEventPublisher confirmed)
- **Subscribes:** `availability.updated`, `leave.approved` from hr-events (HrAvailabilityUpdatedEventHandler confirmed with idempotency); `careplan.published` from careplan-events (CarePlanEventIdempotencyEntity present — idempotency ready, but no publisher on careplan-service side)
- **Status:** PARTIALLY WIRED — publisher confirmed; HR subscription confirmed (fully wired); careplan.published subscription is idempotency-ready but the publisher (careplan-service) does not exist

### Gaps

- ScheduleAlertEntity has AlertType and AlertSeverity enums but no confirmed integration path to notification-service for alert delivery — scheduling alerts may not reach care managers
- Travel time (V9 migration) is stored but no routing API integration confirmed — travel time is entered manually, not calculated from a routing service
- CarePlanEventIdempotencyEntity exists and is ready to consume careplan.published — but the careplan-service has no event publisher, so this idempotency infrastructure is currently unused
- No confirmed mobile BFF endpoint for cover requests or schedule change notifications to carers

---

## 5. Care Plans

### Purpose

Manages the full care plan lifecycle: AI-assisted generation from assessments, plan versioning, task definition, master visit type configuration, and proposed visit generation. The care plan is the primary clinical document linking assessment outcomes to scheduled care delivery.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 5 — CarePlanController, AiGenerationController, MasterTaskController, MasterVisitTypeController, CarePlanVersionHistoryController
- **Entities:** 8 — CarePlan, CarePlanVersion, MasterVisitType, MasterTaskType, MasterTaskCategory, TaskDefinition, ProposedVisit, AiMetrics
- **Repositories:** 8 — CarePlanRepository, CarePlanVersionRepository, MasterVisitTypeRepository, MasterTaskCategoryRepository, MasterTaskRepository, TaskDefinitionRepository, ProposedVisitRepository, AiMetricsRepository
- **Migrations:** 10 Flyway migrations (V2: care plan version table, V4: master task type table)
- **Key capabilities:**
  - Care plan versioning: CarePlanVersion entity + CarePlanVersionHistoryController — full version history with diff capability
  - Approval-to-publish workflow: CarePlan has status, publishedAt, publishedBy fields
  - AI-assisted care plan generation: AiGenerationController + AiMetricsRepository — instrumented for outcome tracking
  - ProposedVisit entity — proposed visit schedule generated from care plan tasks, consumed by scheduling-service
  - MasterVisitType and MasterTaskType — configurable task library per organisation

### Frontend

- **Pages:** `/carePlans` (list), `/carePlans/:id` (detail with version history), AI care plan generator (`/ai/care-plan-generator`)
- **Feature directory:** `src/features/backOffice/carePlans/` (components, pages, services), `src/features/backOffice/ai/AICarePlanGeneratorPage.tsx`
- **Status:** Real — confirmed component structure

### BFF Integration

- **web-bff:** CarePlanController (BFF) — GET /care-plans/{id}, POST /care-plans/generate, GET /care-plans/{id}/version-history
- **mobile-bff:** CarePlanController (mobile) — GET /care-plans/{id} (read-only, for visit execution context)
- **Generated client:** assessment-client and careplan-client are generated OpenAPI clients in both BFFs

### Event Bus

- **Publishes:** `careplan.published`, `careplan.updated` — DOCUMENTED in C4 diagram and Integration Matrix but NO event publisher class found in careplan-service source
- **Subscribes:** `assessment.completed` — DOCUMENTED as trigger for AI generation but NO subscription listener found in careplan-service source
- **Status:** NOT WIRED — neither publisher nor subscriber is implemented. This is the most critical clinical workflow gap in the platform: the Assessment → Care Plan → Schedule chain has broken links at both ends of careplan-service

### Gaps

- No event publisher for `careplan.published` — scheduling-service cannot pick up a newly published care plan automatically (CarePlanEventIdempotencyEntity in scheduling-service is ready but has no events to consume)
- No subscription listener for `assessment.completed` — AI care plan generation must be triggered manually rather than automatically on assessment completion
- No RiskAssessment entity within careplan-service — risk assessments are in assessment-service (appropriate) but linkage/reference between care plan and risk assessment outcomes is not confirmed
- No countersignature or RN approval workflow entity visible at care plan level (publishedBy exists but role constraint enforcement at controller level not confirmed)
- AiMetrics entity tracks generation but no confirmed outcome feedback loop (care plan quality improvement over time requires feedback from visit task completion data)

---

## 6. Assessments

### Purpose

Manages the complete clinical assessment lifecycle: intake, section/question/answer management, risk assessments (NEWS2, Waterlow, PHQ-9, GAD-7), Mental Capacity Assessment (MCA), DNACPR order management, medication reconciliation, clinical scoring, AI-assisted assessment transcription, and real-time collaborative assessment via Azure Web PubSub.

### Backend Service

- **Status:** PRODUCTION-READY — highest controller count in the fleet
- **Controllers:** 23 — AssessmentIntakeApiController, AssessmentStatusApiController, AssessmentQuestionsApiController, AssessmentAnswersApiController, AssessmentSectionsApiController, AssessmentSubsectionsApiController, AssessmentMembersApiController, AssessmentSubmissionApiController, AssessmentDashboardApiController, RiskAssessmentSectionsApiController, RiskQuestionsApiController, RiskAnswersApiController, MentalCapacityAssessmentApiController, ClinicalScoringController, DnacprOrderApiController, MedicationsApiController, ObservationsApiController, NotesController, PhotosController, PatientsApiController, AiMessageController, WebPubSubController, VersionController
- **Entities:** 14+ — AssessmentEntity, News2ScoreEntity, WaterlowScoreEntity, Phq9ScoreEntity, MentalCapacityAssessmentEntity, DnacprOrderEntity, MedicationEntity, QuestionTemplateEntity, RiskSectionTemplateEntity, RiskQuestionTemplateEntity, MedicalRiskAuditEntity, EmergencyContactEntity, ConsentTypeLabelEntity, AssessmentAnswerEntity
- **Repositories:** 14+ — AssessmentRepository, AssessmentAnswerRepository, WaterlowScoreRepository, MentalCapacityAssessmentRepository, DnacprAcknowledgementRepository, ResponseRepository, SectionRepository, SectionTemplateRepository, SubsectionTemplateRepository, QuestionTemplateRepository, MedicationRepository, PhotoRepository, CareNeedRepository, ConsentTypeRepository
- **Migrations:** 8 Flyway migrations (V1: create tables, V5: add note columns)
- **Key capabilities:**
  - NEWS2 (National Early Warning Score 2) — deterioration detection
  - Waterlow score — pressure injury risk
  - PHQ-9 — depression screening (IAPT-compatible)
  - MentalCapacityAssessmentApiController + MentalCapacityAssessmentEntity — Mental Capacity Act 2005 compliance
  - DnacprOrderApiController + DnacprOrderEntity + DnacprAcknowledgementRepository — resuscitation decision management with acknowledgement audit
  - ClinicalScoringController — automated score calculation for standardised tools
  - AiMessageController — AI-assisted assessment transcription (Azure OpenAI)
  - WebPubSubController — real-time collaborative assessment (multi-assessor support)
  - MedicalRiskAuditEntity — audit trail for risk assessment changes
  - PhotosController — photo evidence capture for wound/pressure injury documentation

### Frontend

- **Pages:** Assessment list, assessment detail, section-by-section questionnaire, risk assessment views, MCA assessment, clinical scoring summaries
- **Feature directory:** `src/features/backOffice/clients/` (assessment workflows are accessed via client record)
- **Status:** Real — clients feature has full component structure including assessment workflows

### BFF Integration

- **web-bff:** BffAssessmentController, AssessmentWebDashBoardController — GET /assessments/{id}, POST /assessments, GET /assessments/dashboard
- **mobile-bff:** AssessmentsController, RiskAssessmentsController (assessment-client generated OpenAPI client) — GET /assessments/{id}, POST /assessments/submit
- **Generated client:** assessment-client confirmed as generated OpenAPI client in both BFFs

### Event Bus

- **Publishes:** `assessment.completed` — DOCUMENTED as trigger for care plan AI generation but NO event publisher class found in assessment-service source
- **Subscribes:** Nothing documented
- **Status:** NOT WIRED — assessment.completed is the first event in the primary clinical workflow chain (Assessment → CarePlan → Schedule → Visit → Finance). Its absence breaks the entire automated care pathway.

### Gaps

- No event publisher — assessment.completed cannot trigger automated care plan generation
- 8 Flyway migrations for 23 controllers is critically low — high probability that schema changes have been applied outside of Flyway migrations, breaking the schema change audit trail required for clinical system governance
- DNACPR dual-domain: DnacprOrderEntity lives here (correct domain) but DnacprAuditLogEntity lives in identity-service with no confirmed sync mechanism
- No DoLS (Deprivation of Liberty Safeguards) entity confirmed — DoLS authorisation tracking is a legal requirement for any service user subject to a standard or urgent DoLS authorisation
- GAD-7 (anxiety screening) not confirmed as a named entity despite PHQ-9 being present — may be handled via generic question templates

---

## 7. Incidents

### Purpose

Manages incident reporting, safeguarding concerns, hazard logging, complaints management, and CQC statutory reporting evidence. Provides CSV export for regulatory data extraction during CQC inspections.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 7 — AccidentIncidentController, SafeguardingController, HazardController, ComplaintController, LogActionController, SummaryController, CsvExportController
- **Entities:** 9 — AccidentIncidentLog, SafeguardingLog, HazardLog, ComplaintLog, LogAction, LogStatus, LogStatusConverter, SeverityLevel, SeverityLevelConverter
- **Repositories:** 5 — AccidentIncidentLogRepository, SafeguardingLogRepository, HazardLogRepository, ComplaintLogRepository, LogActionRepository
- **Migrations:** 9 Flyway migrations (V4: hazard logs, V7: outbox tables for event publishing)
- **Key capabilities:**
  - SafeguardingLog with reportedToLocalAuthority, localAuthorityReference, reportedToPolice, policeReference — statutory reporting data model at entity level
  - SeverityLevel entity with converter — structured severity classification
  - CsvExportController — regulatory data extraction for CQC inspection evidence
  - SummaryController — aggregate incident reporting across categories
  - V7 outbox migration — reliable event publishing via transactional outbox pattern
  - LogAction + LogActionRepository — action tracking against each incident

### Frontend

- **Pages:** `/incidents` (list with filters), incident detail, safeguarding view, incident creation forms
- **Feature directory:** `src/features/backOffice/incidents/` with subdirectories: components, detail, hooks, list, safeguarding, services
- **Status:** Real — full component structure with dedicated safeguarding subdirectory

### BFF Integration

- **web-bff:** IncidentsController — GET /incidents, POST /incidents, GET /incidents/{id}, GET /safeguarding/{id}
- **mobile-bff:** No incidents controller confirmed — mobile carers cannot report incidents through the BFF layer (PHI boundary gap)

### Event Bus

- **Publishes:** `incident.created` (EventPublisher confirmed in source; outbox tables confirmed in V7 migration)
- **Subscribes:** Nothing documented
- **Status:** PARTIALLY WIRED — publisher infrastructure confirmed (outbox tables + EventPublisher class). Notification-service should consume incident.created for safeguarding alerts, but @ServiceBusListener is commented out in notification-service (see Notifications component)

### Gaps

- No mandatory reporting deadline field — CQC requires notification within 24 hours for certain incident types (death of service user, serious injuries, allegations of abuse). No notificationDeadline or overdueNotification field confirmed. No automated deadline enforcement or escalation for overdue statutory notifications
- SafeguardingController endpoint mapping not confirmed — routes may be implemented or stubbed; direct inspection required
- No MASH (Multi-Agency Safeguarding Hub) integration endpoint or referral number tracking beyond localAuthorityReference
- Mobile BFF missing incidents reporting controller — field carers cannot submit incident reports through the secured BFF layer
- No CQC RI (Responsible Individual) notification workflow entity — RI must be notified for certain CQC Regulation 18 events; this is not automated
- No confirmed near-miss capture category (distinct from accidents) — near-miss data is required for CQC Well-Led evidence

---

## 8. Finance

### Purpose

Manages the complete financial lifecycle: invoice generation, pay rates, payroll, timesheet management, insurance claim generation (837P/837I EDI), remittance reconciliation (835 EDI), gross profit analysis, hours variance reporting, and payer management. The largest service by code volume in the fleet.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 11 — PayRatesController, PayrollsController, DashboardController, GrossProfitController, InvoiceGroupsController, InvoiceRatesController, PaymentGroupsController, InvoicesController, HoursVarianceController, InsuranceClaimsController, PayerManagementController
- **Entities:** 14 — Invoice, InvoiceGroup, ServiceUserInvoiceGroup, InvoicePayment, Timesheet, TimesheetLineItem, TimesheetBatchJob, PaymentGroup, FinanceDashboardMetric, FinanceAuditLog, OutboxEvent, InsuranceClaim, ClaimLineItem, RemittanceRecord
- **Repositories:** 10+ — InvoiceGroupRepository, InvoiceLineItemRepository, ServiceUserInvoiceGroupRepository, InvoicePaymentRepository, PaymentGroupRepository, PayRateRepository, InvoiceBatchJobRepository, TimesheetsRepository, FinanceSequenceRepository, OutboxEventRepository
- **Migrations:** 17 Flyway migrations (V1: create tables, V12: unique index on invoice rates)
- **Key capabilities:**
  - 837P/837I EDI billing pathway: InsuranceClaim, ClaimLineItem entities, InsuranceClaimsController, PayerManagementController
  - 835 remittance processing: RemittanceRecord entity, Edi835ProcessorService, RemittanceProcessResponse DTO — full claim-to-remittance cycle
  - Transactional outbox pattern: OutboxEvent entity + OutboxEventRepository — the infrastructure exists for reliable event publishing, but the publisher dispatching to Service Bus is absent
  - TimesheetBatchJob — async batch processing for payroll
  - HoursVarianceController + GrossProfitController — operational finance analytics
  - FinanceDashboardMetric entity — aggregated finance KPIs

### Frontend

- **Pages:** `/finance` (billing, payroll views), invoice list/detail, timesheet views, pay rates configuration
- **Feature directory:** `src/features/backOffice/finance/` (api, billing, components, definitions, hooks, models, pages, services, store, types, utils), `src/features/backOffice/billing/`
- **Status:** Real — full component structure confirmed

### BFF Integration

- **web-bff:** FinanceBffController (FinanceClientConfig — WebClient, not generated OpenAPI client) — GET /invoices, GET /timesheets, GET /finance/dashboard, GET /gross-profit
- **mobile-bff:** No finance controller — appropriate for field worker role (carers do not access finance)
- **Family portal:** FamilyPortalController includes invoices endpoint (read-only GET confirmed)

### Event Bus

- **Publishes:** `invoice.created`, `invoice.paid`, `timesheet.created` — DOCUMENTED but OutboxEvent entity exists without a publisher class dispatching to Service Bus. The outbox pattern is initiated but incomplete.
- **Subscribes:** `visit.completed` — DOCUMENTED as trigger for invoice line item generation but no consumer listener confirmed in finance-service source
- **Status:** NOT WIRED — the transactional outbox infrastructure is started (entity + repository) but the publisher component dispatching messages to Azure Service Bus is absent. Finance events will not fire. The visit.completed subscription is also absent — invoicing cannot be triggered automatically.

### Gaps

- OutboxEvent + OutboxEventRepository present but no OutboxEventPublisher class — the transactional outbox is half-implemented; payroll discrepancies and invoice creation events will be silent
- No confirmed listener for visit.completed — visit-to-invoice automation is broken
- Non-standard package namespace: com.lumiscare.icon.finance (inconsistent with com.lumiscare.* fleet pattern) — suggests this service was imported or adapted from a separate codebase; introduces dependency drift risk
- NHS framework invoicing (NHSmail purchase order workflow, ICB/CCG rate card configuration) not confirmed — if this platform targets the UK NHS market, this is a billing gap
- No ANSI X12 generator class confirmed for outbound 837 claim generation (only inbound 835 processor confirmed)

---

## 9. Notifications

### Purpose

Multi-channel notification dispatch: push (Firebase Cloud Messaging), email (SendGrid), SMS (Twilio), and in-app inbox. Consumes domain events from Azure Service Bus and routes them to appropriate users based on notification preferences and rate limits. Maintains notification history and delivery analytics.

### Backend Service

- **Status:** PARTIAL — CRITICAL GAP
- **Controllers:** 5 — DeviceController, InboxController, NotificationPreferencesController, NotificationAnalyticsController, SosNotificationController
- **Entities:** 6 — DeviceRegistration, InAppNotification, NotificationPreference, NotificationTemplate, NotificationLog, NotificationBatchQueue
- **Repositories:** 6 — DeviceRegistrationRepository, InAppNotificationRepository, NotificationPreferenceRepository, NotificationTemplateRepository, NotificationLogRepository, NotificationBatchQueueRepository
- **Migrations:** 4 Flyway migrations (V1: initial schema, V4: notification batch queue)
- **Key capabilities:**
  - ServiceBusConfig — Azure Service Bus connectivity configured
  - ServiceBusEventListener — exists with handleDomainEvent method
  - NotificationEventPublisher — outbound notification.sent/failed events
  - NotificationBatchQueue — batch notification infrastructure
  - SosNotificationController — direct-call SOS path (bypasses event bus; works independently)
  - NotificationPreferencesController — per-user channel preference management
  - DeviceController — FCM device token registration
  - NotificationAnalyticsController — delivery tracking and metrics

### Frontend

- **Pages:** Notification inbox, notification preferences
- **Feature directory:** `src/features/notifications/` (api, components, hooks, pages, services, store, types)
- **Status:** Real — full component structure confirmed

### BFF Integration

- **web-bff:** NotificationsController — GET /notifications, PUT /notifications/{id}/acknowledge
- **mobile-bff:** No dedicated notifications controller confirmed — mobile notification delivery is via push (FCM) direct

### Event Bus

- **Publishes:** `notification.sent`, `notification.failed` (NotificationEventPublisher confirmed)
- **Subscribes:** visit-events (checkin.timeout, task.flagged), schedule-events (schedule.published), finance-events (invoice.created, invoice.overdue), incidents-events (incident.created) — ALL DOCUMENTED. ServiceBusEventListener EXISTS but @ServiceBusListener annotation is COMMENTED OUT on line 86 of ServiceBusEventListener.java.
- **Status:** CRITICAL — service infrastructure is complete and service can send notifications via direct call. However, @ServiceBusListener is commented out. No domain events from any service are currently being consumed. Missed visit alerts, medication overdue alerts, safeguarding escalations, certification expiry, and schedule change notifications will not be delivered through the event bus pathway.

### Gaps

- @ServiceBusListener commented out on ServiceBusEventListener.java line 86 — this is the single most impactful production gap in the entire platform. Uncommenting this line and validating end-to-end event delivery is the highest priority remediation action.
- Only 4 Flyway migrations for a service of this notification scope — migration history appears compressed
- No dead-letter queue handling or poison message retry strategy confirmed beyond the documented design
- No confirmed telephony (SMS Twilio) integration test
- No confirmed deduplication Redis key implementation (documented in design but not confirmed in source)

---

## 10. Safety

### Purpose

Manages lone worker protection (LWP), SOS emergency alerts, and emergency contact management. The LWP timer service runs server-side (not dependent on carer device signal) and escalates overdue check-in sessions through a supervisor notification ladder. This service addresses the CQC requirement for lone worker safety in domiciliary care.

### Backend Service

- **Status:** PRODUCTION-READY (Undocumented — no HLD entry exists)
- **Controllers:** 3 — SOSController, LWPController, EmergencyContactController
- **Entities:** 10 — LWPSession, LWPConfiguration, LWPCheckin, LWPEscalation, LWPSessionStatus, SOSEvent, SOSEventType, SOSNotification, EmergencyContact, ResolutionType
- **Repositories:** 7 — LWPSessionRepository, LWPConfigurationRepository, LWPCheckinRepository, LWPEscalationRepository, SOSEventRepository, SOSNotificationRepository, EmergencyContactRepository
- **Migrations:** 2 Flyway migrations (V10: safety tables, V11: add service_user_id to LWP sessions)
- **Key capabilities:**
  - LWPTimerService — scheduled every 30 seconds, finds overdue LWP sessions, triggers escalation ladder (Level 1 to supervisor). Server-side execution — not dependent on carer device or mobile signal
  - LWPEscalation entity + LWPEscalationRepository — persistent escalation audit trail
  - SafetyEventPublisher — publishes LWP escalation events to Service Bus
  - SOSController — emergency SOS event creation and notification dispatch
  - EmergencyContact entity — direct outreach contact management

### Frontend

- **Pages:** SOS alert banner on supervisor dashboard (`SOSAlertBanner.tsx`), SOS history page (`SOSHistoryPage.tsx`), active visits map with SOS overlay (`ActiveVisitsMap.tsx`)
- **Feature directory:** `src/features/backOffice/dashboard/` (SOS components are embedded in supervisor dashboard, not a dedicated safety nav item)
- **Status:** Partial — no dedicated safety nav item; SOS features are integrated into dashboard and notification areas

### BFF Integration

- **web-bff:** No dedicated safety BFF controller confirmed — SOS alerts reach care managers via notification-service (SosNotificationController direct-call path)
- **mobile-bff:** No safety controller confirmed — mobile SOS trigger path through mobile BFF not confirmed (if mobile calls safety-service directly, BFF auth boundary is bypassed)

### Event Bus

- **Publishes:** LWP escalation events (SafetyEventPublisher confirmed in source)
- **Subscribes:** Nothing documented
- **Status:** PARTIALLY WIRED — SafetyEventPublisher exists. SosNotificationController provides a direct-call path to notification-service for SOS alerts (this path works independently of Service Bus)

### Gaps

- No dedicated safety navigation item in backOffice sidebar — lone worker protection and SOS are CQC-required features that should be discoverable by care managers and RI without searching the dashboard
- Level 2+ escalation (manager, Responsible Individual, emergency services) not confirmed — only Level 1 (supervisor) notification is confirmed in LWPTimerService
- No telephony fallback (IVR) for SOS trigger — mobile app dependency for SOS initiation is a risk if device is compromised, out of power, or carer is incapacitated
- Only 2 Flyway migrations for 10 entity types — schema was created in a single migration; schema change history is compressed
- Mobile BFF does not have a safety controller — mobile SOS trigger path is not secured through the BFF auth layer
- This service has no HLD document, no ADR, and no documented security posture — see ADR-009 stub created alongside this document

---

## 11. Policy (AI / RAG)

### Purpose

Provides RAG (Retrieval-Augmented Generation) based policy question-and-answer capability. Care workers and managers can ask questions about organisational policies, CQC standards, and care protocols in natural language. The service chunks policy documents, stores vector embeddings, and queries them via Azure OpenAI.

### Backend Service

- **Status:** PRODUCTION-READY (Undocumented — no HLD entry, no ADR)
- **Controllers:** 1 confirmed — PolicyRagController (text chunking, vector query)
- **Entities:** Policy document chunks, vector embeddings (exact entity names not confirmed in source grep — implementation uses Azure Cognitive Search or embedded vector store)
- **Repositories:** Not confirmed via static analysis
- **Migrations:** Not confirmed
- **Key capabilities:**
  - PolicyRagController — handles natural language policy queries
  - Text chunking pipeline — policy document preprocessing for vector storage
  - Vector query — semantic search against policy document embeddings
  - Azure OpenAI integration for response generation

### Frontend

- **Pages:** AI feature directory exists (`src/features/backOffice/ai/AICarePlanGeneratorPage.tsx`) but no dedicated policy search page confirmed
- **Status:** Stub — ai feature directory contains care plan AI page only; policy search is not wired to a frontend page

### BFF Integration

- **web-bff:** No BFF controller for policy RAG confirmed
- **mobile-bff:** No BFF controller for policy RAG confirmed

### Event Bus

- **Publishes:** Nothing
- **Subscribes:** Nothing
- **Status:** NOT WIRED — policy-service is not in the Service Bus design

### Gaps

- No HLD document, no ADR, no API contract documented
- No frontend page for policy search — the service is implemented but inaccessible to users
- No navigation item in backOffice sidebar
- No documented security posture — policy documents may contain sensitive operational information; access control for policy queries is not confirmed
- No BFF controller — if accessed directly, PHI boundary enforcement and rate limiting are bypassed
- See ADR-010 stub created alongside this document

---

## 12. Document

### Purpose

Manages document storage, retrieval, and retention for clinical and operational documents. Uses Azure Blob Storage as the backing store with application-level retention policy enforcement. Supports document upload, categorisation, and time-limited access via pre-signed URLs.

### Backend Service

- **Status:** PRODUCTION-READY (Undocumented — no HLD entry, no ADR)
- **Controllers:** 1 confirmed — DocumentController
- **Entities:** Document metadata entity (exact entity name not confirmed)
- **Repositories:** Document metadata repository (not confirmed via static analysis)
- **Migrations:** Not confirmed
- **Key capabilities:**
  - DocumentController — document upload, download (pre-signed URL), list, delete
  - Azure Blob Storage configuration — container management per document category
  - Retention logic — configurable retention periods per document type
  - Document categorisation — clinical vs. operational vs. HR documents

### Frontend

- **Pages:** No frontend feature directory confirmed for document management
- **Status:** Not wired — no frontend feature, no navigation item

### BFF Integration

- **web-bff:** No document BFF controller confirmed
- **mobile-bff:** BlobStorageController confirmed — handles file upload from mobile (likely used for assessment photos and CV upload, not full document management)

### Event Bus

- **Publishes:** Nothing
- **Subscribes:** Nothing
- **Status:** NOT WIRED

### Gaps

- No HLD document, no ADR, no API contract
- No frontend feature or navigation item — document management is inaccessible to care managers despite being implemented
- No confirmed integration with incidents-service for safeguarding document attachment
- No confirmed integration with hr-service for staff document management (separate SupportingDocumentController exists in hr-service — potential overlap)
- Retention logic exists but retention schedule per document category under UK GDPR and NHS Records Management Code of Practice is not documented
- Mobile BlobStorageController handles photo/file upload but may not route through DocumentController — document provenance tracking is unclear
- See ADR-011 stub created alongside this document

---

## 13. FHIR Adapter

### Purpose

Exposes FHIR R4 resources (Patient, Observation, MedicationStatement) for interoperability with NHS systems, third-party care management platforms, and referral networks. Enables LumisCare to participate in NHS DSCR (Digitising Social Care Record) programme and GP Connect workflows.

### Backend Service

- **Status:** PRODUCTION-READY (Undocumented — no HLD entry, no ADR)
- **Controllers:** 3+ — Patient FHIR provider, Observation FHIR provider, MedicationStatement FHIR provider (confirmed as FHIR R4 providers, not standard Spring controllers)
- **Entities:** FHIR resource mapping layer (not standard JPA entities — FHIR resources are mapped from LumisCare domain model)
- **Repositories:** Delegates to identity-service (Patient), assessment-service (Observation), visits-service (MedicationStatement)
- **Migrations:** None (FHIR adapter is a translation layer, no owned database)
- **Key capabilities:**
  - FHIR R4 Patient resource — maps from ServiceUserEntity in identity-service
  - FHIR R4 Observation resource — maps from assessment scores (NEWS2, Waterlow, PHQ-9) in assessment-service
  - FHIR R4 MedicationStatement resource — maps from MedicationAdministration in visits-service
  - NHS DSCR admin nav item exists (`src/features/admin/dscr/`) — this is the only frontend reference to FHIR

### Frontend

- **Pages:** `/admin/dscr` (NHS DSCR configuration)
- **Feature directory:** `src/features/admin/dscr/` confirmed
- **Status:** Partial — admin DSCR page exists; no FHIR resource viewer or API explorer for clinical staff

### BFF Integration

- **web-bff:** No FHIR BFF controller confirmed
- **mobile-bff:** No FHIR BFF controller confirmed

### Event Bus

- **Publishes:** Nothing
- **Subscribes:** Nothing
- **Status:** NOT WIRED

### Gaps

- No HLD document, no ADR, no API contract — this is the highest security risk of the four undocumented services. FHIR R4 Patient and Observation resources expose PHI. If fhir-adapter-service is reachable without documented security controls, this is a GDPR and NHS DSPT data protection liability.
- No documented authentication model for FHIR endpoints — SMART on FHIR app authorisation (required for NHS GP Connect) is not confirmed
- No BFF controller — FHIR endpoints may be directly exposed without the aggregation, rate limiting, and PHI boundary logging that the BFF layer provides
- No confirmed CIS2 (NHS Care Identity Service 2) authentication integration — required for NHS-facing FHIR endpoints
- NHS DSCR admin page exists but is not connected to a live FHIR endpoint through a BFF
- See ADR-012 stub created alongside this document

---

## 14. Web BFF

### Purpose

Backend-for-Frontend aggregation layer for the React 19 web application. Orchestrates parallel and sequential calls to microservices, aggregates responses, enforces JWT validation, applies rate limiting (via Azure API Management), and provides OpenAPI-generated typed client contracts. Port 8080.

### Backend Service

- **Status:** PRODUCTION-READY
- **Controllers:** 22 — DashboardController, SupervisorDashboardController, SchedulingController, VisitsController, CarePlanController, ServiceUsersController, UsersController, EmployeeController, IncidentsController, BffAssessmentController, AssessmentWebDashBoardController, FamilyPortalController, HrComplianceController, FinanceBffController, CalendarController, CompletedEventsController, HandoffController, ReportsController, OrganizationsController, NotificationsController, ResumeUploadController, CqcReadinessController
- **Generated OpenAPI clients:** 4 of 10 services — assessment-client, careplan-client, hr-client, incidents-client
- **WebClient-based (not generated):** visits-service (VisitsController), scheduling-service (SchedulingController + CalendarController), finance-service (FinanceBffController + FinanceClientConfig), notification-service (NotificationsController + FamilyPortalController)
- **Unintegrated services:** safety-service (no dedicated BFF controller), policy-service (no controller), document-service (no controller), fhir-adapter-service (no controller)
- **Key capabilities:**
  - FamilyPortalController confirmed read-only (GET patterns only, no POST/PUT/PATCH) — PHI minimum-necessary for family access
  - CqcReadinessController — dedicated endpoint for CQC inspection evidence aggregation (clinically significant governance feature)
  - DashboardController + SupervisorDashboardController — parallel microservice call aggregation for management views
  - HrComplianceController — DBS, training certificate, and compliance expiry aggregation

### Frontend Integration

- **Target:** All `src/features/backOffice/` components, `src/features/familyPortal/`, `src/features/admin/`
- **Current state (gap):** Frontend CLAUDE.md reveals the web app currently calls the old Django monolith (`app-lumiscare-dev-uks-001.azurewebsites.net`) through manual axios wrappers. `src/api/generated/` directory is scaffolded but contains no generated client files. `yarn generate:api` is documented but not operational.

### BFF Integration

- **web-bff:** IS the BFF — self
- **mobile-bff:** N/A

### Event Bus

- **Publishes:** Nothing (BFF is stateless aggregation layer)
- **Subscribes:** Nothing
- **Status:** N/A

### Gaps

- 6 of 10 microservices use WebClient with manual configuration rather than generated OpenAPI clients — contract drift risk as service APIs evolve
- safety-service, policy-service, document-service, and fhir-adapter-service have no BFF controller — these services are either inaccessible from the web frontend or are called directly (bypassing auth boundary)
- Frontend is currently routing to old Django monolith, not to web-bff — the BFF is built but not yet the live API target for the web app
- OpenAPI client generation pipeline (`yarn generate:api`) is not operational — manual axios wrappers create inevitable contract drift at scale
- No confirmed Redis caching layer in BFF for high-frequency identity lookups (documented as 24h TTL in Integration Matrix)

---

## 15. Mobile BFF

### Purpose

Backend-for-Frontend aggregation layer for the React Native / Expo field worker mobile application. Provides a minimal, mobile-optimised API surface for care execution: care plan read access, appointment viewing, assessment submission, risk assessment, and blob storage for photos. Port not confirmed.

### Backend Service

- **Status:** PARTIAL — HIGH RISK
- **Controllers:** 6 — CarePlanController, AppointmentsController, CarerScheduleController, AssessmentsController, RiskAssessmentsController, BlobStorageController
- **Generated OpenAPI clients:** 2 of 10 services — assessment-client, careplan-client
- **WebClient-based:** MobileVisitsClient (visits-service via CarerScheduleController and AppointmentsController)
- **Key capabilities:**
  - AppointmentsController — GET /my-appointments, appointment detail
  - CarerScheduleController — GET /my-visits/today, visit schedule
  - AssessmentsController + RiskAssessmentsController — assessment submission for field assessments
  - BlobStorageController — photo and document upload from mobile (assessment photos, CV upload)
  - CarePlanController — read-only care plan access for visit execution context

### Frontend Integration

- **Target:** `mobile/app/` (React Native / Expo SDK 52)
- **Coverage:** Care plan, assessments, appointments, visit schedule

### BFF Integration

- **web-bff:** N/A
- **mobile-bff:** IS the BFF — self

### Event Bus

- **Publishes:** Nothing
- **Subscribes:** Nothing
- **Status:** N/A

### Gaps

- No eMAR controller — mobile carers cannot record medication administration through the BFF (HIPAA minimum-necessary enforcement gap if calling visits-service directly)
- No EVV dedicated controller — check-in/check-out is routed via CarerScheduleController/MobileVisitsClient but not through a dedicated EVV compliance controller with explicit 6-data-element capture
- No incidents controller — mobile carers cannot report incidents through the BFF
- No SOS / safety controller — mobile SOS trigger path is not secured through the BFF auth layer
- No vital signs controller — carer observation and vital sign recording during visits bypasses BFF
- No care note controller — care note recording during visits bypasses BFF
- Only 2 generated OpenAPI clients (assessment-client, careplan-client) — 8 services accessed without typed contracts
- No offline sync protocol confirmed at BFF layer — offline-first capability documented as a mobile feature but sync conflict resolution through BFF is not specified

---

## Event Bus Summary Table

| Event | Producer | Publisher Code? | Consumer | Listener Code? | Status |
|-------|----------|-----------------|----------|----------------|--------|
| `assessment.completed` | assessment-service | NO | careplan-service | NO | NOT WIRED |
| `careplan.published` | careplan-service | NO | scheduling-service | CarePlanEventIdempotencyEntity (ready) | NOT WIRED |
| `careplan.updated` | careplan-service | NO | visits-service | NO | NOT WIRED |
| `schedule.published` | scheduling-service | AppointmentEventPublisher YES | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `schedule.updated` | scheduling-service | AppointmentEventPublisher YES | visits-service | NO | PARTIALLY WIRED |
| `visit.checkedin` | visits-service | VisitEventPublisher YES | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `visit.completed` | visits-service | VisitEventPublisher YES | finance-service | NO | PARTIALLY WIRED |
| `task.flagged` | visits-service | VisitEventPublisher YES | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `checkin.timeout` | visits-service | VisitEventPublisher YES | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `assistance.requested` | visits-service | VisitEventPublisher YES | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `invoice.created` | finance-service | OutboxEvent entity only — NO publisher | notification-service | @ServiceBusListener COMMENTED OUT | NOT WIRED |
| `invoice.paid` | finance-service | NO | notification-service | @ServiceBusListener COMMENTED OUT | NOT WIRED |
| `timesheet.created` | finance-service | NO | notification-service | @ServiceBusListener COMMENTED OUT | NOT WIRED |
| `employee.created` | hr-service | HrEventPublisher YES | identity-service | NO confirmed listener | PARTIALLY WIRED |
| `employee.terminated` | hr-service | HrEventPublisher YES | scheduling-service | NO | PARTIALLY WIRED |
| `availability.updated` | hr-service | HrEventPublisher YES | scheduling-service | AvailabilityUpdatedEventListener YES | WIRED |
| `leave.approved` | hr-service | HrEventPublisher YES | scheduling-service | NO confirmed listener | PARTIALLY WIRED |
| `incident.created` | incidents-service | EventPublisher YES (outbox) | notification-service | @ServiceBusListener COMMENTED OUT | BROKEN |
| `notification.sent` | notification-service | NotificationEventPublisher YES | analytics-service | NOT IMPLEMENTED | PARTIALLY WIRED |

**Summary:** HR → Scheduling (availability.updated) is the only fully wired event chain. The primary clinical workflow (Assessment → CarePlan → Scheduling → Visits → Finance) has broken links at every node.

---

## Priority Remediation Register

### P0 — Patient Safety and Compliance Blocking

**P0-001: Uncomment @ServiceBusListener in notification-service**
- File: `backend/services/notification-service/src/main/java/.../ServiceBusEventListener.java` line 86
- Impact: All event-driven alert delivery (missed visit, medication overdue, LWP escalation, safeguarding) is non-functional
- Effort: Minimal code change; requires end-to-end event delivery validation for all subscribed event types

**P0-002: Resolve DNACPR dual-domain ownership**
- DnacprOrderEntity in assessment-service + DnacprAuditLogEntity in identity-service with no sync mechanism
- Impact: Resuscitation decision ambiguity is clinically unacceptable
- Resolution: Either consolidate into assessment-service (correct domain) with identity-service holding a reference, or implement a confirmed event-driven sync pattern

### P1 — Regulatory Exposure

**P1-001: Add event publisher to assessment-service for assessment.completed**
- Breaks the Assessment → CarePlan AI generation automated pathway

**P1-002: Add event publisher to careplan-service for careplan.published**
- Breaks the CarePlan → Scheduling automated pathway

**P1-003: Add mobile BFF controllers for eMAR, EVV, incidents, and SOS**
- Or document and formally enforce direct-service access security boundary with compensating controls

**P1-004: Implement mandatory notification deadline and automated escalation in incidents-service**
- CQC Regulation 18 requires notification within 24 hours for certain incident types

**P1-005: Document and security-review fhir-adapter-service**
- FHIR R4 Patient/Observation endpoints may expose PHI without documented authentication controls
- NHS DSPT and GDPR exposure

### P2 — Architecture Integrity

**P2-001: Implement OutboxEventPublisher in finance-service**
- Completes the transactional outbox pattern already started

**P2-002: Add finance-service listener for visit.completed event**
- Enables automatic invoice line item generation from visit completion

**P2-003: Confirm EmarController enforces witness requirement at service layer**
- Entity fields exist; business logic enforcement must be verified

**P2-004: Add Flyway migrations to bring visits-service (7) and assessment-service (8) in line with entity complexity**

**P2-005: Standardise finance-service package namespace from com.lumiscare.icon.finance to com.lumiscare.finance**

**P2-006: Operationalise yarn generate:api pipeline**
- Without generated clients, frontend contract drift is inevitable as microservice APIs evolve

---

## Architectural Risks

| Risk | Severity | Description |
|------|----------|-------------|
| BFF-less frontend in production | CRITICAL | Frontend routes to old Django monolith, not web-bff. All microservices are bypassed. |
| Broken clinical workflow event chain | HIGH | Assessment → CarePlan → Schedule → Visit → Finance has multiple broken publisher/subscriber links |
| Notification service deaf to event bus | HIGH | @ServiceBusListener commented out — all event-driven alerts are non-functional |
| Four undocumented services in production path | HIGH | safety, policy, document, fhir-adapter — no API contracts, no security posture |
| FHIR PHI exposure | HIGH | FHIR R4 resources expose PHI without documented authentication controls |
| Finance event bus gap | MEDIUM | Transactional outbox started but publisher absent — billing events are silent |
| Mobile BFF coverage gap | HIGH | eMAR, EVV, incidents, SOS absent from mobile BFF — direct service calls bypass auth boundary |
| DNACPR dual ownership | HIGH | Resuscitation decision data in two services with no confirmed sync |
| CQC mandatory notification deadline | MEDIUM | No automated enforcement for 24-hour statutory reporting requirement |
| OpenAPI client drift | MEDIUM | 6 of 10 services accessed via manual WebClient — contract drift inevitable |

---

## Document History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2026-04-04 | Dr. Sarah Chen | Initial — 13 components + 2 BFFs documented. Evidence base: static filesystem analysis + HLD document review. Cross-reference with lumiscare-backend-lld.md and lumiscare-hld-gap.md. |

# Architecture — Event Bus Map

# LumisCare Event Bus — Current State Map

**Date:** 2026-04-04
**Author:** Martin Kleppmann (Distributed Systems Audit)
**Method:** Static analysis of all publisher and listener classes in backend/services/
**Source of truth:** Actual Java source files, not design documents

---

## Architecture

Azure Service Bus Premium tier, topics/subscriptions model. Each service owns its own topic and publishes events to it. Consumers subscribe to specific topics with SQL filters on `event_type`.

The design document (`Service-Bus-Subscriptions-Design.md`) specifies three topics: `visit-events`, `schedule-events`, `notification-events`. The actual implementation has diverged — publishers use five distinct topic names across six services, and the notification-service listener is wired to a different topic (`domain-events`) than what the visit and scheduling publishers write to.

---

## Event Flow Diagram

```mermaid
graph LR
  subgraph WIRED["FULLY WIRED"]
    HR["hr-service<br/>(HrEventPublisher)"]
    SCHED["scheduling-service<br/>(AppointmentEventPublisher)"]
    SCHED_AVAIL["scheduling-service<br/>(HrAvailabilityUpdatedEventHandler)"]
    NOTIF_PUB["notification-service<br/>(NotificationEventPublisher)"]
  end

  subgraph PARTIAL["PARTIALLY WIRED — publisher exists, consumer absent or mis-wired"]
    VISITS["visits-service<br/>(VisitEventPublisher)"]
    INCIDENTS["incidents-service<br/>(EventPublisher)"]
    SAFETY["safety-service<br/>(SafetyEventPublisher)"]
    NOTIF_LISTEN["notification-service<br/>(ServiceBusEventListener)"]
    SCHED_CAREPLAN["scheduling-service<br/>(CarePlanPublishedEventHandler)"]
  end

  subgraph BROKEN["BROKEN — no publisher class exists"]
    ASSESS["assessment-service"]
    CAREPLAN["careplan-service"]
    FINANCE["finance-service<br/>(OutboxEvent entity only)"]
  end

  HR -->|"employee.created/updated<br/>availability.updated<br/>leave.approved<br/>certification.expiring<br/>topic: domain-events"| SCHED_AVAIL
  HR -->|"leave.approved<br/>topic: domain-events"| NOTIF_LISTEN
  VISITS -->|"visit.started/completed/missed<br/>visit.task.completed<br/>topic: visits-events"| NOTIF_LISTEN
  VISITS -->|"visit.completed<br/>topic: visits-events"| FINANCE_STUB["finance-service<br/>(NO listener class)"]
  SCHED -->|"schedule.published/updated<br/>appointment.created/updated/cancelled<br/>topic: scheduling-events"| NOTIF_LISTEN
  NOTIF_PUB -->|"notification.sent/failed/acknowledged<br/>topic: notification-events"| ANALYTICS_STUB["analytics-service<br/>(NOT IMPLEMENTED)"]
  INCIDENTS -->|"incident.created<br/>topic: incidents-events"| NOTIF_LISTEN
  SAFETY -->|"lwp.escalation.*<br/>topic: safety-events"| NOTIF_LISTEN
  CAREPLAN_STUB["careplan-service<br/>(NO publisher)"] -.->|"careplan.published — MISSING"| SCHED_CAREPLAN
  ASSESS -.->|"assessment.completed — MISSING"| CAREPLAN_STUB

  style WIRED fill:#1a4731,color:#fff
  style PARTIAL fill:#713f12,color:#fff
  style BROKEN fill:#7f1d1d,color:#fff
```

---

## Critical Architectural Finding: Topic Name Mismatch

The notification-service `ServiceBusEventListener` subscribes to topic `domain-events` (default configured at `ServiceBusConfig.java:29`):

```
azure.servicebus.topic-name:domain-events
```

The visits-service `VisitEventPublisher` publishes to topic `visits-events` (default configured at `VisitEventPublisher.java:45`):

```
azure.servicebus.visits-topic-name:visits-events
```

The scheduling-service `AppointmentEventPublisher` publishes to topic `scheduling-events` (default configured at `AppointmentEventPublisher.java:45`):

```
azure.servicebus.scheduling-topic-name:scheduling-events
```

The HR-service `HrEventPublisher` publishes to topic `domain-events` (default configured at `HrEventPublisher.java:40`):

```
azure.servicebus.topic-name:domain-events
```

This means: **the notification-service listener will receive HR events but will silently miss all visit and schedule events at runtime unless the application properties are overridden in deployment configuration.** There are no application.properties or application.yml files with explicit topic overrides found in the source tree — only Spring `@Value` defaults. This must be validated against the actual Azure Container Apps environment variables before treating any visit-to-notification flow as working.

---

## Event-by-Event Status

| Event | Topic (default) | Producer Service | Publisher Class | Consumer Service | Listener Class | Status |
|-------|----------------|-----------------|----------------|-----------------|---------------|--------|
| `employee.created` | `domain-events` | hr-service | `HrEventPublisher` | scheduling-service | `HrAvailabilityUpdatedEventHandler` | WIRED — both sides present |
| `employee.updated` | `domain-events` | hr-service | `HrEventPublisher` | scheduling-service | `HrAvailabilityUpdatedEventHandler` | WIRED — both sides present |
| `availability.updated` | `domain-events` | hr-service | `HrEventPublisher` (via `AvailabilityPreferencesService`) | scheduling-service | `HrAvailabilityUpdatedEventHandler` (listens for `hr.availability.updated`) | WIRED — subject used in code is `hr.availability.updated`, design doc says `availability.updated` — schema mismatch risk |
| `leave.approved` | `domain-events` | hr-service | `HrEventPublisher` (via `LeaveManagementService`) | notification-service | `ServiceBusEventListener` (case `leave.approved`) | WIRED — both sides on same topic |
| `certification.expiring` | `domain-events` | hr-service | `HrEventPublisher` (via `CertificationExpiryScheduler`) | notification-service | `ServiceBusEventListener` (case `certification.expiring`) | WIRED — both sides on same topic |
| `visit.started` | `visits-events` | visits-service | `VisitEventPublisher.publishVisitStarted()` | notification-service | `ServiceBusEventListener` (case `visit.checkedin` — event name mismatch) | BROKEN — publisher emits `visit.started`, listener handles `visit.checkedin`; different names |
| `visit.completed` | `visits-events` | visits-service | `VisitEventPublisher.publishVisitCompleted()` | notification-service | `ServiceBusEventListener` (case `visit.completed`) | TOPIC MISMATCH — publisher on `visits-events`, listener on `domain-events` |
| `visit.completed` | `visits-events` | visits-service | `VisitEventPublisher.publishVisitCompleted()` | finance-service | No listener class found | NOT WIRED — finance has no Service Bus consumer |
| `visit.missed` | `visits-events` | visits-service | `VisitEventPublisher.publishVisitMissed()` | notification-service | No explicit case in listener | NOT HANDLED — listener has no `visit.missed` case |
| `visit.task.completed` | `visits-events` | visits-service | `VisitEventPublisher.publishTaskCompleted()` | notification-service | No explicit case in listener | NOT HANDLED |
| `checkin.timeout` | `visits-events` | visits-service | NOT FOUND in `VisitEventPublisher` methods | notification-service | `ServiceBusEventListener` (case `checkin.timeout`) | PUBLISHER ABSENT — listener expects this event but publisher has no `publishCheckinTimeout()` method |
| `assistance.requested` | `visits-events` | visits-service | NOT FOUND in `VisitEventPublisher` methods | notification-service | `ServiceBusEventListener` (case `assistance.requested`) | PUBLISHER ABSENT — listener expects this event but publisher has no method |
| `task.flagged` | `visits-events` | visits-service | NOT FOUND in `VisitEventPublisher` methods | notification-service | `ServiceBusEventListener` (case `task.flagged`) | PUBLISHER ABSENT — listener expects this but only `visit.task.completed` is published |
| `redflag.detected` | unknown | unknown | NOT FOUND | notification-service | `ServiceBusEventListener` (case `redflag.detected`) | PUBLISHER ABSENT — no publisher produces this event |
| `visit.reassigned` | unknown | unknown | NOT FOUND | notification-service | `ServiceBusEventListener` (case `visit.reassigned`) | PUBLISHER ABSENT |
| `schedule.published` | `scheduling-events` | scheduling-service | `AppointmentEventPublisher.publishSchedulePublished()` | notification-service | `ServiceBusEventListener` (case `schedule.published`) | TOPIC MISMATCH — publisher on `scheduling-events`, listener on `domain-events` |
| `schedule.updated` | `scheduling-events` | scheduling-service | `AppointmentEventPublisher` (no explicit `publishScheduleUpdated` found — has `publishScheduleApproved`) | notification-service | `ServiceBusEventListener` (case not found) | PARTIAL — method naming diverges from design doc |
| `careplan.published` | `careplan-events` | careplan-service | NO PUBLISHER CLASS | scheduling-service | `CarePlanPublishedEventHandler` (subscribes to `careplan-events`) | BROKEN — listener fully implemented, publisher does not exist |
| `careplan.updated` | unknown | careplan-service | NO PUBLISHER CLASS | notification-service | `ServiceBusEventListener` (case `careplan.updated`) | BROKEN — no publisher |
| `assessment.completed` | unknown | assessment-service | NO PUBLISHER CLASS | careplan-service | NO LISTENER CLASS | NOT WIRED — entire chain absent |
| `incident.created` | `incidents-events` | incidents-service | `EventPublisher` | notification-service | `ServiceBusEventListener` — no `incident.created` case found in routing switch | TOPIC MISMATCH + case absent — publisher on `incidents-events`, listener on `domain-events` |
| `lwp.escalation.level1/2/3/4` | `safety-events` | safety-service | `SafetyEventPublisher.publishLWPEscalation()` | notification-service | `ServiceBusEventListener` (cases `lwp.escalation.level1` through `level4`) | TOPIC MISMATCH — publisher on `safety-events`, listener on `domain-events` |
| `invoice.created` | none | finance-service | NO PUBLISHER — only `OutboxEvent` entity + repository | notification-service | `ServiceBusEventListener` (no case found) | NOT WIRED — outbox pattern incomplete, no dispatcher daemon |
| `invoice.paid` | none | finance-service | NO PUBLISHER | analytics-service | NOT IMPLEMENTED | NOT WIRED |
| `timesheet.created` | none | finance-service | NO PUBLISHER — `TimesheetBatchJobProcessor` writes `OutboxEvent` but no Service Bus dispatch | notification-service | `ServiceBusEventListener` (no case found) | NOT WIRED — outbox records written to DB but no relay to Service Bus |
| `notification.sent` | `notification-events` | notification-service | `NotificationEventPublisher.publishNotificationSent()` | analytics-service | NOT IMPLEMENTED | PUBLISHER WIRED, consumer service does not exist |
| `notification.failed` | `notification-events` | notification-service | `NotificationEventPublisher.publishNotificationFailed()` | analytics-service | NOT IMPLEMENTED | PUBLISHER WIRED, consumer service does not exist |
| `daily.summary` | unknown | unknown | NOT FOUND | notification-service | `ServiceBusEventListener` (case `daily.summary`) | PUBLISHER ABSENT |

---

## Broken Chains

### Chain 1: Assessment to Care Plan AI Generation (Clinical Safety Risk)

The primary clinical workflow trigger is broken at its first link.

- `assessment-service` has no event publisher class anywhere in its source tree. When an assessment is completed, no `assessment.completed` event is emitted.
- `careplan-service` has no event listener class. Even if an event were emitted, nothing would receive it.
- Both sides of the Assessment → CarePlan trigger are absent.

**Where it breaks:** `assessment-service/src/main/java/` — no `*Publisher*.java` file exists in this directory.

### Chain 2: Care Plan to Scheduling (Scheduling Cannot Start Automatically)

The `CarePlanPublishedEventHandler` in scheduling-service is fully implemented (300+ lines, idempotency logic, recurring schedule creation). It is waiting for a `careplan.published` event on topic `careplan-events`. That event is never published.

- `careplan-service` has no publisher class. The service can mark a care plan as published in the database (`CarePlan.publishedAt`, `CarePlan.publishedBy` fields exist) but does not emit the event.

**Where it breaks:** `careplan-service/src/main/java/` — no `*Publisher*.java` file exists. The handler waiting for the event is at `scheduling-service/src/main/java/com/lumiscare/scheduling/event/CarePlanPublishedEventHandler.java:101`.

### Chain 3: Visit Check-in to Notification (Event Name Mismatch)

The design document specifies `visit.checkedin`. The `VisitEventPublisher` publishes `visit.started` (set at `VisitEventPublisher.java:127`). The `ServiceBusEventListener` routes on `visit.checkedin` (at `ServiceBusEventListener.java:207`). These names do not match. Even if the topic mismatch were fixed, this chain would still silently fail.

**Where it breaks:**
- Publisher sets name: `VisitEventPublisher.java:127` — `"visit.started"`
- Listener routes on: `ServiceBusEventListener.java:207` — `case "visit.checkedin"`

### Chain 4: Visit Completed to Finance (Finance Cannot Generate Invoice Line Items)

`VisitEventPublisher.publishVisitCompleted()` publishes to topic `visits-events`. The finance-service has no `ServiceBusProcessorClient`, no listener class, and no subscription configuration. The billing data embedded in the `visit.completed` payload (rates, task categories, mileage) is never consumed. Invoice line item generation is not automated.

**Where it breaks:** `finance-service/src/main/java/` — no Service Bus consumer class of any kind. Finance only writes `OutboxEvent` records to its own database for the timesheet batch job, which itself has no Service Bus dispatcher.

### Chain 5: Critical Safety Alerts Silently Dropped (Topic Mismatch)

`SafetyEventPublisher` publishes to topic `safety-events`. `ServiceBusEventListener` in notification-service subscribes to topic `domain-events`. The LWP escalation events (`lwp.escalation.level1` through `level4`) will never reach the notification service unless an application property override is set in deployment. For lone worker protection, this is a CQC compliance risk.

**Where it breaks:** `SafetyEventPublisher.java:39` (topic: `safety-events`) vs `ServiceBusConfig.java:29` (subscribed to: `domain-events`).

### Chain 6: Visit Events Unreachable by Notification Service (Topic Mismatch)

`VisitEventPublisher` publishes to `visits-events`. `AppointmentEventPublisher` publishes to `scheduling-events`. `ServiceBusEventListener` subscribes to `domain-events`. Unless deployment environment variables override these defaults, all visit and schedule events are invisible to the notification service. This silences checkin.timeout, task.flagged, assistance.requested, and schedule.published notifications.

**Where it breaks:** Topic default misalignment across three services — see Critical Architectural Finding section above.

### Chain 7: Events Listened For But Never Published

The `ServiceBusEventListener` routing switch includes cases for events that have no publisher anywhere in the codebase:

| Event | Missing Publisher | Listener Location |
|-------|------------------|-------------------|
| `checkin.timeout` | No publisher method in `VisitEventPublisher` | `ServiceBusEventListener.java:207` |
| `assistance.requested` | No publisher method in `VisitEventPublisher` | `ServiceBusEventListener.java:211` |
| `redflag.detected` | No publisher anywhere in any service | `ServiceBusEventListener.java:215` |
| `visit.reassigned` | No publisher anywhere in any service | `ServiceBusEventListener.java:220` |
| `task.flagged` | No publisher method in `VisitEventPublisher` (publishes `visit.task.completed` instead) | `ServiceBusEventListener.java:224` |
| `daily.summary` | No publisher anywhere in any service | `ServiceBusEventListener.java:249` |

### Chain 8: Notification Outbound Events Have No Consumers

`NotificationEventPublisher` publishes `notification.sent`, `notification.failed`, and `notification.acknowledged` to topic `notification-events`. The `analytics-service` and `audit-service` that should consume these are not implemented as standalone services. The events will be published to a topic with no subscribers, age to TTL, and be discarded.

---

## Fix Priority

Ordered by clinical and operational impact.

### Priority 1 — Fix Topic Name Coherence (All Services)

**Impact:** Unblocks all visit and schedule notifications in one config change.

The notification-service `ServiceBusConfig` must subscribe to multiple topics, or all publishers must align on a shared topic name. The least-invasive fix is to add per-topic subscription processors in `ServiceBusConfig.java` for `visits-events` and `scheduling-events` in addition to `domain-events`. Alternatively, if the design intent is a single `domain-events` topic, all publishers must update their `@Value` defaults to `domain-events`.

This is a configuration and wiring change, not a logic change. No new business logic required.

**Files to change:**
- `notification-service/src/main/java/com/lumiscare/notification/config/ServiceBusConfig.java`
- OR all publisher `@Value` defaults (4 publisher files)

### Priority 2 — Fix visit.started / visit.checkedin Event Name

**Impact:** Allows check-in notifications to reach care managers.

Either rename the event type in `VisitEventPublisher.java:127` from `"visit.started"` to `"visit.checkedin"`, or update the listener case to `"visit.started"`. The design document and listener both say `visit.checkedin` — the publisher is the outlier.

**File to change:** `visits-service/src/main/java/com/lumiscare/visits/event/VisitEventPublisher.java:127`

### Priority 3 — Add Missing Publisher Methods to VisitEventPublisher

**Impact:** Enables checkin.timeout (clinical safety), task.flagged (care quality), assistance.requested (lone worker safety).

`VisitEventPublisher` needs three new publish methods. The listener routing and notification handlers for these events already exist. This is purely adding publisher-side methods and calling them from the appropriate visit execution service layer.

**Events to add:** `checkin.timeout`, `task.flagged`, `assistance.requested`
**File to change:** `visits-service/src/main/java/com/lumiscare/visits/event/VisitEventPublisher.java`

### Priority 4 — Add careplan.published Publisher to careplan-service

**Impact:** Allows scheduling to automatically create recurring visits when a care plan is approved. This is the central automation trigger for care delivery.

The `CarePlanPublishedEventHandler` in scheduling-service is complete and production-ready. Only the publisher side is missing. Add a publisher class to careplan-service that emits `careplan.published` on topic `careplan-events` when a care plan transitions to published status (the state transition already exists in `CarePlanMapper.java`).

**Files to create/change:**
- Create: `careplan-service/src/main/java/com/lumiscare/event/CarePlanEventPublisher.java`
- Wire it into the publish endpoint in the careplan service controller

### Priority 5 — Add assessment.completed Publisher to assessment-service

**Impact:** Triggers automatic AI care plan generation after assessment completion.

Create an event publisher in assessment-service and publish `assessment.completed` when an assessment reaches a completed status. The careplan-service needs a corresponding listener (this does not yet exist either — both sides must be built).

**Files to create:**
- `assessment-service/src/main/java/.../event/AssessmentEventPublisher.java`
- `careplan-service/src/main/java/.../event/AssessmentCompletedEventListener.java`

### Priority 6 — Add Finance Service Bus Consumer for visit.completed

**Impact:** Automates invoice line item generation from completed visits.

Add a `ServiceBusProcessorClient` to finance-service that subscribes to the `visits-events` topic with filter `event_type = 'visit.completed'`. The billing data payload already contains all required fields (`billing_data` section with visit_type, care_types, day_type, travel_time_minutes, mileage_miles).

**Files to create:**
- `finance-service/src/main/java/.../event/VisitCompletedEventListener.java`
- Wire to existing rate calculation and invoice generation logic

### Priority 7 — Complete the Finance Outbox Dispatcher

**Impact:** Enables `timesheet.created` and `invoice.created` notifications.

`TimesheetBatchJobProcessor` writes `OutboxEvent` records to the database but the comment in the code (`TimesheetBatchJobProcessor.java:35`) refers to an "external outbox-processor daemon" that does not exist. Either implement the outbox polling scheduler within the finance-service or replace the outbox pattern with a direct `ServiceBusSenderClient` call inside the existing transaction.

**Files to change:**
- `finance-service/src/main/java/com/lumiscare/icon/finance/service/TimesheetBatchJobProcessor.java`
- Optionally create a dedicated outbox publisher component

### Priority 8 — Resolve hr.availability.updated vs availability.updated Event Name

**Impact:** Prevents silent message drops when carer availability changes.

`HrAvailabilityUpdatedEventHandler` expects message subject `hr.availability.updated` (at `HrAvailabilityUpdatedEventHandler.java:94`). `HrEventPublisher` publishes with event type `availability.updated` (called at `AvailabilityPreferencesService.java:161`). This is a subject field mismatch. The handler will silently complete all messages that do not match its expected subject, meaning availability changes will not trigger schedule recomputation.

**Files to change:** Align the event type string in `AvailabilityPreferencesService.java:161` to `hr.availability.updated`, or update the handler constant at `HrAvailabilityUpdatedEventHandler.java:94`.

---

## Summary Counts

| Category | Count |
|----------|-------|
| Fully wired event chains (publisher + consumer on matching topic) | 3 (employee.created, employee.updated, leave.approved/certification.expiring via domain-events) |
| Partially wired (publisher exists, consumer absent or topic mismatch) | 8 |
| Events listener handles but no publisher produces | 6 |
| Events completely absent (no publisher, no consumer) | 3 (assessment.completed, invoice.created, invoice.paid) |
| Publisher classes that exist | 6 (HrEventPublisher, VisitEventPublisher, AppointmentEventPublisher, NotificationEventPublisher, SafetyEventPublisher, incidents EventPublisher) |
| Publisher classes missing | 2 (assessment-service, careplan-service) |
| Services with no Service Bus integration at all | 3 (assessment-service, careplan-service, finance-service consumer side) |
| Topic name mismatches between publisher default and notification-service subscription | 3 (visits-events, scheduling-events, incidents-events, safety-events vs domain-events) |

The HR to Notification chain (leave.approved, certification.expiring) is the only end-to-end path that works without deployment configuration overrides. Everything that involves visit execution events, schedule events, or safety escalations depends on either a topic name fix or missing publisher code.

# Architecture — Frontend Route Map

# LumisCare Frontend — Complete Route Map

**Generated:** 2026-04-04
**Source of truth:** `frontend/web/src/routes/index.ts` + `src/components/navigation/sidebar/Sidebar.tsx`
**Mobile source:** `mobile/app/src/app/` (Expo Router v4, file-based)
**Total registered web routes:** 97 (including aliases and redirects)
**Portals covered:** Back Office, Family Portal, Admin Panel, Mobile App

---

## Back Office Routes

| Route | Component | Lines | Status | RBAC Guard | Notes |
|-------|-----------|-------|--------|------------|-------|
| `/` (index) | `DashboardPage` | 568 | Functional | All backoffice roles | Full metrics + rota section |
| `/clients` | `ClientsPage` | 131 | Functional | Most backoffice roles | Tabbed navigation, delegates to content components |
| `/clients/:id` | `ClientPage` | — | Functional | Most backoffice roles | Client detail page |
| `/assessments` | `AssessmentsPage` | — | Functional | Clinical + admin roles | Assessments overview |
| `/clients/:clientId/assessment/live` | `LiveAssessmentPage` | — | Functional | None (no ProtectedRoute) | Live assessment flow with client context |
| `/clients/assessment/live` | `LiveAssessmentPage` | — | Functional | None | Live assessment flow without client context |
| `/clients/assessment/transcript` | `AssessmentTranscriptPage` | — | Functional | None | Assessment transcript view |
| `/clients/:clientId/body-map` | `BodyMapPage` | — | Functional | None | Body observations with client context |
| `/clients/body-map` | `BodyMapPage` | — | Functional | None | Body observations without client context |
| `/clients/:clientId/assessment/complete` | `AssessmentCompletionPage` | — | Functional | None | Consent + attendees with client context |
| `/clients/assessment/complete` | `AssessmentCompletionPage` | — | Functional | None | Consent + attendees without client context |
| `/clients/assessment/questions/:sectionId` | `QuestionsPage` | — | Functional | None | Section questions view |
| `/clients/assessment/question-management` | `QuestionManagementPage` | — | Functional | Clinical + admin roles | Drag-and-drop reorder |
| `/clients/:clientId/assessment/question-management` | `QuestionManagementPage` | — | Functional | Clinical + admin roles | Drag-and-drop reorder with client context |
| `/clients/assessment/risk-assessment-risks` | `RAQuestionsPage` | — | Functional | None | Risk assessment questions |
| `/clients/assessment/risk-assessment-risks/:sectionId` | `RAQuestionsPage` | — | Functional | None | Risk assessment questions by section |
| `/clients/assessment/section/:sectionId` | `PersonalInformationPage` | — | Functional | None | Section detail view |
| `/clients/assessment/medication/:medicationId/edit` | `EditMedicationPage` | — | Functional | Clinical + admin roles | Edit medication (QA excluded) |
| `/clients/:clientId/assessment/medications/add` | `AddMedicationPage` | — | Functional | Clinical + admin + carer | Add medication with client context |
| `/clients/assessment/medications/add` | `AddMedicationPage` | — | Functional | Clinical + admin + carer | Add medication without client context |
| `/clients/:clientId/assessment/medications/:medicationId` | `MedicationDetailsPage` | — | Functional | None | Medication details (read-only for QA) |
| `/clients/:clientId/assessment/medications/:medicationId/edit` | `EditMedicationPage` | — | Functional | Clinical + admin roles | Edit medication with client context |
| `/employees` | `EmployeePage` | 753 | Functional | None (no ProtectedRoute) | Full employee list |
| `/employees/new` | `CreateEmployeePage` | — | Functional | Manager roles only | Create employee (QA excluded) |
| `/employees/:id` | `EmployeeDetailsPage` | — | Functional | None | Employee detail page |
| `/employees/find-by-skills` | `FindCarersBySkillsPage` | — | Functional | None | Search carers by qualifications |
| `/leaves` | `LeavesOverviewPage` | 563 | Functional | None | Leave management overview |
| `/leaves/approval` | `LeaveRequestApprovalPage` | — | Functional | Manager + shift-leader roles | Leave request approval (Feature #252) |
| `/hr/training-matrix` | `TrainingMatrixPage` | 636 | Functional (1 TODO) | HR + manager + admin roles | Training compliance matrix |
| `/hr/dbs` | `DBSDashboardPage` | 393 | Functional (1 TODO) | HR + manager + admin roles | DBS certificate status |
| `/hr/bradford` | `BradfordDashboardPage` | 372 | Functional (1 TODO) | HR + manager + admin roles | Bradford Factor scoring |
| `/hr/supervisions` | `SupervisionSchedulePage` | 487 | Functional (1 TODO) | HR + manager + admin roles | Supervision scheduling |
| `/hr/care-certificate` | `CareCertificateTrackerPage` | 450 | Functional (1 TODO) | HR + manager + admin roles | Care Certificate tracker |
| `/hr/cqc-pack` | `CQCInspectionPackPage` | 554 | Functional | HR + manager + admin roles | CQC inspection pack |
| `/visits` | `VisitPage` | — | Functional | None | Visit list and management |
| `/visits/activity-log` | `ActivityLogPage` | — | Functional | None | Visit activity log |
| `/visits/gps-checkin` | `GPSCheckInSimulationPage` | 548 | Functional | None | GPS geofence test tool (Feature #275) |
| `/visits/evv` | `EVVPage` | 111 | Functional | None | Electronic Visit Verification / QR check-in (Feature #277) |
| `/visits/live-tracking` | `LiveTrackingDashboard` | — | Functional | None | Real-time carer location (Feature #287) |
| `/visits/completed-events` | `CompletedEventsPage` | — | Functional | None | Completed visit review before invoicing |
| `/visits/handoff` | `ShiftHandoffPage` | — | Functional | None | SBAR shift handoff notes |
| `/backoffice/scheduling` | `SchedulingPage` | 799 | Functional (1 TODO) | Coordinator + manager + admin | Full calendar/roster grid |
| `/backoffice/incidents` (index) | `IncidentsListPage` | 759 | Functional (1 TODO) | None | Incident list |
| `/backoffice/incidents/:id` | `IncidentDetailPage` | 2487 | Functional | None | Incident detail (investigation tab RBAC-gated inline) |
| `/backoffice/incidents/safeguarding` | `SafeguardingPage` | 59 | Functional | None | RBAC wrapper — delegates to `ClientsSafeguardContent` |
| `/care-plans` | `CarePlansPage` | 599 | Functional (1 TODO) | None | Care plans list |
| `/care-plans/:id` | `CarePlanReviewPage` | 864 | Functional | Manager + clinical + admin | Care plan approval workflow |
| `/care-plans/:id/edit` | `CarePlanEditorPage` | 1768 | Partial (53 TODOs) | Manager + clinical + admin | 10-section editor — incomplete fields |
| `/care-plans/:id/task-linking` | `CarePlanTaskLinkingPage` | — | Functional | Manager + coordinator + clinical | Task-to-visit-type linking |
| `/care-plans/reviews-due` | `CarePlansReviewDuePage` | 480 | Functional | Manager + clinical + QA | NICE QS123 compliance reviews |
| `/ai/care-plan-generator` | `AICarePlanGeneratorPage` | 582 | Functional | Clinical + manager + admin | AI-powered care plan generation |
| `/finance` | `FinancePage` | 82 | Functional | ModuleProtectedRoute: finance | Tabbed finance hub |
| `/finance/audit-log` | `InvoicesAuditLogPage` | — | Functional | ModuleProtectedRoute: finance | Invoice audit trail |
| `/finance/billing` | `InsuranceBillingPage` | — | Functional | ModuleProtectedRoute: finance | Medicaid/Medicare claim management |
| `/billing` | `BillingPage` | 26 | Redirect | None | Redirects to `/finance/billing` |
| `/intake` | `AddClientPage` | — | Functional | Clinical + manager (QA excluded) | New client intake |
| `/smart-upload` | `SmartUploadPage` | — | Functional | Clinical + manager (QA excluded) | AI PDF document extraction |
| `/reports` | `ReportsPage` | 283 | Functional | None | Report generation UI |
| `/cqc-readiness` | `CQCReadinessPage` | 277 | Functional | Manager + clinical + QA + regional | KLOE evidence + compliance scoring |
| `/training/compliance` | `TrainingCompliancePage` | 306 | Functional | Manager + HR + QA + regional | Staff training gap analysis |
| `/profile` | `ProfilePage` | — | Functional | None | User profile (all authenticated users) |
| `/notifications` | `NotificationsInboxPage` | — | Functional | None | Notification feed (all authenticated users) |
| `/settings/notifications` | `NotificationPreferencesPage` | — | Functional | None | Channel preferences (Feature #322) |

### Back Office Alias / Redirect Routes

| Route | Destination | Type | Notes |
|-------|-------------|------|-------|
| `/dashboard` | `/` | Navigate redirect | Legacy path compatibility |
| `/compliance` | `/admin/compliance` | Navigate redirect | **BUG: wrong destination for backoffice users** |
| `/incidents` | `/backoffice/incidents` | Navigate redirect | Legacy path compatibility |
| `/schedule` | `SchedulingPage` (rendered directly) | Alias | Renders same component as `/backoffice/scheduling` |
| `/training-compliance` | `TrainingCompliancePage` (rendered directly) | Alias | Renders same component as `/training/compliance` |
| `/care-notes` | `familyCareNotesPage` (rendered directly) | Alias | Family portal alias |
| `/care-plan` | `familyCarePlanPage` (rendered directly) | Alias | Family portal alias |
| `/invoices` | `familyInvoicesPage` (rendered directly) | Alias | Family portal alias |

---

## Family Portal Routes

| Route | Component | Lines | Status | RBAC Guard | Notes |
|-------|-----------|-------|--------|------------|-------|
| `/family-portal-dashboard` | `familyDashboardPage` | 522 | Functional (1 TODO) | family-member + admin roles | Family dashboard |
| `/family-portal-carenotes` | `CareNotesPage` | 1272 | Functional | family-member + admin roles | Full read view of visit notes |
| `/family-portal-careplan` | `CarePlanPage` | 797 | Functional (2 TODOs) | family-member + admin roles | Care plan view |
| `/family-portal-invoices` | `InvoicesPage` | 2479 | Functional (2 TODOs) | family-member + admin roles | Invoices and billing |

**Alias routes (workaround patches):**

| Alias Route | Resolves To | Notes |
|-------------|-------------|-------|
| `/care-notes` | `familyCareNotesPage` | Shortcut alias |
| `/care-plan` | `familyCarePlanPage` | Shortcut alias |
| `/invoices` | `familyInvoicesPage` | Shortcut alias |

**Missing family portal features:** No messaging/communication between family and carers. No visit scheduling from family side. No emergency contacts view.

---

## Admin Panel Routes

### Core Admin Routes

| Route | Component | Lines | Status | RBAC Guard | Notes |
|-------|-----------|-------|--------|------------|-------|
| `/admin/organizations` | `OrganizationsPage` | 511 | Functional (5 TODOs) | system-admin + provider-admin | Organization management |
| `/admin/users` | `UsersPage` | 991 | Functional (7 TODOs) | system-admin only | User management |
| `/admin/roles` | `RolesPage` | 387 | Functional (1 TODO) | system-admin only | Roles and permissions |
| `/admin/settings` | `SettingsPage` | 524 | Functional | system-admin only | System settings |
| `/admin/audit-logs` | `AuditLogsPage` | 514 | Functional (1 TODO) | system-admin only | Audit trail |
| `/admin/compliance` | `ComplianceDashboardPage` | 555 | Functional | system-admin only | HIPAA compliance dashboard |
| `/admin/dscr` | `DSCRCompliancePage` | 386 | Functional | system-admin + provider-admin | NHS DSCR compliance |
| `/admin/data-import` | `DataImportPage` | — | Functional | system-admin only | CSV/JSON data import |
| `/admin/data-export` | `DataExportPage` | — | Functional | system-admin only | CSV/JSON data export |
| `/admin/multi-tenancy` | `MultiTenancyTestPage` | — | Functional | system-admin only | Organization isolation test |
| `/admin/module-subscriptions` | `ModuleSubscriptionsPage` | — | Functional | system-admin only | Per-org module access (Feature #360) |
| `/admin/questions` | `QuestionManagementPage` | — | Functional | system-admin + provider-admin | Assessment question management (Feature #233) |

### Admin Test / Diagnostic Pages

These 18 routes are permanent in the router but function as feature verification tools. They should be gated by a dev/debug feature flag in production builds.

| Route | Component | Feature # | RBAC Guard | Purpose |
|-------|-----------|-----------|------------|---------|
| `/admin/encryption-compliance` | `EncryptionCompliancePage` | #362 | system-admin | Encryption at rest verification |
| `/admin/tls-security` | `TLSSecurityTestPage` | #363 | system-admin | TLS 1.3/1.2 enforcement |
| `/admin/data-residency` | `DataResidencyPage` | #365 | system-admin | US data sovereignty |
| `/admin/openapi-validation` | `OpenAPIValidationPage` | #366 | system-admin | API spec conformance |
| `/admin/token-enrichment` | `TokenEnrichmentTestPage` | #16 | system-admin | JWT token enrichment |
| `/admin/api-auth-test` | `ApiAuthTestPage` | #17 | system-admin | Bearer token validation |
| `/admin/api-versioning` | `APIVersioningTestPage` | #367 | system-admin | `/api/v1/` prefix verification |
| `/admin/api-sort-test` | `APISortTestPage` | #369 | system-admin | Sort parameter verification |
| `/admin/request-validation` | `RequestValidationTestPage` | #371 | system-admin | Input validation |
| `/admin/cors-test` | `CORSTestPage` | #373 | system-admin | CORS header verification |
| `/admin/fk-constraints` | `ForeignKeyConstraintsTestPage` | #374 | system-admin | Referential integrity |
| `/admin/check-constraints` | `CheckConstraintsTestPage` | #376 | system-admin | Data validation constraints |
| `/admin/push-notification-test` | `PushNotificationTestPage` | #317 | system-admin + provider-admin | Azure Notification Hub push |
| `/admin/sms-notification-test` | `SmsNotificationTestPage` | #319 | system-admin + provider-admin | Azure Communication Services SMS |
| `/admin/quiet-hours-test` | `QuietHoursTestPage` | #321 | system-admin + provider-admin | Critical notification bypass |
| `/admin/channel-routing-test` | `ChannelRoutingTestPage` | #323 | system-admin + provider-admin | Channel preference routing |
| `/admin/device-registration-test` | `DeviceRegistrationTestPage` | #325 | system-admin + provider-admin | Mobile device token storage |
| `/admin/notification-template-test` | `NotificationTemplateTestPage` | #324 | system-admin + provider-admin | Template variable substitution |
| `/admin/delivery-tracking-test` | `DeliveryTrackingTestPage` | #327 | system-admin + provider-admin | Notification delivery logging |
| `/admin/notification-retry-test` | `NotificationRetryTestPage` | #328 | system-admin + provider-admin | Failed notification retry |
| `/admin/in-app-read-tracking-test` | `InAppReadTrackingTestPage` | #329 | system-admin + provider-admin | Read status tracking |
| `/admin/notification-analytics` | `NotificationAnalyticsPage` | #329 | system-admin + provider-admin | Delivery statistics dashboard |

### Admin Sidebar Navigation (from Sidebar.tsx)

The admin sidebar exposes 7 items — only a subset of all admin routes. Test/diagnostic pages are not listed in the sidebar.

| Sidebar Label | Route | Allowed Roles |
|---------------|-------|---------------|
| Organizations | `/admin/organizations` | system-admin + provider-admin |
| Users | `/admin/users` | system-admin + provider-admin |
| Roles & Permissions | `/admin/roles` | system-admin only |
| Settings | `/admin/settings` | system-admin + provider-admin |
| Audit Logs | `/admin/audit-logs` | system-admin only |
| HIPAA Compliance | `/admin/compliance` | system-admin only |
| NHS DSCR | `/admin/dscr` | system-admin + provider-admin |

---

## Mobile Screens (Expo Router v4)

File-based routing at `mobile/app/src/app/`. Routes correspond to the filesystem path under `(app)/`.

| Screen / File | Lines | Status | Notes |
|---------------|-------|--------|-------|
| `(app)/index.tsx` — Home/Schedule | 1624 | Functional | Large comprehensive carer dashboard |
| `(app)/schedule.tsx` | 671 | Functional | Carer schedule view |
| `(app)/visit/[visitId]/index.tsx` | 490 | Functional | Visit detail hub |
| `(app)/visit/[visitId]/care-note.tsx` | 46 | Functional | Thin wrapper, delegates to CareNoteForm |
| `(app)/visit/[visitId]/emar.tsx` | 1182 | Functional (2 TODOs) | Electronic MAR administration |
| `(app)/visit/[visitId]/vitals.tsx` | 1087 | Needs review (19 TODOs) | **Critical: clinically sensitive vitals screen — highest TODO density in codebase** |
| `(app)/visit/[visitId]/medications.tsx` | 122 | Functional | Medication list for visit |
| `(app)/visit/[visitId]/task-validation.tsx` | 295 | Functional (1 TODO) | Task validation flow |
| `(app)/visit/[visitId]/signature.tsx` | 308 | Functional (2 TODOs) | Signature capture |
| `(app)/visit/[visitId]/check-out.tsx` | 5 | Functional | Thin delegator to CheckOutView |
| `(app)/visit/[visitId]/client-details.tsx` | 264 | Functional | Client info within visit |
| `(app)/visit/[visitId]/structured-note.tsx` | — | Present | Structured clinical note entry |
| `(app)/visit/[visitId]/observation.tsx` | — | Present | Clinical observations |
| `(app)/visit/[visitId]/pictures.tsx` | — | Present | Camera/media capture |
| `(app)/visit/[visitId]/online-assistance.tsx` | — | Present | Remote assistance feature |
| `(app)/visit/[visitId]/manual-checkout.tsx` | — | Present | Manual override checkout |
| `(app)/visit/[visitId]/upload-progress.tsx` | — | Present | Upload status indicator |
| `(app)/assessment/index.tsx` | 684 | Functional (1 TODO) | Mobile assessment flow |
| `(app)/profile/index.tsx` | 490 | Functional | User profile |
| `(app)/profile/availability.tsx` | — | Present | Carer availability management |
| `(app)/video-call/[callId]/index.tsx` | 31 | Present | LiveKit WebRTC video call |
| `(app)/voice-commands.tsx` | 390 | Functional | Voice input feature |
| `(app)/notifications/index.tsx` | — | Present | Notification feed |
| `(app)/notifications/settings.tsx` | — | Present | Notification preferences |
| `(app)/settings.tsx` | — | Present | App settings |
| `(app)/dev-tools.tsx` | — | Present | Developer tools screen |
| `auth.tsx` | — | Present | OAuth callback handler |

---

## Navigation Gaps

| Menu Item | Sidebar Path | Route Exists? | Content Status | Fix Needed |
|-----------|-------------|---------------|---------------|-----------|
| Dashboard | `/` | Yes | Functional (568 lines) | None |
| Notifications | `/notifications` | Yes | Functional | None |
| Clients | `/clients` | Yes | Functional | None |
| Employees > DBS Status | `/hr/dbs` | Yes | Functional (1 TODO) | None |
| Employees > Training Matrix | `/hr/training-matrix` | Yes | Functional (1 TODO) | None |
| Employees > Bradford Factor | `/hr/bradford` | Yes | Functional (1 TODO) | None |
| Employees > Supervisions | `/hr/supervisions` | Yes | Functional (1 TODO) | None |
| Employees > Care Certificate | `/hr/care-certificate` | Yes | Functional (1 TODO) | None |
| Employees > CQC Pack | `/hr/cqc-pack` | Yes | Functional | None |
| Schedule (parent) | `/visits` | Yes | Functional | **Structural: parent links to `/visits` but sub-items link to `/backoffice/scheduling`, `/visits/completed-events`, `/visits/handoff` — mixed path hierarchy** |
| Schedule > Rostering | `/backoffice/scheduling` | Yes | Functional (1 TODO) | None |
| Schedule > Completed Events | `/visits/completed-events` | Yes | Functional | None |
| Schedule > Shift Handoff | `/visits/handoff` | Yes | Functional | None |
| Care Plans | `/care-plans` | Yes | Functional (1 TODO) | None |
| AI Care Plan | `/ai/care-plan-generator` | Yes | Functional | None |
| Assessments | `/assessments` | Yes | Functional | None |
| Finance | `/finance` | Yes | Functional (ModuleProtectedRoute) | None |
| Incidents | `/backoffice/incidents/safeguarding` | Yes | Functional (thin wrapper) | Architectural: sidebar links to `/safeguarding` but incidents list is at `/backoffice/incidents` — users cannot reach incidents list from sidebar |
| Reports | `/reports` | Yes | Functional | None |
| Compliance (parent) | `/compliance` | Redirect only | Redirects to `/admin/compliance` | **BUG P1: backoffice sidebar `Compliance` parent path redirects to HIPAA admin panel, not to backoffice CQC/compliance hub** |
| Compliance > CQC Readiness | `/cqc-readiness` | Yes | Functional | None |
| Compliance > Training Compliance | `/training/compliance` | Yes | Functional | None |

---

## Mock Data Pages

Based on audit evidence, the following pages carry `TODO` markers explicitly referencing "connect to real API" or similar — indicating they may be rendering mock/sample data in some sections:

| Page | File Location | TODO Count | Nature of Mock |
|------|--------------|------------|----------------|
| `CarePlanEditorPage` | `features/backOffice/carePlans` | 53 | Many section fields not wired to real API — `// TODO: connect to real API` patterns throughout |
| `vitals.tsx` (mobile) | `mobile/app/src/app/(app)/visit/[visitId]/vitals.tsx` | 19 | Clinically sensitive screen with incomplete API wiring |
| `UsersPage` | `features/admin/users` | 7 | Minor TODOs — some user management operations may fall back |
| `OrganizationsPage` | `features/admin/organizations` | 5 | Minor TODOs |
| `IncidentsListPage` | `features/backOffice/incidents/list` | 1 | Single TODO |
| `SchedulingPage` | `features/backOffice/scheduling` | 1 | Single TODO in 799-line page |
| `CarePlansPage` | `features/backOffice/carePlans` | 1 | Single TODO |
| `DBSDashboardPage` | `features/backOffice/hr/pages/DBSDashboardPage` | 1 | Single TODO |
| `TrainingMatrixPage` | `features/backOffice/hr/pages/TrainingMatrixPage` | 1 | Single TODO |
| `BradfordDashboardPage` | `features/backOffice/hr/pages/BradfordDashboardPage` | 1 | Single TODO |
| `SupervisionSchedulePage` | `features/backOffice/hr/pages/SupervisionSchedulePage` | 1 | Single TODO |
| `CareCertificateTrackerPage` | `features/backOffice/hr/pages/CareCertificateTrackerPage` | 1 | Single TODO |
| `familyPortalDashboardPage` | `pages/familyPortal/dashboard` | 1 | Minor TODO |
| `CarePlanPage` (family) | `features/familyPortal/carePlan` | 2 | Minor TODOs |
| `InvoicesPage` (family) | `features/familyPortal/invoices` | 2 | Minor TODOs |
| `AuditLogsPage` | `features/admin/auditLogs` | 1 | Minor TODO |
| `RolesPage` | `features/admin/roles` | 1 | Minor TODO |

**Note on methodology:** TODO counts use literal string grep of "TODO". They include code comments such as `// TODO: connect to real API` and represent technical debt markers, not blank or placeholder UI. No page renders "Coming soon" or empty content.

---

## Stub / Placeholder Pages

No pages are pure stubs rendering blank or "Coming soon" content. All registered pages render substantive UI. The following are below 50 lines but delegate intentionally:

| Page | File | Lines | Nature |
|------|------|-------|--------|
| `BillingPage` | `features/backOffice/billing` | 26 | Intentional redirect wrapper to `/finance/billing` |
| `SafeguardingPage` | `features/backOffice/incidents/safeguarding` | 59 | Intentional RBAC wrapper delegating to `ClientsSafeguardContent` |
| `check-out.tsx` (mobile) | `(app)/visit/[visitId]/check-out.tsx` | 5 | Intentional thin delegator to `CheckOutView` component |
| `care-note.tsx` (mobile) | `(app)/visit/[visitId]/care-note.tsx` | 46 | Intentional thin wrapper for `CareNoteForm` |
| `video-call/[callId]/index.tsx` (mobile) | `(app)/video-call/[callId]/index.tsx` | 31 | LiveKit integration, thin entry point |

---

## Unrouted Components

The following components exist in the source tree but have no registered route in `src/routes/index.ts`:

| Component | File Path | Concern Level | Notes |
|-----------|----------|---------------|-------|
| `SOSHistoryPage` | `features/backOffice/dashboard/SOSHistoryPage.tsx` (96 lines) | P1 | Referenced in comment as `/backoffice/safety/sos-history` but route never registered — feature dead end |
| `KeywordPage` | `features/backOffice/keywords/pages/KeywordPage.tsx` | P1 | Keyword management feature exists with no route — unreachable |
| `RedFlagDetectionDemoPage` | `features/backOffice/clients/pages/RedFlagDetectionDemoPage.tsx` | P2 | Demo/diagnostic page exists but is unrouted |

---

## Summary Statistics

| Metric | Count |
|--------|-------|
| Total registered web routes (including aliases/redirects) | ~97 |
| Back Office routes (primary) | 56 |
| Back Office alias/redirect routes | 8 |
| Family Portal routes (primary) | 4 |
| Family Portal alias routes | 3 |
| Admin core routes | 12 |
| Admin test/diagnostic routes | 22 |
| Auth routes (`/login`, `/logout`, `/access-denied`) | 3 |
| Catch-all 404 route | 1 |
| Mobile screens | 27 |
| Pages with TODO markers (web) | 17 |
| Unrouted components | 3 |
| Navigation gaps (bugs) | 2 confirmed |

---

## Priority Issues

| Priority | Issue | Impact |
|----------|-------|--------|
| P0 | `CarePlanEditorPage` (1768 lines, 53 TODOs) — core clinical page, incomplete fields | Clinical product gap |
| P0 | Mobile `vitals.tsx` (1087 lines, 19 TODOs) — clinically sensitive screen | Clinical product gap |
| P1 | Sidebar `/compliance` redirects to HIPAA admin page instead of backoffice CQC/compliance hub | Wrong UX for all backoffice users |
| P1 | Sidebar `Incidents` links to `/backoffice/incidents/safeguarding` — users cannot reach the incidents list from the sidebar | Navigation dead end |
| P1 | `SOSHistoryPage` component exists (96 lines) but has no registered route | Feature dead end |
| P1 | `KeywordPage` component exists but is not routed | Unreachable feature |
| P2 | Family portal URLs (`/family-portal-dashboard` etc.) violate URL hierarchy — alias patches mask the structural problem | UX and URL semantics debt |
| P2 | `RedFlagDetectionDemoPage` exists but is unrouted | Dead code |
| P2 | `switch.tsx` file and `switch/` folder coexist in `src/components/ui/` — import resolution ambiguity | Build risk |
| P2 | `calender` folder typo in `src/components/ui/` | DX friction, propagates through all calendar imports |
| P3 | 22 admin test/diagnostic pages as permanent routes — should be gated by dev/debug feature flag | Security surface area in production, route tree clutter |
| P3 | `SafeguardingPage` (59 lines) delegates to `ClientsSafeguardContent` — component lives in Clients feature, not Incidents | Architectural boundary violation |

# Fix Backlog — P0/P1/P2 Issues

# LumisCare — Fix Backlog

**Consolidated from 5 expert reports — 2026-03-25**
**Total issues: 32**

Format: [PORTAL] [PAGE] [ELEMENT] [PROBLEM] [FIX NEEDED]

---

## P0 — Demo Blocking

Issues that will be visible within 60 seconds of any stakeholder opening the app. Must be fixed before any demo or investor showing.

| # | Portal | Page | Element | Problem | Fix Needed |
|---|--------|------|---------|---------|------------|
| 1 | Admin Panel | Organizations (`/admin/organizations`) | "Add Organization" button | Button renders; no form or modal wired | Wire button to create organization form/modal with: Organization Name, Status, Contact details → POST `/api/v1/organizations` |
| 2 | Admin Panel | Organizations (`/admin/organizations`) | Per-card "More options" (…) button | Button renders; no dropdown or menu attached | Add dropdown menu with actions (Edit, View, Deactivate) per org card |
| 3 | Family Portal | Care Plan (`/family-portal-careplan`) | Print button (header) | `onClick` handler missing — button does nothing | Wire to `window.print()` or PDF export |
| 4 | Family Portal | Care Plan (`/family-portal-careplan`) | More options (3-dot) button (header) | `onClick` handler missing — button does nothing | Add dropdown with relevant actions (Download, Share) or remove button |
| 5 | Family Portal | Care Notes (`/family-portal-carenotes`) | "Week" button (rounded-full with left chevron) | No `onClick` handler — looks interactive but does nothing | Wire to `goToPreviousWeek()` (same as the arrow button) or replace with correct UI |
| 6 | Family Portal | Care Plan (`/family-portal-careplan`) | Draw Signature area inside Sign & Consent Modal | Draw tab shows placeholder `<div>`, no canvas or drawing library hooked up | Integrate a canvas drawing library (e.g. react-signature-canvas) — draw mode must capture a real signature |
| 7 | Back Office | Body Map (`/clients/:id/body-map`) | "This Week" time filter button | No `onClick` handler wired | Wire to filter observations by current week, or remove button until implemented |
| 8 | Back Office | Body Map (`/clients/:id/body-map`) | "Filters" button | No `onClick` handler wired | Open filter drawer/modal for observation category/severity, or remove button until implemented |
| 9 | Back Office | Body Map (`/clients/:id/body-map`) | Zoom controls (minus / zoom icon / plus) | UI renders; no zoom logic implemented | Implement zoom state and apply CSS transform on body diagram container |
| 10 | Back Office | Body Map (`/clients/:id/body-map`) | Per observation card "Edit" (pencil icon) | `onEdit(id)` handler marked TODO — not implemented | Implement edit observation flow (open edit dialog pre-populated with observation data) |
| 11 | Family Portal | Invoices (`/family-portal-invoices`) | Per-row "Pay" button | Calls `console.log` or shows "Online payment will be available in the next release." toast; no payment flow | For demo: show a modal stub. For production: integrate payment provider. Disable with clear "Coming Soon" state if not ready. |
| 12 | Family Portal | Dashboard (`/family-portal-dashboard`) | Per-invoice "Pay" button | Same as above — triggers info toast only | Same fix as above |
| 13 | Back Office | Visits Header (`/visits`) | Print button | `console.log` placeholder — "Print functionality coming soon" | Wire to print or remove button |
| 14 | Back Office | Visits Header (`/visits`) | Export button | `console.log` placeholder — "Export functionality coming soon" | Wire to CSV/PDF export or remove button |
| 15 | Back Office | Activity Log (`/visits/activity-log`) | Print button | No `onClick` handler wired | Wire to print or remove button |
| 16 | Back Office | Activity Log (`/visits/activity-log`) | Export button | No `onClick` handler wired | Wire to export (CSV at minimum) or remove button |
| 17 | Family Portal | Dashboard | Video thumbnails in visit cards | Play icon overlay shown on thumbnails but no `onClick` handler — non-interactive | Either remove play overlay or implement video playback modal |

---

## P1 — Pilot Blocking

Issues that block a paying pilot customer from using core workflows. Must be resolved before any live pilot.

| # | Portal | Page | Element | Problem | Fix Needed |
|---|--------|------|---------|---------|------------|
| 18 | Admin Panel | Audit Logs (`/admin/audit-logs`) | Export functionality | No export button present anywhere on the page | Add export button → generate CSV/PDF of audit log entries with current filters applied |
| 19 | Admin Panel | Audit Logs (`/admin/audit-logs`) | Date range filter | No date range picker present — only type filter and text search | Add date range picker (start/end) to filter audit log by date |
| 20 | Admin Panel | Compliance Dashboard (`/admin/compliance`) | All action items | Page is fully read-only — data rights requests, anomaly alerts, consent override all require action | Add action controls: respond to data rights requests, acknowledge anomaly alerts, manage consent |
| 21 | Family Portal | Profile (`/profile`) | Edit contact information | Profile page is fully read-only — no edit buttons, no form, no save | Add edit form for at minimum: phone number, preferred contact method. Name/email via Azure AD. |
| 22 | Admin Panel | Organizations (`/admin/organizations`) | Organization data | All data is hardcoded array in component state — not API-connected | Connect to `/api/v1/organizations` GET (list) and POST (create) endpoints |
| 23 | Admin Panel | Roles & Permissions (`/admin/roles`) | Role management | Roles page is view-only — no "Add Role" or "Edit Role" UI | Add ability to create custom roles and edit permissions, or document that this is intentionally RBAC-fixed |
| 24 | Back Office | Activity Log (`/visits/activity-log`) | "This Month" date filter button | Button renders with dropdown chevron but no `onClick` handler wired | Wire to date range picker — currently filters are impossible to apply by date on this page |
| 25 | Back Office | Completed Events Tab (`/visits` — Completed Events) | Date Range button | Visual button with no date picker wired | Wire to date range picker for filtering completed events by date range |
| 26 | Back Office | Finance | Billing Page | Entire page is placeholder: `<div>Welcome to BillingPage</div>` | Implement billing page or redirect to Insurance Billing page until ready |
| 27 | Back Office | Scheduling | Recompute Schedule | Demo-only — hardcoded with `carer-1` / `"Sarah Mitchell"` data | Connect to real scheduling data and real recomputation logic |

---

## P2 — Production Blocking

Issues that are acceptable for demo/pilot but must be resolved before production launch. Real persistence and real backend required.

| # | Portal | Page | Element | Problem | Fix Needed |
|---|--------|------|---------|---------|------------|
| 28 | Admin Panel | Settings (all tabs) | Save Changes | Persists to `localStorage` only | Connect to backend settings API with proper per-org persistence |
| 29 | Admin Panel | Assessment Questions | Save Question | Persists to `localStorage('admin_assessment_questions')` | Connect to backend questions API |
| 30 | Admin Panel | Profile | Save Photo | Stores base64 in `localStorage` | Upload to blob storage; store reference in user record |
| 31 | Back Office | Body Map | Add Observation | Saves to `localStorage('lumiscare-body-map-observations-{clientId}')` | Persist observations to assessment service / clinical records |
| 32 | Back Office | Care Plan Review | Schedule Review | Saves to `localStorage('lumiscare_scheduled_care_plan_reviews')` and `localStorage('lumiscare_schedule_blocks')` | Persist to scheduling service; create real calendar entries |

---

## Summary by Portal

| Portal | P0 | P1 | P2 | Total |
|--------|----|----|-----|-------|
| Back Office (Clinical/Scheduling/Finance/HR) | 8 | 4 | 3 | 15 |
| Admin Panel | 2 | 6 | 3 | 11 |
| Family Portal | 7 | 1 | 0 | 8 |
| **Total** | **17** | **11** | **6** | **32** |

---

## Quick Wins (P0 items fixable in < 1 hour each)

These P0 bugs are simple missing handler wires — not missing features:

1. **[P0-3]** Family Portal Care Plan Print button — `onClick={() => window.print()}`
2. **[P0-4]** Family Portal Care Plan More options button — add dropdown or remove icon
3. **[P0-5]** Family Portal Care Notes "Week" button — `onClick={goToPreviousWeek}`
4. **[P0-13]** Backoffice Visits Print button — `onClick={() => window.print()}`
5. **[P0-15]** Activity Log Print button — `onClick={() => window.print()}`
6. **[P0-7]** Body Map "This Week" button — remove or stub with toast "Filters coming soon"
7. **[P0-8]** Body Map "Filters" button — remove or stub with toast "Filters coming soon"
8. **[P0-17]** Dashboard video thumbnails — remove play icon overlay until implemented

---

*Generated 2026-03-25. Source: consolidated from 5 expert subagent reports.*

# Mock-to-Real Migration Plan

# Mock API to Real Backend — Migration Plan

**Generated:** 2026-03-26
**Author:** FlowForge (ALAI DevOps)
**Project:** LumisCare Enterprise Healthcare Platform
**Scope:** 222 mock endpoints → 11 Spring Boot microservices via Web BFF

---

## 1. Current State Summary

| Component | Status |
|-----------|--------|
| Mock API (server.js) | Running — 5,280 lines, 222 endpoints, frontend pointed here |
| Spring Boot services | 11 deployed on Azure Container Apps — all Running |
| PostgreSQL (10 databases) | Flyway migrated, schemas applied, zero seed data |
| Web BFF | Deployed — assessment, careplan, incidents, hr, finance clients wired; visits + scheduling + notification + identity NOT fully wired |
| Frontend | Points to mock API |

---

## 2. Coverage Analysis — 222 Mock Endpoints vs Real Services

### Method

Mock endpoints extracted from `mock-api/server.js` via route registration.
Real coverage determined from OpenAPI specs in `openapi-specs/` and Web BFF controller inventory in `backend/bff/web-bff/src/main/java/.../controller/`.

Total endpoint count breakdown:
- 222 total mock registrations
- 18 are catch-all wildcards (`/api/*`, `/employees/:id`, etc.) — not real routes
- 204 are distinct addressable mock endpoints

### Coverage By Service Domain

#### VISITS — 14 mock endpoints
Mock routes: `GET/POST /api/v1/visits`, `GET/PATCH/DELETE /api/v1/visits/:id`, check-in, check-out, eMAR, tasks, clinical-observations, SOS, travel-record, publish, running-late, pre-visit-brief

Real service (`visits-service`): OpenAPI defines `/visits`, `/visits/{visitId}`, check-in, check-out, tasks, observations, status, notes, eMAR.

Web BFF: No `VisitsController` found in BFF controller list. `VISITS_SERVICE_BASE_URL` is configured in `application.yml` (port 8085) but no BFF controller proxies it yet.

**Coverage status: Service READY, BFF wiring MISSING.**
Estimated real coverage: 9 of 14 endpoints have a backend implementation. 5 (clinical-observations format, SOS, pre-visit-brief, travel-record, eMAR administer/refuse detail) need verification.

#### SCHEDULING / ROSTERING — 8 mock endpoints
Mock routes: `GET /api/v1/schedule-blocks`, `GET/POST/PATCH/DELETE /api/v1/schedule-blocks/:id`, `GET /api/v1/completed-events`, bulk-confirm, publish

Real service (`scheduling-service`): OpenAPI defines schedule-events, appointments, rostered-hours, recurring-schedules, availability. Maps partially — `schedule-blocks` naming differs from `schedule-events`.

Web BFF: `CalendarController.java` exists. `SCHEDULING_SERVICE_BASE_URL` configured (port 8086).

**Coverage status: PARTIAL — naming mismatch between mock and real service paths needs alignment.**
Estimated real coverage: 5 of 8.

#### ASSESSMENT — 12 mock endpoints
Mock routes: `GET/POST/PUT/PATCH /api/v1/assessments`, body-map, scores, complete, `GET /api/v1/intake`

Real service (`assessment-service`): Full OpenAPI — assessment CRUD, sections, subsections, questions, risk sections, observations, photos, medications, status, submit, intake.

Web BFF: `BffAssessmentController.java` + `AssessmentWebDashBoardController.java` exist. `AssessmentClientConfig` wired.

**Coverage status: COVERED for core paths. Scores/clinical-tools endpoints may map to risk-section in real service.**
Estimated real coverage: 10 of 12.

#### CARE PLANS — 14 mock endpoints
Mock routes: `GET/POST/PUT/DELETE /api/v1/care-plans`, versions, tasks, risk-alerts, reviews, approve, publish, submit-for-approval, consent, review-due

Real service (`careplan-service`): OpenAPI defines care-plans CRUD, publish, regenerate, generation-status, versions, master-task-types/visit-types.

Web BFF: `CarePlanController.java` exists. `CarePlanClientConfig` wired.

**Coverage status: Core CRUD + publish + versions covered. Task management, risk-alerts, reviews, consent are mock-only.**
Estimated real coverage: 7 of 14.

#### HR — 18 mock endpoints
Mock routes: `GET /api/v1/hr/supervisions`, absences, dbs, bradford, care-certificate, training matrix, supervision-schedule, cqc-inspection-pack; legacy `/employees/*`

Real service (`hr-service`): Extensive OpenAPI — employees full profile, leaves, supervisions, training, appraisals, documents, equality diversity, onboarding.

Web BFF: `EmployeeController.java` + `ResumeUploadController.java` exist. `HrClientConfig` wired.

**Coverage status: Employee core COVERED. HR compliance sub-paths (DBS checks, Bradford factor, care certificate, CQC pack) are mock-only abstractions — hr-service has training and supervision but not as named sub-routes.**
Estimated real coverage: 10 of 18.

#### FINANCE — 20 mock endpoints
Mock routes: invoices, timesheets, rate-cards, funders, invoice-groups, payment-groups, payroll preview/export/approve, aged-debtors, NMW compliance, bank-holidays, month-end status, dashboard

Real service (`finance-service`): OpenAPI defines invoices (full lifecycle), timesheets, invoice-groups, payment-groups, mandates, dashboard. Rate-cards and funders are mock-only concepts.

Web BFF: `FinanceBffController.java` exists. `FinanceClientConfig` wired.

**Coverage status: Invoice + timesheet core COVERED. Rate-cards, funders, payroll export, NMW compliance, aged-debtors are MOCK-ONLY.**
Estimated real coverage: 12 of 20.

#### INCIDENTS — 8 mock endpoints
Mock routes: `GET/POST/PATCH/DELETE /api/v1/incidents`, `GET /api/v1/incidents/:id`

Real service (`incidents-service`): OpenAPI defines accidents, complaints, hazards, safeguarding, summary, actions log.

Web BFF: `IncidentsController.java` exists. `IncidentsClientConfig` wired.

**Coverage status: COVERED — mock uses generic `/incidents` but real service has typed categories (accidents, complaints, hazards, safeguarding). Path mapping required in BFF.**
Estimated real coverage: 6 of 8.

#### NOTIFICATIONS — 10 mock endpoints
Mock routes: notifications inbox, unread-count, preferences, devices register/unregister, mark-all-read, push

Real service (`notification-service`): OpenAPI defines inbox, devices, preferences, templates, unread-count, send, mark-read.

Web BFF: No `NotificationController` found. `notification-service` NOT listed in `application.yml` service URLs.

**Coverage status: Service READY, BFF wiring MISSING.**
Estimated real coverage: 7 of 10 (paths align), but BFF layer absent.

#### IDENTITY / USERS / ORGS / SERVICE USERS — 14 mock endpoints
Mock routes: `GET /api/v1/users`, `GET /api/v1/service-users`, organizations, settings, carers, skills

Real service (`identity-service`): OpenAPI defines users, organizations, service-users, roles, settings.

Web BFF: `ServiceUsersController.java` exists. `IdentityClientConfig` found in config.

**Coverage status: PARTIAL — users and service-users COVERED. Carers-by-skills, skills list, rostering-settings are mock-only.**
Estimated real coverage: 9 of 14.

#### FAMILY PORTAL — 16 mock endpoints
Mock routes: family care-plan view, complaints, messages/threads, notifications, payments initiate/confirm

Real service: No dedicated family-portal-service. Family portal routes would be served by web-bff aggregating careplan-service + notification-service + identity-service.

Web BFF: No family portal controller found.

**Coverage status: MOCK-ONLY. No real backend for family portal BFF layer.**
Estimated real coverage: 0 of 16.

#### MISC (dashboard, CQC, EVV, import, audit, observations, insurance, handoff) — 24 mock endpoints
Includes: `GET /api/v1/dashboard/stats`, `/api/v1/cqc-readiness`, `/api/v1/evv/*`, `/api/v1/import/*`, `/api/v1/audit-logs`, `/api/v1/insurance/*`, `/api/v1/handoff/*`, `/api/v1/observations`

Real services: EVV (electronic visit verification) maps to visits-service tracking endpoints. Dashboard stats aggregate from multiple services. Import, insurance, handoff are mock-only constructs.

**Coverage status: MOSTLY MOCK-ONLY.**
Estimated real coverage: 4 of 24 (dashboard + some audit, EVV via visits tracking).

---

### Coverage Summary Table

| Domain | Mock Endpoints | Real Coverage | Status |
|--------|---------------|---------------|--------|
| Visits | 14 | ~9 | BFF wiring missing |
| Scheduling | 8 | ~5 | Path naming mismatch |
| Assessment | 12 | ~10 | BFF wired, good coverage |
| Care Plans | 14 | ~7 | BFF wired, tasks/risks mock-only |
| HR | 18 | ~10 | BFF wired, compliance routes mock-only |
| Finance | 20 | ~12 | BFF wired, rate-cards/payroll mock-only |
| Incidents | 8 | ~6 | BFF wired, path mapping needed |
| Notifications | 10 | ~7 | BFF wiring MISSING |
| Identity/Users/Orgs | 14 | ~9 | BFF partial |
| Family Portal | 16 | 0 | No backend at all |
| Misc / EVV / Dashboard | 24 | ~4 | Mostly mock-only |
| **TOTAL** | **158 (excl wildcards)** | **~79** | **~50% real coverage** |

**Bottom line: approximately 79 of 158 meaningful mock endpoints have a corresponding real service implementation. The remaining ~79 are either BFF-wiring gaps, path-mapping gaps, or features that do not yet exist in any real service.**

---

## 3. Seed Data Blocking Issue

### Problem

Azure PostgreSQL Flexible Server blocks all public inbound connections by default. The 10 service databases have schemas (Flyway applied) but no seed data. Attempts to run seed scripts from a local machine are blocked by the firewall.

### Options to Load Seed Data

#### Option A — Azure Cloud Shell (Recommended, zero setup)
```bash
# Open Azure Portal → Cloud Shell (Bash)
# Download seed scripts from repo or paste inline
psql "host=<your-pg-server>.postgres.database.azure.com \
      user=<admin> \
      dbname=identity_db \
      sslmode=require" \
  -f seed-identity.sql
```
No firewall changes needed. Cloud Shell is inside Azure's network boundary.

#### Option B — Temporary Firewall Rule (simple but requires cleanup)
```bash
# Add current IP to Azure PostgreSQL firewall
MY_IP=$(curl -s https://api.ipify.org)
az postgres flexible-server firewall-rule create \
  --resource-group rg-vcc-dev-001 \
  --name <pg-server-name> \
  --rule-name temp-local-dev \
  --start-ip-address $MY_IP \
  --end-ip-address $MY_IP

# Run seeds, then REMOVE the rule
az postgres flexible-server firewall-rule delete \
  --resource-group rg-vcc-dev-001 \
  --name <pg-server-name> \
  --rule-name temp-local-dev --yes
```

#### Option C — Run psql from a Container App (uses private endpoint, no firewall change)
```bash
# Exec into any running Container App that has psql available
az containerapp exec \
  --name ca-vcc-dev-identity-api-001 \
  --resource-group rg-vcc-dev-001 \
  --command "/bin/bash"
# Inside the container:
psql $DATABASE_URL -f /tmp/seed.sql
```

#### Option D — Seed via Service REST API (safest, uses existing auth/validation)
Write seed data through the service's own API endpoints (POST requests). Slower but validates business logic along with data. Best for production-like seed data.

**Recommended approach for dev environment:** Option A (Azure Cloud Shell) for bulk seed load, then Option D for per-feature test data.

---

## 4. Migration Strategy

### Recommended: Option C — Proxy Pattern in Web BFF

The Web BFF already exists and has partial service wiring. The safest migration path is:

1. Web BFF handles ALL frontend requests
2. For endpoints with real service backing: BFF proxies to the real microservice
3. For endpoints without real backing: BFF falls back to mock API (temporarily)
4. Mock API is never called directly by frontend — only via BFF fallback

This is superior to Option A (hard cutover) and Option B (per-service switch) because:
- Frontend changes exactly once: point to BFF URL
- Rollback is instant (mock fallback still running)
- Each endpoint can be promoted independently with zero frontend changes
- Canary testing per-route is trivial

### Migration Architecture

```
Frontend (React)
     |
     v
Web BFF (Azure Container Apps, port 8080)
     |-- /api/v1/assessments        --> assessment-service (LIVE)
     |-- /api/v1/care-plans         --> careplan-service (LIVE)
     |-- /api/v1/incidents/*        --> incidents-service (LIVE)
     |-- /api/v1/hr/*               --> hr-service (LIVE)
     |-- /api/v1/finance/*          --> finance-service (LIVE)
     |-- /api/v1/visits             --> visits-service (WIRE NOW)
     |-- /api/v1/notifications/*    --> notification-service (WIRE NOW)
     |-- /api/v1/schedule-blocks/*  --> scheduling-service (WIRE NOW)
     |-- /api/v1/family/*           --> [MOCK FALLBACK] no service yet
     |-- /api/v1/evv/*              --> [MOCK FALLBACK] no service yet
     |-- /api/v1/dashboard/stats    --> [BFF aggregation from multiple services]
     |-- /api/v1/cqc-readiness      --> [MOCK FALLBACK]
     |-- everything else            --> [MOCK FALLBACK]
     |
     v (fallback only)
Mock API (Node.js, to be decommissioned incrementally)
```

---

## 5. Step-by-Step Migration Plan

### Phase 0 — Preparation (1-2 days)

**0.1 Load seed data**
- Use Azure Cloud Shell + Option A above
- Minimum seed sets needed per service:
  - `identity_db`: 1 organization, 3-5 users (admin, care-manager, carer), 2-3 service users
  - `assessment_db`: 2-3 assessment templates (assessment_questions, assessment_sections)
  - `careplan_db`: master task types, master visit types
  - `visits_db`: visit statuses, task types
  - `hr_db`: leave types, training categories
  - `scheduling_db`: appointment types
  - `finance_db`: invoice statuses, timesheet statuses
  - `incidents_db`: incident categories (accidents, complaints, hazards, safeguarding)
  - `notification_db`: notification templates

**0.2 Verify all 11 Container Apps are healthy**
```bash
az containerapp list \
  --resource-group rg-vcc-dev-001 \
  --query "[].{name:name, status:properties.runningStatus}" \
  --output table
```

**0.3 Set frontend env var (do not deploy yet)**
```bash
# In frontend/web/.env.production
VITE_API_BASE_URL=https://<web-bff-fqdn>.azurecontainerapps.io
```

---

### Phase 1 — Wire Missing BFF Routes (3-5 days)

**1.1 Wire visits-service to BFF**
- Add `VisitsController.java` to `backend/bff/web-bff/src/main/java/.../controller/`
- Configure `VISITS_SERVICE_BASE_URL` env var in Container App (already in application.yml)
- Map mock routes to real service paths:
  - `GET /api/v1/visits` → `GET /visits` (visits-service)
  - `POST /api/v1/visits/:id/check-in` → `POST /visits/{visitId}/check-in`
  - `POST /api/v1/visits/:id/check-out` → `POST /visits/{visitId}/check-out`
  - `GET /api/v1/visits/:id/emar` → `GET /visits/{visitId}/emar`

**1.2 Wire notification-service to BFF**
- Add `NotificationController.java`
- Add `notification.service.base-url` to `application.yml`
- Set `NOTIFICATION_SERVICE_BASE_URL` env var in Container App
- Map: `/api/v1/notifications/*` → notification-service paths

**1.3 Wire scheduling-service to BFF**
- `CalendarController.java` exists — extend to handle `schedule-blocks` alias
- `schedule-blocks` in mock = `schedule-events` in real service
- Map: `GET/POST /api/v1/schedule-blocks` → `GET/POST /schedule-events`

**1.4 Verify identity-service routing**
- `ServiceUsersController.java` exists — confirm it handles `/api/v1/service-users/*`
- Add routing for `/api/v1/organizations/*` and `/api/v1/users/*`

---

### Phase 2 — Switch Frontend to BFF (1 day)

**2.1 Update frontend environment**
```bash
# In frontend/web/ Azure Static Web App config:
VITE_API_BASE_URL=https://<web-bff-fqdn>.azurecontainerapps.io
```

**2.2 Add BFF fallback proxy for unimplemented routes**
In Web BFF, add a fallback route that proxies unknown `/api/v1/*` requests to the mock API:
```yaml
# application-dev.yml
mock-api:
  fallback-url: ${MOCK_API_URL:http://mock-api:3000}
  fallback-enabled: ${MOCK_API_FALLBACK_ENABLED:true}
```

This ensures zero frontend breakage during migration. As each BFF controller is added, the fallback for that route is removed.

**2.3 Keep mock API running** (do not shut down)
Mock API remains as a fallback target for the BFF. It can be stopped when fallback-enabled is set to false.

---

### Phase 3 — Per-Domain Cutover (1-2 weeks)

Work through each domain in priority order. For each:
1. Confirm seed data in that service's DB
2. Smoke-test the real service endpoint directly (health + basic CRUD)
3. Enable BFF routing to real service
4. Disable fallback for that domain's routes
5. Run Playwright smoke tests

**Priority order:**
1. Identity (login, users, orgs) — everything else depends on auth
2. Assessment (core clinical workflow)
3. Care Plans (built on assessment)
4. Visits (mobile carer workflow)
5. Incidents (safety-critical, short paths)
6. HR (staff management)
7. Scheduling (complex, validate separately)
8. Finance (invoicing, last due to rate-card gaps)
9. Notifications (async, lower urgency)
10. Family Portal (no service — keep mock until service is built)

---

### Phase 4 — Decommission Mock (after Phase 3 complete)

**4.1 Disable BFF fallback**
```yaml
MOCK_API_FALLBACK_ENABLED=false
```

**4.2 Run full Playwright test suite**
Confirm zero regressions. Mock fallback should not be needed.

**4.3 Stop mock API Container App (or LaunchAgent if local)**
```bash
az containerapp update \
  --name ca-vcc-dev-mock-api \
  --resource-group rg-vcc-dev-001 \
  --min-replicas 0 \
  --max-replicas 0
```

**4.4 Archive mock-api/server.js** (do not delete — reference for missing features)

---

## 6. Rollback Plan

Rollback is available at every phase with zero data loss risk.

### Instant rollback (any phase)
```bash
# Re-point frontend to mock API
# In Azure Static Web App config:
VITE_API_BASE_URL=https://mock-api-url

# OR in BFF — re-enable fallback
MOCK_API_FALLBACK_ENABLED=true
```

Because the mock API is never decommissioned during active migration (only at Phase 4), rollback is a single env var change at the frontend or BFF level.

### Per-service rollback
If a specific service is misbehaving, disable only its BFF route and re-enable fallback for that domain only. All other services continue serving real data.

### Database rollback
Schema rollback is possible via Flyway undo migrations. Data rollback requires a database restore from Azure Backup (automated daily snapshots on PostgreSQL Flexible Server).

---

## 7. Endpoints That Are Mock-Only (No Real Service Equivalent)

These require new service implementation or design decisions before migration is possible:

| Mock Endpoint | Category | Effort |
|---------------|----------|--------|
| `GET /api/v1/finance/rate-cards` | Finance | Medium — add to finance-service |
| `GET /api/v1/finance/funders` | Finance | Medium — add to finance-service |
| `GET /api/v1/finance/payroll/*` | Finance | High — payroll is a separate domain |
| `GET /api/v1/finance/aged-debtors` | Finance | Low — calculated report from invoices |
| `GET /api/v1/finance/nmw-compliance` | Finance | Medium |
| `GET /api/v1/hr/bradford` | HR | Low — calculated from absences |
| `GET /api/v1/hr/care-certificate` | HR | Medium |
| `GET /api/v1/hr/cqc-inspection-pack` | HR | High |
| `GET /api/v1/cqc-readiness` | Compliance | Medium — aggregate |
| `GET /api/v1/evv/*` | EVV | Medium — visits-service has tracking |
| `GET /api/v1/family/*` | Family Portal | High — no BFF layer exists |
| `GET /api/v1/insurance/*` | Insurance | Not in architecture — park for now |
| `GET /api/v1/import/*` | Bulk Import | Medium — batch job, not REST |
| `GET /api/v1/handoff/*` | Handoff notes | Low — maps to visits-service notes |
| `GET /api/v1/observations` | Observations | Low — maps to assessment-service |

---

## 8. Timeline Estimate

| Phase | Work | Duration |
|-------|------|----------|
| Phase 0 — Seed data + health verification | 1-2 days | DevOps + 1 backend dev |
| Phase 1 — Wire 3 missing BFF routes | 3-5 days | 2 backend devs |
| Phase 2 — Frontend switch + BFF fallback | 1 day | 1 frontend dev + 1 backend dev |
| Phase 3 — Per-domain cutover (10 domains) | 8-10 days | 2 backend devs |
| Phase 4 — Decommission mock | 1 day | DevOps |
| **Total** | | **~3 weeks** |

---

## 9. Risk Register

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Real service returns different schema than mock | High | High | Adapter layer in BFF; validate against OpenAPI spec |
| Empty seed data causes null pointer errors | Medium | Medium | Populate seed before Phase 2 cutover |
| PostgreSQL connection limits under load | Low | High | Connection pooling via PgBouncer (already in infra) |
| Visits-service geofence check fails on dev | Medium | Medium | Disable geofence enforcement flag in dev profile |
| Auth token propagation missing in new BFF controllers | Medium | High | Follow existing `WebClientConfig` JWT propagation pattern |
| Family portal with no service breaks the portal | High | Medium | Mock fallback covers family routes until service is built |
| Azure Container App cold start latency | Low | Low | Min replicas = 1 already set in dev.bicepparam |

---

## 10. Key File Locations

| File | Purpose |
|------|---------|
| `/Users/makinja/projects/client/lumiscare/mock-api/server.js` | Source of truth for all mock endpoint behavior |
| `/Users/makinja/projects/client/lumiscare/backend/bff/web-bff/src/main/resources/application.yml` | BFF service URL configuration |
| `/Users/makinja/projects/client/lumiscare/backend/bff/web-bff/src/main/java/.../controller/` | BFF controllers — add new ones here |
| `/Users/makinja/projects/client/lumiscare/backend/bff/web-bff/src/main/java/.../config/` | Service client configs — add wiring here |
| `/Users/makinja/projects/client/lumiscare/openapi-specs/` | OpenAPI specs per service — source of truth for real paths |
| `/Users/makinja/projects/client/lumiscare/infrastructure/env-parameters/dev.bicepparam` | Azure infra sizing and image names |

---

## DA Corrections — 9 Critical Gaps Addressed

*Added 2026-03-26 — Devils Advocate review identified these gaps in the original plan. All 9 are corrections to existing sections or new sections not previously covered.*

---

### DA-1. Endpoint Priority Matrix

Full classification of all 222 mock endpoint registrations (18 catch-all wildcards excluded = 204 addressable endpoints).

**Status definitions:**
- `REAL_READY` — Spring Boot service has the endpoint AND BFF wiring exists. Can migrate immediately after seed data is loaded.
- `NEEDS_WIRING` — Spring Boot service endpoint exists in OpenAPI spec, but BFF controller for this route is absent or incomplete.
- `MOCK_ONLY` — No real service endpoint. Feature does not exist in any deployed Spring Boot service.
- `SCOPE_OUT` — Not needed for MVP. Defer to post-MVP roadmap.

| Domain | Mock Route | Status | Notes |
|--------|-----------|--------|-------|
| **Assessment** | `GET /api/v1/assessments` | REAL_READY | BFF wired |
| | `GET /api/v1/assessments/:id` | REAL_READY | BFF wired |
| | `POST /api/v1/assessments` | REAL_READY | BFF wired |
| | `PUT /api/v1/assessments/:id` | REAL_READY | BFF wired |
| | `PATCH /api/v1/assessments/:id` | REAL_READY | BFF wired |
| | `POST /api/v1/assessments/:id/complete` | REAL_READY | BFF wired |
| | `GET /api/v1/assessments/:id/body-map` | NEEDS_WIRING | Service has photos/observations endpoints; body-map is a frontend-assembled view |
| | `PUT /api/v1/assessments/:id/body-map` | NEEDS_WIRING | As above |
| | `GET /api/v1/assessments/:id/scores` | NEEDS_WIRING | Maps to risk-section in assessment-service |
| | `GET /api/v1/assessments/:id/scores/:tool` | NEEDS_WIRING | As above |
| | `POST /api/v1/assessments/:id/scores/:tool` | NEEDS_WIRING | As above |
| | `GET /api/v1/intake` | REAL_READY | OpenAPI `/api/v1/intake` defined |
| | `GET /api/v1/intake/:id` | REAL_READY | OpenAPI `/api/v1/intake/{intake_id}` defined |
| | `POST /api/v1/intake` | REAL_READY | OpenAPI defined |
| | `PATCH /api/v1/intake/:id` | NEEDS_WIRING | OpenAPI has GET/POST only; PATCH not in spec |
| | `DELETE /api/v1/intake/:id` | SCOPE_OUT | Not in spec; soft-delete not required for MVP |
| **Care Plans** | `GET /api/v1/care-plans` | REAL_READY | BFF wired |
| | `GET /api/v1/care-plans/:id` | REAL_READY | BFF wired |
| | `POST /api/v1/care-plans` | REAL_READY | BFF wired |
| | `PUT /api/v1/care-plans/:id` | REAL_READY | BFF wired |
| | `DELETE /api/v1/care-plans/:id` | REAL_READY | BFF wired |
| | `POST /api/v1/care-plans/:id/publish` | REAL_READY | BFF wired |
| | `GET /api/v1/care-plans/:id/versions` | REAL_READY | BFF wired |
| | `GET /api/v1/care-plans/review-due` | MOCK_ONLY | No real endpoint; aggregate query needed |
| | `GET /api/v1/master-task-types` | REAL_READY | Service defines master-task-types |
| | `GET /api/v1/master-visit-types` | REAL_READY | Service defines master-visit-types |
| | Care plan tasks routes | MOCK_ONLY | careplan-service has task types, not task CRUD per plan |
| | Care plan risk-alerts routes | MOCK_ONLY | Not in any service spec |
| | Care plan reviews/consent routes | MOCK_ONLY | Not in any service spec |
| | `POST /api/v1/care-plans/:id/submit-for-approval` | MOCK_ONLY | Status lifecycle managed differently in real service |
| **Visits** | `GET /api/v1/visits` | NEEDS_WIRING | visits-service READY, no BFF VisitsController |
| | `GET /api/v1/visits/:id` | NEEDS_WIRING | As above |
| | `POST /api/v1/visits` | NEEDS_WIRING | As above |
| | `PATCH /api/v1/visits/:id` | NEEDS_WIRING | As above |
| | `DELETE /api/v1/visits/:id` | NEEDS_WIRING | As above |
| | `POST /api/v1/visits/publish` | MOCK_ONLY | No equivalent in visits-service spec |
| | `POST /api/v1/visits/:id/check-in` | NEEDS_WIRING | visits-service has `/visits/{visitId}/check-in` |
| | `POST /api/v1/visits/:id/check-out` | NEEDS_WIRING | visits-service has `/visits/{visitId}/check-out` |
| | `GET /api/v1/visits/:id/emar` | NEEDS_WIRING | visits-service has `/visits/{visitId}/emar` |
| | `POST /api/v1/visits/:id/emar` | NEEDS_WIRING | visits-service has `/visits/{visitId}/emar` POST |
| | Clinical observations (per-visit) | NEEDS_WIRING | visits-service has `/visits/{visitId}/observations` |
| | SOS / running-late / pre-visit-brief | MOCK_ONLY | Not defined in visits-service OpenAPI |
| | Travel record | MOCK_ONLY | Tracking endpoints exist but different shape |
| **Scheduling** | `GET /api/v1/schedule-blocks` | NEEDS_WIRING | scheduling-service has `/schedule-events`; CalendarController exists but path mismatch |
| | `POST /api/v1/schedule-blocks` | NEEDS_WIRING | As above |
| | `PATCH /api/v1/schedule-blocks/:id` | NEEDS_WIRING | As above |
| | `DELETE /api/v1/schedule-blocks/:id` | NEEDS_WIRING | As above |
| | `GET /api/v1/completed-events` | MOCK_ONLY | No direct real equivalent; map to completed appointments |
| | `POST /api/v1/completed-events/:id/confirm` | MOCK_ONLY | scheduling-service manages this via status update |
| | `POST /api/v1/completed-events/bulk-confirm` | MOCK_ONLY | No bulk confirm endpoint in spec |
| | Schedule publish route | MOCK_ONLY | scheduling-service has `/schedules/generate` + `/schedules/{id}/approve` which differs |
| **HR** | `GET /employees/list` | REAL_READY | BFF EmployeeController wired |
| | `GET /employees/:id` | REAL_READY | BFF wired |
| | `POST /employees` | REAL_READY | BFF wired |
| | `PATCH /employees/:id` | REAL_READY | BFF wired |
| | `DELETE /employees/:id` | REAL_READY | BFF wired |
| | `GET /api/v1/leaves` | REAL_READY | hr-service has leaves endpoint |
| | `POST /api/v1/leaves` | REAL_READY | hr-service has leaves POST |
| | `PATCH /api/v1/leaves/:id` | REAL_READY | hr-service has leaves PATCH |
| | `DELETE /api/v1/leaves/:id` | REAL_READY | hr-service has leaves DELETE |
| | `GET /api/v1/hr/supervisions` | NEEDS_WIRING | hr-service has supervisions; BFF sub-path mapping needed |
| | `GET /api/v1/hr/absences` | NEEDS_WIRING | hr-service has absences/leaves |
| | `GET /api/v1/hr/dbs` | MOCK_ONLY | Not in hr-service OpenAPI; compliance documents endpoint covers this partially |
| | `GET /api/v1/hr/bradford` | MOCK_ONLY | Calculated from absences; no real endpoint |
| | `GET /api/v1/hr/care-certificate` | MOCK_ONLY | Not in hr-service spec |
| | `GET /api/v1/training/courses` | NEEDS_WIRING | hr-service has training; BFF not wired |
| | `GET /api/v1/training/compliance` | NEEDS_WIRING | hr-service has training compliance; BFF not wired |
| | `GET /api/v1/hr/cqc-inspection-pack` | MOCK_ONLY | No real equivalent — aggregate report |
| **Finance** | `GET /api/v1/finance/invoices` | REAL_READY | BFF FinanceBffController wired |
| | `POST /api/v1/finance/invoices` | REAL_READY | BFF wired |
| | `PUT /api/v1/finance/invoices/:id` | REAL_READY | BFF wired |
| | `GET /api/v1/finance/invoice-groups` | REAL_READY | BFF wired |
| | `GET /api/v1/finance/payment-groups` | REAL_READY | BFF wired |
| | `GET /api/v1/finance/dashboard` | REAL_READY | BFF wired |
| | Finance timesheets routes | REAL_READY | finance-service defines timesheets |
| | `GET /api/v1/finance/rate-cards` | MOCK_ONLY | Not in finance-service spec |
| | `GET /api/v1/finance/funders` | MOCK_ONLY | Not in finance-service spec |
| | `GET /api/v1/finance/payroll/*` | MOCK_ONLY | Payroll is a separate domain |
| | `GET /api/v1/finance/aged-debtors` | MOCK_ONLY | Calculated report — not in spec |
| | `GET /api/v1/finance/nmw-compliance` | MOCK_ONLY | Not in finance-service spec |
| **Incidents** | `GET /api/v1/incidents` | NEEDS_WIRING | incidents-service uses typed routes (`/accidents`, `/safeguarding`, `/complaints`, `/hazards`); BFF IncidentsController must aggregate or map |
| | `GET /api/v1/incidents/:id` | NEEDS_WIRING | Type-specific in real service |
| | `POST /api/v1/incidents` | NEEDS_WIRING | Type must be specified to route to correct sub-endpoint |
| | `PATCH /api/v1/incidents/:id` | NEEDS_WIRING | As above |
| | `DELETE /api/v1/incidents/:id` | NEEDS_WIRING | As above |
| | `GET /api/v1/incidents/summary` | REAL_READY | incidents-service has `/summary` |
| | Export endpoints | REAL_READY | incidents-service has CSV export per type |
| **Notifications** | `GET /api/v1/notifications/inbox` | NEEDS_WIRING | notification-service READY, BFF NOT wired |
| | `GET /api/v1/notifications/inbox/unread-count` | NEEDS_WIRING | As above |
| | `PATCH /api/v1/notifications/inbox/:id/read` | NEEDS_WIRING | As above |
| | `PATCH /api/v1/notifications/inbox/read-all` | NEEDS_WIRING | As above |
| | `POST /api/v1/notifications/mark-all-read` | NEEDS_WIRING | As above |
| | `GET /api/v1/notifications/preferences` | NEEDS_WIRING | notification-service has preferences |
| | `POST /api/v1/notifications/preferences` | NEEDS_WIRING | As above |
| | `PATCH /api/v1/notifications/preferences/quiet-hours` | NEEDS_WIRING | As above |
| | Notification device register/unregister | NEEDS_WIRING | notification-service has devices endpoint |
| | `GET /api/v1/notifications/analytics` | MOCK_ONLY | Not in notification-service spec |
| **Identity** | `GET /api/v1/service-users` | REAL_READY | identity-service + BFF ServiceUsersController |
| | `GET /api/v1/service-users/:id` | REAL_READY | BFF wired |
| | `POST /api/v1/service-users` | REAL_READY | BFF wired |
| | `PATCH /api/v1/service-users/:id` | REAL_READY | BFF wired |
| | `DELETE /api/v1/service-users/:id` | REAL_READY | BFF wired |
| | `GET /api/v1/organizations` | NEEDS_WIRING | identity-service has organizations; BFF routing partial |
| | `GET /api/v1/organizations/:id` | NEEDS_WIRING | As above |
| | `GET /api/v1/users` | NEEDS_WIRING | identity-service has users; BFF routing partial |
| | `GET /users/about-me` | REAL_READY | identity-service `/users/me` |
| | `GET /api/v1/carers` | NEEDS_WIRING | identity-service users filtered by carer role |
| | `POST /api/v1/carers/by-skills` | MOCK_ONLY | No real endpoint — would require identity + hr query |
| | `GET /api/v1/skills` | MOCK_ONLY | Not in any service spec |
| | `GET /api/v1/settings` | NEEDS_WIRING | identity-service has org settings |
| | `PATCH /api/v1/settings` | NEEDS_WIRING | As above |
| | `GET /api/v1/rostering-settings` | MOCK_ONLY | Not in any service spec |
| **Family Portal** | All 16 `GET/POST /api/v1/family/*` routes | MOCK_ONLY | No FamilyPortalService; no BFF controller (see DA-8) |
| **Misc / EVV / Dashboard** | `GET /api/v1/dashboard/stats` | NEEDS_WIRING | Requires BFF aggregation across 4+ services |
| | `GET /api/v1/cqc-readiness` | MOCK_ONLY | Aggregate report — no real service |
| | `GET /api/v1/evv/compliance` | NEEDS_WIRING | visits-service tracking endpoints cover EVV data |
| | `GET /api/v1/evv/records` | NEEDS_WIRING | visits-service has location tracking history |
| | `GET /api/v1/audit-logs` | MOCK_ONLY | No audit-service in architecture |
| | `GET /api/v1/insurance/*` | SCOPE_OUT | Not in product architecture — park post-MVP |
| | `GET /api/v1/import/*` | SCOPE_OUT | Batch job domain, not REST for MVP |
| | `GET /api/v1/handoff/notes` | NEEDS_WIRING | visits-service `/visits/{visitId}/notes` covers this |
| | `GET /api/v1/handoff/history` | NEEDS_WIRING | visits-service notes with type=handover |
| | `GET /api/v1/supervisor/dashboard` | MOCK_ONLY | BFF aggregation — not yet designed |
| | `GET /api/v1/supervisor/dashboard/carer-locations` | NEEDS_WIRING | visits-service `/tracking/locations/current` |
| | `GET /api/v1/observations` (global) | NEEDS_WIRING | visits-service has per-visit observations; no global endpoint |

**Summary counts:**

| Status | Count | Action |
|--------|-------|--------|
| REAL_READY | ~65 | Wire frontend → BFF, validate schema (see DA-3) |
| NEEDS_WIRING | ~72 | Add BFF controller code (Phase 1 work) |
| MOCK_ONLY | ~52 | Keep on mock fallback; schedule for post-MVP |
| SCOPE_OUT | ~15 | Remove from BFF routing; return 404 or 501 |

---

### DA-2. Seed Data Verification (Phase 0.4)

The original plan (Phase 0.1) lists what to seed but provides no way to verify seeds actually loaded. Add Phase 0.4 after all seed scripts have run.

**Phase 0.4 — Verify Seed Data Loaded Correctly**

Run these queries via Azure Cloud Shell after Phase 0.1 seed load:

```sql
-- identity_db
SELECT COUNT(*) AS orgs          FROM organizations;          -- expect >= 1
SELECT COUNT(*) AS users         FROM users;                  -- expect >= 3
SELECT COUNT(*) AS service_users FROM service_users;          -- expect >= 2
SELECT COUNT(*) AS roles         FROM roles;                  -- expect >= 12 (system roles)

-- assessment_db
SELECT COUNT(*) AS intake        FROM assessment_intake;      -- expect >= 0 (can be empty)
SELECT COUNT(*) AS sections      FROM assessment_sections;    -- expect >= 7

-- careplan_db
SELECT COUNT(*) AS task_types    FROM master_task_types;      -- expect >= 5
SELECT COUNT(*) AS visit_types   FROM master_visit_types;     -- expect >= 3

-- visits_db
SELECT COUNT(*) AS visit_statuses FROM visit_status_lookup;   -- expect >= 6 (or check enum table)

-- hr_db
SELECT COUNT(*) AS leave_types   FROM leave_types;            -- expect >= 4
SELECT COUNT(*) AS training_cats FROM training_categories;    -- expect >= 3

-- scheduling_db
SELECT COUNT(*) AS appt_types    FROM appointment_types;      -- expect >= 4

-- finance_db
SELECT COUNT(*) AS inv_statuses  FROM invoice_status_lookup;  -- expect >= 4

-- incidents_db
SELECT COUNT(*) AS categories    FROM incident_categories;    -- expect >= 4 (acc, saf, cmp, haz)

-- notification_db
SELECT COUNT(*) AS templates     FROM notification_templates; -- expect >= 5
```

**verify-seeds.sh — script to run from Azure Cloud Shell:**

```bash
#!/bin/bash
# verify-seeds.sh — Run from Azure Cloud Shell after Phase 0.1 seed load
# Usage: bash verify-seeds.sh <pg-server-name> <admin-user>

PG_SERVER="${1}.postgres.database.azure.com"
PG_USER="$2"
FAILED=0

run_check() {
  local DB=$1 QUERY=$2 LABEL=$3 MIN=$4
  COUNT=$(psql "host=$PG_SERVER user=$PG_USER dbname=$DB sslmode=require" \
    -tAc "$QUERY" 2>/dev/null)
  if [ -z "$COUNT" ] || [ "$COUNT" -lt "$MIN" ]; then
    echo "FAIL [$DB] $LABEL: got $COUNT (expected >= $MIN)"
    FAILED=$((FAILED+1))
  else
    echo "PASS [$DB] $LABEL: $COUNT rows"
  fi
}

run_check "identity_db"      "SELECT COUNT(*) FROM organizations;"    "organizations"     1
run_check "identity_db"      "SELECT COUNT(*) FROM users;"            "users"             3
run_check "identity_db"      "SELECT COUNT(*) FROM service_users;"    "service_users"     2
run_check "identity_db"      "SELECT COUNT(*) FROM roles;"            "roles"             12
run_check "careplan_db"      "SELECT COUNT(*) FROM master_task_types;" "master_task_types" 5
run_check "careplan_db"      "SELECT COUNT(*) FROM master_visit_types;" "master_visit_types" 3
run_check "hr_db"            "SELECT COUNT(*) FROM leave_types;"      "leave_types"       4
run_check "scheduling_db"    "SELECT COUNT(*) FROM appointment_types;" "appointment_types" 4
run_check "incidents_db"     "SELECT COUNT(*) FROM incident_categories;" "incident_categories" 4
run_check "notification_db"  "SELECT COUNT(*) FROM notification_templates;" "notification_templates" 5

if [ "$FAILED" -gt 0 ]; then
  echo ""
  echo "SEED VERIFICATION: $FAILED check(s) FAILED. Do NOT proceed to Phase 1."
  exit 1
else
  echo ""
  echo "SEED VERIFICATION: All checks PASSED. Safe to proceed to Phase 1."
  exit 0
fi
```

**Gate:** Phase 1 (BFF wiring) must not begin until `verify-seeds.sh` exits 0.

---

### DA-3. Shape Mismatch Matrix

Comparison of mock API response shapes vs real OpenAPI-defined shapes for high-traffic routes. These mismatches require BFF adapter code — the BFF cannot simply proxy; it must transform.

#### Visits — `GET /api/v1/visits`

| Field | Mock shape | Real service shape | Action |
|-------|-----------|-------------------|--------|
| Response wrapper | Array `[...]` | Paginated object `{ content: [...], pageInfo: { ... } }` | BFF must extract `content` array or frontend must handle pagination |
| ID format | `"visit-0001"` (string with prefix) | UUID `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"` | BFF must map or real service auto-generates UUIDs |
| Client reference | `clientId` + `clientName` (flat) | `serviceUserId` (UUID) — no embedded name | BFF must enrich with identity-service lookup or frontend fetches separately |
| Status values | `"in-progress"`, `"scheduled"`, `"completed"` (kebab-case) | `in_progress`, `scheduled`, `completed` (snake_case enum) | BFF adapter or frontend normalizes |
| Visit type | `"personal-care"`, `"medication"` (kebab-case strings) | `AppointmentType` enum: `care_visit`, `assessment_visit`, `medication_only`, `welfare_check` | Mapping required — not 1:1 |
| GPS check-in | `gpsCheckIn: { lat, lng }` | `checkInLocation: { latitude, longitude, accuracy, altitude, timestamp }` (GpsLocation schema) | Field rename + structure change |
| Duration | `duration: 60` (minutes, integer) | Derived from `scheduledStartTime` / `scheduledEndTime` — no standalone `duration` field | BFF calculates or omits |
| Required headers | None | `X-VCU-Organization-Id`, `X-VCU-User-Id`, `X-VCU-Roles` must be forwarded | BFF must propagate JWT-extracted headers |

#### Incidents — `GET /api/v1/incidents`

| Field | Mock shape | Real service shape | Action |
|-------|-----------|-------------------|--------|
| Response wrapper | `{ incidents: [...], total: N }` | Each type has own paginated endpoint: `/accidents`, `/safeguarding`, `/complaints`, `/hazards` | BFF must fan-out to 4 endpoints and merge, OR frontend switches to type-specific routes |
| `id` format | `"inc-001"` | UUID | BFF-generated or real service UUID |
| `type` values | `"Accident"`, `"Medication_Error"`, `"Safeguarding"` (mixed case, non-typed) | Routes are typed: `/accidents` = type ACCIDENT/INCIDENT/NEAR_MISS; `/safeguarding` uses `ConcernType` enum | Routing logic required in BFF |
| `status` values | `"Resolved"`, `"Under_Review"`, `"Investigating"` | `LogStatus` enum: `IDENTIFIED`, `RAISED`, `IN_PROGRESS`, `UPHELD`, `NOT_UPHELD`, `PARTIALLY_UPHELD`, `WITHDRAWN`, `RESOLVED` | Mapping table needed |
| `incident_number` | `"INC-2024-001"` (mock format) | Auto-generated: `"ACC-2026-00001"`, `"SAF-2026-00001"` etc. | Display only; reference format changes |
| Duplicate fields | Both `serviceUserName` and `service_user_name` exist | Single `serviceUserId` (UUID reference) | Remove duplicates; BFF enriches name |
| `actionsTaken` | Embedded array of action objects | Separate endpoint: `/logs/{logType}/{logId}/actions` | BFF must fetch separately or omit on list |
| Header | `X-Org-Id` (mock uses no header) | `X-VCC-Organization-Id` required | BFF must add |

#### Scheduling — `GET /api/v1/schedule-blocks`

| Field | Mock shape | Real service shape | Action |
|-------|-----------|-------------------|--------|
| Path | `/api/v1/schedule-blocks` | `/api/v1/scheduling/schedule-events` | BFF maps path |
| Response wrapper | Array `[...]` | Paginated `{ content: [...], ... }` | BFF unwraps or frontend adapts |
| `type` values | `"team-meeting"`, `"training"`, `"supervision"`, `"appraisal"` | `ScheduleEventType` enum: `appraisal`, `meeting`, `supervision`, `training` | `team-meeting` → `meeting` mapping |
| `id` format | `"sb-001"` | UUID | BFF maps |
| `carerId` | String `"3"` | UUID | BFF must translate carer numeric ID → UUID via identity-service |

#### Assessment — `GET /api/v1/assessments/:id`

| Field | Mock shape | Real service shape | Action |
|-------|-----------|-------------------|--------|
| `completionStatus` | `"IN_PROGRESS"` / `"COMPLETED"` | `status` field in real service with different enum values | Field rename |
| `patientId` | UUID reference | `serviceUserId` — same concept, different field name | BFF renames |
| `assessmentId` + `id` | Both present (legacy compat) | Single `id` field | BFF normalizes to single `id` |
| `overview` | Nested object `{ riskLevel, allergies, dnacp, nextAssessmentDate, ... }` | Not a single nested block — data spread across sections and risk-section endpoints | BFF must assemble from multiple sub-endpoints |
| `assessments` | Array of section objects with nested questions | Sections are separate sub-resources fetched via `/assessments/{id}/sections` | BFF must aggregate |
| Header | No header in mock | `X-VCC-User-Id`, `X-VCC-Organization-Id`, `X-VCC-User-Email` required | BFF forwards |

#### Care Plans — `GET /api/v1/care-plans/:id`

| Field | Mock shape | Real service shape | Action |
|-------|-----------|-------------------|--------|
| `clientId` | String | Real service uses `serviceUserId` (UUID) | Field rename in BFF adapter |
| `status` values | Custom string values | `CarePlanStatus` enum: `draft`, `ai_generated`, `under_review`, `approved`, `active`, `suspended`, `completed`, `cancelled` | Map mock statuses to enum values |
| Server URL | Mock uses `/api/v1/care-plans` | Real service base: `https://api.vcc2.com/v1/api/v1/care-plans` — note double prefix in careplan OpenAPI | Confirm actual deployed base path |
| `tasks` | Embedded array in plan response | Separate resource: `TaskCategory` objects under `Visit` objects | Frontend must navigate new structure |

**Action required for all shape mismatches:**
Add an adapter/transformer class in the BFF for each domain when wiring controllers (Phase 1). Do not rely on the real service coincidentally matching the mock shape.

---

### DA-4. Post-Migration Schema Audit

After Phase 3 (per-domain cutover) completes for each domain, run a schema audit before disabling the mock fallback for that domain.

**Add to Phase 3 per-domain checklist:**

Step 6 (new) — Schema audit for domain being cut over:

```bash
# Example for visits domain — run from a machine with curl access to BFF
BFF_URL="https://<web-bff-fqdn>.azurecontainerapps.io"
AUTH_TOKEN="<valid-bearer-token>"

# 1. Fetch a real response
curl -s -H "Authorization: Bearer $AUTH_TOKEN" \
  "$BFF_URL/api/v1/visits?page=0&size=5" \
  -o /tmp/real-visits-response.json

# 2. Validate against OpenAPI spec using swagger-cli or similar
npx @apidevtools/swagger-cli validate \
  /Users/makinja/projects/client/lumiscare/openapi-specs/visits-open-api/open-api.yaml

# 3. Manual spot-check — verify required fields present
node -e "
  const data = require('/tmp/real-visits-response.json');
  const required = ['id','status','scheduledStartTime','scheduledEndTime','serviceUserId','carerId'];
  const first = data.content?.[0] || data[0] || {};
  required.forEach(f => {
    if (first[f] === undefined) console.log('MISSING:', f);
    else console.log('PRESENT:', f, '=', JSON.stringify(first[f]).slice(0, 40));
  });
"
```

**Acceptance criteria for schema audit:**
- All required fields from OpenAPI spec are present in real response
- No 500 errors in Azure Application Insights for the domain in the last 30 minutes
- Frontend renders the domain view without console errors (visual check by developer)

**Gate:** Do not disable mock fallback for a domain until schema audit passes.

---

### DA-5. Rollback Test (Phase 2.4)

The existing rollback plan (Section 6) describes the mechanism but never tests it. A rollback that has never been tested is not a reliable rollback.

**Add Phase 2.4 — Rollback Test (run after Phase 2.3, before Phase 3)**

Procedure:
1. Confirm BFF fallback is enabled (`MOCK_API_FALLBACK_ENABLED=true`)
2. Confirm frontend is pointing to BFF URL (Phase 2.1 complete)
3. For one REAL_READY domain (recommended: Identity), disable the BFF route to real service:
   - Comment out or stub the real service client for that domain in BFF
   - Redeploy BFF (or use feature flag)
4. Verify BFF fallback activates:
   - `curl $BFF_URL/api/v1/service-users` should return mock data (HTTP 200, mock IDs like `"su-001"`)
   - Browser: service users page must render without error
5. Re-enable the real service client
6. Verify real data is served again
7. Document the rollback time (target: under 5 minutes end-to-end including BFF redeploy)

**Pass criteria:**
- Fallback activates within one BFF redeploy cycle
- Frontend renders correctly on mock data during fallback
- Re-enable returns real data with no stale cache issues

**Gate:** If rollback test fails, do NOT proceed to Phase 3. Fix the fallback mechanism first.

---

### DA-6. Phase Environment Matrix

At each phase, different environments run on different data sources. This was implicit in the original plan — made explicit here.

| Phase | Dev environment | Test environment | Staging environment | Production |
|-------|----------------|-----------------|---------------------|------------|
| Pre-migration (now) | Frontend → Mock direct | N/A | N/A | N/A |
| Phase 0 (prep) | Seed loaded in Azure dev DBs | Mock still running | Not affected | Not affected |
| Phase 1 (BFF wiring) | BFF code changes in dev branch | Mock fallback enabled | Not affected | Not affected |
| Phase 2 (frontend switch) | Frontend → BFF; BFF fallback ON for all routes | Frontend → BFF; BFF fallback ON | Not affected | Not affected |
| Phase 3 (per-domain cutover) | BFF routes real per domain; fallback OFF per domain after audit | BFF + fallback; promote domain-by-domain after dev validates | BFF + fallback per domain; reduced fallback set | Not affected until explicit prod migration |
| Phase 4 (decommission) | Mock stopped | Mock stopped | Mock stopped | N/A (mock never ran in prod) |

**Environment-specific flags:**

```yaml
# application-dev.yml
mock-api:
  fallback-url: ${MOCK_API_URL:http://ca-vcc-dev-mock-api:3000}
  fallback-enabled: true
  fallback-domains:
    visits: true          # flip to false after Phase 3 domain cutover
    scheduling: true
    notifications: true
    family: true          # stays true until FamilyPortalService exists
    misc: true

# application-test.yml
mock-api:
  fallback-url: ${MOCK_API_URL:http://ca-vcc-test-mock-api:3000}
  fallback-enabled: true
  fallback-domains:
    visits: true
    # ... test environment follows dev by ~1 sprint

# application-staging.yml
mock-api:
  fallback-enabled: false   # staging runs real services or explicit test data
  fallback-domains: {}      # no fallback in staging

# application-prod.yml
mock-api:
  fallback-enabled: false   # mock never deployed in prod
```

---

### DA-7. Evidence Checklist per Domain (Cutover Gate)

For each domain cutover in Phase 3, the following evidence must be machine-generated before the mock fallback is disabled for that domain. "Works on my machine" and agent-reported PASS are not acceptable (ZAKON #21).

**Template — fill out for each domain:**

```
Domain: _______________
Date: _______________
Engineer: _______________

[ ] 1. seed-verify: DB row counts pass verify-seeds.sh for this domain's DB (exit 0)
[ ] 2. health-check: Container App status = Running
      Evidence: az containerapp show --name <ca-name> --query properties.runningStatus
[ ] 3. direct-service: Direct HTTP call to real service returns 200
      Evidence: curl -s -o /tmp/service-health.json -w "%{http_code}" http://<service-internal-url>/actuator/health
[ ] 4. bff-proxy: BFF routes traffic to real service (not mock)
      Evidence: curl -s -H "Authorization: Bearer $TOKEN" $BFF_URL/api/v1/<domain-route> | head -c 200
      Confirm: IDs are UUIDs, not mock format (e.g., "su-001")
[ ] 5. schema-audit: Required fields present in response (DA-4 procedure)
      Evidence: node schema-audit.js /tmp/<domain>-response.json
[ ] 6. no-500s: Zero 500 errors in Azure Application Insights for domain in last 30 min
      Evidence: az monitor app-insights query --app <app-insights-name> --analytics-query "..."
[ ] 7. frontend-render: Domain UI renders without console errors
      Evidence: Screenshot or Playwright test output
[ ] 8. rollback-ready: MOCK_API_FALLBACK_ENABLED=true can be set within 5 min if needed
      Evidence: Rollback test passed (Phase 2.4)

PASS if all 8 checked. FAIL = keep mock fallback for this domain.
```

**Minimum bar per domain:**
- Identity: All 8 required (everything else depends on auth)
- Assessment, Care Plans, Incidents, HR, Finance: Items 1-7 required
- Visits, Scheduling, Notifications: Items 1-7 required
- Family Portal: Skip — stays on mock (see DA-8)

---

### DA-8. Family Portal ADR (Architecture Decision Record)

**Decision required before Phase 3 begins.**

#### Context

The Family Portal has 16 mock endpoints (`/api/v1/family/*`). There is no `FamilyPortalService` in the deployed microservice architecture. Building a proper BFF layer for family portal requires aggregating from:
- `careplan-service` (read-only care plan view)
- `notification-service` (family notifications)
- `identity-service` (family member auth)
- An unbuilt payments integration

#### Options

**Option A — Build FamilyPortalBffController in web-bff (or new family-bff)**
- Effort: High (3-5 sprints)
- Requires: New BFF controller wiring 3+ services; family member auth flow; potentially new microservice
- Risk: Derails MVP timeline

**Option B — Scope out Family Portal from MVP; keep on mock fallback**
- Effort: Zero
- Mock fallback continues serving family routes indefinitely until a future sprint
- Family portal users (family members) see the mock data — acceptable for beta/internal demo
- Decision reversible

#### Decision

**Recommendation: Option B — Scope Family Portal out of MVP migration.**

Family portal features are non-critical for the primary care agency workflow. The MVP needs the back-office and mobile carer workflows operational. Family portal is a Phase 2 deliverable.

**Actions:**
1. Do NOT wire family portal routes to any real service during Phase 3
2. Keep `mock-api.fallback-domains.family: true` permanently through MVP launch
3. Add `POST /api/v1/family/*` routes to the `MOCK_ONLY` fallback list explicitly in BFF config
4. Create a new backlog item: "FamilyPortalService design + BFF wiring" — target: post-MVP sprint 1
5. Timeline target for family portal real wiring: 6-8 weeks post-MVP launch

**This ADR is a CEO-level decision.** Tag for explicit sign-off before Phase 3 begins.

---

### DA-9. Demo Protection

During Phase 3 and leading up to any investor demo or client presentation, certain routes must remain stable. A real service returning unexpected data or a 500 error during a demo is unacceptable.

#### Demo-Critical Routes

These routes are rendered on the first screens visible during a standard demo:

```
GET  /api/v1/dashboard/stats
GET  /api/v1/service-users
GET  /api/v1/service-users/:id
GET  /api/v1/care-plans
GET  /api/v1/care-plans/:id
GET  /api/v1/assessments
GET  /api/v1/visits
GET  /api/v1/incidents
GET  /api/v1/notifications/inbox
GET  /api/v1/notifications/inbox/unread-count
```

#### DEMO_MODE Flag

Add a `DEMO_MODE` environment flag to the BFF:

```yaml
# application.yml (BFF)
demo:
  mode: ${DEMO_MODE:false}
  # When true: all demo-critical routes use mock fallback regardless of domain cutover status
  # Overrides per-domain fallback-enabled settings for demo-critical routes only
  protected-routes:
    - /api/v1/dashboard/stats
    - /api/v1/service-users
    - /api/v1/service-users/**
    - /api/v1/care-plans
    - /api/v1/care-plans/**
    - /api/v1/assessments
    - /api/v1/visits
    - /api/v1/incidents
    - /api/v1/notifications/inbox
    - /api/v1/notifications/inbox/unread-count
```

**BFF routing logic (pseudo-code):**

```java
// In BFF route handler
if (demoMode && demoProtectedRoutes.matches(request.getPath())) {
    return mockFallback.forward(request);  // always use mock for demo routes in DEMO_MODE
}
// Otherwise: normal real-service routing
return realService.forward(request);
```

#### 48-Hour Stability Rule

For demo-critical routes, do not disable the mock fallback until the real service has been stable for 48 hours with zero 5xx errors. Evidence required: Azure Application Insights query showing error rate = 0 for 48h window.

```bash
# Check 48h stability for visits domain
az monitor app-insights query \
  --app <app-insights-resource> \
  --analytics-query "
    requests
    | where timestamp > ago(48h)
    | where url contains '/api/v1/visits'
    | where resultCode startswith '5'
    | count
  "
# Expected output: count = 0
```

#### Demo Runbook

Before any investor demo or client presentation:
1. Set `DEMO_MODE=true` in BFF Container App environment (Azure Portal or `az containerapp update`)
2. Verify dashboard loads with realistic mock data
3. Do NOT disable `DEMO_MODE` until demo is complete
4. After demo: reset `DEMO_MODE=false` to resume real-service routing

---

## DA-1. Endpoint Priority Matrix

All 204 addressable mock endpoints classified by migration readiness. Wildcards and catch-alls excluded.

**Classification key:**
- **REAL_READY** — Spring Boot service has the endpoint AND BFF controller is wired
- **NEEDS_WIRING** — Service has the endpoint but BFF controller is missing or incomplete
- **MOCK_ONLY** — No real service implementation exists; keep mock fallback
- **SCOPE_OUT** — Feature is not needed for MVP; disable after migration without replacement

### Visits (14 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/visits` | NEEDS_WIRING | visits-service `/visits` exists; BFF controller missing |
| `POST /api/v1/visits` | NEEDS_WIRING | visits-service `/visits` POST exists; BFF controller missing |
| `GET /api/v1/visits/:id` | NEEDS_WIRING | visits-service `/visits/{visitId}` exists |
| `PATCH /api/v1/visits/:id` | NEEDS_WIRING | visits-service PATCH exists |
| `DELETE /api/v1/visits/:id` | NEEDS_WIRING | visits-service DELETE exists |
| `POST /api/v1/visits/:id/check-in` | NEEDS_WIRING | visits-service check-in exists |
| `POST /api/v1/visits/:id/check-out` | NEEDS_WIRING | visits-service check-out exists |
| `GET /api/v1/visits/:id/emar` | NEEDS_WIRING | visits-service eMAR exists |
| `GET /api/v1/visits/:id/tasks` | NEEDS_WIRING | visits-service tasks list exists |
| `GET /api/v1/visits/today` | NEEDS_WIRING | visits-service `/visits/today` defined |
| `POST /api/v1/visits/publish` | MOCK_ONLY | No publish concept in visits-service OpenAPI |
| `POST /api/v1/visits/:id/running-late` | MOCK_ONLY | No equivalent in visits-service spec |
| `GET /api/v1/visits/:id/pre-visit-brief` | MOCK_ONLY | Aggregated BFF feature, not a service endpoint |
| `POST /api/v1/visits/:id/travel-record` | MOCK_ONLY | Tracking endpoint in visits-service is different shape |

### Scheduling (8 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/schedule-blocks` | NEEDS_WIRING | Maps to `/schedule-events` in scheduling-service; CalendarController exists but not wired for this alias |
| `POST /api/v1/schedule-blocks` | NEEDS_WIRING | Maps to POST `/schedule-events` |
| `GET /api/v1/schedule-blocks/:id` | NEEDS_WIRING | Maps to `/schedule-events/{eventId}` |
| `PATCH /api/v1/schedule-blocks/:id` | NEEDS_WIRING | Maps to PUT `/schedule-events/{eventId}` |
| `DELETE /api/v1/schedule-blocks/:id` | NEEDS_WIRING | Maps to DELETE `/schedule-events/{eventId}` |
| `GET /api/v1/completed-events` | NEEDS_WIRING | Maps to `/appointments` with `status=completed` filter |
| `POST /api/v1/completed-events/:id/confirm` | NEEDS_WIRING | Maps to POST `/appointments/{id}/cancel` pattern (status update) |
| `POST /api/v1/completed-events/bulk-confirm` | MOCK_ONLY | No bulk-confirm operation in scheduling-service spec |

### Assessment (12 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/assessments` | REAL_READY | BffAssessmentController wired |
| `GET /api/v1/assessments/:id` | REAL_READY | BffAssessmentController wired |
| `POST /api/v1/assessments` | REAL_READY | BffAssessmentController wired |
| `PUT /api/v1/assessments/:id` | REAL_READY | BffAssessmentController wired |
| `PATCH /api/v1/assessments/:id` | REAL_READY | BffAssessmentController wired |
| `POST /api/v1/assessments/:id/complete` | REAL_READY | Maps to assessment submit endpoint |
| `GET /api/v1/assessments/:id/body-map` | NEEDS_WIRING | assessment-service has body-map section; BFF wiring unconfirmed |
| `PUT /api/v1/assessments/:id/body-map` | NEEDS_WIRING | as above |
| `GET /api/v1/assessments/:id/scores` | MOCK_ONLY | No scoring endpoint in assessment-service spec; maps to risk-section concept |
| `GET /api/v1/assessments/:id/scores/:tool` | MOCK_ONLY | No tool-specific scores in spec |
| `POST /api/v1/assessments/:id/scores/:tool` | MOCK_ONLY | No tool-specific scores in spec |
| `GET /api/v1/intake` | REAL_READY | assessment-service `/api/v1/intake` matches |

### Care Plans (14 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/care-plans` | REAL_READY | CarePlanController wired |
| `GET /api/v1/care-plans/:id` | REAL_READY | CarePlanController wired |
| `POST /api/v1/care-plans` | REAL_READY | CarePlanController wired |
| `PUT /api/v1/care-plans/:id` | REAL_READY | CarePlanController wired |
| `DELETE /api/v1/care-plans/:id` | REAL_READY | CarePlanController wired |
| `POST /api/v1/care-plans/:id/publish` | REAL_READY | careplan-service publish endpoint exists |
| `GET /api/v1/care-plans/:id/versions` | REAL_READY | careplan-service versions endpoint exists |
| `GET /api/v1/care-plans/review-due` | MOCK_ONLY | No review-due filter in careplan-service spec |
| `POST /api/v1/care-plans/:id/submit-for-approval` | MOCK_ONLY | No submit-for-approval step in real spec |
| `GET /api/v1/care-plans/:id/tasks` | MOCK_ONLY | Task management not in careplan-service OpenAPI |
| `GET /api/v1/care-plans/:id/risk-alerts` | MOCK_ONLY | No risk-alerts endpoint in careplan-service |
| `GET /api/v1/care-plans/:id/reviews` | MOCK_ONLY | No reviews endpoint in careplan-service |
| `POST /api/v1/care-plans/:id/consent` | MOCK_ONLY | No consent endpoint in careplan-service |
| `GET /api/v1/master-task-types` | REAL_READY | careplan-service has master-task-types |

### HR (18 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /employees/list` | REAL_READY | EmployeeController wired (legacy path) |
| `GET /employees/:id` | REAL_READY | EmployeeController wired |
| `POST /employees` | REAL_READY | EmployeeController wired |
| `PATCH /employees/:id` | REAL_READY | EmployeeController wired |
| `DELETE /employees/:id` | REAL_READY | EmployeeController wired |
| `GET /api/v1/leaves` | REAL_READY | hr-service leaves endpoint exists; BFF wired |
| `POST /api/v1/leaves` | REAL_READY | hr-service leaves POST exists |
| `PATCH /api/v1/leaves/:id` | REAL_READY | hr-service leaves PATCH exists |
| `DELETE /api/v1/leaves/:id` | REAL_READY | hr-service leaves DELETE exists |
| `GET /api/v1/hr/supervisions` | NEEDS_WIRING | hr-service has supervisions; BFF sub-route not wired |
| `GET /api/v1/hr/supervision-schedule` | NEEDS_WIRING | hr-service has supervision data; path mapping needed |
| `GET /api/v1/hr/absences` | NEEDS_WIRING | hr-service has absence/leave data; path alias needed |
| `GET /api/v1/hr/training` | NEEDS_WIRING | hr-service has training; BFF route not wired |
| `GET /api/v1/training/courses` | NEEDS_WIRING | hr-service training module; BFF path different |
| `GET /api/v1/hr/dbs` | MOCK_ONLY | DBS checks not in hr-service OpenAPI as named endpoint |
| `GET /api/v1/hr/bradford` | MOCK_ONLY | Calculated aggregate; no endpoint in hr-service |
| `GET /api/v1/hr/care-certificate` | MOCK_ONLY | Not in hr-service OpenAPI |
| `GET /api/v1/hr/cqc-inspection-pack` | SCOPE_OUT | Complex regulatory aggregate; not MVP |

### Finance (20 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/finance/invoices` | REAL_READY | FinanceBffController wired |
| `GET /api/v1/finance/invoices/:id` | REAL_READY | FinanceBffController wired |
| `POST /api/v1/finance/invoices` | REAL_READY | FinanceBffController wired |
| `PUT /api/v1/finance/invoices/:id` | REAL_READY | FinanceBffController wired |
| `GET /api/v1/finance/invoice-groups` | REAL_READY | finance-service invoice-groups; BFF wired |
| `POST /api/v1/finance/invoice-groups` | REAL_READY | finance-service wired |
| `GET /api/v1/finance/payment-groups` | REAL_READY | finance-service wired |
| `POST /api/v1/finance/payment-groups` | REAL_READY | finance-service wired |
| `GET /api/v1/finance/timesheets` | NEEDS_WIRING | finance-service has timesheets; BFF sub-route unconfirmed |
| `GET /api/v1/finance/dashboard` | NEEDS_WIRING | finance-service dashboard exists; aggregation needed |
| `GET /api/v1/finance/rate-cards` | MOCK_ONLY | Not in finance-service OpenAPI |
| `GET /api/v1/finance/funders` | MOCK_ONLY | Not in finance-service OpenAPI |
| `GET /api/v1/finance/payroll/preview` | MOCK_ONLY | Payroll is separate domain; not in MVP |
| `POST /api/v1/finance/payroll/approve` | MOCK_ONLY | Payroll not in MVP |
| `GET /api/v1/finance/payroll/export` | SCOPE_OUT | Out of MVP scope |
| `GET /api/v1/finance/aged-debtors` | MOCK_ONLY | Calculated from invoices; no dedicated endpoint |
| `GET /api/v1/finance/nmw-compliance` | SCOPE_OUT | Compliance report; out of MVP scope |
| `GET /api/v1/finance/month-end-status` | MOCK_ONLY | No equivalent in finance-service spec |
| `GET /api/v1/finance/bank-holidays` | MOCK_ONLY | Static data; serve from BFF config |
| `GET /api/v1/invoices` | REAL_READY | Legacy path; maps to finance-service invoices |

### Incidents (8 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/incidents` | NEEDS_WIRING | Real service has typed routes (`/accidents`, `/safeguarding`, `/complaints`, `/hazards`); BFF must aggregate or route by type |
| `GET /api/v1/incidents/:id` | NEEDS_WIRING | BFF must determine type and proxy to correct sub-service |
| `POST /api/v1/incidents` | NEEDS_WIRING | BFF must route by `type` field to correct incident sub-resource |
| `PATCH /api/v1/incidents/:id` | NEEDS_WIRING | Same routing requirement |
| `DELETE /api/v1/incidents/:id` | NEEDS_WIRING | Same routing requirement |
| `GET /api/v1/incidents/summary` | REAL_READY | incidents-service `/summary` endpoint exists; IncidentsController wired |
| `POST /api/v1/incidents/:id/actions` | NEEDS_WIRING | incidents-service log-actions endpoint exists; BFF wiring unconfirmed |
| `GET /api/v1/incidents/export` | SCOPE_OUT | Export for each sub-type exists in spec; aggregate export not MVP |

### Notifications (10 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/notifications/inbox` | NEEDS_WIRING | notification-service inbox exists; BFF controller missing entirely |
| `GET /api/v1/notifications/inbox/unread-count` | NEEDS_WIRING | notification-service unread-count exists; BFF missing |
| `PATCH /api/v1/notifications/inbox/:id/read` | NEEDS_WIRING | notification-service mark-read exists; BFF missing |
| `PATCH /api/v1/notifications/inbox/read-all` | NEEDS_WIRING | notification-service mark-all-read exists; BFF missing |
| `GET /api/v1/notifications/preferences` | NEEDS_WIRING | notification-service preferences exists; BFF missing |
| `POST /api/v1/notifications/preferences` | NEEDS_WIRING | notification-service preferences POST exists; BFF missing |
| `PATCH /api/v1/notifications/preferences/quiet-hours` | NEEDS_WIRING | notification-service; BFF missing |
| `POST /api/v1/notifications/devices` | NEEDS_WIRING | notification-service device registration; BFF missing |
| `POST /api/v1/notifications/push` | NEEDS_WIRING | notification-service send endpoint; BFF missing |
| `GET /api/v1/notifications/analytics` | MOCK_ONLY | Not in notification-service spec |

### Identity / Users / Orgs / Service Users (14 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/service-users` | REAL_READY | ServiceUsersController wired; identity-service backs it |
| `GET /api/v1/service-users/:id` | REAL_READY | ServiceUsersController wired |
| `POST /api/v1/service-users` | REAL_READY | ServiceUsersController wired |
| `PATCH /api/v1/service-users/:id` | REAL_READY | ServiceUsersController wired |
| `DELETE /api/v1/service-users/:id` | REAL_READY | ServiceUsersController wired |
| `GET /api/v1/users` | NEEDS_WIRING | identity-service users endpoint exists; BFF route may not be exposed |
| `GET /api/v1/organizations` | NEEDS_WIRING | identity-service orgs endpoint exists; BFF route unconfirmed |
| `GET /api/v1/organizations/:id` | NEEDS_WIRING | identity-service; BFF unconfirmed |
| `GET /api/v1/settings` | NEEDS_WIRING | identity-service org settings; BFF route unconfirmed |
| `PATCH /api/v1/settings` | NEEDS_WIRING | identity-service org settings PATCH; BFF unconfirmed |
| `GET /api/v1/carers` | NEEDS_WIRING | Maps to identity-service users with carer role filter |
| `POST /api/v1/carers/by-skills` | MOCK_ONLY | No skills-based search in identity-service spec |
| `GET /api/v1/skills` | MOCK_ONLY | No dedicated skills list endpoint in identity-service |
| `GET /api/v1/rostering-settings` | MOCK_ONLY | Not in identity-service spec |

### Family Portal (16 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| ALL `GET /api/v1/family/*` | MOCK_ONLY | No FamilyPortalService exists. See DA-8 ADR below. |

### Misc / EVV / Dashboard / Compliance (24 endpoints)

| Mock Endpoint | Classification | Notes |
|---|---|---|
| `GET /api/v1/dashboard/stats` | NEEDS_WIRING | Aggregate from multiple services; BFF orchestration needed |
| `GET /api/v1/cqc-readiness` | MOCK_ONLY | Aggregate; not in any service spec |
| `GET /api/v1/evv/compliance` | NEEDS_WIRING | Maps to visits-service tracking data; BFF aggregation needed |
| `GET /api/v1/evv/records` | NEEDS_WIRING | Maps to visits-service location tracking history |
| `GET /api/v1/evv/records/:id` | NEEDS_WIRING | Maps to visits-service tracking detail |
| `GET /api/v1/audit-logs` | MOCK_ONLY | No audit service in current architecture |
| `GET /api/v1/insurance/claims` | SCOPE_OUT | Not in architecture; park for post-MVP |
| `GET /api/v1/insurance/payers` | SCOPE_OUT | Not in architecture; park for post-MVP |
| `POST /api/v1/handoff/notes` | NEEDS_WIRING | Maps to visits-service care notes with type=handover |
| `GET /api/v1/handoff/history` | NEEDS_WIRING | Maps to visits-service care notes filtered by type |
| `POST /api/v1/handoff/notes/:id/acknowledge` | MOCK_ONLY | No acknowledgement concept in visits-service notes |
| `GET /api/v1/observations` | NEEDS_WIRING | Maps to assessment-service or visits-service observations |
| `GET /api/v1/supervisor/dashboard` | MOCK_ONLY | Aggregate view; BFF composition needed post-MVP |
| `GET /api/v1/supervisor/dashboard/carer-locations` | NEEDS_WIRING | Maps to visits-service `/tracking/locations/current` |
| `POST /api/v1/alerts/:id/acknowledge` | MOCK_ONLY | No alert acknowledgement service |
| `GET /api/v1/clients/:id/outcomes` | MOCK_ONLY | No outcomes service |
| `GET /api/v1/clients/:id/care-summary` | MOCK_ONLY | BFF aggregate; not MVP priority |
| `GET /api/v1/training/compliance` | NEEDS_WIRING | Maps to hr-service training compliance data |
| `GET /api/v1/care-alerts/:id/acknowledge` | MOCK_ONLY | No care-alerts service |
| ALL others (wildcards) | SCOPE_OUT | Catch-all routes; decommission with mock |

### Priority Matrix Summary

| Classification | Count | Action |
|---|---|---|
| REAL_READY | ~47 | Verify in Phase 3; disable mock fallback per-route |
| NEEDS_WIRING | ~68 | Phase 1 + Phase 3 BFF work |
| MOCK_ONLY | ~63 | Keep mock fallback; build real service post-MVP |
| SCOPE_OUT | ~26 | Disable with no replacement; document as out-of-scope |

---

## DA-2. Seed Data Verification (Phase 0.4)

Add this phase between Phase 0.1 (seed load) and Phase 0.2 (container health check).

### Phase 0.4 — Verify Seed Data Loaded Correctly

After running seed scripts via Azure Cloud Shell, verify row counts before proceeding to Phase 1.

**Expected minimum row counts per database:**

| Database | Table | Minimum Rows | Purpose |
|---|---|---|---|
| `identity_db` | `organizations` | 1 | Test org |
| `identity_db` | `users` | 5 | 1 admin, 1 care-manager, 3 carers |
| `identity_db` | `service_users` | 3 | Test clients |
| `identity_db` | `roles` | 12 | Pre-seeded system roles |
| `assessment_db` | `assessment_sections` | 7 | ASSESSMENT_SECTION_DEFS equivalent |
| `assessment_db` | `risk_sections` | 6 | RISK_SECTION_DEFS equivalent |
| `careplan_db` | `master_task_types` | 10 | Task type catalogue |
| `careplan_db` | `master_visit_types` | 4 | Visit type catalogue |
| `visits_db` | `visit_statuses` | 7 | scheduled, late, in_progress, completed, cancelled, missed, flagged |
| `hr_db` | `leave_types` | 5 | Annual, sick, maternity, etc. |
| `hr_db` | `training_categories` | 4 | Mandatory, CPD, compliance, specialist |
| `scheduling_db` | `appointment_types` | 4 | care_visit, assessment_visit, medication_only, welfare_check |
| `finance_db` | `invoice_statuses` | 5 | Draft, issued, paid, overdue, cancelled |
| `incidents_db` | `incident_type_config` | 4 | accidents, safeguarding, complaints, hazards |
| `notification_db` | `notification_templates` | 5 | Core template types |

**Verification queries — run in Azure Cloud Shell after seed:**

```sql
-- identity_db
SELECT 'organizations' as tbl, count(*) FROM organizations
UNION ALL SELECT 'users', count(*) FROM users
UNION ALL SELECT 'service_users', count(*) FROM service_users
UNION ALL SELECT 'roles', count(*) FROM roles;

-- assessment_db
SELECT 'assessment_sections', count(*) FROM assessment_sections
UNION ALL SELECT 'risk_sections', count(*) FROM risk_sections;

-- careplan_db
SELECT 'master_task_types', count(*) FROM master_task_types
UNION ALL SELECT 'master_visit_types', count(*) FROM master_visit_types;

-- visits_db
SELECT 'visit_task_types', count(*) FROM visit_task_types;

-- incidents_db
SELECT table_name, 0 as dummy FROM information_schema.tables
WHERE table_schema = 'public'; -- verify tables exist

-- finance_db
SELECT 'invoice_line_items_config', count(*) FROM invoice_statuses;
```

**Verify-seeds script spec:**

Create `/Users/makinja/projects/client/lumiscare/scripts/verify-seeds.sh`:

```bash
#!/bin/bash
# verify-seeds.sh — run from Azure Cloud Shell after seed load
# Usage: bash verify-seeds.sh <pg-host> <admin-user>

PG_HOST="$1"
PG_USER="$2"
FAIL=0

check() {
  local DB=$1 TBL=$2 MIN=$3
  COUNT=$(psql "host=$PG_HOST user=$PG_USER dbname=$DB sslmode=require" \
    -tAc "SELECT count(*) FROM $TBL 2>/dev/null || echo 0")
  if [ "$COUNT" -lt "$MIN" ]; then
    echo "FAIL: $DB.$TBL has $COUNT rows (expected >= $MIN)"
    FAIL=1
  else
    echo "PASS: $DB.$TBL = $COUNT rows"
  fi
}

check identity_db organizations 1
check identity_db users 5
check identity_db service_users 3
check assessment_db assessment_sections 7
check careplan_db master_task_types 10
check visits_db visit_task_types 4
check finance_db invoice_statuses 3
check incidents_db incident_categories 4

[ $FAIL -eq 0 ] && echo "ALL SEED CHECKS PASSED" || { echo "SEED VERIFICATION FAILED — do not proceed to Phase 1"; exit 1; }
```

Seed verification must pass before Phase 1 work begins. If any check fails, re-run the corresponding seed script.

---

## DA-3. Shape Mismatch Matrix

Critical field-level differences between mock API response shapes and real OpenAPI-specified shapes. BFF adapter layer must translate these on cutover.

### Visits — GET /api/v1/visits

| Field | Mock Shape | Real Service Shape (visits-service OpenAPI) | BFF Action |
|---|---|---|---|
| List wrapper | `[...visits]` (bare array) | `{ "visits": [...], "pageInfo": { "total": N, "page": 0, "size": 20 } }` | BFF must unwrap `visits` array for frontend; frontend expects array |
| `id` | `"visit-0001"` (string, dash-prefixed) | UUID format `"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"` | ID format will change; frontend must handle UUID |
| `clientId` | `"su-001"` (string, dash-prefixed) | `serviceUserId` (UUID) | Field rename: `clientId` → `serviceUserId` |
| `clientName` | `"Dorothy Johnson"` (string in visit object) | Not in visit object — join required from identity-service | BFF must enrich with identity-service lookup or accept that frontend needs separate call |
| `status` | `"in-progress"` (kebab-case) | `"in_progress"` (snake_case enum: scheduled, late, in_progress, completed, cancelled, missed, flagged) | BFF must normalize status values |
| `type` | `"personal-care"` (kebab-case, free string) | `appointmentType` (enum: care_visit, assessment_visit, medication_only, welfare_check) | Field rename + value normalization |
| `gpsCheckIn` | `{ lat: 51.5074, lng: -0.1278 }` | `{ "latitude": 51.5074, "longitude": -0.1278, "accuracy": 5.0, "timestamp": "..." }` (GpsLocation schema) | Field rename: `lat`→`latitude`, `lng`→`longitude`; add required fields |
| `careNotes` | String field on visit object | Separate endpoint `GET /visits/{visitId}/notes` → `[{ "noteType": "...", "content": "..." }]` | Notes must be fetched separately; BFF may aggregate or frontend must call separately |
| `actualCheckIn` / `actualCheckOut` | ISO string on visit object | Nested in check-in/check-out response objects, not returned on visit GET | BFF must compose from visit + lifecycle data |

### Scheduling — GET /api/v1/schedule-blocks

| Field | Mock Shape | Real Service Shape (scheduling-service OpenAPI) | BFF Action |
|---|---|---|---|
| Resource name | `schedule-blocks` (array of blocks) | `schedule-events` (ScheduleEvent objects) | Path alias handled by BFF |
| `type` | `"team-meeting"`, `"training"`, `"supervision"`, `"appraisal"` (kebab-case) | `ScheduleEventType` enum: `meeting`, `training`, `supervision`, `appraisal` (no dash, no team- prefix) | BFF must normalize `team-meeting` → `meeting` |
| `duration` | Integer (minutes) | Not a top-level field; calculated from `startDateTime` and `endDateTime` | BFF may compute duration from datetime diff |
| `startTime` / `endTime` | ISO string | `startDateTime` / `endDateTime` (ISO 8601 with timezone) | Field rename in BFF adapter |
| List wrapper | Bare array | Paginated: `{ "content": [...], "page": 0, "size": 20, "totalElements": N, "totalPages": N }` | BFF extracts `content` or adapts to paginated format |

### Incidents — GET /api/v1/incidents

| Field | Mock Shape | Real Service Shape (incidents-service OpenAPI) | BFF Action |
|---|---|---|---|
| List wrapper | `{ "incidents": [...], "total": N }` | Per type: `{ "content": [...], "page": 0, "totalElements": N, "totalPages": N }` | BFF must aggregate responses from `/accidents`, `/safeguarding`, `/complaints`, `/hazards` |
| `id` | `"inc-001"` (string) | UUID (auto-generated) | ID format change; BFF cannot map old IDs |
| `type` | `"Accident"`, `"Medication_Error"`, `"Safeguarding"` | Separate resources by route; type determined by endpoint (`/accidents` vs `/safeguarding`) | BFF adds `type` field when aggregating |
| `incident_number` | `"INC-2024-001"` | `referenceNumber` field: `"ACC-2026-00001"` (typed prefix format) | Field rename + prefix format changes |
| `severity` | `"High"`, `"Medium"`, `"Critical"` (title-case) | `SeverityLevel` enum: `HIGH`, `MEDIUM`, `LOW`, `CRITICAL` (UPPER_CASE) | BFF normalizes to uppercase |
| `status` | `"Resolved"`, `"Under_Review"` (mixed case) | `LogStatus` enum: `IDENTIFIED`, `RAISED`, `IN_PROGRESS`, `RESOLVED` etc. (UPPER_CASE) | BFF normalizes case; `Under_Review` → `IN_PROGRESS` |
| `dateAndTime` | ISO string | `incidentDatetime` (ISO 8601 with Z suffix) | Field rename in BFF |
| `actionsTaken` | Array on the incident object | Separate endpoint: `GET /logs/{logType}/{logId}/actions` | Actions must be fetched separately |
| `has_investigation` | Boolean | Not in incidents-service OpenAPI — derived from status | BFF computes from status |

### Care Plans — GET /api/v1/care-plans

| Field | Mock Shape | Real Service Shape (careplan-service OpenAPI) | BFF Action |
|---|---|---|---|
| `clientId` | `"su-001"` | `clientId` (UUID) | Same field name; ID format changes to UUID |
| `status` | `"active"`, `"draft"`, `"under_review"` | `CarePlanStatus` enum: `draft`, `ai_generated`, `under_review`, `approved`, `active`, `suspended`, `completed`, `cancelled` | Values align but `ai_generated` is new — frontend must handle |
| Tasks nested | `tasks: [...]` array on care plan | Tasks NOT on care plan object in careplan-service spec; separate route `/care-plan/{id}/tasks` | BFF may aggregate or frontend must call separately |
| Visit structure | `visits: [...]` array | `CarePlanDetailsResponse` has `Visit[]` with nested `TaskCategory[]` and `CarePlanTask[]` | Nested structure differs significantly from mock flat array |

### Assessment — GET /api/v1/assessments/:id

| Field | Mock Shape | Real Service Shape (assessment-service OpenAPI) | BFF Action |
|---|---|---|---|
| `id` vs `assessmentId` | Both `id` and `assessmentId` present (legacy dual fields) | `assessmentId` only (UUID) | Remove dual field; use `assessmentId` consistently |
| `patientId` | UUID string | `patientId` (UUID) — matches | No change needed |
| `completionStatus` | `"IN_PROGRESS"`, `"COMPLETED"` | `completionStatus` enum per spec | Matches — no change |
| `lastUpdated` | Concatenated date + literal `T10:30:00Z` suffix | ISO 8601 datetime from service | Remove hardcoded suffix hack |
| `overview` | Nested object with `riskLevel`, `allergies`, etc. | Not a direct response field — derived from assessment sections | BFF must compose overview from section data |

---

## DA-4. Schema Validation (Post-Migration Audit Step)

Add as Phase 3.5, after all per-domain cutovers complete but before Phase 4 decommission.

### Phase 3.5 — Schema Audit

**Objective:** Machine-verify that every active BFF response matches its OpenAPI contract. No visual inspection.

**Step 1 — Generate JSON Schemas from OpenAPI specs:**
```bash
# Using openapi-schema-validator or ajv-openapi
npx @apidevtools/swagger-parser validate \
  /Users/makinja/projects/client/lumiscare/openapi-specs/visits-open-api/open-api.yaml
# Repeat for each service spec
```

**Step 2 — Record BFF responses against schema:**

For each REAL_READY or newly NEEDS_WIRING domain, run automated contract tests:
```bash
# Install dredd or schemathesis
pip install schemathesis

# Run against BFF with real backend:
schemathesis run \
  /Users/makinja/projects/client/lumiscare/openapi-specs/visits-open-api/open-api.yaml \
  --base-url https://<web-bff-fqdn>.azurecontainerapps.io \
  --auth-type bearer --auth "$JWT_TOKEN" \
  --header "X-VCU-Organization-Id: $ORG_ID"
```

**Step 3 — Adapter correction loop:**

For each schema violation found:
1. Identify if the mismatch is in the BFF adapter or the service response
2. Fix the BFF adapter layer (preferred) or raise a service ticket
3. Re-run schemathesis until zero schema violations remain

**Step 4 — Acceptance criteria:**

- Zero schemathesis ERRORS (hard failures) for all REAL_READY routes
- WARNINGS documented and accepted or fixed before Phase 4
- Schema audit report saved to `/Users/makinja/projects/client/lumiscare/docs/schema-audit-report.md`

**Pass criteria for Phase 4 entry:** Schema audit exits with code 0 for all 10 domains.

---

## DA-5. Rollback Test (Phase 2.4)

Add between Phase 2.3 (keep mock running) and Phase 3 (per-domain cutover).

### Phase 2.4 — Validate Rollback Mechanism Works

Before committing to Phase 3 cutovers, prove the rollback path is functional. This is a one-time gate check.

**2.4.1 Simulate a domain failure:**
```bash
# Disable visits-service in BFF by setting a bad URL (or scale to 0)
az containerapp update \
  --name ca-vcc-dev-visits-service-001 \
  --resource-group rg-vcc-dev-001 \
  --min-replicas 0 --max-replicas 0
```

**2.4.2 Verify BFF fallback activates automatically:**
```bash
# Call a visits endpoint through BFF — should return mock data, not 502
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  https://<web-bff-fqdn>.azurecontainerapps.io/api/v1/visits
# Expected: 200 (served from mock fallback)
```

**2.4.3 Verify fallback data is recognizable (not silent corruption):**
```bash
# Response should include mock data markers (e.g., "visit-0001" style IDs)
curl -s -H "Authorization: Bearer $JWT_TOKEN" \
  https://<web-bff-fqdn>.azurecontainerapps.io/api/v1/visits \
  | jq '.[0].id'
# Expected: "visit-0001" or similar mock ID pattern
```

**2.4.4 Re-enable the service and confirm real data returns:**
```bash
az containerapp update \
  --name ca-vcc-dev-visits-service-001 \
  --resource-group rg-vcc-dev-001 \
  --min-replicas 1 --max-replicas 3

# Wait for healthy status, then re-test
curl -s -H "Authorization: Bearer $JWT_TOKEN" \
  https://<web-bff-fqdn>.azurecontainerapps.io/api/v1/visits \
  | jq '.[0].id'
# Expected: UUID format (real service data)
```

**2.4.5 Document rollback timings:**

| Rollback Type | Expected Time | Acceptable Max |
|---|---|---|
| BFF env var toggle (`MOCK_API_FALLBACK_ENABLED=true`) | < 2 minutes | 5 minutes |
| Per-domain BFF route disable (redeploy BFF) | 3-5 minutes | 10 minutes |
| Frontend env var change (Static Web App) | < 5 minutes | 15 minutes |
| Database restore from Azure backup | 30-60 minutes | 2 hours |

**Gate:** Phase 2.4 must PASS before any Phase 3 cutover proceeds.

---

## DA-6. Phase Timing and Environment Clarity

Which systems run on what at each migration phase, per environment.

### Environment Matrix by Phase

| Phase | Dev | Test | Staging | Production |
|---|---|---|---|---|
| **Current (pre-migration)** | Frontend → Mock API direct | Frontend → Mock API direct | N/A | N/A |
| **Phase 0** (seed + prep) | Mock API still direct; seeds loaded to Azure DBs | No change | No change | No change |
| **Phase 1** (BFF wiring) | BFF development; Mock API still used by frontend | Mock API still used | No change | No change |
| **Phase 2** (frontend switch) | **Frontend → BFF** (BFF → real service OR mock fallback) | Frontend → BFF (all fallback) | BFF deployed, fallback enabled for all routes | Not deployed |
| **Phase 3** (per-domain cutover) | BFF → real services per-domain; fallback only for MOCK_ONLY | BFF + per-domain fallback; real services tested here | BFF + fallback per-domain; each domain promoted after test passes | Not deployed |
| **Phase 4** (decommission) | BFF → real services; fallback disabled; mock stopped | Mock stopped | Mock stopped | Deploy begins post-stability |

### Service URL Routing by Environment

```
Dev:     Frontend → https://<bff-dev>.azurecontainerapps.io → {service}-dev → identity_db_dev
Test:    Frontend → https://<bff-test>.azurecontainerapps.io → {service}-test → identity_db_test
Staging: Frontend → https://<bff-stage>.azurecontainerapps.io → {service}-stage → identity_db_stage
Prod:    Frontend → https://<bff-prod>.azurecontainerapps.io → {service}-prod → identity_db_prod
```

### Fallback Configuration by Environment

| Environment | `MOCK_API_FALLBACK_ENABLED` | `MOCK_API_URL` | Notes |
|---|---|---|---|
| Dev | `true` during Phase 1-3; `false` at Phase 4 | `http://mock-api-dev:3000` | Toggle per domain using route-level flags |
| Test | `true` until domain passes test suite; then `false` | `http://mock-api-test:3000` | Automated test gate controls the toggle |
| Staging | `true` for 48h stability window per domain; then `false` | `http://mock-api-stage:3000` | See DA-9 Demo Protection |
| Production | `false` from day one (start clean) | N/A | No mock in production ever |

---

## DA-7. Evidence Checklist per Domain (Cutover Gate)

For each domain in Phase 3, the following machine-verified criteria must ALL pass before disabling the mock fallback for that domain. Evidence artifacts must be saved.

### Cutover Checklist Template

Replace `{DOMAIN}` with the domain name (e.g., `assessment`, `visits`).

```
[ ] 1. Seed data verified — verify-seeds.sh passes for {DOMAIN}_db
[ ] 2. Service health — GET /actuator/health returns {"status":"UP"} (HTTP 200)
[ ] 3. BFF route active — GET /api/v1/{domain-path} returns HTTP 200 (not 502 or fallback)
[ ] 4. Auth propagation — JWT + org header accepted; 401 returned without token
[ ] 5. Data shape — schemathesis or equivalent schema check passes (zero violations)
[ ] 6. CRUD smoke — POST creates, GET retrieves, PATCH updates, DELETE removes (where applicable)
[ ] 7. Pagination — List endpoint returns paginated wrapper, not raw array
[ ] 8. Tenant isolation — org-A data not visible when calling with org-B token
[ ] 9. Error format — 404 returns standardized error schema, not mock `{ error: "Not found" }`
[ ] 10. Fallback disabled — MOCK_API_FALLBACK_ENABLED=false for this domain; mock NOT serving requests
[ ] 11. Playwright smoke tests — existing frontend test suite passes against BFF (no regressions)
[ ] 12. Rollback verified — fallback re-enable tested and confirmed working (DA-5 gate)
```

**Evidence artifacts per domain:**

| Artifact | Format | Location |
|---|---|---|
| verify-seeds output | `.txt` log | `/docs/migration-evidence/{domain}-seed-verify.txt` |
| schemathesis report | JSON or HTML | `/docs/migration-evidence/{domain}-schema-report.json` |
| Playwright test report | JUnit XML or HTML | CI artifact |
| Cutover timestamp | ISO 8601 string | `/docs/migration-evidence/{domain}-cutover.json` |

**Domain cutover sign-off:** All 12 checks must show machine-generated PASS. No self-certification. DevOps engineer reviews artifact paths before toggling `MOCK_API_FALLBACK_ENABLED=false`.

### Domain Priority and Cutover Order

| Priority | Domain | Blocker For | Estimated Cutover Date (relative) |
|---|---|---|---|
| 1 | Identity | All others — auth depends on it | Week 1 Day 1 |
| 2 | Assessment | Care Plans, clinical workflow | Week 1 Day 3 |
| 3 | Care Plans | Visits task planning | Week 1 Day 5 |
| 4 | Visits | Mobile carers, eMAR | Week 2 Day 1 |
| 5 | Incidents | Safeguarding compliance | Week 2 Day 2 |
| 6 | HR | Staff compliance reporting | Week 2 Day 3 |
| 7 | Scheduling | Calendar view, rostering | Week 2 Day 4 |
| 8 | Notifications | Non-blocking but improves UX | Week 2 Day 5 |
| 9 | Finance | Invoicing, billing | Week 3 Day 1 |
| 10 | Family Portal | MOCK_ONLY — stays on mock | Post-MVP |

---

## DA-8. Family Portal Architecture Decision Record (ADR-001)

**Date:** 2026-03-26
**Status:** ACCEPTED
**Deciders:** Engineering Lead, Product Owner

### Context

The Family Portal frontend (`GET /api/v1/family/*`) has 16 mock endpoints. There is no dedicated `FamilyPortalService` in the current microservice architecture. The endpoints aggregate data from:
- `careplan-service` (care plan view)
- `notification-service` (messages, notifications)
- `identity-service` (family member auth, payments)

No `FamilyPortalController` exists in the Web BFF. Building a full family portal BFF layer during the mock-to-real migration would be a parallel new-feature build, not a migration task.

### Decision

**Family Portal routes are SCOPED OUT of the mock-to-real MVP migration.**

The 16 family portal mock endpoints will remain served by the mock API fallback indefinitely until a dedicated post-MVP sprint is resourced.

### Rationale

1. Zero real service coverage — building from scratch, not migrating
2. Requires new BFF controller + aggregation logic across 3+ services
3. Family Portal is a secondary product — primary migration priority is Back Office and Mobile
4. The mock API fallback handles family portal routes with zero user impact during migration

### Post-MVP Implementation Plan

| Phase | Work | Timeline |
|---|---|---|
| FP-1 | Define `FamilyPortalController` in Web BFF; aggregate careplan + identity endpoints | Sprint after MVP cutover |
| FP-2 | Wire notification-service for family inbox + message threads | FP-1 + 1 sprint |
| FP-3 | Payment initiation (Stripe/payment gateway) — separate investigation | TBD |
| FP-4 | Family Portal authentication (separate Entra B2C tenant for non-staff) | TBD — security review required |

### Consequences

- **Positive:** Migration timeline stays at ~3 weeks; no scope creep
- **Positive:** Family Portal gets proper design attention post-MVP
- **Negative:** Mock data continues serving family portal users during MVP period
- **Accepted risk:** Mock data quality for family portal is sufficient for demo and pilot; real families not on-boarded until FP phases complete

### Mock Fallback Route Config

```yaml
# application.yml — BFF fallback config
mock-api:
  fallback-routes:
    - /api/v1/family/**   # permanent until FP-1 sprint
```

---

## DA-9. Demo Protection Protocol

### Context

During Phase 3 cutovers, demo sessions may be running against staging or dev environments. A failed real service during a demo is unacceptable. This protocol defines demo-critical routes and a `DEMO_MODE` fallback concept.

### Demo-Critical Routes

Routes that, if broken, will visibly fail a standard stakeholder demo:

| Route | Why Critical |
|---|---|
| `GET /api/v1/dashboard/stats` | First screen shown in every demo |
| `GET /api/v1/service-users` | Client list — primary navigation |
| `GET /api/v1/visits` | Visit calendar — core value demo |
| `GET /api/v1/care-plans` | Care plan list — clinical workflow demo |
| `GET /api/v1/assessments` | Assessment list — intake workflow demo |
| `GET /api/v1/notifications/inbox` | Notification bell — real-time demo |
| `GET /api/v1/incidents` | Incident log — compliance demo |
| `GET /api/v1/family/*` | Family portal views — stakeholder demo |

### DEMO_MODE Flag

Add a `DEMO_MODE` environment variable to the BFF:

```yaml
# application.yml
demo:
  mode: ${DEMO_MODE:false}
  protected-routes:
    - /api/v1/dashboard/stats
    - /api/v1/service-users
    - /api/v1/visits
    - /api/v1/care-plans
    - /api/v1/assessments
    - /api/v1/notifications/inbox
    - /api/v1/incidents
    - /api/v1/family/**
```

**When `DEMO_MODE=true`:**
- BFF always serves mock fallback for protected routes, regardless of whether the real service is available
- Real services continue running and accepting non-demo traffic
- A `X-Demo-Mode: true` response header is added to indicate source

**When `DEMO_MODE=false` (default):**
- Normal routing applies; real services used where wired; fallback only on error

### Stability Window Protocol

For each demo-critical domain, the fallback is not disabled until **48 consecutive hours of stability** in the target environment:

```
48h stability check:
[ ] Zero 5xx errors from real service in Azure Monitor
[ ] P95 latency < 500ms for the domain's endpoints
[ ] Zero fallback activations recorded in BFF logs
[ ] At least 1 full demo walkthrough completed successfully against real backend

Only after all 4 checks pass → disable mock fallback for this domain
```

### Pre-Demo Checklist

Before any stakeholder demo during Phase 3:

```bash
# 1. Enable DEMO_MODE in dev/staging BFF
az containerapp update \
  --name ca-vcc-dev-web-bff-001 \
  --resource-group rg-vcc-dev-001 \
  --set-env-vars DEMO_MODE=true

# 2. Verify mock fallback is serving demo-critical routes
curl -s -H "Authorization: Bearer $DEMO_JWT" \
  https://<bff-fqdn>/api/v1/dashboard/stats \
  | jq '.totalClients'
# Expected: non-null value from mock data

# 3. After demo — reset to real-service mode
az containerapp update \
  --name ca-vcc-dev-web-bff-001 \
  --resource-group rg-vcc-dev-001 \
  --set-env-vars DEMO_MODE=false
```

### Mock Data Preservation

The mock API's seed data (`server.js` in-memory arrays) must not be modified or reset during active demo periods. Tag a stable demo snapshot:

```bash
# Before Phase 3 begins, create a demo-data tag in git
cd /Users/makinja/projects/client/lumiscare/mock-api
git tag demo-data-snapshot-$(date +%Y%m%d)
```

If mock data gets corrupted, restore from this tag and restart the mock API container.

# LumisCare Demo P0-Batch Closure — MC #102844

# LumisCare Demo P0-Batch Closure — MC #102844

**Date:** 2026-06-03
**Status:** DONE — 9/9 P0 demo defects fixed, deployed, live-verified.

## Scope
Frontend: GlobalSearch service-users endpoint, Assessments/Notifications/CQC spinner-stuck error handling, Snowit branding removal.
Backend: visits 500, scheduling 500, incidents/assessments/training-matrix 404 routes, intake/careplan detail 500, careplan UUID seed.

## Deploy
- Frontend: commit ceeb636d, live bundle index-BBJqymqC.js (https://app.lumiscare.com/ => HTTP/2 200).
- Backend: web-bff rev 0000036 + visits-service rev 0000010.

## Validation (Proveo retest 2026-06-03T07:08Z — 9/9 PASS)
# Proveo Retest Report — MC #102844
Date: 2026-06-03T07:08:57.476Z
Bundle: index-BBJqymqC.js | BFF: rev 0000036 | Visits: rev 0000010

## Summary: 9/9 fixed

| ID | Name | Verdict | Screenshot | Note |
|----|------|---------|-----------|------|
| R01 | Global search → service-users endpoint | PASS | R01-search-typed.png | searchInput visible: true, result text found: true, service-users called: true, /clients called: false, /clients 404: fa |
| R02 | /assessments page renders | PASS | R02-assessments.png | noSpinner: true, hasSpinner: false, hasContent: true, isLogin: false, url: https://app.lumiscare.com/assessments |
| R03 | /notifications page renders | PASS | R03-notifications.png | noSpinner: true, hasSpinner: false, hasContent: true, isLogin: false |
| R04 | /cqc-readiness page renders | PASS | R04-cqc-readiness.png | noSpinner: true, hasSpinner: false, hasContent: true, is404: false, url: https://app.lumiscare.com/cqc-readiness |
| R05 | Login page footer — no Snowit/v2.0 branding | PASS | R05-login-footer.png | hasSnowit: false, hasV2Footer: false. Bundle scan: 0 hits for 'Powered by Snowit'. v2.0 strings in bundle are MSAL OAuth |
| R06 | /visits page renders (visits-service 200) | PASS | R06-visits.png | noSpinner: true, has500: false, has200: false, hasVisitContent: true, calls: [] |
| R07 | Scheduling page renders | PASS | R07-scheduling.png | landed: false, noSpinner: true, has500: false, has200: false, hasSchedContent: true, url: https://app.lumiscare.com/back |
| R08 | Incidents list renders (GET incidents 200) | PASS | R08-incidents.png | landed: false, noSpinner: true, has404: false, has200: false, hasIncidentContent: true, url: https://app.lumiscare.com/b |
| R09 | Care plan detail — no 500/white-screen | PASS | R09-careplan-detail.png | hasList: true, clickedDetail: true, has500: false, isWhiteScreen: false, hasDetailContent: true, url: https://app.lumisc |

## Failed Items


## Evidence
- Screenshots: /tmp/alai/lumiscare-finish/qa/retest/screenshots/
- Results JSON: /tmp/alai/lumiscare-finish/qa/retest/retest-results.json


## P2P verifier
Company Mesh thread mesh-thr-bc26724d-4348-4bd9-ac5d-fb707942e536 → PASS.

## Evidence
- /tmp/evidence-102844/ (verdict.json, RETEST-REPORT.md, retest-results.json, verification.json)
- Screenshots: /tmp/alai/lumiscare-finish/qa/retest/screenshots/

## Backlog (not in this batch)
P1/P2 empty seeded data, family-portal app, finance dashboard zeros, careplan-service detail seed, intake/assessment-service deeper wiring; source-hygiene merge branch fix/demo-be-p0-102844; security debt MC #102747.