Pharmacy (workflow slice)
Two drug safety checks. Two points. One safety net with no gaps.
The prescriber writes the Rx. The pharmacist dispenses it. In between, something might have changed — a new prescription from a different provider, a controlled substance filled at a different pharmacy, the state PDMP report becoming stale. The pharmacy module ships the *workflow enforcement* at that seam: two checkpoints, four PRIORITY 100 rules, no gaps.
No drug-drug interaction database. No doctor-shopping pattern detection. No MME calculator. All FDA-regulated CDS or state-regulated content, both out of scope here. What's shipped is the *safety-net workflow* — the parts every state pharmacy board expects a pharmacist to *do*, not the parts that require a certified device to interpret.
The three views
Pending dispense — the pharmacist's queue. Every Rx received but not yet handed to the patient, with a PDMP-current indicator per controlled substance. Green = query in past 3 days, red = missing (dispense will be refused). Inline *PDMP query* + *Dispense* actions.
Prescriptions — full list of everything written for the tenant, with filters for status and controlled-only. EPCS enforcement fires at write time here — paper/verbal on a controlled substance is refused.
Refill requests — pending refill queue. Portal-source requests for controlled substances were refused at *creation*. Non-portal requests all live here; *Evaluate* runs the deterministic guardrails.
The four PRIORITY 100 rules
1. Controlled substance without current PDMP query (at dispense)
RULE controlled_without_pdmp {
WHEN PDMPContext.controlled_no_pdmp_query == true
THEN FLAG "controlled_substance_cannot_be_dispensed_without_PDMP_query"
SEVERITY critical
PRIORITY 100
}
Every Schedule II–V dispense refuses unless PdmpQuery records exist for the patient within the past 3 days. The window is configurable; 3 days matches the README's default. This fires *even if the prescriber checked PDMP at order entry* — three days is a long time in opioid prescribing.
2. Controlled substance on paper/verbal transmission (at write)
RULE controlled_must_be_electronic {
WHEN ERxContext.controlled_paper_not_electronic == true
THEN FLAG "controlled_substance_must_use_EPCS_paper_prescription_blocked"
SEVERITY critical
PRIORITY 100
}
create_prescription refuses at write time. EPCS is federal requirement (DEA 21 CFR Part 1311); the enforcement point is the earliest one — the prescriber, not the pharmacy.
3. Portal refill of a controlled substance (at request)
RULE controlled_portal_refill_blocked {
WHEN RefillPortalContext.refill_for_controlled_via_portal == true
THEN FLAG "controlled_substance_refill_via_portal_blocked_phone_or_office_required"
SEVERITY critical
PRIORITY 100
}
The patient portal cannot originate a controlled-substance refill. The patient must call or come in. Refuses at create_refill_request.
4. Controlled substance early refill (at evaluation)
RULE controlled_early_refill {
WHEN RefillContext.controlled_refill_early == true
THEN FLAG "controlled_substance_early_refill_blocked_state_law_compliance"
SEVERITY critical
PRIORITY 100
}
Any days remaining on a controlled substance refuses the refill and routes to pharmacist review. The 50-state variation on "how early" gets flattened into a single safe default: no early refill without a human deciding otherwise.
The 25% rule (warning, not block)
RULE refill_too_soon {
WHEN RefillContext.refill_too_soon == true
THEN FLAG "refill_too_soon_more_than_25pct_days_remaining"
SEVERITY warning
PRIORITY 85
}
Non-controlled refills refuse when > 25% of the last dispense's days supply remains. The prescriber can still authorize early — but the default answer is "wait." That's the difference between the warning class and the critical class: warnings can be overridden by the pharmacist; PRIORITY 100 blocks require a rule-level exception.
The two-checkpoint story
The pharmacy README's opener: *the prescriber checks drug interactions at order entry; the pharmacist checks at dispense.* This module owns the *workflow* half of that story — both events are logged as separate records, each with their own timestamp, each with the actor who performed them. The audit log tells you unambiguously who made which decision at which moment.
For the demo, this shows up as: a controlled substance Rx is written (Prescription record), a PDMP query is performed (PdmpQuery record), a dispense happens (Dispense record). Three writes to the audit log, three distinct workflow events, all inspectable in git.
What's wired
| Entity | Purpose | |---|---| | Prescription | The Rx itself — drug, quantity, days supply, controlled flag, EPCS-vs-paper | | PdmpQuery | Log of every PDMP query event (workflow only, no response interpretation) | | Dispense | Pharmacist-side dispense event, linked to the current PDMP query if applicable | | RefillRequest | Patient-initiated refill request with pending → approved/denied lifecycle |
Tools (10): list_prescriptions, create_prescription, record_pdmp_query, list_pdmp_queries, dispense_prescription, list_dispenses, create_refill_request, evaluate_refill_request, list_refill_requests, pharmacy_dashboard.
What's NOT wired (and why)
Deliberately excluded from this slice:
- Doctor-shopping pattern detection — requires interpreting PDMP response content across multiple prescribers. Clinical judgment on prescribing patterns. SaMD.
- Opioid + benzodiazepine detection — interaction judgment.
- MME threshold enforcement — clinical dosing math.
- Contraindicated drug interaction database — RxNorm cross-sensitivity + interaction rules = FDA-regulated CDS. Epic ships this.
- Formulary + prior authorization + step therapy — payer-specific business logic. Belongs in its own module.
- AI-generated PA request narratives — content generation slice.
The pitch is unambiguous: we do the *workflow* — the PDMP query event, the EPCS enforcement, the refill request lifecycle, the two-checkpoint dispense. We don't do the *content interpretation* that certified pharmacy systems have been shipping for decades. This module is the ops layer *above* the pharmacy system, not a replacement for it.