04-clinicalwave: W6filled5 citations

Appointments — Scheduling and Calendar

Audiences: doctor, clinical-buyer, developer

Appointments — Scheduling and Calendar

Dudoxx HMS manages the full appointment lifecycle — booking, conflict detection, check-in, no-show tracking, recurring schedules, and FHIR synchronization — through a layered NestJS service stack that is the entry point for every clinical encounter.

Business Purpose

Scheduling is the operational heartbeat of a clinic: every revenue event, every FHIR record, and every clinical visit originates with an appointment. Dudoxx HMS provides: (1) real-time conflict detection so double-bookings cannot happen; (2) dual-mode appointments (on-site vs. telemed with automatic Jitsi room creation); (3) FHIR R4 Appointment resource synchronization so the data lives in a standards-compliant store usable by external systems; (4) automated reminders and waitlist management to reduce no-shows and fill cancellation slots. Clinical buyers see fewer scheduling errors and lower administrative burden; investors see a defensible workflow moat that goes beyond basic CRUD.

Audiences

  • Investor: Scheduling with FHIR sync is a prerequisite for any hospital integration or insurance claim workflow. Dudoxx owning the appointment lifecycle closes the EHR loop.
  • Clinical buyer (doctor/nurse/receptionist): Receptionists book, reschedule, and cancel through the portal; doctors see their daily schedule with patient context; nurses manage check-in and status updates. All changes are audited.
  • Developer/partner: REST endpoints grouped by concern (CRUD, scheduling, dashboard, automation). FHIR sync is via ResourceFacade<AppointmentResource> (NF2.1 migration) — inject APPOINTMENT_FACADE token, never call FhirService directly.
  • Internal (ops/support): NoShowDetectionService runs a cron to auto-mark no-shows; AppointmentRemindersService queues SMS/email reminders. Both run via @nestjs/schedule — no BullMQ.

Architecture

The appointment domain lives in ddx-api/src/clinical/appointments/ and is structured in three tiers:

Tier 1 — Controllers (thin, 3 sub-controllers):

ControllerRoutes
AppointmentsCrudControllerPOST/GET/PUT/DELETE /appointments
AppointmentsScheduleController/appointments/:id/status, /reschedule, /check-in
AppointmentsQueryController/appointments/dashboard, /export

Tier 2 — AppointmentsService (orchestrator facade — backward-compatible API):

CollaboratorResponsibility
AppointmentCrudServiceDB CRUD, pagination, export
AppointmentSchedulingServiceConflict detection, slot reservation
FhirAppointmentServiceFHIR R4 CRUD via APPOINTMENT_FACADE
AppointmentNotificationHandlerServiceEmail/SMS dispatch

Tier 3 — BookingOrchestratorService (single booking entry point since v2.0.0), ordered steps:

#StepOwner
1Generate appointment numberAppointmentNumberingService
2Create FHIR AppointmentFhirAppointmentSyncServiceAPPOINTMENT_FACADE
3Create Prisma Appointment record
4Create Jitsi video meeting if mode=ONLINEVideoService
5Send notificationsAppointmentNotificationService
6Emit audit logAuditService

APPOINTMENT_FACADE is a ResourceFacade<AppointmentResource> token registered in AppointmentsModule. The facade handles the Prisma ↔ FHIR ↔ MinIO storage pattern and upserts fhir_resource_link rows after every successful write (NF2.1 migration).

Appointment status is a separate AppointmentStatusService that enforces status-transition business rules (e.g. sets cancelledAt/checkedInAt timestamps and writes audit records).

Tech Stack & Choices

ConcernChoiceRationale
DB storageprisma-main Appointment modelPostgres UUID primary keys, indexed by organizationId + startTime for schedule queries
FHIR syncResourceFacade<AppointmentResource> via APPOINTMENT_FACADE tokenNF2.1 migration away from deprecated FhirService; facade handles link-row upserts
FHIR resourceFHIR R4 Appointment with Participant arrayMaps practitioner, patient, and location FHIR references
Video meetingsJitsi (port 15800) via VideoServiceOn-premise video, no external SaaS cost; room created only when mode=ONLINE
RemindersAppointmentRemindersService via @nestjs/schedulePolling cron; no BullMQ; Email + SMS via MailModule + SmsModule
No-show detectionNoShowDetectionService cronAuto-marks appointments past end time still in CONFIRMED status
Conflict detectionAppointmentSchedulingServiceChecks practitioner slots and overlapping DB records before write
Appointment numberingAppointmentNumberingServiceGenerates human-readable sequential numbers per organization

