Medication Service — Application Logic
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template
1. Use Cases (Commands)
| Command | Actor | Aggregate | Notes |
|---|---|---|---|
DraftPrescriptionCommand | prescriber | Prescription | Creates draft; no alerts yet |
SignPrescriptionCommand | prescriber | Prescription | Runs safety checks; emits alert overrides; persists MedicationRequest; optionally posts to gateway |
DiscontinuePrescriptionCommand | prescriber | Prescription | Reason required; terminal |
HoldPrescriptionCommand | prescriber / pharmacist | Prescription | Temporary |
ResumePrescriptionCommand | prescriber | Prescription | From on-hold |
OverrideAlertCommand | prescriber / pharmacist | AlertOverride | ≥ 10 char reason, KB snapshot |
RecordAdministrationCommand | nurse | MedicationAdministration | MAR entry |
StartReconciliationCommand | prescriber | ReconciliationSession | On admission/transfer/discharge |
CompleteReconciliationCommand | prescriber | ReconciliationSession | Freezes decisions |
DispenseCommand | pharmacist | DispensingEvent | Atomic: dispense + inventory decrement; counter-sign if Schedule II |
PartialDispenseCommand | pharmacist | DispensingEvent | Records partial; status=in-progress |
ReturnDispenseCommand | pharmacist | DispensingEvent | Inventory credit, reason required |
ReceiveStockCommand | pharmacy technician | StockItem | GRN with lot, expiry, supplier |
TransferStockCommand | pharmacy technician | StockItem | Node-to-node transfer |
AdjustStockCommand | pharmacy technician | StockItem | Threshold-gated supervisor approval |
MarkLotRecalledCommand | pharmacist | StockItem | Blocks future dispense |
2. Use Cases (Queries)
| Query | Returns |
|---|---|
GetMedicationListQuery | Per-patient medication list with status filters |
GetPrescriptionQuery | Single prescription + associated dispenses + overrides |
GetDispenseQueueQuery | Pharmacy queue filtered by node/status/priority |
GetInventoryQuery | Stock items for a node, filterable by code/lot/expiry |
GetReorderAlertsQuery | Items below reorder point |
GetExpiryAlertsQuery | Items within expiry horizon |
GetReconciliationSessionQuery | Session + side-by-side diff |
GetMarQuery | MAR events per prescription or patient+window |
GetControlledSubstanceLedgerQuery | All CS dispense events in tenant + window |
3. Orchestration Flows
3.1 Prescribe + Sign
3.2 Dispense
3.3 Gateway MR Ingestion (inbound)
- NATS consumer receives
eprescribing.medication_request.created.v1. - Idempotent upsert into
prescriptionsrow keyed bygateway_medication_request_fhir_id. - Emit
medication.fulfillment.queued.v1.
3.4 Reconciliation
- Start session on ADT admit/transfer/discharge event.
- Load home meds + facility meds.
- Prescriber reviews each — continue / discontinue / modify.
- Complete session — freeze, emit
medication.reconciliation.completed.v1.
4. Ports (Application Interfaces)
| Port | Purpose |
|---|---|
PrescriptionRepository | CRUD for Prescription aggregate |
DispenseRepository | CRUD for DispensingEvent |
StockItemRepository | Inventory persistence |
ReconciliationSessionRepository | Session persistence |
MedicationAdministrationRepository | MAR entries |
DrugKnowledgeBase | Drug-drug, drug-allergy, drug-condition, duplicate-therapy checks |
DoseCalculator | Pediatric/renal dose calculation |
FormularyService | Tenant formulary lookup and substitution rules |
FhirClient | Reads AllergyIntolerance, Condition from patient-chart-service |
EprescribingGatewayClient | POST MedicationRequest / MedicationDispense to gateway |
EventPublisher | Outbox-backed NATS publisher |
AuditClient | Emits audit events |
NcpdpScriptAdapter (optional) | External pharmacy transmission when configured |
LabelPrinter | Generates prescription / dispense labels |
ClockPort | Injectable time source |
5. Saga / Outbox Patterns
| Concern | Pattern |
|---|---|
| Cross-tenant national interop (post to gateway) | Outbox — local commit first, then async POST to gateway with retry + DLQ |
| Inventory decrement + dispense | Single DB transaction — no saga; atomic |
| Gateway Rx ingestion | Inbox — idempotent consumer keyed by event id + resource id |
| External NCPDP transmission failure | Bounded retry with exponential backoff (max 3), then DLQ + prescriber notification |
| Dispense — billing charge | Outbox — medication.dispensing.completed.v1 — billing-service subscriber |
6. Error Handling
| Situation | Response |
|---|---|
| Blocking alert without override | 422 with alert array |
Stale If-Match on prescription update | 412 |
| Insufficient stock | 409 INSUFFICIENT_STOCK with emergency override hint |
| Dispense against expired/recalled lot | 409 LOT_UNAVAILABLE |
| Schedule II dispense without counter-sign | 403 COUNTER_SIGN_REQUIRED |
| Drug KB timeout | 503 degraded; safety-critical operations reject close |
| Cross-tenant reference | 403 CROSS_TENANT (no RLS leak) |
| Gateway post failure | Retained in outbox, 5 retries, DLQ after |