Skip to main content

Medication Service — Application Logic

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template

1. Use Cases (Commands)

CommandActorAggregateNotes
DraftPrescriptionCommandprescriberPrescriptionCreates draft; no alerts yet
SignPrescriptionCommandprescriberPrescriptionRuns safety checks; emits alert overrides; persists MedicationRequest; optionally posts to gateway
DiscontinuePrescriptionCommandprescriberPrescriptionReason required; terminal
HoldPrescriptionCommandprescriber / pharmacistPrescriptionTemporary
ResumePrescriptionCommandprescriberPrescriptionFrom on-hold
OverrideAlertCommandprescriber / pharmacistAlertOverride≥ 10 char reason, KB snapshot
RecordAdministrationCommandnurseMedicationAdministrationMAR entry
StartReconciliationCommandprescriberReconciliationSessionOn admission/transfer/discharge
CompleteReconciliationCommandprescriberReconciliationSessionFreezes decisions
DispenseCommandpharmacistDispensingEventAtomic: dispense + inventory decrement; counter-sign if Schedule II
PartialDispenseCommandpharmacistDispensingEventRecords partial; status=in-progress
ReturnDispenseCommandpharmacistDispensingEventInventory credit, reason required
ReceiveStockCommandpharmacy technicianStockItemGRN with lot, expiry, supplier
TransferStockCommandpharmacy technicianStockItemNode-to-node transfer
AdjustStockCommandpharmacy technicianStockItemThreshold-gated supervisor approval
MarkLotRecalledCommandpharmacistStockItemBlocks future dispense

2. Use Cases (Queries)

QueryReturns
GetMedicationListQueryPer-patient medication list with status filters
GetPrescriptionQuerySingle prescription + associated dispenses + overrides
GetDispenseQueueQueryPharmacy queue filtered by node/status/priority
GetInventoryQueryStock items for a node, filterable by code/lot/expiry
GetReorderAlertsQueryItems below reorder point
GetExpiryAlertsQueryItems within expiry horizon
GetReconciliationSessionQuerySession + side-by-side diff
GetMarQueryMAR events per prescription or patient+window
GetControlledSubstanceLedgerQueryAll CS dispense events in tenant + window

3. Orchestration Flows

3.1 Prescribe + Sign

3.2 Dispense

3.3 Gateway MR Ingestion (inbound)

  1. NATS consumer receives eprescribing.medication_request.created.v1.
  2. Idempotent upsert into prescriptions row keyed by gateway_medication_request_fhir_id.
  3. Emit medication.fulfillment.queued.v1.

3.4 Reconciliation

  1. Start session on ADT admit/transfer/discharge event.
  2. Load home meds + facility meds.
  3. Prescriber reviews each — continue / discontinue / modify.
  4. Complete session — freeze, emit medication.reconciliation.completed.v1.

4. Ports (Application Interfaces)

PortPurpose
PrescriptionRepositoryCRUD for Prescription aggregate
DispenseRepositoryCRUD for DispensingEvent
StockItemRepositoryInventory persistence
ReconciliationSessionRepositorySession persistence
MedicationAdministrationRepositoryMAR entries
DrugKnowledgeBaseDrug-drug, drug-allergy, drug-condition, duplicate-therapy checks
DoseCalculatorPediatric/renal dose calculation
FormularyServiceTenant formulary lookup and substitution rules
FhirClientReads AllergyIntolerance, Condition from patient-chart-service
EprescribingGatewayClientPOST MedicationRequest / MedicationDispense to gateway
EventPublisherOutbox-backed NATS publisher
AuditClientEmits audit events
NcpdpScriptAdapter (optional)External pharmacy transmission when configured
LabelPrinterGenerates prescription / dispense labels
ClockPortInjectable time source

5. Saga / Outbox Patterns

ConcernPattern
Cross-tenant national interop (post to gateway)Outbox — local commit first, then async POST to gateway with retry + DLQ
Inventory decrement + dispenseSingle DB transaction — no saga; atomic
Gateway Rx ingestionInbox — idempotent consumer keyed by event id + resource id
External NCPDP transmission failureBounded retry with exponential backoff (max 3), then DLQ + prescriber notification
Dispense — billing chargeOutbox — medication.dispensing.completed.v1 — billing-service subscriber

6. Error Handling

SituationResponse
Blocking alert without override422 with alert array
Stale If-Match on prescription update412
Insufficient stock409 INSUFFICIENT_STOCK with emergency override hint
Dispense against expired/recalled lot409 LOT_UNAVAILABLE
Schedule II dispense without counter-sign403 COUNTER_SIGN_REQUIRED
Drug KB timeout503 degraded; safety-critical operations reject close
Cross-tenant reference403 CROSS_TENANT (no RLS leak)
Gateway post failureRetained in outbox, 5 retries, DLQ after