Anaya Care Docs

Health Monitoring

Part of the Anaya Care product wiki. See 00-overview.md.

Purpose

Health Monitoring covers the clinical-tracking surface of the platform: vital-sign metrics derived from care task submissions, wound care ("Healing Ally") with AI photo analysis, DNR / advance-directive acknowledgments by caregivers, and doctors' appointments with automated reminders and AI-generated visit-history summaries. It spans three backend modules:

  • apps/backend/src/health-metrics/ — read-side analytics over vitals task submissions + AI period reports.
  • apps/backend/src/wound-care/ — wound records, assessments, BullMQ-based AI photo analysis.
  • apps/backend/src/clients/ (subset) — DNR acknowledgments, doctors' appointments, appointment monitoring/reminders, appointment-history summaries.

Change-of-condition health observations are a sibling feature documented in 02-assessments.md and are not re-documented here.

Entities & Data Model

Vitals (no dedicated collection)

There is no vitals entity. Health metrics are computed on read from TaskSubmission documents (collection task_submissions, see 04-care-plans-and-tasks.md) whose TaskTemplate.type is one of seven vitals types (apps/backend/src/health-metrics/health-metrics.dto.ts:9-17):

WEIGHT_CHECK, BLOOD_PRESSURE_CHECK, HEART_RATE_CHECK, OXYGEN_SATURATION_CHECK, BLOOD_SUGAR_CHECK, TEMPERATURE_CHECK, RESPIRATORY_RATE_CHECK.

Only submissions with status: 'SUBMITTED' count (apps/backend/src/health-metrics/health-metrics.service.ts:84-93). Values are extracted from the submission's responses[].fieldName (e.g. systolic, diastolic, heartRateBPM, spo2, bloodGlucoseLevel, measuredWeight/weight, bodyTemperature/temperature, respiratoryRate) — health-metrics.service.ts:450-669.

HealthMetricsReport — collection health_metrics_reports

AI-assisted period report drafts (apps/backend/src/health-metrics/entities/health-metrics-report.entity.ts).

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired
generatedByObjectId → Userrequired
reportType'single' | 'comparative'required
status'draft' | 'finalized'default draft
dateRange{start, end} Datescurrent period
previousDateRange{start, end} or nullcomparative mode only
periodReportDataObject (PeriodReportData)full data payload built by period-report-data.service.ts
aiSummaryObject or nullAI-generated PeriodSummary
editedSummaryObject or nullcare-manager edits over the AI summary
recommendationsObject or nullAnayaRecommendations (care plan / hours / family-DPOA notes)

Indexes: {client, createdAt}, {business, createdAt} (lines 95-96).

WoundRecord — collection wound_records

apps/backend/src/wound-care/entities/wound-record.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
clientObjectId → Clientrequired, indexed
createdByObjectId → Userrequired
bodyLocationenum BodyLocation (32 values)required (packages/shared/src/enums/wound-care.ts:57-90)
statusenum WoundStatus: ACTIVE, HEALING, HEALED, CHRONIC, REFERREDdefault ACTIVE
trajectoryenum WoundTrajectory: IMPROVING, STABLE, WORSENING, INDETERMINATEdefault INDETERMINATE
onsetDate, notesDate / Stringoptional
latestAssessmentId, latestAssessmentDateObjectId → WoundAssessment / Datedenormalized, updated atomically on each new assessment
assessmentCountNumberdefault 0, $inc'd per assessment
healedDateDateset by close

Indexes: {business, client, status}, {client, status, latestAssessmentDate}, {business, createdAt} (lines 99-101).

WoundAssessment — collection wound_assessments

apps/backend/src/wound-care/entities/wound-assessment.entity.ts

