Hospital Decision Twin
Operational/strategic decision-intelligence: seeded DES/Monte-Carlo engine, 7 live agents, Readers boundary, recommender-first.
Status: P0–P5 substantially SHIPPED (2026-06-04). The
twinHORUS lens (web/packages/intelligence-kit/src/horus/twin/, 32 files): seeded DES/Monte-Carlo engine, 7 live agents,Readersboundary (OQ-11), rule-row constraints + pruning (recommends 10 beds, budget-capped from 12), the optimizer + Guide outputs (readiness / investment memo / execution roadmap), scenario comparison (base/surge/worst), Capture (templates + brief + New-Decision wizard + stakeholders), Analyze (data readiness & coverage), the HITL decision gate (4 sign-offs release optimized→decided — the device firewall), a recommender-only narrative (Ollama, fail-soft), and a Portfolio Command preview — all on seed data. Persistence: migrations20260604a/b/c(sync-health + integrity +bed_occupancy_spans+twin_*). Packaging:infrastructure/modules/hospital-twin/module.json. Demo:?target=Twin(or?target=HorusShell&lens=twin). Verified:tsc --strictclean (32 files) + engine/constraint runtime smoke (coherent + deterministic) + sandbox render smoke (0 console errors). Remaining (deploy + depth): live MedOS per-metric RPCs, AcknowledgementRequest fan-out + audit-lineage UI, standard ERP/HRIS connectors + full crosswalks, country rollup edge fn + market-pack rule rows, back-test harness + recalibration cron — see §14. Host decision: a new HORUS lens (twin) inside@ever-medos/intelligence-kit, not a new package — see §5. Read before any twin / simulation / capacity-optimization / decision-intelligence work, and read alongside horus-intelligence-ai-platform-master.md, modular-multicountry-deployment.md, and fpa-data-warehouse.md.
0. The one-paragraph thesis
“Innovera for hospitals” is not “AI recommends treatments.” Innovera’s atom is a business initiative under uncertainty, so the faithful hospital translation is the hospital’s strategic and operational initiatives — open a step-down unit, add a cath lab, renegotiate an NHSO/UCS scheme, stand up a dengue-surge plan, in-source vs. regionalize blood supply. Commercially this is close to one product: the buyer is the owner / C-suite, the pitch is the same de-risk-the-bet pitch, and in a for-profit hospital even a clinical pathway carries a P&L — so “business initiative” describes almost every decision in the building.
There is an inversion that runs in our favor. Innovera applies stochastic programming to words because its inputs are qualitative — it models opportunities it has no hard data on. We have the opposite problem: MedOS already emits the structured substrate (bed-state logs, vitals hypertables, OR costing, claims facts). So a hospital twin applies stochastic optimization to real operational data, which is more defensible and lands much closer to classical operations research — OR scheduling, bed/queue management, and blood inventory are textbook stochastic problems. The hospital instance is arguably a stronger version of the Innovera thesis than the original.
The one place the framing needed correction: the regulatory boundary does not track business-vs-clinical. It tracks the shape of the output. That single guardrail — and why it’s an asset, not a tax — is §1.
1. The atom and the device line
1.1 The atom: a hospital initiative under uncertainty
A twin is always anchored to a decision a manager is trying to make, not a patient. Canonical examples:
| Initiative kind | Decision variable(s) | Objective | Owner |
|---|---|---|---|
capacity |
open unit? bed count, phased ramp | risk-adjusted NPV ↑, boarding hours ↓ | COO / dept head |
staffing |
nurse/physician FTE per shift, roster mix | overtime cost ↓, coverage ↑ | nurse manager |
supply |
reorder points, in-source vs regionalize | stock-out risk ↓, wastage ↓ | blood-bank / pharmacy lead |
scheme |
accept/renegotiate payor terms | margin ↑, denial rate ↓ | revenue-cycle lead |
surge |
activate surge plan thresholds, escalation tiers | unmet demand ↓, cost ↓ | incident commander |
capital |
buy equipment? lease vs own, sequencing | ROI ↑, payback ↓ | CFO |
service-line |
expand / launch a service line | contribution margin ↑ | service-line director |
1.2 The axis that matters: output shape, not business-vs-clinical
The device line runs along the shape of the output, not whether money rides on it. Commercial weight is orthogonal — pushing a decision “more commercial” slides it sideways, not across the line.
OUTPUT SHAPE ▲ population / resource-level output ┌── THIS PRODUCT lives on the whole top band ──┐
(the device │ ┌────────────────────────┬────────────────────────┐
axis) │ │ open a cath lab, │ set bundle pricing, │ NON-DEVICE
│ │ plan the surge │ optimize the payer mix │ (ops / strategy / capital)
── DEVICE LINE ┤ ├────────────────────────┼────────────────────────┤
│ │ patient-level clinical │ patient-level │ DEVICE territory.
│ │ recommendation │ MARGIN-AWARE nudge │ bottom-right detonates:
│ │ (may fit CDS carve-out) │ (fails carve-out by def)│ trust + liability + SaMD
│ └────────────────────────┴────────────────────────┘
▼ individual, named-patient output
clinical framing ◀──────── COMMERCIAL WEIGHT ────────▶ commercial framing
(orthogonal — slides sideways, never across the line)
- Top band (population / resource): open a cath lab, plan the surge, set bundle pricing, optimize the payer mix. Non-device. This is the entire product.
- Bottom-left (patient-level, clinically framed): a transparent, non-time-critical recommendation a clinician can independently review can stay a non-device — the Cures Act clinical-decision-support carve-out in the US, with comparable (generally stricter) lines in the EU (MDR Rule 11), Thai FDA, and PMDA.
- Bottom-right (patient-level, margin-aware): the danger quadrant. The margin-optimizing version fails the carve-out almost by definition, because the “basis” it can’t expose to the clinician is the hospital’s margin. It’s not only regulated — it detonates clinician trust and your liability exposure. Being a for-profit hospital makes a margin-aware patient-level nudge look worse, not better.
1.3 Why the guardrail is an asset, not a tax
The clinical/commercial fusion is an asset on the input side and a liability only on the output side. Hold that distinction and you get the best of both:
- Clinical signals are exactly what make a private-hospital twin good — let them all in as inputs. Acuity, pathway adherence, complication and readmission rates feed throughput, margin, and capital outputs. You lose nothing by ingesting them; you gain the fidelity that makes the twin worth buying.
- Holding the output line buys four things: speed (ship the strategy/ops product now, with no device clearance on the critical path), adoption (clinicians won’t touch a tool that visibly optimizes their orders for the house), liability containment, and the option to spin up a separately-cleared clinical track later if the market pulls you there.
The line isn’t a concession to public-sector squeamishness; it’s the thing that lets the commercial product move fast.
1.4 The firewall (hard invariants)
- Outputs are population / resource-level only. Every twin output is about beds, rooms, staff, supply, schemes, pricing, capital, thresholds — never “what to do for this named patient.” Clinical signals are welcome as inputs; they may never appear as a patient-directed output.
- Cohort, not patient. Where the twin reads acuity (e.g. EWS from
vital_signs_ts), it does so only to size an eligible cohort for capacity/throughput math — never to triage, prioritize, or route an individual. - Read-only on clinical data. The twin reads read-model tables and writes only to
twin_*tables. Never toencounter_journey_cache,department_queues, clinical records, or any order. - AI is recommender-only. Every number is computed deterministically by the simulator/optimizer; the LLM writes narrative + second-opinion commentary, never decides a number and never gates a decision. (Mirrors
disease/progression.ts.) - Human sign-off is mandatory. No recommendation becomes a “decision” without the required expert sign-offs. This is the SaMD firewall — the human owns the call.
- De-identified across boundaries. Calibration runs on in-region data; only de-identified aggregates cross a tier boundary (Invariant 6 of modular-multicountry-deployment.md).
⚠️ Conceptual map, not legal advice. The real classification is jurisdiction-specific across all six countries (TH/PH/JP/CN/US/EN). A private-hospital product wants a device-regulatory read before committing the architecture (see OQ-6). Conceptually, “commercially identical, with one output guardrail” is the right way to hold it.
2. Concept map (the three colors)
┌───────────────────────────────────────────────────────┐
PURPLE (machinery) │ CAPTURE ─▶ ANALYZE ─▶ MODEL ─▶ GUIDE ─▶ (PORTFOLIO) │
the journey + engine │ twin engine: DES + Monte-Carlo + optimizer │
└───────────────────────────▲───────────────────────────┘
│ compose over a shared clock
TEAL (the hospital ┌─────────┬─────────┬───────┴───┬─────────┬─────────┬─────────┐
modeled — 7 agents) │ Demand │Capacity │ Workforce │ Supply │ Reimb. │ Quality │ Compliance
│ &case-mix│ & flow │ │&pharmacy│ │ &safety │
└────▲────┴────▲────┴─────▲─────┴────▲────┴────▲────┴────▲────┴───▲──┘
│ │ │ │ │ │ │ calibrate()
GRAY (raw substrate) ┌────┴─────────┴──────────┴──────────┴─────────┴─────────┴────────┴──┐
the read-model tables │ bed_status_log · er_bed_stay_log · or_case_costing · vital_signs_ts │
(already emitted) │ observations · department_queues · queue_history · admission_log… · │
│ blood_* · stock_balance_cache · gold_fact_claims · gold_monthly_kpi │
└────────────────────────────────────────────────────────────────────┘
- Purple = the journey (Capture→Analyze→Model→Guide→Portfolio) + the optimization engine (net-new code).
- Teal = the hospital’s reality being modeled: seven domain agents (see §7.6).
- Gray = operational data MedOS already emits (read models).
3. Two products on one engine
The platform packages into two sellable products on a shared engine + governance layer:
| Layer | Product | Buyer | What it is |
|---|---|---|---|
| Initiative Workbench | one decision, end-to-end | teams, service lines, dept heads | the Capture→Analyze→Model→Guide spine for a single initiative |
| Portfolio Command | the roll-up + monitoring | C-suite / board | all initiatives by status/readiness/value-at-stake + actuals-vs-forecast |
| Twin engine (shared) | — | — | agents + DES/Monte-Carlo/optimizer; both products call it |
| Platform / governance (shared) | — | — | tenancy, residency/sovereignty, RBAC, model registry, audit/lineage |
This separation lets you sell the Workbench to a service line now and add Portfolio Command when a multi-site customer materializes — the engine and governance are common to both.
4. Product surface — ~40 screens across seven areas
The full inventory, grouped by the journey. Each screen is tagged by product — [W] Initiative Workbench · [P] Portfolio Command · [X] cross-cutting/Platform — and ★ marks the v1 cut (see §14). Everything untagged-★ can wait.
A · Workspace shell (cross-cutting) [X]
★Home / My work — decisions assigned to me, what needs my sign-off, recent runs- Global search — find any decision, twin, report, or source across the org
★Approvals & notifications inbox — sign-off requests, run-complete + drift alerts (reusesAcknowledgementInbox)- Org / facility / tier switcher — hospitals, countries, local→country→global tiers
- Account & preferences
B · Capture — decision intake & framing [W]
★New Decision wizard — pick a type, state the question★Template gallery — open a unit, add an OR/cath lab, surge plan, in-source vs. regionalize (blood, lab), payer/scheme renegotiation, capital equipment, service-line expansion, staffing-model change★Decision brief / canvas — objective, scope, constraints, success metrics, horizon, budget envelope, owner★Stakeholder & expert assignment — name the experts-in-the-loop + approvers per facet- Decision register — every framed decision, its status + owner (the queue)
C · Analyze — evidence & baseline [W]
- Data source connectors — toggle MedOS FHIR feeds + external sources; show what’s wired
- Evidence inbox — ingest market data, vendor quotes, internal docs; auto-extract figures (LLM-RAG, recommender-only)
★Data readiness & coverage — what’s available per facet, gaps, freshness, quality flags★Baseline snapshot — current-state metrics (occupancy, OR utilization, case-mix, payer/scheme mix, TAT, complication + readmission rates)★Assumptions register — explicit, editable assumptions feeding the model
D · Model — Twin Studio & simulation [W]
★Twin overview — the hospital twin map; the seven agent tiles + their status★Agent workspaces (one per facet) — inputs, model, levers, projected outputs (v1: 3 of 7 live)★Scenario builder — define base / surge / worst-case; set demand distributions + levers★Constraint & policy editor — staffing ratios, regulatory limits, scheme gates the twin must respect (rule rows)★Simulation runner — launch Monte-Carlo / DES runs; queue + progress★Scenario comparison — outcomes side-by-side- Sensitivity / driver analysis — tornado view of which variables move the result
★Expert validation — HITL sign-off; flag implausible outputs + lock the twin
E · Guide — decision outputs [W]
★Readiness Score — composite + per-facet sub-scores, each with rationale★Investment memo / business case — generated, editable document★Executive summary — the one-pager★Execution roadmap — phased plan, staffing ramp, milestones (Gantt)- Scenario playbook — contingency triggers + monitoring thresholds (if-then)
★Recommendation & decision log — the recommendation, the human’s decision, rationale, audit trail
F · Portfolio Command — portfolio & monitoring [P] (all deferred past v1)
- Portfolio dashboard — all initiatives by status, readiness, value-at-stake
- Risk & opportunity heatmap — initiative × risk, or facility × initiative
- Capital allocation — what’s funded, ROI tracking, sequencing
- Actuals vs. forecast — post-launch monitoring; alerts when reality diverges from the twin
- Cross-facility roll-up — multi-hospital + multi-country aggregation
- Board / governance pack — generate the governance deck
G · Governance & platform admin [X] (mostly deferred; only the minimum for v1 ships early)
- Org & facility management — tenants, jurisdictions, tier topology
- Data governance & sovereignty — residency, PDPA/APPI/HIPAA mapping, right-to-deletion (planned — via pseudonym-vault key erasure per ai-training-corpus.md; there is no crypto-shred / EVFS infrastructure in the repo today — “EVFS” exists only as a UI label string in the sovereign-genome miniapp, and crypto-shredding is explicitly disavowed in
order/addons/README.md) ★Users, roles & approvers — RBAC, expert rosters, approval chains (minimum viable)- Agent & model registry — which models/versions per jurisdiction + the SaMD-safe output-boundary config
★Audit & data lineage — every recommendation’s lineage + sign-off (the regulatory shield)- Integrations — MedOS/FHIR endpoints, external data sources
- Template & playbook library — manage decision templates + contingency playbooks
5. Where it lives (host + packaging)
Host = a new HORUS lens. HORUS is already the repo’s decision-intelligence surface, and its extension contract is one entry + one self-contained module:
// web/packages/intelligence-kit/src/horus/views.tsx (existing)
export interface HorusViewDef { id; label; labelTh; icon; Component: LazyExoticComponent }
export const HORUS_VIEWS: HorusViewDef[] = [ /* ask, kpi, insights, surveillance, fpa, capacity, queue, disease, atlas, security */ ]
Adding the twin = append { id: 'twin', … Component: lazy(() => import('./twin/TwinWorkspace')) } + drop a horus/twin/ module. Reuses the shell, theme, aggregate bar, cross-domain fact layer (network-facts.ts), the seed-fallback data pattern, and the SEIR stepSimulation engine as the DES template (§7). (Reality check from the recheck: network-facts.ts + most HORUS Ask domains are seed generators today — only the KPI branch reads live get_kpi_history — so the twin’s live readers/ are genuinely net-new; that’s exactly where the canonical-reader boundary of §6.2.1 belongs.)
web/packages/intelligence-kit/src/twin/ ← new module (additive; touches nothing existing)
├── TwinWorkspace.tsx # lens entry — the Capture→Analyze→Model→Guide wizard + panels (areas A–E)
├── registry.ts # TWIN_AGENTS registry (mirrors HORUS_VIEWS shape)
├── types.ts # Initiative, TwinAgent, Scenario, RunResult, Distribution
├── engine/ # des.ts · montecarlo.ts · queueing.ts · optimize.ts · distributions.ts
├── agents/ # demand · capacity · workforce · supply · reimbursement · quality · compliance
├── readers/ # canonical-reader iface: MedOS read models OR migration-adapter output (+ seed fallback) — §6.2.1, OQ-11
├── narrative/ # Ollama recommender-only explanation (mirrors progression.ts)
├── guide/ # readiness score · memo · roadmap · decision log (area E)
└── seed/ # deterministic seed data (mulberry32) so the demo never blanks
Packaging (P4): a module infrastructure/modules/hospital-twin/module.json (requires: ['read-model-core', …], flags: { VITE_HOSPITAL_TWIN_ENABLED }), gated by moduleEnabled() like fpa-dashboard/entitlements.ts. Per-country differences (currency, scheme rules, ramp norms, jurisdiction limits) ship as market-pack rule rows, zero code (Invariant 5 of modular deploy). The two products map to two entitlement flags (…_WORKBENCH, …_PORTFOLIO) on the one module.
Sandbox: demo-able at http://localhost:5179/?target=HorusShell&lens=twin (register a TwinWorkspace target in web/sandbox/registry.ts).
6. Two data halves — native substrate (easy) + external integration (hard)
The clinical and flow data is the easy half: MedOS-native, you own the FHIR substrate, ingestion is basically free. The expensive half — HR, workforce, supply, accounting — lives in systems you don’t own (HRIS, payroll, ERP/GL, rostering, SCM, CMMS, contract mgmt), and across the SEA markets those are often local tools or spreadsheets with no clean API. So a large chunk of the build is connectors + file/manual ingestion + a normalization layer, not FHIR — the same “Odoo for healthcare” integration muscle, pointed at non-clinical data. But you are not starting that muscle from zero: a read-only source-adapter + an EHR-adapter framework, a HOSxP/JHCIS market-pack adapter registry, seed code crosswalks, and a dbt/Airflow transform spine already exist in the repo (§6.2.1) — the twin promotes and points them rather than inventing them.
Tags: [MedOS] native · [ext: System] external connector/file-load · (derived) computed by joining feeds. These are what the Data source connectors + Data readiness & coverage screens (§4·C) expose.
6.1 Native substrate (gray) — already in the read model
Verified against migrations. Two names from the original concept sketch do not exist and must be derived/created:
| Concept sketch said | Reality | Note |
|---|---|---|
vital_observations |
observations (20260512) + vital_signs_ts / lab_results_ts hypertables (021) + aggregates vitals_hourly/vitals_daily |
use these for acuity/biomarker series |
bed_occupancy_spans |
does not exist. bed_status_log is an append-only state machine with no duration column (043). |
P1 ships a bed_occupancy_spans view pairing consecutive rows into (bed, from→to, minutes). er_bed_stay_log already has a GENERATED duration_minutes. |
Native calibration tables each agent can already read:
| Agent | Native tables | Migration |
|---|---|---|
| Demand & case-mix | admission_log_cache, encounter_journey_cache, department_queues/queue_history, gold_* case-mix |
052 / 003 / 013 |
| Capacity & flow | bed_status_log→bed_occupancy_spans, er_bed_stay_log, or_room_runtime_state, procedure_day_queue |
043 / 20260526b / 20260514g / 079 |
| Workforce | staff_assignments, department_queues.assigned_to, user_tasks (thin — needs HRIS, OQ-7) |
20260515 |
| Supply & pharmacy | blood_inventory_movements, blood_dispense_events, blood_bag_disposition, stock_balance_cache, OR supply/implant line items |
20260325 / 034 / 20260424 |
| Reimbursement | gold_fact_claims, gold_monthly_kpi, gold_dim_scheme, or_case_costing, rcm_* |
013 / 084 / 010 |
| Quality & safety | vital_signs_ts/observations (EWS), cds_rule_evaluations, readmission via admission history — cohort-level only |
021 / 20260512 / 20260514b |
| Compliance | policy_gates, facility_billing_rule (designed), market-pack rule rows — enforces constraints, doesn’t generate demand |
20260425 |
6.2 The hard half — external feeds
| Source system | Carries | Why it’s hard |
|---|---|---|
| HRIS / credentialing | establishment, headcount, skill-mix, licenses, privileges | often local; PII-heavy; privileges gate whether a new service line can even be staffed |
| WFM / rostering | shift rosters, on-call | usually a separate scheduling tool |
| T&A | worked hours, OT, absence, agency/locum | separate clock system |
| Payroll | wage/OT rates, benefit loading, agency premiums | the most sensitive PII |
| ERP / GL | service-line P&L, cost centers, revenue net/gross, AR, CapEx | canonical money truth; bespoke chart-of-accounts per hospital |
| SCM / materials mgmt | procurement, lead times, prices, par levels | vendor-specific |
| CMMS | asset register, maintenance, downtime | often offline |
| contract mgmt | payer terms, bundle terms, escalators | frequently documents, not data |
| incident / survey / registries / MoPH / census / market intel | safety events, experience, outcomes, epi, benchmarks | heterogeneous, external orgs |
6.2.1 What’s already built — don’t start the hard half from zero
The recheck (2026-06-04) overturns the “build connectors + a normalization layer from scratch” framing: ~half the integration muscle is already scaffolded in the repo. The twin’s job is to promote and point it, not invent it.
| Already in the repo | Where | What it gives the twin |
|---|---|---|
Read-only SQL source adapter (MySQL/MariaDB/MSSQL/PG/Oracle) — header literally reads READ-ONLY. Never writes to source system. |
sql.adapter.ts |
the non-disruptive extraction discipline §6.2 argues for — already coded |
EHR adapter framework → FHIR R4 (Epic / TrakCare / TakeCare / Cerner / Allscripts / Meditech / custom); syncMode: 'realtime' | 'polling' | 'bulk' | 'hybrid' + since incremental cursors |
ehr/adapter-framework.ts, ehrSync.controller.mixin.ts |
foreign HIS → canonical FHIR, with the realtime/incremental hooks already in the contract |
| Market-pack adapter registry — HOSxP 3.x + 4.x, JHCIS v2.x, oracle-custom schema-mapping packs (entityPriority, knownQuirks, complianceProfile, TIS-620 / Buddhist-era handling) | infrastructure/market-packs/adapters/ |
the “build the HOSxP adapter once → unlock most Thai public hospitals” wedge is already scaffolded |
| Crosswalk seeds — ATC→TMT (drug), ICD10→ICD10TM (dx) + ICD10/TMT validators | adapters/common/crosswalks/ |
partial coverage of the §6.4 code crosswalks (drug + ICD), already seeded |
| Claude-assisted mapping + reconcile / validation / dedup / dlq + country profiles (TH/JP/PH/SE: national-ID validators, Buddhist-era date-converter, TIS-620 encoding-handler) | services/migration/ claude/, modules/, country/ |
schema discovery, field mapping, dbt generation, dedup — the bootstrapping toil §6.4 / OQ-10 worried about |
| dbt + Airflow + gold medallion (staging→gold star schema for revenue/claims) | infrastructure/dbt/, infrastructure/airflow/dags/ |
an existing transform/semantic substrate to build the canonical layer on, not beside |
Connector + sync-stats substrate — migration_jobs (source/extracted/loaded/error + RAG green/yellow/red counts, 12-state status), migration_tenants (country_code ISO-2 + market_pack_code + tenant_id RLS), connector-registry/dispatch-log pattern (coding_connectors) |
017_migration_tables.sql, 021_migration_multi_tenant.sql, 041_coding_connectors.sql |
the twin’s Data readiness & coverage / ingestion-run stats — already persisted, already multi-country, already RLS-isolated; the twin reads it, doesn’t rebuild it |
Production sync appliance — ever-sync-adapter (sibling @ever repo, not in this tree): Electron edge node, HIS→MOPH-Cloud, live at 15 hospitals; read-only Direct-DB pull (Oracle/MySQL/MSSQL/PG) + per-mode field-map + normalize (BE→CE/datetime) + FHIR R5 transform; sync-stats = log_jobs (atomic-action + parent_run_id + trigger_source + retry/heartbeat/crash + deep-grain children) + immutable sync_events hash-chain (tamper-evident) |
sibling repo ever-sync-adapter (3× SQLcipher SQLite, Kysely) |
the production reference for the twin’s sync-stats + integrity model (supersedes the migration_jobs-only shape — OQ-12) and the Thailand instance of the per-country edge-adapter tier (multi-country) |
Net-new (genuinely absent — verified): CDC/Debezium, an object-store lake (MinIO), OMOP CDM, Great Expectations, ClickHouse/DuckDB. The clinical-canonical gap is OMOP; everything in the table above is reuse.
The delta, restated: (a) promote the migration/EHR adapters from one-time-ETL + on-demand-sync into a continuous live read-only overlay (the contract already supports realtime/since; what’s missing is running it as a loop); (b) build the semantic layer on the existing dbt star schema + crosswalk seeds, filling OMOP as the clinical-canonical gap. Much smaller than “from zero.”
Sync-stats are already multi-country — reuse, don’t fork. The twin’s connector + ingestion-run observability (its Data readiness & coverage screen, §4·C) maps almost 1:1 onto the migration appliance’s existing substrate: migration_jobs (per-run source/extracted/loaded/error + RAG green/yellow/red counts + 12-state status) + migration_tenants (country_code ISO-2, market_pack_code, tenant_id + RLS via app.tenant_id) + the connector-registry/dispatch-log pattern (coding_connectors). That’s already keyed by market-pack / country / tenant with row-level isolation — the exact two-axis + per-region-residency shape of §9. So twin_connectors/twin_ingestion_runs collapse to a twin-scoped view over it (per facet × connector × market-pack: coverage / freshness / error-rate), not a parallel store — and residency falls out for free (raw per-hospital sync rows stay behind tenant RLS in-region; only de-identified coverage/freshness aggregates roll up, Invariants 5/6). NetworkHub’s node-table / metrics / globe components are the visualization to feed — but it is a mock shell today, not a data source. Open sub-decision: generalize migration_jobs “migration job” → “ingestion run” semantics vs. a same-pattern sibling table (OQ-12).
The production reality — ever-sync-adapter (the @ever portfolio’s deployed sync engine). Health-data sync is not hypothetical in this ecosystem. ever-sync-adapter (sibling repo) is an Electron edge appliance live at 15 hospitals, syncing HIS → MOPH Cloud (Thailand’s national exchange) — read-only Direct-DB pull (Oracle/MySQL/MSSQL/PG) + per-mode field-map + normalize (BE→CE/datetime) + FHIR R5 transform. It is the outbound twin of the medOS migration service’s inbound (foreign-HIS→medOS) adapters — the same connector machinery, opposite direction (OQ-13) — and a production realization of the “promote the adapters to a continuous read-only overlay” delta above. Two consequences for this design:
- Adopt its sync-stats shape as canonical (revamps the OQ-12 lean). Its model is richer than
migration_jobs: a two-tierlog_jobs(atomic-action +parent_run_id+trigger_source+ retry/heartbeat/crash recovery + deep-grain per-file/per-batch/per-visit children) plus an immutablesync_eventshash-chain (prev_hash/record_hash, tamper-evident, ADR-060). The twin’s Data readiness & coverage model should mirror that — it adds an integrity dimension (hash-chain-verified) themigration_jobsshape lacks. - Multi-country falls straight out.
ever-sync-adapteris the Thailand instance of a per-country national-cloud sync adapter (PH→PhilHealth/DOH, JP→MHLW/kaigo, …) — each a local edge node that keeps raw data in-country (SQLcipher SQLite) and pushes only to its national cloud. So the twin consumes a per-(country × connector) sync-health aggregate emitted by each adapter — coverage / freshness / error-rate /hash-chain-verified— and never reaches into a local appliance store. That is exactly the §9 residency model (raw stays local; only de-identified aggregates roll up — Invariants 5/6). Integration is therefore a thin aggregate bridge, not coupling — the adapter already hassystem:health+system:metrics+ thelog_jobsaggregates to source it from. The model + the bridge are now specified in ever-sync-adapter-integration.md and shipped as migration20260604a(in medOS — the adapter repo is untouched).
The seam this exposes — lock it before more readers/ land. Because the EHR adapters already normalize foreign HIS → FHIR R4 → MedOS, the twin’s readers/ (§5) should target a canonical-reader interface that resolves to either MedOS read models (native deploy) or a migration-adapter’s normalized output (foreign-HIS deploy) — never hard-binding the twin to raw MedOS table names. Otherwise every reader silently welds the twin to MedOS and the “sit on HOSxP first, sell the migration later” wedge dies in the data layer. The migration-adapter framework is the natural place to put that boundary. This is the one decision worth resolving before P1’s readers/ get written (OQ-11).
6.3 Three ingestion paths (plan for hospitals with no integratable systems)
Many SEA targets have no connectable HRIS/ERP — it’s local software or Excel. So every external feed needs three paths, surfaced in the Analyze screens:
- Standard connector — for the major ERPs/HRIS.
- CSV/Excel batch upload.
- Manual-entry fallback.
The Data readiness & coverage screen then honestly shows which facets are modelable and which run on stale or hand-entered data — and degrades derived KPIs accordingly (Invariant 7).
6.4 The unglamorous core — master-data crosswalks & a semantic layer
The real work is joins, not feeds. “Doctor workload” and “cost per case” don’t exist as a feed — they exist only as a join between MedOS activity and an HR/GL feed. Required crosswalks:
- Provider identity — clinical ID ↔ HR ID ↔ billing ID.
- Org hierarchy — cost center ↔ department ↔ ward ↔ service line.
- Codes — item, drug, payer-scheme, DRG/TDRG canonical mappings.
Build a canonical data model / semantic layer everything normalizes into (twin_crosswalks, §11). Expect this to be the bulk of the integration effort — and the gate on whether the Workforce and Reimbursement agents can compute anything at all.
Starting point (not zero): the migration service already seeds ATC→TMT (drug) + ICD10→ICD10TM (diagnosis) crosswalks and ships a claude-assisted schemaAnalyzer/fieldMapper (§6.2.1). So drug + ICD code mapping starts warm; provider-identity, cost-center→ward→service-line, payer-scheme, and DRG/TDRG are the genuinely net-new maps — and those are exactly the ones the Workforce + Reimbursement agents gate on.
6.5 Minimize HR/payroll PII
Staff data is sensitive personal data under PDPA/APPI exactly like patient data. The twin doesn’t need it raw — it needs aggregates: FTEs, role-level cost rates, unit rosters, never individual payslips. Ingest at role/unit aggregation; keep individual compensation out of the twin entirely (Invariant 10). Smaller compliance surface, and the right governance posture for the sovereign architecture.
6.6 Per-agent data-source matrix
The artifact you hand an integration team. Type: N=native [MedOS] · E=external connector/file · D=derived (join) · M=mixed · C=config.
| Agent | Feed | Source | Type | Freq | v1 |
|---|---|---|---|---|---|
| Demand & case-mix | Historical encounter volumes | MedOS ADT/OPD/ED/IPD | N | daily | ★ |
| Demand & case-mix | Case-mix & coding | MedOS DRG/TDRG/ICD-10 | N | daily | ★ |
| Demand & case-mix | Scheduled/booked demand | MedOS bookings/appts/waitlist | N | near-RT | ★ |
| Demand & case-mix | Referrals in/out | MedOS + ext: referral network | M | daily | |
| Demand & case-mix | Epidemiology & seasonality | ext: MoPH/weather/calendars | E | daily | |
| Demand & case-mix | Catchment & demographics | ext: census/market | E | quarterly | |
| Capacity & flow | Bed inventory & occupancy | MedOS bed_occupancy_spans |
N | near-RT | ★ |
| Capacity & flow | LOS & discharge | MedOS | N | daily | ★ |
| Capacity & flow | OR/procedure utilization (incl. cath/endo) | MedOS | N | daily | ★ |
| Capacity & flow | ED flow (door-to-doc, boarding, LWBS) | MedOS | N | near-RT | |
| Capacity & flow | Ancillary TAT (lab/imaging/pharmacy) | MedOS | N | near-RT | |
| Capacity & flow | Equipment/room availability | MedOS + ext: RTLS/asset | M | near-RT | |
| Capacity & flow | Transfers (internal + inter-facility) | MedOS | N | near-RT | |
| Workforce | Establishment & headcount | ext: HRIS | E | monthly | ★ |
| Workforce | Credentials & privileges | ext: HRIS/credentialing | E | monthly | ★ |
| Workforce | Shift rosters & on-call | ext: WFM/rostering | E | weekly | ★ |
| Workforce | Time & attendance (actuals) | ext: T&A | E | weekly | |
| Workforce | Labor cost rates (role-aggregated) | ext: payroll | E | monthly | ★ |
| Workforce | Provider workload/productivity | MedOS ÷ HRIS FTE | D | daily | ★ |
| Workforce | Vacancy/attrition/burnout | ext: HRIS + derived | M | monthly | |
| Workforce | Training/competency pipeline | ext: HRIS/L&D | E | quarterly | |
| Supply & pharmacy | Inventory & consumption | MedOS + ext: materials mgmt | M | daily | |
| Supply & pharmacy | Blood bank inventory & usage | MedOS | N | near-RT | |
| Supply & pharmacy | Pharmacy/formulary usage | MedOS | N | daily | |
| Supply & pharmacy | Procurement & lead times | ext: ERP/SCM | E | weekly | |
| Supply & pharmacy | Consumable cost per case | ext: ERP × MedOS | D | daily | |
| Supply & pharmacy | Asset register & maintenance | ext: CMMS + finance | E | monthly | |
| Reimbursement | Claims & coding (43-files) | MedOS | N | daily | ★ |
| Reimbursement | Payer/scheme mix | MedOS + ext: contract mgmt | M | daily | ★ |
| Reimbursement | Fee schedules & DRG weights | ext: ref tables (NHSO/TDRG) | E | on release | ★ |
| Reimbursement | Service-line P&L / cost accounting | ext: ERP/GL | E | monthly | ★ |
| Reimbursement | Revenue, net vs gross | ext: ERP/GL + MedOS billing | M | monthly | ★ |
| Reimbursement | AR & cash | ext: finance/AR | E | weekly | |
| Reimbursement | Capital budget & CapEx pipeline | ext: finance | E | quarterly | |
| Reimbursement | Contract terms | ext: contract mgmt | E | on event | |
| Quality & safety | Outcomes (mortality/cx/readmit/HAI/falls) | MedOS + ext: registries | M | monthly | |
| Quality & safety | Deterioration/early warning (NEWS2/PEWS/MEOWS) | MedOS vital_signs_ts/observations |
N | near-RT | |
| Quality & safety | Incidents & safety events | ext: incident-reporting | E | on event | |
| Quality & safety | Patient experience | ext: survey tool | E | monthly | |
| Quality & safety | Quality/accreditation indicators (JCI/HA) | MedOS + ext: quality system | M | monthly | |
| Quality & safety | Pathway variation (order-set adherence) | MedOS | D | monthly | |
| Compliance | Jurisdiction rule set (PDPA/APPI/HIPAA) | config/regulatory KB | C | on release | |
| Compliance | Licensing & accreditation status | ext: admin | E | on event | |
| Compliance | Mandatory reporting (43-files/LIFE/kaigo) | MedOS + ext: regulatory | M | per cycle | |
| Compliance | Consent & data-sharing ledger | MedOS (consent ledger planned; no EVFS) | M | on event | |
| Compliance | Output/SaMD classification config | config | C | on release | |
| Cross-cutting | Strategic plan & targets | internal docs | E | on event | |
| Cross-cutting | Market & competitive intelligence | ext | E | quarterly | |
| Cross-cutting | Peer benchmarks | ext | E | quarterly | |
| Cross-cutting | Macro/policy & FX (6 countries) | ext | E | daily |
6.7 v1 externals (the single capacity decision)
For the step-down unit, everything clinical/flow comes free from MedOS; the must-add externals are exactly three:
- HR establishment + rosters — [ext: HRIS / WFM]
- Labor cost rates (role-aggregated) — [ext: payroll]
- GL service-line cost / P&L — [ext: ERP/GL]
Everything else phases in behind those. v1 ingestion for all three = CSV/Excel + manual fallback — no standard connector required to ship.
Pattern (HORUS house style): every reader is live read → deterministic seed fallback, with an explicit isLive/“estimated” badge. Demos never blank; estimates never masquerade as measured.
7. The engine (purple)
Net-new — the dig confirmed zero OR/simulation/optimization code or libraries in the repo (no glpk, lp-solver, jstat, simple-statistics, mathjs). The one existing forward model — the SEIR engine — establishes the house style we copy.
7.1 The template that already works
// web/packages/intelligence-kit/src/seir-globe/engine/seir-model.ts (existing, pure)
export function stepSimulation(nodes, edges, params): SeirNode[] {
// …advance each node one tick; couple nodes via weighted edges;
// history: [...node.history.slice(-89), newCompartments] ← rolling window
}
A pure step function + driver loop + parameter panel + forward-fold for curves + live-data seeding (SeirControlPanel.tsx, useSeirSurveillance.ts). The twin generalizes this from a fixed compartmental ODE to a discrete-event engine over composable agents.
7.2 Contracts
// twin/types.ts
type Rng = () => number; // mulberry32(seed) — like shared/seed.ts
type AgentId = 'demand'|'capacity'|'workforce'|'supply'|'reimbursement'|'quality'|'compliance';
interface Distribution { sample(rng: Rng): number } // empirical | lognormal | exp | poisson
interface SimEvent { t: number; kind: string; entity: string; payload?: unknown }
interface SimState { clock: number; resources: ResourcePool; queues: QueueState; metrics: Metrics }
interface TwinAgent<P = unknown> { // ← TEAL. one per subsystem.
id: AgentId;
calibrate(readers: Readers, scope: Scope): Promise<P>; // fit params from read-model history
seedEvents(p: P, sc: Scenario, horizon: number, rng: Rng): SimEvent[]; // e.g. Poisson arrivals
step(state: SimState, evt: SimEvent, p: P, rng: Rng): SimEvent[]; // advance + schedule follow-ons
kpis(state: SimState): Record<string, number>;
}
interface Initiative {
id: string; title: string; kind: 'capacity'|'staffing'|'supply'|'scheme'|'surge'|'capital'|'service-line';
scope: Scope; // facility, wards, service line, cohort def
decisionSpace: DecisionVar[]; // e.g. beds:int[0..12], ramp:Schedule
objective: RuleRow; // reuse policy-gate evalCondition/readPath
constraints: RuleRow[]; // budget, nurse:bed ratio, rooms, jurisdiction
scenarios: Scenario[]; // base | surge | worst
status: 'draft'|'analyzing'|'simulating'|'optimized'|'in_review'|'decided'|'monitoring'|'archived';
}
7.3 The three engine pieces
function runScenario(agents, params, sc, horizon, seed): RunResult // des.ts — pure + seeded
function monteCarlo(agents, params, sc, horizon, seeds[]): {p10;p50;p90;…} // montecarlo.ts — N runs
function erlangC(arrivalRate, serviceRate, servers): {waitProb; meanWait} // queueing.ts — fast bounds
7.4 Objectives & constraints are rule rows, not code
They reuse the shipped, pure predicate evaluators: policy-gate.service.ts evalCondition/readPath (dotted-path conditions over {candidate, result}); RUDS evaluate.ts evalPredicate + scoreDelta (recursive all/any + weighted accumulation ⇒ a natural weighted-objective evaluator); CDS token grammar (cdsEngineDb.ts) for numeric constraint lines.
7.5 The optimizer & where compute runs
function optimize(space, objective, constraints, simulate): RankedCandidate[]
// grid (small) → simulated annealing (ramps/mixed) → (Bayesian opt later); each candidate scored
// by monteCarlo() across scenarios; constraint-infeasible candidates pruned.
Output = ranked candidates with P10/P50/P90 risk bands + recommended phased plan + ROI. Compute runs in a Web Worker for P1–P2 (client-side, demo-friendly); the pure kernel ports to a Deno edge fn / microservice unchanged if runs get heavy (OQ-2).
✅ Shipped (2026-06-04): guide/score.ts recommend() sweeps the bed candidates, scores each by net value (revenue − boarding penalty − bed opex), and ranks with P10/P50/P90; engine/optimize.ts holds gridOptimize + simulatedAnnealing for larger spaces. The Guide generators (guide/{readiness,memo,roadmap}.ts) turn the recommendation into a readiness score + investment memo + phased ramp, rendered by GuidePanel.tsx. Runs client-side in the lens today.
7.6 The seven agents (teal)
| Agent | Models | v1? |
|---|---|---|
| Demand & case-mix | arrivals, case-mix, seasonality | ★ |
| Capacity & flow | beds, OR rooms, queues, LOS, boarding/diversion | ★ |
| Workforce | staffing, rosters, utilization, overtime | — |
| Supply & pharmacy | blood/drug/consumable stock, wastage, stock-out risk | — |
| Reimbursement | revenue/case, denial, payer mix, payment lag, ROI | ★ |
| Quality & safety | acuity, complication/readmission, pathway adherence — cohort sizing only, never per-patient output | — |
| Compliance | regulatory limits, scheme gates, policy constraints the twin must respect | — |
8. The journey (purple) — Capture → Analyze → Model → Guide → Portfolio
The user-facing journey wraps the engine’s internal phases:
| Journey (product) | Area | Engine phase | What happens |
|---|---|---|---|
| Capture | B | Frame | author the Initiative: type, question, objective, constraints, scope, experts |
| Analyze | C | (baseline) | connectors + evidence inbox; baseline snapshot; assumptions register |
| Model | D | Simulate | agents calibrate(); DES + Monte-Carlo run base/surge/worst ⇒ distributions; expert validation |
| Guide | E | Optimize + Decide | rank candidates; readiness score, memo, roadmap, decision log; sign-off releases the gate |
| Portfolio | F | Monitor | post-decision actuals-vs-forecast, drift alerts, cross-facility roll-up |
9. Three-tier rollup (the twin’s overlay on the platform’s per-region residency)
Framing correction (recheck 2026-06-04): the platform is two orthogonal axes — country-agnostic modules × per-country market packs (modular-multicountry-deployment.md §1) — not a literal local→country→global topology. The three tiers below are the twin’s own governance rollup, which reuses the platform’s per-region residency + de-identified-aggregate federation (Invariant 6 of that doc). The tiers are the twin’s; the residency they sit on is the platform’s.
| Tier | Where | Holds | Sees |
|---|---|---|---|
| Local node (ward/dept) | the facility’s in-region Supabase | twin_* tables, raw calibration on PHI-bearing read models |
initiative-level intelligence; PHI stays here |
| Country | country gold layer (twin_gold_initiative_outcomes, the gold_*/fpa_fact_* pattern) |
de-identified initiative kind, service line, scenario, predicted+realized KPIs — no patient rows | cross-facility benchmarking |
| Global (C-suite/board) | Portfolio Command on the fpa-dashboard surface, entitlement-gated |
portfolio of initiatives across countries | ROI-ranked portfolio, de-identified only |
Rollup is an additive edge function (mirrors gold-layer-refresh); it emits aggregates upward, never patient data.
10. Human-in-the-loop (the device firewall, made concrete)
Phase Guide reuses shipped infrastructure: acknowledgement_requests + the global AcknowledgementInbox FAB (fan out a sign-off request per role), and policy_gates (a twin_decide gate that blocks optimized → decided until all required sign-offs are present, hard_stop).
| Role | Validates | Keeps the twin honest about… |
|---|---|---|
| Medical director | acuity / cohort assumptions | clinical realism of cohort sizing |
| Nurse manager | staffing ratios, ramp feasibility | whether the plan is operable |
| Revenue-cycle lead | reimbursement, scheme rules, ROI inputs | the money math |
| Compliance officer | output-shape attestation (population/resource, never patient-directed) | that the initiative stayed on the top band |
Experts do double duty: keep the twin from hallucinating and their sign-off holds the product on the right side of the device line.
11. Persistence (writes only here)
twin_initiatives (id, title, kind, scope jsonb, decision_space jsonb, objective jsonb,
constraints jsonb, scenarios jsonb, status, created_by, created_at, updated_at)
twin_baselines (id, initiative_id, facet, snapshot jsonb, captured_at) -- Analyze
twin_assumptions (id, initiative_id, key, value, rationale, editable_by, updated_at)-- Analyze
twin_evidence (id, initiative_id, source, kind, extracted jsonb, ingested_at) -- Analyze (RAG)
twin_agent_params (id, initiative_id, agent_id, scope jsonb, params jsonb, fitted_at,
source_window tstzrange, is_live bool) -- calibration cache
twin_scenario_runs (id, initiative_id, candidate jsonb, scenario, seed bigint, kpis jsonb,
percentiles jsonb, created_at) -- reproducible, append-only
twin_recommendations (id, initiative_id, rank, candidate jsonb, score, risk_bands jsonb,
narrative text, readiness jsonb, created_at) -- Guide
twin_signoffs (id, initiative_id, role, user_id, facet, decision, attestation text,
ack_request_id, signed_at)
-- integration layer (the hard half — §6). REUSE the existing migration-appliance sync-stats
-- substrate; do NOT stand up a parallel store (§6.2.1, OQ-12). It is already multi-country + RLS:
-- • ingestion-run stats → `migration_jobs` (market_pack_code + hospital_code + tenant_id; source/
-- extracted/transformed/loaded/error counts; RAG green/yellow/red; 12-state
-- status; started/completed) — append-only run history, already per-country
-- • connector registry → `coding_connectors` / `migration_connector_manifests` pattern
-- (module/capability/provider/status/hospital_code) — extend, don't fork
-- • multi-country dim → `migration_tenants` (country_code ISO-2, market_pack_code, tenant_id,
-- RLS via app.tenant_id) — the residency boundary the twin tiers sit on (§9)
-- • canonical shape → mirror `ever-sync-adapter`'s `log_jobs` (operational) + `sync_events`
-- (tamper-evident hash-chain) — the production model (§6.2.1); adds an
-- integrity dim (hash-chain-verified). For edge-adapter-fed countries the
-- view's source is the adapter's emitted sync-health aggregate, NOT a local
-- store (thin bridge, not coupling — OQ-12/OQ-13)
twin_readiness_v (SHIPPED — migration 20260604a; VIEW over sync_health_aggregate, twin-scoped: per
(country, connector, facet) coverage / freshness / error-rate / hash-chain-verified
— Data readiness screen. Fed by per-country edge adapters' de-identified
SyncHealthSummary. Canonical raw model = sync_run + sync_event (hash-chain).
See ever-sync-adapter-integration.md)
twin_crosswalks (id, domain 'provider'|'cost_center'|'item'|'drug'|'payer_scheme'|'drg',
source_system, source_code, canonical_id, mapping jsonb, confidence) -- the semantic layer
-- seed from adapters/common/crosswalks/ (ATC→TMT, ICD10→ICD10TM); §6.2.1
twin_gold_initiative_outcomes (de-identified; initiative_kind, service_line, scenario,
predicted jsonb, realized jsonb, decision, facility_hash, period) -- country tier
All twin_*: RLS on, service-role writes, frontend reads via realtime. twin_scenario_runs is append-only (reproducibility/audit).
12. Worked example — the first twin: cardiac step-down unit
“Should Vajira open a 10-bed cardiac step-down unit, and how do we phase the staffing ramp?” — exercises every phase and output, and never touches an individual treatment decision (top band, §1.2).
- Capture. Decision space
{ open: bool, total_beds: 0..12, ramp: [(month, beds, nurses)] }. Objective = maximize risk-adjusted NPV; secondary = minimize boarding hours. Constraints = capex/opex budget, nurse:bed ratio, physical rooms. Scope = Vajira, cardiac service line. - Analyze. Baseline snapshot (current cardiac occupancy, OR utilization, payer mix, readmission rate); assumptions register (ramp norms, discount rate).
- Model (v1: 3 of 7 agents). Demand & case-mix fits cardiac arrivals + case-mix; Capacity & flow fits LOS/occupancy from
bed_status_log-derived spans + post-op inflow fromor_case_costing/er_bed_stay_log; Reimbursement fits revenue/case fromgold_fact_claims. (Quality & safety is the likely 4th — cohort sizing from EWS.) DES + Monte-Carlo run base / surge (seasonal MI, dengue) / worst (pandemic). - Guide. Rank bed counts × ramp schedules by NPV with P10/P50/P90; output a recommended ramp (e.g. 4 → 7 → 10 beds over 6 months) + staffing plan + payback period + investment memo. Med director / nurse manager / RCM lead / compliance sign off; the
twin_decidegate releases. - Portfolio (post-go-live). Back-test realized occupancy/boarding/revenue vs predicted; drift feeds recalibration.
13. Invariants
- Outputs are population / resource-level only — never a patient-directed recommendation (the device firewall). Clinical signals are welcome as inputs.
- Read-only on clinical data — writes only to
twin_*tables. - Numbers are deterministic — the LLM writes narrative only; it never decides a number or gates a decision.
- No decision without sign-off —
optimized → decidedis gated on the required expert sign-offs. - Data stays local; only aggregates roll up — calibration uses in-region data; de-identified aggregates only cross a tier boundary.
- Every run is reproducible — seeded RNG + pinned params + recorded inputs;
twin_scenario_runsis append-only. - Fail-soft, never silent — missing live data ⇒ seed fallback + explicit “estimated” badge; never a blank, never a silent zero.
- Off by default — feature-flagged + role-gated; disabled ⇒ invisible, zero behavior change.
- Back-test before trust — predictions validated against held-out history before recommendations carry decision weight.
- HR/finance data enters at role/unit aggregation — never individual compensation or staff PII into the twin (PDPA/APPI surface minimization).
- No silent derived KPI — a derived feed (e.g. provider workload = activity ÷ FTE) renders only when its crosswalk + both inputs are present; otherwise it shows “needs data,” never a fabricated number (the Data readiness screen owns this).
14. Checklist roadmap
The realistic v1 = one template (a capacity decision like the step-down unit), Twin Studio with 3 of 7 agents live, and the Guide outputs. All of F (Portfolio Command) and most of G (governance) wait. The ★ tags in §4 mark the v1 screens.
P0 — Foundations & demo shell ✅ SHIPPED 2026-06-04 (demo-able: ?target=Twin / ?target=HorusShell&lens=twin)
- [x] This design doc (regulatory framing, contracts, 7 agents, screen inventory)
- [x] Contracts in
twin/types.ts:Initiative,TwinAgent,Scenario,RunResult,Distribution,Readers(+agents/,readers/,seed/) - [x] Pure seeded DES kernel (
engine/des.ts+montecarlo.ts+distributions.ts+queueing.ts+optimize.ts) +mulberry32seed - [x] HORUS
twinlens registered (HORUS_VIEWSentry) +TwinWorkspaceshell on seed data (+ live base-case bed sweep) - [x] Sandbox target registered in
web/sandbox/registry.ts(Twin) - [x] Capture (area B): Template gallery
★(1 live template + 3 framed), Decision brief★— New Decision wizard + Stakeholder assignment deferred to P1
P1 — Live calibration & base-case simulation (core SHIPPED 2026-06-04; live-DB wiring remains)
- [x]
bed_occupancy_spansview (pairsbed_status_logrows) — migration20260604b - [x] Readers (
twin/readers/) live→seed-fallback withisLivebadge —Readersboundary +METRIC_SOURCES - [ ] External ingestion (v1 trio): HR establishment+rosters [ext: HRIS/WFM], labor cost rates [ext: payroll, role-aggregated], GL service-line P&L [ext: ERP/GL] — via CSV/Excel batch + manual fallback (no connector needed to ship);
twin_connectors/twin_ingestion_runs - [ ] Master-data crosswalks (
twin_crosswalks): provider identity + cost-center→ward→service-line (gates Reimbursement + Workforce) - [x] Demand & case-mix, Capacity & flow, Reimbursement agents
calibrate()— wired to theReadersboundary; reading seed today, per-metric live MedOS series (RPCs) is the remainder - [x] Distribution fitting (
engine/distributions.ts) — empirical + lognormal/exp/poisson + method-of-moments fitters - [x] Base-scenario DES + Monte-Carlo → P10/P50/P90
- [ ] Analyze (area C): Data source connectors
★, Data readiness & coverage★, Baseline snapshot★, Assumptions register★ - [x] Model (area D): Twin overview
★(7 agents) — Agent workspaces + Simulation runner UI remain
P2 — Optimization & outputs (optimizer + Guide + scenario compare SHIPPED early, 2026-06-04)
- [x] Optimization engine (
engine/optimize.tsgridOptimize/simulatedAnnealing+guide/score.tsrecommend()) — sweep → score (net value = revenue − boarding penalty − bed opex) → rank w/ P10/P50/P90 - [ ] Objective/constraint rule rows (reuse
evalCondition/evalPredicate) - [x] Base / surge / worst scenario sweep
- [x] Model: Scenario comparison
★— Scenario builder + Constraint & policy editor UI remain - [x] Guide (area E): Readiness Score
★, Investment memo★, Executive summary★(memo §), Execution roadmap★—guide/{score,readiness,memo,roadmap}.ts+GuidePanel.tsx
P3 — HITL & the device firewall (core SHIPPED 2026-06-04)
- [x]
twin_signoffstable (migration20260604c) + theDecisionGatesign-off flow — AcknowledgementRequest fan-out (cross-app) remains - [x]
twin_decidegate logic (decide/evalDecideGateblocksoptimized → decided) — the livepolicy_gatesrow + hard_stop wiring remains - [x] Guide: Recommendation & decision log
★(the gate’s decision log) - [x] Ollama recommender-only narrative (
narrative/narrateMemo, fail-soft; mirrorsprogression.ts) - [x] Compliance output-shape attestation step (the compliance-officer sign-off checkbox)
- [ ] Platform (area G minimum): Users/roles/approvers
★, Audit & data lineage★ - [ ] Shell (area A): Home/My work
★, Approvals & notifications inbox★
P4 — Full agent roster, packaging, country tier (agents + migration + module SHIPPED 2026-06-04)
- [x] Workforce, Supply & pharmacy, Quality & safety, Compliance agents (live; quality is cohort-sizing only)
- [ ] Standard connectors for the major ERPs/HRIS (per design-partner systems — OQ-8); remaining external feeds (SCM/CMMS/contracts/incident/survey/registries)
- [~] Full crosswalk coverage (
twin_crosswalkstable shipped in20260604c; item/drug/payer-scheme/DRG seed maps remain) - [x]
twin_*migration (20260604c— all tables in §11; connectors/ingestion superseded by the sync-health substrate) - [x]
infrastructure/modules/hospital-twin/module.json+VITE_HOSPITAL_TWIN_ENABLED+…_WORKBENCH/…_PORTFOLIOflags - [ ] Country rollup gold table (
twin_gold_initiative_outcomesshipped) + rollup edge fn (de-identified) — edge fn remains - [ ] Market-pack rule rows per country (
seed-twin-rules.sql); jurisdiction limits in Compliance agent - [ ] Remaining Capture/Analyze/Guide screens (Decision register, Evidence inbox, Scenario playbook, Sensitivity)
P5 — Portfolio Command & monitoring (second product; preview SHIPPED 2026-06-04)
- [ ] Back-test harness (predicted vs realized) + drift metric
- [ ] Recalibration cron in the
cron_jobsregistry - [x] Portfolio (area F): Portfolio dashboard + actuals-vs-forecast (
PortfolioPanelpreview) — Risk/opportunity heatmap, Capital allocation, Cross-facility roll-up, Board pack remain - [ ] Remaining governance (area G): Org/facility mgmt, Data governance & sovereignty (residency + right-to-deletion; no EVFS — see the §4·G note), Agent & model registry, Integrations, Template/playbook library
15. How to extend
- Add a domain → one agent module under
twin/agents/<facet>/implementingTwinAgent+ one entry inTWIN_AGENTS(mirrors theHORUS_VIEWScontract). - Add a country → drop rule rows in
infrastructure/market-packs/medos-<country>/seed-twin-rules.sql(currency, scheme rules, ramp norms, jurisdiction limits). Zero TypeScript. - Add an initiative kind → a new
kind+ decision-space template; the engine and agents are kind-agnostic. - Add a template → a Template-gallery entry mapping a
kindto a pre-filledInitiativeskeleton.
16. Open questions
- OQ-1 — distribution fitting. Hand-roll (empirical CDF + method-of-moments, ~150 LOC, no dep) vs. add
simple-statistics. Lean: hand-roll for P1. - OQ-2 — compute placement. Web Worker (P1–P2) vs. Deno edge fn / microservice. The pure kernel ports either way. Lean: Worker first.
- OQ-3 — overlap with seed-only HORUS lenses. Capacity/Queue/FP&A lenses are seed-only today; the twin’s live readers could wire them too. Lean: shared
twin/readers/. - OQ-4 — back-test windowing. How much held-out history before a twin is “trusted” (Invariant 9)? Per-agent or per-initiative? Defer to P5.
- OQ-5 — doc index. Add a row to the root
CLAUDE.mdKey Files table. Offer, don’t auto-edit the shared file. - OQ-6 — device-regulatory read. Jurisdiction-specific classification across TH/PH/JP/CN/US/EN (Cures Act CDS carve-out, EU MDR Rule 11, Thai FDA, PMDA). Commission before committing the architecture of any patient-level surface; the population/resource product is unaffected.
- OQ-7 — Workforce data depth. Staffing/roster data is thinner than beds/claims (
staff_assignments+user_tasks). May need a roster source before the Workforce agent is trustworthy. - OQ-8 — connector build order. Which ERPs/HRIS/WFM get standard connectors first depends on what the design-partner hospitals actually run. Until known, v1 leans on CSV/Excel + manual; the connector backlog is demand-driven, not speculative.
- OQ-9 — semantic layer: build vs. buy & where it lives. The canonical model / crosswalks (
twin_crosswalks) could live as Supabase tables + RPCs, the existing dbt transform layer (infrastructure/dbt/— already a staging→gold star schema), or the migration service (which already owns the adapter crosswalk seeds + claude-assisted field mapping). The recheck makes the lean clearer: reuse dbt + the migration service rather than stand up a fourth substrate. Decide before P1’s crosswalk work. See §6.2.1. - OQ-10 — crosswalk bootstrapping. Initial provider/cost-center/code mappings are manual or fuzzy-matched. Who authors and signs off the canonical mappings (data-governance role), and what confidence threshold lets a derived KPI render (Invariant 11)? (The cold-start is warmer than it looks: the migration service already ships ATC→TMT + ICD10→ICD10TM seed crosswalks + a claude-assisted
fieldMapper/schemaAnalyzer, so drug/ICD mapping starts warm; provider-identity + cost-center + payer-scheme + DRG are the genuinely cold maps — §6.2.1.) - OQ-11 — canonical-reader contract (the one to lock before P1 readers). The twin’s
readers/should read a canonical boundary that resolves to MedOS read models or a migration-adapter’s normalized FHIR output (§6.2.1), so the twin stays HIS-agnostic — an overlay on HOSxP / foreign HIS, not welded to MedOS table names. The twin design doc currently wires readers directly to specific MedOS tables (§5/§6.1); that’s fine for a MedOS-native demo but must move behind the interface before the “sell the migration later” wedge can survive. The migration-adapter framework is the natural seam. Decide before morereaders/land — this is the schedule-risk decision, more than any single agent or screen. → SHIPPED (P0): theReadersboundary is built —Readersintwin/types.ts+makeSeedReaders/makeMedosReaders/resolveReadersintwin/readers/(live→seed fallback,isLivebadge, readstwin_readiness_v). Remaining = wiring each metric’s live MedOS series (P1). - OQ-12 — sync-stats canonical shape + integration (REVAMPED after reviewing
ever-sync-adapter). The richer, production-proven model is notmigration_jobs— it is the @ever portfolio’s deployed applianceever-sync-adapter(15 sites):log_jobs(atomic-action +parent_run_id+trigger_source+ retry/heartbeat/crash + deep-grain children) + an immutablesync_eventshash-chain (tamper-evident) — §6.2.1. Lean: the twin’s sync-stats / data-readiness model mirrors that shape (incl. ahash-chain-verifiedintegrity dim), and integrates via a thin sync-health aggregate bridge — each per-country edge adapter emits coverage/freshness/error-rate/integrity per (market-pack × connector); the twin consumes the aggregate, never the local SQLite. Open: (a) the aggregate-bridge contract + transport (export file vs. signed summary endpoint vs. a roll-up intomigration_jobs-style rows); (b) whetherEHRSyncResult/migration_jobs(inbound medOS ingestion) converge on the same shape aslog_jobs(outbound), or stay separate (→ OQ-13). → First cut SHIPPED: the canonical model (sync_run+sync_eventhash-chain) + the de-identified bridge (sync_health_aggregate+twin_readiness_v+ theSyncHealthSummaryemit contract) landed in migration20260604a; see ever-sync-adapter-integration.md. Remaining = the emit transport (a). - OQ-13 — shared connector kernel (
ever-sync-adapter⇄ medOS migration service).ever-sync-adapter(outbound HIS→MOPH) andservices/migration(inbound foreign-HIS→medOS) are two directions of one machinery: read-only DB source adapters (Oracle/MySQL/MSSQL/PG) + per-mode field-map + normalize (BE→CE/datetime) + FHIR transform + operational-logging + integrity. Today they are separate repos with separate implementations. Decision: extract a shared connector kernel (an@ever/*package) vs. have the medOS migration service adoptever-sync-adapter’slog_jobs/sync_eventsmodel vs. leave them parallel. Affects long-term portfolio maintenance, not the twin’s P1. Portfolio-level call — surface to the user; do not auto-decide. Theever-sync-adapterrepo has strict TDD/ADR/HOTFIX discipline; any change there is its own scoped effort, not a side-effect of twin work. → Resolved (first arm): the shared model now lives in medOS (sync_run/sync_event, migration20260604a); the adapter conforms by emitting the de-identifiedSyncHealthSummary, untouched. The shared-code@ever/*kernel extraction stays deferred. See ever-sync-adapter-integration.md §1.
17. File & symbol references
| Thing | Path / symbol |
|---|---|
| Lens registry (extension point) | web/packages/intelligence-kit/src/horus/views.tsx — HORUS_VIEWS, HorusViewDef |
| Shell | web/packages/intelligence-kit/src/horus/HorusShell.tsx |
| Sim template (pure step fn) | web/packages/intelligence-kit/src/seir-globe/engine/seir-model.ts — stepSimulation |
| Param panel / live-seed templates | seir-globe/SeirControlPanel.tsx, seir-globe/hooks/useSeirSurveillance.ts |
| Recommender-only stance | web/packages/intelligence-kit/src/horus/disease/progression.ts |
| AI tool-loop + guardrail | web/src/services/ai/shared/runner-engine.ts — runAgentLoop, SeenMatches |
| Ollama config | VITE_LOCAL_LLM_URL (qwen2.5:14b-instruct), services/llm microservice, PH-demo mistral:7b |
| Rule evaluators (objectives/constraints) | web/src/services/policy-gate.service.ts, web/packages/security-kit/src/ruds/evaluate.ts, web/src/services/cds/cdsEngineDb.ts |
| Event spine | infrastructure/medbase/functions/encounter-orchestrator/index.ts |
| Module system | infrastructure/modules/, resolve-modules.mjs, install-module.sh, moduleEnabled() |
| Gold layer / FP&A | infrastructure/medbase/migrations/013_gold_layer.sql, fpa-data-warehouse.md |
| Source adapters (read-only) | services/migration/.../adapters/sql.adapter.ts, adapters/ehr/adapter-framework.ts |
| Market-pack adapter registry | infrastructure/market-packs/adapters/ (HOSxP 3.x/4.x, JHCIS; common/crosswalks/ ATC→TMT, ICD10→ICD10TM) |
| Transform / analytics spine | infrastructure/dbt/ (staging→gold), infrastructure/airflow/dags/ |
| Production sync appliance (sibling repo) | ever-sync-adapter — log_jobs + sync_events hash-chain (canonical sync-stats + integrity model), MophCloudClient/Pc1BundlePushClient (push), Modes A/B/C connectors; .agents/AGENTS.md is its canonical contract |
| HITL infra | acknowledgement_requests + AcknowledgementInbox, policy_gates |