medOS ultra

Compounding Room Admixture System

Configurable sterile/IV compounding (USP 797/800) where every pillar is independently optional via a capability matrix.

10 min read diagramsUpdated 2026-05-28docs/architecture/compounding-room-admixture-system.md

Status: Design (2026-05-27). Not yet implemented. Builds on the worklist port in web/src/containers/compounding-room/worklist.tsx (route /pharmacy/compounding-room, kit @medical-kit/pharmacy-ipd/components/work-list with type="compounding-room").

One-liner: Upgrade the compounding-room from a generic single-product dispense dialog into a global-standard IV admixture / sterile compounding workspace — where every pillar is independently optional, driven by config, reusing the CDS engine, acknowledgement system, dose-pattern visualizers, and policy gates already in the repo.


1. Why — the gap vs. global standard

The ported worklist gives us the pipeline (screen → prepare → verify → dispense → return + stock linkage via createTransactionsWithStockAdjustment / createStockReturn). It is not at the standard of IV workflow management systems (BD Cato, Omnicell IVX, Baxter DoseEdge, PharmacyKeeper) or USP <797>/<800> / ASHP guidance. Those rest on four pillars the generic modal lacks, plus safety/documentation layers:

Pillar Global standard Current state
Recipe / formula Ingredient-level base + additives, dose/volume/concentration math Single product line
Barcode (BCMA) Scan every component before use None
Verification Gravimetric (weight) or volumetric, tolerance gated Manual screen/verify only
Photo documentation Step-by-step image capture for remote pharmacist check None
Beyond-use dating Auto BUD per USP category + stability None
Hazardous (USP 800) Hood/PPE/CSTD checklist, chemo segregation None
Independent double-check Forced for high-alert/chemo None
Bulk / batch Batch prep, cart batching, bulk verify One row at a time

Design stance: none of this is forced on. The default config reproduces today’s behavior exactly; each capability is an opt-in toggle so a small clinic, a teaching hospital, and a chemo center each get a different shape from the same code.


2. Configuration-first — compounding_config capability matrix

A single per-scope config object gates everything. Resolution order (most specific wins): deptfacilityglobal → built-in default. Stored in Supabase compounding_configs (read-model / live-editable), authored at /admin/compounding-config (new admin page, same shape as /admin/cds-rules and /admin/policy-gates).

interface CompoundingConfig {
  scope: 'global' | 'facility' | 'dept';
  scopeId?: string;

  // Each pillar independently optional. All default false except `recipe`.
  capabilities: {
    recipe: boolean;            // ingredient breakdown + calc (default true)
    barcodeVerify: boolean;     // BCMA scan of each component
    gravimetric: boolean;       // weight-based verification
    volumetric: boolean;        // volume-based verification
    photoCapture: boolean;      // per-step image documentation
    beyondUseDate: boolean;     // auto BUD + stability
    hazardous: boolean;         // USP 800 checklist (hood/PPE/CSTD)
    independentCheck: boolean;  // forced double-check (see policy gate)
    bulk: boolean;              // batch/cart bulk processing
    aiAssist: boolean;          // Ollama recommender (per-tenant flag, off by default)
  };

  workMode: 'standard' | 'supervised' | 'throughput' | 'simulation';

  // Tunables (all optional, sane fallbacks)
  gravimetricTolerancePct?: number;   // default 5
  budRules?: BudRulePack;             // USP category → hours; per-region overridable
  requiredSteps?: CompoundingStep[];   // ordered; subset of the pillar steps
  doubleCheckDrugClasses?: string[];   // e.g. ['chemo','tpn','high-alert']
  icons?: Record<CompoundingStep, string>; // override step icons per tenant
}

The worklist reads this config once (new hook useCompoundingConfig) and turns each modal step on/off. A step that is false is not rendered and not required — the completeness ring and save-guard ignore it.


3. Data model — canonical vs. read-model

Follows the repo’s two-layer rule (see ipd-dispense-cycles.md): the medication domain service (NestJS, Mongo) is canonical truth; Supabase is the realtime/collab projection.

Canonical (MongoDB, medication service) — extend the existing dispense flow, do not fork it. New subdocument compounding on ProductDispense / MedicationRequest:

compounding?: {
  formula: { base: IngredientLine; additives: IngredientLine[] };  // qty, unit, concentration, lot, expiry
  verification: { method: 'gravimetric'|'volumetric'|'visual'; steps: VerificationStep[] };
  photos: { step: CompoundingStep; fileId: string }[];             // filestore/IPFS ids
  beyondUseAt?: string;
  hazardClass?: 'none'|'usp800'|'chemo';
  preparedBy?: Ref; verifiedBy?: Ref; checkedBy?: Ref;             // independent check
  batchId?: string;                                                 // bulk grouping
}

Read-model (Supabase, projected by inventory-orchestrator / encounter-orchestrator):

