04-clinicalwave: W2filled14 citations

Clinical Data — Prescriptions (Medication Orders)

Audiences: doctor, nurse, clinical-buyer, developer

Clinical Data — Prescriptions (Medication Orders)

Prescription writing is a dual-path (Prisma legacy + FHIR-first, feature-flag gated) system that produces interoperable FHIR MedicationRequest resources while maintaining fast Prisma-backed queries and German Rezeptnummer numbering.

Business Purpose

Prescribing medication is one of the highest-liability clinical acts. Errors — wrong drug, wrong dose, drug-allergy conflict, drug-drug interaction — can cause patient harm. Dudoxx HMS addresses this with:

  1. Structured prescriptions — each prescription is a FHIR R4 MedicationRequest resource (when FHIR SoT flag is ON) or a Prisma MedicationRequest row (legacy path) — either way the data is coded and auditable.
  2. Sequential prescription numberingPrescriptionNumberingService generates Rezeptnummer-style sequential IDs per organization, used for regulatory compliance in German practice contexts.
  3. Drug-interaction checkingPrescriptionInteractionService runs at create time, checking drug-drug and drug-allergy conflicts before saving.
  4. Multi-item prescriptionsMultiPrescriptionsService handles a RequestGroup + N × MedicationRequest chain for issuing several medications in one clinical act.
  5. Prescription templatesPrescriptionTemplatesService allows doctors to save and reuse frequent medication order sets.
  6. PDF export — prescriptions are exportable as PDFs via the document generation pipeline (ddx-pdf-engine).

Clinics benefit from reduced medication errors, automated numbering, and FHIR-interoperable records for insurance, referral, and German eRezept workflows.

Audiences

  • Investor: FHIR-coded prescriptions enable future payer integrations (eRezept, ePrescription APIs). The dual-path migration strategy (feature-flag gated FHIR SoT) shows architectural maturity — zero-downtime migration without big-bang rewrites.
  • Clinical buyer (doctor/nurse/receptionist): Doctors write prescriptions during visits. The system warns on interactions, auto-assigns prescription numbers, and allows template reuse. Patients see their prescriptions in the patient portal.
  • Developer/partner: Single prescription: POST /api/v1/prescriptions. Multi-item: POST /api/v1/multi-prescriptions. FHIR SoT is feature-flag controlled per org (FHIR_SOT_MedicationRequest flag). Bulk operations supported via BulkOperationDto.
  • Internal (ops/support): Legacy Prisma path is default (flag OFF). Enable FHIR-first per org via FeatureFlag table row. Shadow FHIR write (fhir-parity event) runs even on legacy path for drift detection. Prisma model: MedicationRequest in ddx_api_main.

Architecture

diagram
PrescriptionsController     (/api/v1/prescriptions)
  └── PrescriptionsService  — dual-path orchestrator
        ├── isFhirSotEnabled()          — reads FeatureFlag.FHIR_SOT_MedicationRequest
        ├── fhirFirstCreate()           — FHIR transaction bundle → Prisma bookkeeping
        ├── legacyPrismaCreate()        — Prisma primary → shadow FHIR write
        ├── PrescriptionNumberingService — sequential Rezeptnummer generation
        ├── PrescriptionInteractionService — drug-drug + drug-allergy check
        ├── PatientIdResolverService    — UUID → FHIR Patient ID
        ├── PractitionerIdMapper        — UUID → FHIR Practitioner ID
        └── OrganizationResolverService — slug → UUID

MultiPrescriptionsController (/api/v1/multi-prescriptions)
  └── MultiPrescriptionsService — RequestGroup + N × MedicationRequest bundle

FHIR flag-gated dual path (CR-021, P1D pattern):

  • Flag ON (FHIR-first): fhirClient.transaction(bundle, slug) posts a single-entry Bundle with the MedicationRequest. On success, prisma.$transaction() creates fhirIndex + medicationRequest rows atomically. If FHIR throws, no Prisma rows are written.
  • Flag OFF (legacy Prisma): Prisma is the primary write. A shadow FHIR write emits a fhir-parity event (success or failure) — never silently swallowed. Prisma row is the SoT.

This mirrors the P1A pilot in diagnostic-reports.service.ts.

Tech Stack & Choices

LayerTechnologyNotes
APINestJS 11, @Controller('prescriptions')ResourceMeta introspection, BulkOperationDto support
FHIR storageHAPI FHIR R4 MedicationRequestFlag-gated; FHIR_CLIENT DI token
Prisma storageMedicationRequest model (ddx_api_main)Legacy path + bookkeeping rows (fhirIndex)
Feature flagsPrisma FeatureFlag tableOrg-level rollout percentage; FHIR_SOT_MedicationRequest flag
Prescription numberingPrescriptionNumberingServiceSequential per-org; German Rezeptnummer format
Interaction checkingPrescriptionInteractionServiceEmbedded drug DB + FHIR allergy lookup
Multi-itemMultiPrescriptionsServiceFHIR RequestGroup + N × MedicationRequest
TemplatesPrescriptionTemplatesService (Prisma)Saved medication order sets per doctor
Auth@RequireRole(DOCTOR, NURSE, CLINIC_ADMIN, ...)Write requires DOCTOR minimum; reads open to PATIENT
Audit@AuditMeta interceptorCREATE/UPDATE/DELETE tracked