FieldTypeNotes
business, woundRecord, client, assessedByObjectId refsall required, first three indexed
assessmentDateDaterequired
photosString[]S3 URLs
photoKeysString[]S3 keys; stripped from JSON output by the toJSON transform (line 204) — API responses get fresh signed URLs instead
measurements{length, width, depth, area, unit='cm'}area auto-computed as length × width in a pre-save hook (lines 304-311)
woundBed{tissueType, color, percentGranulation, percentSlough, percentNecrotic}BWAT-aligned enums
exudate{amount, type, color, odor}
edges{description, undermining, tunneling}
surroundingSkin{macerated, erythema, induration, callused, notes}
painLevelNumber 0–10
painNotes, treatmentApplied, clinicalNotesString
aiAnalysisWoundAIAnalysis subdocstatus (PENDING/PROCESSING/COMPLETED/FAILED), measurements, clinicalDescription, tissueClassification, recommendations[], worseningIndicators[], comparisonWithPrevious, confidence, analyzedAt, errorMessage
bwatScoreNumberexists in schema but never written by any code path (see Gaps)
statusenum WoundAssessmentStatus: PENDING_ANALYSIS, ANALYZING, COMPLETED, ANALYSIS_FAILEDdefault COMPLETED

DnrAcknowledgment — collection dnr_acknowledgments

apps/backend/src/clients/entities/dnr-acknowledgment.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
caregiverObjectId → Userrequired
clientObjectId → Clientrequired
acknowledgedAtDatedefault now
dnrDocumentUpdatedAtDaterequired — uploadedAt of the newest DNR/AD document at acknowledgment time; used for staleness detection

Unique index on {caregiver, client} — one active acknowledgment per caregiver-client pair (lines 54-57); re-acknowledgment upserts the same document.

ClientDoctorsAppointment — collection client_doctors_appointments

apps/backend/src/clients/entities/client-doctors-appointment.entity.ts

FieldTypeNotes
businessObjectId → Businessrequired, indexed
client, doctor, authorObjectId refs (Client / User / User)doctor is a MedicalProfessional user
startDateDaterequired
endDateDateoptional
timezoneStringrequired
travelTime{hours, minutes}required; applied symmetrically before and after the appointment when computing the blocked window (client-doctors-appointments.service.ts:1086-1098)
transportationenum TransportationType or null
alertTimingsNumber[] (minutes before start)drives the reminder cron; labels for 1440/240/120/60/30/15/10/5 in appointment.constants.ts:1-10
isRecurring, recurrenceIdBoolean / String (UUID)recurrence is materialized into individual documents sharing a recurrenceId
groupIdStringdeclared in schema; never written by service code
locationAddress subdoc
purpose, titleStringtitle is AI-generated from purpose, or "Dr. X - <date>" fallback (service lines 166-187)
status'scheduled' | 'completed' | 'cancelled' | 'rescheduled'default scheduled (plain strings, not a shared enum)
isRemindedBooleandefault false; never written anywhere
filesObjectId[] → LockCarepre-visit documents
completedsubdocdiagnosis, prescription, notes, completedBy, completedAt, files[], medications[] (AppointmentMedicationEntry), labResults, followUpInstructions, referrals, nextAppointmentDate
cancelledsubdocreason, cancelledBy, cancelledAt
rescheduledsubdocnewStartDate, newEndDate, rescheduledBy

AppointmentHistorySummary — collection appointment_history_summaries

apps/backend/src/clients/entities/appointment-history-summary.entity.ts

FieldTypeNotes
business, client, generatedByObjectId refsrequired
upcomingAppointmentIdObjectId → ClientDoctorsAppointmentoptional context for the summary
markdownSummaryStringrequired, AI-generated markdown
appointmentsIncludedObjectId[]the completed appointments fed to the LLM
generatedAtDaterequired

Workflows & State Machines

Vitals recording & analytics

  1. Recording happens through the care-task system, not this module: care providers submit vitals task templates (e.g. blood-pressure check) during shifts; the submission's field responses carry the readings (see 04-care-plans-and-tasks.md).
  2. Read APIs under clients/:clientId/health-metrics (apps/backend/src/health-metrics/health-metrics.controller.ts):
    • GET / — raw readings per metric for a date range, with per-reading isAbnormal + warnings, summary averages and trends.
    • GET /recharts — same data flattened into chart points keyed by hour/day/week/month buckets, plus the threshold table (health-metrics.service.ts:182-274, 1022-1046).
    • GET /latest — most recent submission per metric type (getLatestVitals, lines 276-329).
    • GET /trends?metric=... — single-metric trend; default window derived from period (24h / 30d / 12w / 12m, lines 1048-1070). Trend = compare first-half vs second-half averages; >±5% change ⇒ increasing/decreasing, else stable (lines 973-989).
    • GET /abnormal — abnormal readings with severity low|high|critical (lines 356-448).
  3. Abnormality thresholds are hardcoded (health-metrics.service.ts:40-51): BP systolic 90/140/180, diastolic 60/90/110; HR 50/100/120; SpO2 <95 low, ≤90 critical; glucose 70/140/200 (plus fasting >100 rule, line 750); weight 90/300/400; temperature 96.8/99.5/103 °F (Celsius converted, lines 800-808); respiratory rate 12/20/30.