Table Purpose
compounding_configs the capability matrix above (authored, not projected)
compounding_worksheets realtime worklist projection + presence (cursors / per-row edit-lock, dynamic-sheet PresenceProvider pattern)
compounding_verification_log append-only audit (who/what/when per step + photo + scan + weight)

No frontend writes to read-model tables — writes go through the medication service; the orchestrator projects to Supabase; the worklist subscribes via department_queues (dept_type='compounding-room') for realtime refresh.


4. Hooks (mirrors periops-kit perioperative-status-kit)

Pure calculation + a `` each, sharing one 30 s tick via useNow (reuse packages/periops-kit/.../perioperative-status/useNow.ts). New module packages/medical-kit/src/compounding/hooks/:

Hook Computes Drives
useCompoundingConfig(scope) resolved capability matrix which steps render at all
useCompoundingFormula(order) ingredient lines from order + drug master recipe table
useDoseCalc(formula) dose ↔ volume ↔ concentration (final conc, total volume, overfill) calc panel + warnings
useGravimetricCheck(expected, measured, tol) within/over/under tolerance tolerance dial badge
useBeyondUseDate(formula, budRules) BUD timestamp + countdown expiry badge / ticker
useCompoundingCompleteness(steps, config) % of required (config-gated) steps done `` + save-guard
useBulkSelection(rows) multi-select + per-action eligibility bulk toolbar
useHazardChecklist(config) USP 800 checklist state hood/PPE/CSTD badges

Calc lives in a framework-agnostic packages/medical-kit/src/compounding/calc/ (unit conversion, concentration math, overfill, BUD tables) so it is unit-testable and reusable backend-side. Hooks are thin wrappers over the calc module + useNow.


5. Visualization + icons

Reuse, don’t reinvent:

  • Dose patterns — reuse ui-kit/.../dynamic-scheduler-2/summary-container/TimePerDay.tsx
    • dosePatternKind.ts for infusion/bolus/continuous visualization in the modal summary.
  • New compounding visuals (small additions in the compounding module):
    • bag/syringe fill gauge (volume vs. container capacity),
    • concentration bar (mg/mL with safe-range band),
    • gravimetric tolerance dial (green / amber over / red under),
    • step-progress tracker + photo gallery per step,
    • `` (reuse periops-kit) in the worklist row and modal footer.

Icons — MUI v7 icon set, with per-step overrides via config.icons: ScienceOutlined (formula), QrCodeScannerOutlined (barcode), ScaleOutlined (gravimetric), PhotoCameraOutlined (photo), ScheduleOutlined (BUD), MasksOutlined/WarningAmberOutlined (hazardous), HowToRegOutlined (double-check), ChecklistOutlined (bulk). Steps that are config-off don’t render their icon.


6. Work modes (the teaching-hospital vs. real-hospital answer)

config.workMode reshapes the same modal — no fork:

Mode For Behavior
standard small clinic minimal: recipe + verify + dispense, double-check off
supervised teaching hospital technician/resident prepares → preceptor independent verify+sign always on; teaching annotations; competency capture to compounding_verification_log
throughput real / high-volume hospital cart/batch prep, barcode-first fast path, double-check forced only for doubleCheckDrugClasses, BUD enforced
simulation teaching / onboarding full UI, no real stock decrement, no canonical write — practice runs logged separately

Mode is a config field, so a teaching hospital can run supervised on the chemo dept and throughput on general IV from the same deployment.


7. Roles + independent double-check (policy gate, not hardcoded)

Map to existing RBAC: technician (prepare), pharmacist (verify/release), preceptor (teaching co-sign), nurse (receipt). The independent-check rule is a policy gate row (policy_gates, /admin/policy-gates) keyed on drug risk class — not an if in the component. New gate triggers: compounding_release, compounding_independent_check, compounding_simulation_to_live. Gate hard_stop + policy_gate_overrides (override protocol, 20260527d) handle the rare break-glass with audit.

8. Allergy + CDS — no bespoke subsystem

Do not rebuild the old mockup’s allergy subsystem. Allergies are clinical → MongoDB canonical, projected to Supabase for realtime surfacing. Run ingredient allergy / incompatibility / dose-range checks through the existing CDS engine (src/services/cds/cdsEngineDb.ts + cdsDispatcher.ts, edge fn infrastructure/medbase/functions/evaluate-cds-rules, admin /admin/cds-rules). Each additive’s drug code is fed to CDS on formula change. Close the noted “allergy-vs-order conflict” CDS gap and add seed rules: incompatibility pairs, max-concentration, hazardous-handling-required. Config-editable, consistent with the rest of the system.

9. Alerting — reuse the alert surface + ack

Critical hits (allergy match, dose over-range, incompatibility, expired component) surface:

  • inline in the modal via InlineCdsAlertBadge (src/common/components/medical/surface/cds-alert/InlineCdsAlertBadge.tsx),
  • globally via CdsAlertSurface (FAB/drawer/toast),
  • as an AcknowledgementRequest (src/services/ever-foundation/acknowledgement-request/) with the existing escalation chain for criticals.