Data Flow

On-site booking (happy path):

  1. Receptionist POSTs CreateAppointmentDto to AppointmentsCrudController.create().
  2. AppointmentsService delegates to BookingOrchestratorService.bookAppointment().
  3. AppointmentNumberingService generates a sequential display number for the org.
  4. FhirAppointmentSyncService.createFhirAppointment() builds the FHIR R4 resource and calls APPOINTMENT_FACADE.create() → HAPI FHIR at port 18080.
  5. Prisma appointment.create() record written in ddx_api_main.
  6. If mode=ONLINE: VideoService.createMeeting() provisions a Jitsi room; meeting URL stored on the appointment record.
  7. AppointmentNotificationService.notifyCreated() dispatches confirmation email/SMS.
  8. AuditService.log() writes to audit_trail in ddx_api_log.
  9. ResponseTransformInterceptor wraps result in { data, meta } envelope.

Status transitions (check-in / cancel / no-show):

  • PUT /:id/statusAppointmentStatusService.updateStatus() → validates transition, sets timestamp fields, writes audit record.

Implicated Code

  • ddx-api/src/clinical/appointments/booking-orchestrator.service.ts:48BookingOrchestratorService; unified 7-step booking flow; v2.0.0 single entry point for all booking operations
  • ddx-api/src/clinical/appointments/appointments.service.ts:46AppointmentsService orchestrator facade; delegates to 4 domain services (crud, scheduling, fhir, notification)
  • ddx-api/src/clinical/appointments/fhir-appointment-sync.service.ts:56FhirAppointmentSyncService; NF2.1 migration to APPOINTMENT_FACADE; builds FHIR R4 Appointment resource
  • ddx-api/src/clinical/appointments/appointment-status.service.ts:17AppointmentStatusService; status transition rules, cancelledAt/checkedInAt timestamps, audit logging at :60
  • ddx-api/src/clinical/appointments/appointments.module.ts:1 — module wiring; APPOINTMENT_FACADE token provided as ResourceFacade<AppointmentResource>; forwardRef cycle with VideoModule and VisitsModule
  • ddx-api/src/clinical/appointments/no-show-detection.service.tsNoShowDetectionService cron
  • ddx-api/src/clinical/appointments/helpers/fhir-appointment-mapper.helper.ts — FHIR resource builder (maps Prisma fields to FHIR R4 Participant array)
  • ddx-api/src/clinical/appointments/helpers/appointment-validator.helper.ts — business-rule guard (conflict detection pre-checks)

Operational Notes

  • FHIR dependency: Booking will fail if HAPI FHIR (port 18080) is unreachable — FhirAppointmentSyncService surfaces InternalServerErrorException carrying error.kind. Check HAPI_FHIR_BASE_URL env var.
  • Appointment → Visit link: Completing an appointment (status COMPLETED) triggers VisitAppointmentLinkingService to create or associate a clinical visit record. This is the bridge to the visits domain.
  • Recurring appointments: Handled by RecurringAppointmentsModule (separate module at ddx-api/src/clinical/recurring-appointments/) — creates a series of individual appointment records.
  • Waitlist: WaitlistService in this module manages the per-practitioner waitlist; when a slot opens (cancellation), waitlisted patients are notified.
  • forwardRef cycles: AppointmentsModule has circular imports with VideoModule and VisitsModule — both resolved via forwardRef(); do not refactor without validating the DI cycle still resolves.
  • On-site vs. telemed split: AppointmentMode enum (IN_PERSON / ONLINE) drives video room creation. mode=IN_PERSON skips VideoService entirely.
  • Clinical Visits — appointment completion triggers visit creation via VisitAppointmentLinkingService
  • FHIR Partitions & Tenancy — FHIR Appointment resources are stored in the tenant's HAPI partition
  • Audit Logging — every status change and booking event is written to audit_trail
    Appointments — Scheduling and Calendar — Dudoxx Docs | Dudoxx