Clinical Data — Prescriptions (Medication Orders)
Audiences: doctor, nurse, clinical-buyer, developer
Prescription writing is a dual-path (Prisma legacy + FHIR-first, feature-flag gated) system that produces interoperable FHIR
MedicationRequestresources 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:
- Structured prescriptions — each prescription is a FHIR R4
MedicationRequestresource (when FHIR SoT flag is ON) or a PrismaMedicationRequestrow (legacy path) — either way the data is coded and auditable. - Sequential prescription numbering —
PrescriptionNumberingServicegenerates Rezeptnummer-style sequential IDs per organization, used for regulatory compliance in German practice contexts. - Drug-interaction checking —
PrescriptionInteractionServiceruns at create time, checking drug-drug and drug-allergy conflicts before saving. - Multi-item prescriptions —
MultiPrescriptionsServicehandles aRequestGroup+ N ×MedicationRequestchain for issuing several medications in one clinical act. - Prescription templates —
PrescriptionTemplatesServiceallows doctors to save and reuse frequent medication order sets. - 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_MedicationRequestflag). Bulk operations supported viaBulkOperationDto. - Internal (ops/support): Legacy Prisma path is default (flag OFF). Enable FHIR-first per org via
FeatureFlagtable row. Shadow FHIR write (fhir-parityevent) runs even on legacy path for drift detection. Prisma model:MedicationRequestinddx_api_main.
Architecture
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-entryBundlewith theMedicationRequest. On success,prisma.$transaction()createsfhirIndex+medicationRequestrows 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-parityevent (success or failure) — never silently swallowed. Prisma row is the SoT.
This mirrors the P1A pilot in diagnostic-reports.service.ts.
Tech Stack & Choices
| Layer | Technology | Notes |
|---|---|---|
| API | NestJS 11, @Controller('prescriptions') | ResourceMeta introspection, BulkOperationDto support |
| FHIR storage | HAPI FHIR R4 MedicationRequest | Flag-gated; FHIR_CLIENT DI token |
| Prisma storage | MedicationRequest model (ddx_api_main) | Legacy path + bookkeeping rows (fhirIndex) |
| Feature flags | Prisma FeatureFlag table | Org-level rollout percentage; FHIR_SOT_MedicationRequest flag |
| Prescription numbering | PrescriptionNumberingService | Sequential per-org; German Rezeptnummer format |
| Interaction checking | PrescriptionInteractionService | Embedded drug DB + FHIR allergy lookup |
| Multi-item | MultiPrescriptionsService | FHIR RequestGroup + N × MedicationRequest |
| Templates | PrescriptionTemplatesService (Prisma) | Saved medication order sets per doctor |
| Auth | @RequireRole(DOCTOR, NURSE, CLINIC_ADMIN, ...) | Write requires DOCTOR minimum; reads open to PATIENT |
| Audit | @AuditMeta interceptor | CREATE/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:
POST /api/v1/prescriptionswith{ patientId, practitionerId, medicationName: "Amoxicillin", dosageInstruction: "500mg TID x 7 days", visitId }.PrescriptionsController(prescriptions.controller.ts:76) delegates toPrescriptionsService.create()withorganizationIdfrom gateway user.PrescriptionsService.create()(prescriptions.service.ts:137): a. ResolvesfhirClinicId(slug for FHIR) andresolvedOrganizationId(UUID for Prisma). b. ChecksisFhirSotEnabled()— readsFeatureFlagPrisma row (prescriptions.service.ts:111). c. FHIR-first path: BuildsMedicationRequestFHIR resource, posts as transaction Bundle. On success, atomicprisma.$transaction()createsfhirIndex+medicationRequestrows. d. Legacy path:legacyPrismaCreate()writes Prisma row first, then shadow-writes to FHIR.PrescriptionNumberingServiceassigns sequential Rezeptnummer within the org.PrescriptionInteractionServicechecks drug-drug and drug-allergy conflicts (advisory — does not block save).- 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-prescriptions → MultiPrescriptionsService 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,practitionerIdddx-api/src/clinical-data/prescriptions/prescriptions.controller.ts:57—PrescriptionsController— injectsPrescriptionsService;@AuditMetaon write endpointsddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:79—FHIR_SOT_FLAG_NAME—'FHIR_SOT_MedicationRequest'feature flag nameddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:93—PrescriptionsService— dual-path orchestrator; injects 6 dependenciesddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:111—isFhirSotEnabled()— readsFeatureFlagrow; org-level rollout percentage bucketingddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:137—create()— entry point; resolves org/clinic IDs, calls flag check, routes to fhirFirstCreate or legacyPrismaCreateddx-api/src/clinical-data/prescriptions/prescriptions.service.ts:163— flag routing —fhirFirstCreate()vslegacyPrismaCreate()dispatchddx-api/src/clinical-data/prescriptions/prescription-interaction.service.ts:19—MAJOR_INTERACTIONSmap — embedded drug-drug interaction databaseddx-api/src/clinical-data/prescriptions/prescription-numbering.service.ts:1—PrescriptionNumberingService— sequential Rezeptnummer generationddx-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_MedicationRequestdefaults to disabled — all new orgs use the legacy Prisma path. Enable per-org viaFeatureFlagtable row withrolloutPercentage. 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-parityevent. 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-enginewithdudoxx-hmstheme generates the formatted prescription document. - Interaction warnings are advisory:
PrescriptionInteractionServicereturns 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/bulkacceptsBulkOperationDtofor batch create/update/delete. Used by the TUCAN AI agent's prescription tools.
Related Topics
- Drugs Management — Medication catalog + interaction DB that feeds this service
- Clinical Data — Conditions — Allergy records used for drug-allergy checking
- Clinical Visits — Prescriptions written within visit context
- Diagnosis Engine — AI may suggest medications based on diagnosis
- Medical Cards — Active prescriptions surfaced in patient summary
- Document Management — Prescription PDF generation