No new alerting plumbing.

10. Bulk processing

Gated by capabilities.bulk. useBulkSelection adds a worklist toolbar:

  • bulk screen / claim (assign self as screener across selected rows),
  • batch prepare — group selected orders sharing a base into one batchId, one prep session, shared photo set,
  • cart batching — reuse the dispense-cycle cart-batch model (ipd-dispense-cycles.md),
  • bulk verify/release — one independent-check action over a batch (policy gate still evaluated per row; any blocked row drops out of the batch with a reason).

Stock for a batch is reserved/decremented through the inventory-orchestrator edge fn (bulk path) rather than N single calls. Bulk actions are append-only logged per row in compounding_verification_log so a batch never hides an individual failure.

11. AI / Ollama — recommender-only, hard safety line

Gated by capabilities.aiAssist + the per-tenant LLM feature flag. Follows the repo’s established AI stance (Revenue Platform Master Plan + e-Kardex): recommender-only, async, never blocking, no PHI/free-text in prompts (structured codes only), per-inference audit, named clinical owner. Allowed:

  • draft the compounding worksheet from the order (human edits/accepts),
  • suggest the dose-pattern kind + a second-opinion incompatibility note shown alongside (never replacing) the deterministic CDS rule,
  • optional advisory vision-OCR on the prep photo (“looks like the wrong vial”) — hint only.

Hard rules: AI cannot release, cannot gate, cannot hide/demote a CDS alert. Gravimetric / barcode / dose verification stay 100% deterministic — AI never closes a verification step or a release.


12. Phased rollout (every phase independently shippable + optional)

Phase Scope Demo-ready?
P0 compounding_configs table + useCompoundingConfig + /admin/compounding-config (everything defaults to today’s behavior) infra
P1 Recipe modal: useCompoundingFormula + useDoseCalc + calc module + recipe table/visuals (gated by capabilities.recipe)
P2 CDS allergy/incompatibility wiring + alert surface + ack (seed rules)
P3 BUD (useBeyondUseDate) + completeness ring + save-guard
P4 Verification: gravimetric/volumetric + barcode (BCMA) + tolerance dial + compounding_verification_log
P5 Photo capture per step (filestore/IPFS) + gallery
P6 Work modes (supervised/throughput/simulation) + independent-check policy gates
P7 Bulk: useBulkSelection + batch prepare + cart batching + inventory-orchestrator bulk path
P8 USP 800 hazardous checklist (useHazardChecklist) + chemo segregation
P9 AI/Ollama recommender (worksheet draft, second-opinion, vision hint) — behind flag + compliance preconditions

P1+P2+P3 = the high-value, demo-ready first slice. Each phase reads its own capabilities.* flag, so partial deployment is always valid.


13. File plan (new module — additive, nothing existing rewritten)

web/packages/medical-kit/src/compounding/
├── calc/                       # framework-agnostic: unit conv, concentration, overfill, BUD tables
│   └── index.ts
├── hooks/                      # useCompoundingConfig / Formula / DoseCalc / GravimetricCheck / BeyondUseDate / Completeness / BulkSelection / HazardChecklist
├── components/
│   ├── CompoundingModal.tsx    # config-driven step host (renders only enabled steps)
│   ├── RecipeTable.tsx
│   ├── visuals/                # FillGauge / ConcentrationBar / GravimetricDial / StepTracker / PhotoGallery
│   └── BulkToolbar.tsx
└── index.ts

# wiring (small edits, not rewrites):
web/.../pharmacy-ipd/components/work-list/index.tsx   # mount CompoundingModal when usingPage==='pharmacy' && config gates it
infrastructure/medbase/migrations/2026XXXX_compounding.sql   # compounding_configs + worksheets + verification_log
infrastructure/medbase/functions/inventory-orchestrator      # bulk stock path (extend)
infrastructure/medbase/functions/evaluate-cds-rules          # compounding seed rules (extend)
services/medication/.../   # ProductDispense.compounding subdoc + release action
web/src/common/components/super-admin/compounding-config/CompoundingConfigPage.tsx

The current dispense modal remains the fallback when all capabilities.* are off — i.e. zero behavior change unless a tenant opts in.


14. Open questions

  1. Gravimetric verification needs a hardware scale integration (serial/HID/network) — in scope now, or manual weight entry first and device adapter later?
  2. Barcode source — GS1 DataMatrix on vials vs. internal SKU barcodes; which master?
  3. BUD rule pack: ship USP <797> default categories, or require per-tenant authoring from day one (per-region like the kaigo/philhealth packs)?
  4. Photo storage: filestore/IPFS (existing) vs. a dedicated WORM bucket for compounding evidence retention.
  5. Does simulation mode need a separate competency/grading model, or is the compounding_verification_log enough for teaching sign-off?
Ask Anything