Data Flow

Doctor prescribes amoxicillin

Business outcome: Doctor selects amoxicillin 500mg from the catalog during a visit; a numbered prescription appears in the patient portal within 2 seconds and can be printed as a PDF.

Technical mechanism:

  1. POST /api/v1/prescriptions with { patientId, practitionerId, medicationName: "Amoxicillin", dosageInstruction: "500mg TID x 7 days", visitId }.
  2. PrescriptionsController (prescriptions.controller.ts:76) delegates to PrescriptionsService.create() with organizationId from gateway user.
  3. PrescriptionsService.create() (prescriptions.service.ts:137): a. Resolves fhirClinicId (slug for FHIR) and resolvedOrganizationId (UUID for Prisma). b. Checks isFhirSotEnabled() — reads FeatureFlag Prisma row (prescriptions.service.ts:111). c. FHIR-first path: Builds MedicationRequest FHIR resource, posts as transaction Bundle. On success, atomic prisma.$transaction() creates fhirIndex + medicationRequest rows. d. Legacy path: legacyPrismaCreate() writes Prisma row first, then shadow-writes to FHIR.
  4. PrescriptionNumberingService assigns sequential Rezeptnummer within the org.
  5. PrescriptionInteractionService checks drug-drug and drug-allergy conflicts (advisory — does not block save).
  6. Returns PrescriptionResponseDto.

Patient reads own prescriptions

GET /api/v1/prescriptions?patientId=xxx with PATIENT-role JWT → auto-scoped to the authenticated patient's records. Returns paginated list of PrescriptionResponseDto.

Multi-item prescription

POST /api/v1/multi-prescriptionsMultiPrescriptionsService posts a FHIR Bundle containing one RequestGroup + N × MedicationRequest entries linked by urn:uuid: cross-references. All items are atomic — if any FHIR write fails, the bundle is rejected.

Implicated Code

  • ddx-api/src/clinical-data/prescriptions/prescriptions.controller.ts:42@ResourceMeta — filterable fields: patientId, medicationName, status, practitionerId
  • ddx-api/src/clinical-data/prescriptions/prescriptions.controller.ts:57PrescriptionsController — injects PrescriptionsService; @AuditMeta on write endpoints
  • ddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:79FHIR_SOT_FLAG_NAME'FHIR_SOT_MedicationRequest' feature flag name
  • ddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:93PrescriptionsService — dual-path orchestrator; injects 6 dependencies
  • ddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:111isFhirSotEnabled() — reads FeatureFlag row; org-level rollout percentage bucketing
  • ddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:137create() — entry point; resolves org/clinic IDs, calls flag check, routes to fhirFirstCreate or legacyPrismaCreate
  • ddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:163 — flag routing — fhirFirstCreate() vs legacyPrismaCreate() dispatch
  • ddx-api/src/clinical-data/prescriptions/prescription-interaction.service.ts:19MAJOR_INTERACTIONS map — embedded drug-drug interaction database
  • ddx-api/src/clinical-data/prescriptions/prescription-numbering.service.ts:1PrescriptionNumberingService — sequential Rezeptnummer generation
  • ddx-api/src/clinical-data/prescriptions/multi-prescriptions.controller.ts:1 — multi-item prescription endpoint (RequestGroup + N × MedicationRequest)
  • ddx-api/src/clinical-data/prescriptions/prescription-templates.controller.ts / prescription-templates.service.ts — saved medication order sets (create, createFromPrescription)
  • ddx-api/src/clinical-data/prescriptions/drug-favorites.service.ts — per-practitioner favorite drugs (medicationCodeableConcept, atcCode, default dosage/dispense)

Operational Notes

  • Feature flag is OFF by default: FHIR_SOT_MedicationRequest defaults to disabled — all new orgs use the legacy Prisma path. Enable per-org via FeatureFlag table row with rolloutPercentage. Org assignment uses character-sum bucketing modulo 100 (prescriptions.service.ts:121).
  • Shadow FHIR write on legacy path: Even on the legacy path, a shadow FHIR write emits a structured fhir-parity event. Monitor these events for drift detection before enabling the FHIR-first flag.
  • Prescription PDF export: Prescriptions can be exported as PDFs via the document generation pipeline. The ddx-pdf-engine with dudoxx-hms theme generates the formatted prescription document.
  • Interaction warnings are advisory: PrescriptionInteractionService returns warnings but does NOT block the prescription save. Clinical discretion is preserved — the UI should render warnings prominently.
  • Rezeptnummer: The prescription number format follows German practice requirements. Each org has its own sequential counter in PrescriptionNumberingService (Prisma-backed to be restart-safe).
  • Bulk operations: POST /api/v1/prescriptions/bulk accepts BulkOperationDto for batch create/update/delete. Used by the TUCAN AI agent's prescription tools.
    Clinical Data — Prescriptions (Medication Orders) — Dudoxx Docs | Dudoxx