Period reports (draft → finalized)

apps/backend/src/health-metrics/health-metrics-export.service.ts:

  1. POST reports/generate (permission HEALTH_METRICS_MANAGE) builds a single or comparative data payload via PeriodReportDataService (vitals + behavioral + care-delivery data, period-report-data.service.ts), then calls Anthropic (generateText, structured output) for a summary + recommendations, and saves a draft report (lines 52-103). AI failure is caught and logged — the draft is still produced (lines 350-353).
  2. PATCH reports/:id merges care-manager edits into editedSummary / recommendations; rejected if finalized (lines 109-143).
  3. POST reports/:id/finalize flips draft → finalized, idempotence guarded (lines 149-165). No un-finalize path exists.
  4. GET reports/:id/export renders a PDF, with edits merged over the AI summary (lines 171-203).

Wound care documentation + AI analysis

Record lifecycle (apps/backend/src/wound-care/wound-care.service.ts):

  • POST clients/:clientId/wound-records creates an ACTIVE record (client must belong to the caller's business, lines 77-81) and emits a platform-log event (lines 94-104).
  • PATCH :id can set any WoundStatus via DTO (dto/update-wound-record.dto.ts:6-10) — no transition rules are enforced.
  • PATCH :id/close sets status = HEALED + healedDate = now regardless of prior status (lines 192-220).

Assessment creation (createAssessment, lines 224-315):

  1. Assessment saved with manual measurements/BWAT-style observations; default status = COMPLETED.
  2. Wound record is updated atomically: $inc assessmentCount, $set latestAssessmentId/latestAssessmentDate (lines 254-263).
  3. Events emitted: wound-care.assessment-created (notifies all business-management-role users, apps/backend/src/notification/listeners/wound-care-notification.listener.ts:27-57) and a platform log.
  4. If at least one prior assessment exists, trajectory is recomputed in the background (lines 298-312).

Trajectory computation (computeAndUpdateTrajectory, lines 549-631) compares the two most recent assessments:

  • Area change > +10% ⇒ WORSENING; < −10% ⇒ IMPROVING (primary signal).
  • Granulation +10 points ⇒ IMPROVING (overrides area).
  • Necrotic +10 points ⇒ WORSENING — always wins (lines 588-594).
  • On WORSENING: emits wound-care.condition-worsening (notifies management roles) + platform log WOUND_CONDITION_WORSENING.

AI photo analysis (queue wound-analysis):

  • Photos are uploaded per assessment via multipart POST .../assessments/:assessmentId/upload (S3, private, folder wound-care/{woundId}/{assessmentId}, service lines 387-414).
  • POST .../analyze requires ≥1 photo and rejects if status is already PENDING_ANALYSIS/ANALYZING (lines 432-443); sets PENDING_ANALYSIS and enqueues analyze-wound-photo with 2 attempts, exponential backoff 5s (lines 461-475).
  • WoundAnalysisProcessor (apps/backend/src/wound-care/wound-analysis.processor.ts, concurrency 2, 10-min lock): sets ANALYZING, downloads the first photo only (line 109), gathers up to 5 previous COMPLETED assessments as text summaries plus the most recent previous photo for visual comparison (lines 124-192), then calls WoundAnalysisAIService.analyzeWoundPhoto.
  • WoundAnalysisAIService (services/wound-analysis-ai.service.ts) calls Anthropic claude-sonnet-4-6 with a BWAT-oriented system prompt and a forced tool call returning a Zod-validated structure (measurements, tissue classification, recommendations, worsening indicators, trajectory comparison, confidence). Truncated or missing tool output throws (lines 282-297).
  • On success: aiAnalysis saved with status: 'COMPLETED', assessment status → COMPLETED, event wound-care.analysis-completed notifies only the triggering user (listener lines 59-77). On failure: aiAnalysis.status = 'FAILED' + errorMessage, assessment status → ANALYSIS_FAILED (processor lines 269-277); re-trigger is allowed from that state.

Progress view: GET :id/progress returns the record, trajectory, per-assessment summaries with first-photo thumbnails, and time series for area, BWAT score and tissue composition (wound-care.service.ts:482-542).

DNR / advance-directive acknowledgment

apps/backend/src/clients/services/dnr-acknowledgment.service.ts:

  1. Requirement: acknowledgment is required iff client.hasDnrOrder || client.hasAdvanceDirective (lines 55-63; flags on apps/backend/src/clients/entities/client.entity.ts:162-165).
  2. GET clients/:clientId/dnr-acknowledgment/status (no permission decorator) returns {required, acknowledged, stale}; stale = true when the newest LockCare file in the client's ADVANCE_DIRECTIVE folder has uploadedAt newer than the stored dnrDocumentUpdatedAt (lines 69-86, 181-194).
  3. POST clients/:clientId/dnr-acknowledgment (permission SHIFTS_EXECUTE, i.e. caregivers — clients.controller.ts:1418-1429) validates the client flags and that an AD document actually exists, then creates or refreshes the unique caregiver-client acknowledgment. Already-current acknowledgment returns {alreadyAcknowledged: true} (lines 92-149).
  4. GET clients/:clientId/dnr-acknowledgments (permission CLIENTS_MANAGE) lists acknowledgments for admin review (lines 154-179).

Doctors' appointments

All endpoints live on ClientsController under clients/:clientId/doctors-appointments (apps/backend/src/clients/controllers/clients.controller.ts:681-905); logic in apps/backend/src/clients/services/client-doctors-appointments.service.ts.

Creation (createDoctorsAppointment, lines 152-355):

  • Title is AI-generated from purpose via AIClientService.generateTitle, else "Dr. {name} - {date}" (lines 166-187).
  • Recurring appointments are expanded with rrule (DAILY/WEEKLY/MONTHLY/YEARLY, weekday/day-of-month options, ends ON/AFTER) into individual documents sharing a random recurrenceId; expansion is capped at the until date or 1 year from start (lines 243-305).
  • Pre-visit files are stored as LockCare records in the DOCTOR_APPOINTMENT folder and linked on the appointment (associateFiles, lines 976-1076).
  • Collision check / task excusal: POST .../check-collisions computes the blocked window (start − travel … end + travel), finds overlapping shift assignments, and rrule-expands the latest published care plan's task occurrences inside the window (lines 1160-1272). On create, selected occurrences are written into the care-plan tasks' excusedOccurrences with reason DoctorAppointment (lines 1282-1335).

Status machine — set directly by endpoints; no transition validation exists:

  • Complete (lines 431-510): stores diagnosis/prescription/notes/labs/follow-up/referrals + post-visit files + medications. Medications marked isNew create ClientMedication records; existing ones get dosage updates — best-effort, errors swallowed (lines 480-494, 512-568).
  • Cancel / Reschedule (lines 710-784): write the respective subdocuments; reschedule also overwrites startDate/endDate.
  • Delete is a hard delete (lines 810-822).
  • GET .../upcoming = startDate >= now AND status scheduled|rescheduled (lines 786-808).

Monitoring & reminders (apps/backend/src/clients/services/appointment-monitoring.service.ts):

  • Cron every 5 minutes (line 54) scans scheduled|rescheduled appointments starting within the next 25 hours that have non-empty alertTimings (lines 59-71, constants in appointment.constants.ts).
  • For each alert timing within ±5 minutes of "minutes until start", it sends a unified notification (in-app + push) to the client's care team: management-role users in the business, accepted/assigned care providers, and active representatives (lines 96-137, 154-198). Sender is the system user.
  • Dedup via AppointmentReminderTrackingService: Redis cache key appointment:reminder:{appointmentId}:{alertMinutes}:{userId} with 24h TTL (appointment-reminder-tracking.service.ts:9-51). Cache read errors fail open (returns false ⇒ may resend).

AI appointment-history summary (queue appointment-history-generation):

  • POST .../generate-history-summary enqueues a job (service lines 659-680). AppointmentHistoryProcessor (apps/backend/src/clients/processors/appointment-history.processor.ts, concurrency 3) loads all completed appointments, builds a text context, and calls OpenAI gpt-4-turbo (temperature 0.3) for a 5-section markdown summary; progress and completion/failure are emitted over the notification WebSocket to the business:{id} room (SocketEvents.APPOINTMENT_HISTORY_GENERATION_*, lines 92-247). Fails if the client has zero completed appointments (line 110-112).
  • GET .../history-summary/latest returns the newest summary; GET .../history-summary/:id/pdf-stream converts the markdown to a PDF stream (service lines 682-708).

Business Rules & Constraints

  • Vitals are derived, never stored separately — deleting/changing task submissions changes history; only SUBMITTED submissions count (health-metrics.service.ts:89).
  • Vitals thresholds are global constants, not per-client baselines (health-metrics.service.ts:40-51). Temperature is normalized to Fahrenheit before comparison (lines 778-808); weight and glucose comparisons ignore the recorded unit.
  • Tenancy: wound-care queries always filter by business from the JWT (wound-care.service.ts:77-81, 120-125, 162-169). Doctors'-appointment reads mostly filter by client only (e.g. findAllAppointments lines 357-374, findAppointmentById lines 376-399) — no business filter (see Gaps).
  • Permissions (enforced by global + controller PermissionsGuard, apps/backend/src/auth/guards/permissions.guard.ts):
    • Health metrics: HEALTH_METRICS_VIEW_ASSIGNED for reads, HEALTH_METRICS_MANAGE for report generate/update/finalize (health-metrics.controller.ts:64,189,233,244). PHI access on list/report endpoints is audit-logged via @AuditPhiAccess (lines 63, 188, 224, 251).
    • Wound care: WOUND_CARE_VIEW_ASSIGNED for reads, WOUND_CARE_MANAGE for create/update/close/assess/upload/analyze (wound-care.controller.ts). Reads are PHI-audited.
    • DNR: acknowledge requires SHIFTS_EXECUTE; admin list requires CLIENTS_MANAGE; status check has no permission decorator (clients.controller.ts:1418-1455).
    • Doctors' appointments: no @RequirePermissions on any endpoint — only the class-level JwtAuthGuard (clients.controller.ts:92), despite APPOINTMENTS_* permissions existing in packages/shared/src/enums/business-permission.ts.
  • Default role grants (packages/shared/src/constants/default-role-permissions.ts): Admin gets HEALTH_METRICS_MANAGE/WOUND_CARE_MANAGE (lines 84-88); CareProvider gets HEALTH_METRICS_VIEW_ASSIGNED, WOUND_CARE_VIEW_ASSIGNED and WOUND_CARE_MANAGE (lines 209-211) plus SHIFTS_EXECUTE; Representative gets WOUND_CARE_VIEW_ASSIGNED only (line 278); MedicalProfessional gets HEALTH_METRICS_VIEW_ASSIGNED, HEALTH_METRICS_MANAGE, WOUND_CARE_VIEW_ASSIGNED (lines 337-339).
  • One acknowledgment per caregiver-client pair, enforced by unique index; staleness is tied to the newest ADVANCE_DIRECTIVE LockCare upload (dnr-acknowledgment.service.ts:81-85).
  • Wound analysis prerequisites: ≥1 photo; not already pending/analyzing (wound-care.service.ts:432-443). Retries: 2 attempts, exponential backoff.
  • AI analysis never mutates the wound recordtrajectory is computed only from manually-entered measurements on assessment creation; aiAnalysis.comparisonWithPrevious.trajectory is stored on the assessment only.
  • Appointment travel time blocks symmetrically on both sides of the appointment for collision detection (client-doctors-appointments.service.ts:1086-1098).
  • Reminder windows: only appointments within 25h are scanned; an alert fires when |minutes-until − alertTiming| ≤ 5; dedup key TTL 24h. A backend outage during a window silently skips that reminder — no catch-up.
  • Recurring appointments are immutable as a series — the recurrence rule is not persisted, only the materialized occurrences with a shared recurrenceId; updates/cancellations apply per-occurrence only.

Surfaces (Web & Mobile)

Web (Next.js dashboard — admins / care managers)

  • Health Metrics: apps/web/app/(app)/(admin)/dashboard/clients/[id]/edit/health-metrics/page.tsx with chart components per vital (_components/*-chart.tsx), latest vitals, abnormal readings, and period-report list/detail (health-reports-list.tsx, health-report-detail.tsx). Data hooks in apps/web/hooks/use-health-metrics.ts call all backend endpoints including reports.
  • Wound Care: list page .../edit/wound-care/page.tsx, record detail .../wound-care/[woundId]/page.tsx, assessment detail .../assessments/[assessmentId]/page.tsx; API client apps/web/lib/wound-care-api.ts (create/update/close records, create assessments, upload photos, trigger analysis, progress).
  • Doctors' Appointments: .../edit/doctors-appointments/page.tsx with calendar, create/edit dialogs, complete/cancel/reschedule forms (_components/), plus a business-wide calendar view (dashboard/calendar/_components/appointment-detail-dialog.tsx).
  • DNR: the client layout shows a DNR indicator from clientQuery.data.hasDnrOrder (apps/web/app/(app)/(admin)/dashboard/clients/[id]/layout.tsx:186). No web UI was found that calls the acknowledgment endpoints (status, acknowledge, or the admin list).

Mobile (Expo — care providers / families)

  • Health (vitals): apps/mobile/app/(client)/[id]/health/index.tsx shows latest vitals plus 30-day trend line charts (heart rate, systolic BP, SpO2, temperature) using useLatestVitals / useHealthMetricsRecharts (apps/mobile/hooks/use-health-metrics.ts). Vitals recording happens through care-task submission screens (sibling module).
  • Wound Care: full flow — list (wound-care/index.tsx), create record (create.tsx), record detail ([woundId]/index.tsx), new assessment (new-assessment.tsx), assessment detail (assessments/[assessmentId].tsx); API client apps/mobile/lib/wound-care-api.ts including photo upload and triggerAnalysis. Care providers can do this because they hold WOUND_CARE_MANAGE by default.
  • Appointments: apps/mobile/lib/appointments-api.ts covers list/upcoming/detail/create/complete/cancel/reschedule and doctor file albums.
  • DNR: client screens render a passive AdvanceDirectiveBanner when hasAdvanceDirective || hasDnrOrder (apps/mobile/app/(client)/[id]/index.tsx:124, care-plan/index.tsx:282, apps/mobile/components/ui/advance-directive-banner.tsx). apps/mobile/lib/client-api.ts:16-32 defines getDnrAcknowledgmentStatus / acknowledgeDnr, but no screen invokes them (frontend-only banner; acknowledgment flow appears unwired — see Gaps).

Frontend-only rules: chart bucketing windows (mobile 30-day fixed window), and any role-based hiding of wound-care/appointment actions are UI conveniences — the backend enforces only the permissions listed above (and none for appointments).

Cross-Module Dependencies

  • health-monitoring → care tasks: vitals are read from TaskSubmission/TaskTemplate (health-metrics.service.ts:53-58); appointment creation excuses care-plan task occurrences in CareProviderTasks (client-doctors-appointments.service.ts:1282-1335). See 04-care-plans-and-tasks.md.
  • health-monitoring → scheduling/shifts: collision detection queries ShiftAssignment (client-doctors-appointments.service.ts:1174-1186); DNR acknowledgment is gated by SHIFTS_EXECUTE. See 05-scheduling-and-shifts.md.
  • health-monitoring → notifications: wound-care events consumed by WoundCareNotificationListener (apps/backend/src/notification/listeners/wound-care-notification.listener.ts); appointment reminders and history-generation progress go through NotificationService / NotificationGateway WebSocket rooms.
  • health-monitoring → files (LockCare/S3): wound photos via FilesService (signed URLs); appointment pre/post-visit files and advance-directive documents are LockCare records (lock-care.service.ts usage in client-doctors-appointments.service.ts:976-1076, dnr-acknowledgment.service.ts:181-194).
  • health-monitoring → AI services: Anthropic via Vercel AI SDK for wound analysis (wound-analysis-ai.service.ts:263-280) and period-report summaries (health-metrics-export.service.ts:43-46); OpenAI for appointment-history summaries (appointment-history.processor.ts:59-62); AIClientService.generateTitle for appointment titles.
  • health-monitoring → medications: completing an appointment syncs prescribed medications into ClientMedication (client-doctors-appointments.service.ts:512-568). See 01-clients.md.
  • health-monitoring → platform logs: wound record/assessment actions and appointment CRUD emit PLATFORM_LOG_ACTIVITY_EVENT; PHI reads are audited via @AuditPhiAccess.
  • Sibling: change-of-condition health observations02-assessments.md.

Open Questions & Gaps

  1. DNR acknowledgment flow appears unwired on the frontends. The backend implements status/acknowledge/list endpoints and the mobile API client defines acknowledgeDnr/getDnrAcknowledgmentStatus (apps/mobile/lib/client-api.ts:16-32), but no mobile screen or web page calls them — only passive banners exist. The service docstring mentions a "clock-in flow" (dnr-acknowledgment.service.ts:45-46), yet nothing in apps/backend/src/shifts/ references DNR. Whether acknowledgment was meant to gate clock-in cannot be determined from code.
  2. Doctors'-appointment endpoints have no permission checks and weak tenant scoping. No @RequirePermissions despite APPOINTMENTS_* permissions existing; several reads/writes (findAllAppointments, findAppointmentById, updateAppointment, deleteAppointment) filter only by clientId/appointmentId with no business filter — any authenticated user with a valid client id can access another tenant's appointments (global BusinessGuard only checks that the caller's own business is active, apps/backend/src/common/guards/business.guard.ts:20-26).
  3. statusCounts aggregation in findAllWoundRecords likely always returns empty. The $match uses raw string businessId/clientId against ObjectId fields (wound-care.service.ts:136-139); unlike find(), aggregation pipelines do not auto-cast strings to ObjectIds. Cannot confirm without runtime data, but the types disagree.
  4. bwatScore is never written. It exists on WoundAssessment, is absent from CreateWoundAssessmentDto, and is charted in the progress endpoint (wound-care.service.ts:516-521) — the BWAT-score series will always be empty.
  5. AI analysis results don't feed trajectory. The AI returns comparisonWithPrevious.trajectory, but WoundRecord.trajectory is only updated from manual measurements at assessment-creation time; AI completion neither recomputes trajectory nor emits worsening events even when worseningIndicators are returned.
  6. Wound status has no state machine. UpdateWoundRecordDto accepts any WoundStatus; close always forces HEALED regardless of current state; HEALING/CHRONIC/REFERRED have no defined transitions or behavior anywhere in backend logic.
  7. Vitals checks are unit-naive. Weight thresholds (90/300/400) are applied to the raw number regardless of the recorded unit (lbs default but kg possible, health-metrics.service.ts:579-603, 761-776); blood glucose ignores mmol/L vs mg/dL. The supplementalOxygen boolean is derived such that any value other than exactly 'No' evaluates true (lines 529-533).
  8. Summary metadata omits two metrics. getAvailableMetrics, calculateAverages and calculateTrends skip temperature and respiratory rate entirely (health-metrics.service.ts:893-971, 1072-1083), and getAbnormalReadings never reports abnormal temperature/respiratory-rate readings (lines 368-433) even though per-reading flags are computed.
  9. Reminder delivery is best-effort. ±5-minute window on a 5-minute cron means a missed cycle (deploy/outage) silently drops that alert; Redis dedup errors fail open and may double-send (appointment-reminder-tracking.service.ts:27-36). isReminded on the appointment entity is dead — never set.
  10. Recurring appointments: the rrule is not persisted (only materialized occurrences + recurrenceId), so series-level edit/cancel is impossible; expansion caps at 1 year with no rolling extension; associateFiles is called per occurrence, creating duplicate LockCare records for the same uploaded file across occurrences (client-doctors-appointments.service.ts:243-305). groupId is never written.
  11. rescheduled keeps only the latest reschedule (single subdocument, overwritten each time), and there is no guard preventing completing/cancelling an already-completed or cancelled appointment — status is overwritten unconditionally.
  12. Mixed AI providers with different failure semantics: wound analysis and period reports use Anthropic (period-report AI failures are swallowed, draft still saved); appointment-history summaries use OpenAI gpt-4-turbo (failure fails the whole job). Whether this split is intentional cannot be determined from code.
  13. DNR staleness only tracks the newest AD document — if a client has hasDnrOrder true but no file in the ADVANCE_DIRECTIVE folder, status reports required yet acknowledge rejects with "No advance directive document found", leaving caregivers unable to acknowledge (dnr-acknowledgment.service.ts:108-113).
  14. Wound analysis examines only the first photo of an assessment (wound-analysis.processor.ts:109); additional uploaded photos are stored but never analyzed.

On this page