medOS ultra

AHP-Led Care Pathway

Physician-absent clinics as a swappable kit: capability grants, red-flag detector, lanes A/B/refer/blocked, default-OFF.

12 min read diagramsUpdated 2026-06-10docs/architecture/ahp-led-care-pathway.md

Status (2026-06-10): P0 scaffold SHIPPED (default-off on both planes, zero behavior change, sandbox-verified at ?target=AhpStationGate). P1+ are design. Read this before any doctorless-clinic / scope-of-practice / standing-order / remote-cosign / rural-deployment work.

1. What this is

A care-setting profile for clinics that are statutorily doctorless — Thai รพ.สต. (Tambon Health Promoting Hospitals) and Vietnamese trạm y tế xã (commune health stations) — where the operators are Allied Health Professionals (NP/พยาบาลเวชปฏิบัติ, RN, y sĩ, pharmacist, CHW) and the responsible physician is remote.

It is NOT a flag that lets AI (or anyone unlicensed) diagnose autonomously. It encodes the real-world legal model those facilities already run on:

  • Lane A — the action falls inside a physician-SIGNED standing-order envelope → permitted without per-case sign-off, but the write is CAPPED at treated_per_standing_order (a treatment act under protocol — never diagnostic authorship). The signing physician is the legal author.
  • Lane B — outside the envelope → remote physician co-sign required before the action becomes of-record (diagnosed_with_cosign, …).
  • refer — the universal safe sink. Always permitted, never gated, works offline.
  • blocked — default-deny: no active capability grant ⇒ the action does not exist for that role (refer remains).

A deterministic, client-local red-flag detector runs before every gate and can force refer regardless of any grant. AI (Smart-Diagnosis / case-finder over the HIE corpus) is a severable recommender adjunct — when it is down, the AHP still operates inside the pre-signed envelope; it never authors an of-record state and never gates anything.

2. The three architectural ideas

  1. One context marker, stamped once. Registration stamps ahp_context = { care_setting:'ahp-led', operating_ahp_role, supervising_physician_id, … } onto encounter_journey_cache (migration 20260610a); the orchestrator projects it onto department_queues rows so every station self-configures. An AHP-led encounter cannot exist without a named supervising physician.
  2. One gate, five chokepoints. The same evaluator (evaluateAhpGate, wrapping the treatment-series 3-outcome shape) runs at screening-commit, registration-complete, assessment-commit, order-release, and disposition. Order of authority: red flags → refer-shortcut → default-deny → VN invariant → lane.
  3. One capability table = all the scope. ahp_capability_grant: one row per (role × jurisdiction × facility_tier × station × action) → {verdict auto|cosign|forbid, envelope_ref, drug_tier, acuity_ceiling, red_flag_pack, priority, version, status, author_physician_id}. Scope-of-practice, standing orders, drug tiers, and red-flag pack selection are all slices of this table. New country = market-pack seed rows, zero TypeScript — but see §9: each country’s rows are a regulated clinical algorithm under change control, not free config.

3. Station contracts

Station AHP does Gate outcome Fail-safe Licensed author-of-record
Screening CC + vitals + danger-sign checklist per signed triage protocol Red-flag check runs FIRST, can hard-stop to EMS/refer; else commit per grant Detector is client-local + synchronous; missing input ⇒ escalate; survives outage Physician who signed the triage-protocol version
Registration Binds encounter to supervising MD + consent; stamps ahp_context Completeness block: no AHP encounter without named MD + active grant + consent No MD resolvable ⇒ degraded all-cosign mode; refer still works The named supervising physician
Assessment Working impression; ICD-of-record field locked in Lane A in-envelope ⇒ treated_per_standing_order / out ⇒ cosign ⇒ diagnosed_with_cosign / red ⇒ refer Cosign channel down ⇒ cannot commit of-record, fails to refer Lane A: envelope signer (capped); Lane B: cosigner
Orders Places orders constrained to role drug tier + envelope whitelist tier-covered ⇒ Lane A / restricted (abx, steroids, injectables) ⇒ cosign / forbidden ⇒ refer Unresolved context ⇒ cosign-hold (NOT the platform’s nurse_ack default) Envelope signer (A) / cosigner (B)
Disposition Discharge-with-followup, refer-up, teleconsult Refer always-auto; out-of-envelope discharge ⇒ cosign Referral works offline (printed/QR packet); source stays open until receiver ACCEPTS The receiving MD who accepts the TransferRequest

The non-negotiable thread: the AI/UI only ever advances proposed → recommended; a transition into any of-record state is only ever written by a licensed-human event (a signed envelope or a captured co-sign).

4. Data model (shipped in P0)

Object Migration Notes
ahp_capability_grant + ahp_gate_log (append-only, dual-plane) + encounter_journey_cache.ahp_context 20260610a Default-deny; active requires author_physician_id (DB CHECK). Gate log has frontend/backend plane column.
ahp_scope_envelope 20260610b Renamed to dodge the standing_orders collision (20260517 = patient RRULE recurring orders, a different thing). signed requires signer + valid_to (envelopes expire).
module_entitlements + module_enabled_server() / ahp_module_enabled() 20260610c GENERIC backend half of module entitlement. No row ⇒ FALSE (regulated default-off, inverted from demo-safe).
commit_diagnosis + commit_prescription policy_gates rows 20260610d These trigger_actions did not exist anywhere. hard_stop TRUE, no override (the emergency path IS refer, which is ungated). Scoped care_settings:['ahp-led'] ⇒ physician-present flow never sees them.
Market-pack seeds medos-thailand/seed-ahp-capability-grant.sql, new medos-vietnam/ pack Bilingual; ALL rows ship status='draft'. VN: cosign_required=TRUE everywhere, no auto outside disposition.

Migrations are manual-apply per the repo’s deployment-status table; nothing runs until an operator applies them.

5. Kit packaging (the swappable module)

One module, two planes, engine-under-adapters. Manifest: infrastructure/modules/ahp-care-pathway/module.json (requires: [read-model-core], the 4 migrations, flags, tier enterprise, postInstall seeds the module_entitlements row disabled — activation is a compliance event, not an install side-effect).

web/packages/medical-kit/src/ahp-care-pathway/   ← @medical-kit/ahp-care-pathway
  types.ts  entitlement.ts  index.ts
  engine/          redFlagDetector.ts + redFlagPacks.ts (core-v1)
                   capabilityResolver.ts  gateEvaluator.ts  outputContracts.ts
                   __tests__/ (red-flag + gate property tests = the drift tripwire)
  components/      StationGateModal.tsx  CosignRequestModal.tsx   (design-kit wrappers)
  adapters/        ahpModule.ts (footer ModuleDefinition)  registerOpdActions.ts (payload builders)
services/clinical/src/api/clinical/modules/ahp/  ← backend trust anchor (P0: UNWIRED)
  ahpEntitlement.helper.ts  ahpEnforcement.helper.ts  redFlagDetector.shared.ts  README.md

Placed inside medical-kit (the compounding/ / treatment-series-engine/ precedent) so no tsconfig/vite change was needed; promotion to a Tier-1 @ever-medos/ahp-care-pathway-kit package is one alias line later, content unchanged.

Entitlement — one logical flag, two planes, regulated default-OFF

Plane Predicate Default File
Frontend (advisory — decides what mounts) ahpEnabled() = VITE_AHP_CARE_PATHWAY_ENABLED==='true' && module listed in VITE_ENABLED_MODULES && tier ⊇ enterprise OFF (explicit opt-in; inverts demo-safe) entitlement.ts (self-contained mirror, fpa-dashboard precedent)
Backend (authoritative — decides what commits) isAhpEntitled() (env AHP_CARE_PATHWAY_ENABLED==='true' + tier) && ahp_module_enabled() DB row OFF ahpEntitlement.helper.ts + migration 20260610c

Off-semantics: frontend off ⇒ the boot block in web/src/store/index.ts no-ops ⇒ no modals, no payload builders — hosts byte-identical. Backend off ⇒ enforceAhpGate REFUSES of-record writes on ahp-led encounters (FEATURE_NOT_ENTITLED) — it never silently falls back to physician-present semantics. Schema persists (platform has no down-migrations) but is inert. The central tier matrix also carries the key (FEATURE_PLANS['ahp.care-pathway']=['enterprise']).

Six-host integration matrix (verified against the real seams)

Host Seam Kit mechanism Fork? P0 state
Footer global modal moduleRegistry + registerDynamicModals() ahpCarePathwayModule.getModals()'ahp-care-pathway/StationGate', 'ahp-care-pathway/CosignRequest'; isActive() re-checked every render No Shipped (guarded registration in store/index.ts)
OPD worklist registerPayloadBuilder() (actions.ts:646) + role_worklists visibility builders 'ahp-station-gate', 'ahp-request-cosign'; actions authored as workflow JSON with visibility.path on care_setting No Shipped (builders registered; inert until a workflow row references them)
Workflow-store workflow_templates IS the registry AHP templates via Deployment-Profile template_set No P3 (template authoring)
Workflow-editor NodeComponentMap hardcoded; removal of a custom nodeType crashes saved canvases Fork avoided: gate expressed as policy_gates data on the existing PolicyGateDecision node + edge conditions No (avoided) Gate rows shipped (20260610d)
Admin config AdminRoutes.tsx hardcoded Near-term: grant editor via scheme-extensions slot / kit miniapp tab; right fix: generic registerAdminPage() registry One-time generic seam P2
IPD worklist IpdRow.tsx orphaned from workflow-config One-time generic migration of /ipd/work-list onto ConfigDrivenTable (already the platform’s own backlog item; INVARIANT #0 fail-open preserved) One-time generic seam P3+

6. The red-flag detector (the front-door safety net)

Net-new and deliberately not built on the existing CDS engine, which is structurally disqualified for this job (verified): evaluate-cds-rules is numeric-vitals-only (skips non-numeric inputs), non-blocking and failure-swallowed at the call site (observation-handlers.ts .catch(()=>undefined)), qSOFA is seeded as three independent amber toasts, and the vitals util getVitalSignValueState returns NORMAL for null input (fail-open). The detector inverts all four properties:

  • Presentation-based: chest pain → ACS, FAST → stroke, severe bleeding, anaphylaxis, active seizure, respiratory distress, eclampsia — these present with normal vitals.
  • Aggregated scores: qSOFA ≥2 fires as ONE critical finding.
  • Unknown ≠ normal: required vitals (RR/SBP/SpO2/mentation) and the explicit presentation checklist must be assessed; missing ⇒ incomplete-screening ⇒ escalate. IMCI danger-sign checklist for under-5s must be explicitly answered.
  • Fail-toward-escalation: an internal detector error returns escalate + degraded:true.
  • Dual-plane by design: advisory copy in the kit, trust copy in services/clinical/.../ahp/redFlagDetector.shared.ts. Same algorithm, duplicated on purpose (backend can’t import web/; frontend must work offline). The kit’s Jest vectors are the drift tripwire — change a rule in one copy ⇒ change both + the vectors in one commit.
  • Packs (core-v1 baked in; unknown pack id falls back, never empty) are selected per grant row, so jurisdictions can extend via data later. Thresholds/rules require clinical validation before P1 go-live — they are seed scaffolding, not validated medicine.

7. Backend enforcement (the trust anchor) — and the verified bug list

enforceAhpGate() re-makes the lane decision server-side. P0 ships it UNWIRED (zero behavior change); wiring is the P2 hard gate. The three of-record write paths:

  1. commitDiagnosisnet-new action (verified: no such handler exists in services/clinical today; createDiagnosis writes a full ICD diagnosis with zero scope/authority check).
  2. orderRequest sign (services/medication) — wrap the existing sign path. Must also fix the activateStandingOrders.ts laundering bug (verified): it hardcodes isDoctorAssign:true and ignores co_sign_required, writing doctor-attributed orders with no human author event — the most direct bypass of this entire legal model. The pickOrderReleaseGateMode fail-safe default is nurse_ack (policy-gate.service.ts:332), not cosign — AHP mode must override it to cosign-hold, regression-tested against existing order flows.
  3. transferRequest.accept — when the TransferRequest entity ships (transfer-request-system.md); today transfer validates the status transition but not who accepts.

Also verified frontend-only today: usePolicyGate evaluates locally and nothing re-checks gates on the write path — which is exactly why the policy-gate rows in 20260610d + enforceAhpGate wiring are ship-blockers, not enhancements. Offline-outbox replays must re-enter the same backend gate (no trusted client writes).

8. Invariants

  1. No active grant ⇒ blocked (default-deny). Refer is never gated, at any station, ever.
  2. Lane A is capped: a standing order delegates a treatment act, never diagnostic authorship. diagnosed requires a physician event.
  3. An AHP-led encounter cannot exist without a named supervising physician.
  4. Red flags beat grants: detector escalation overrides everything; missing input ⇒ escalate; detector error ⇒ escalate.
  5. Entitlement-off ⇒ refuse, never fall back: with the module off, AHP-scoped of-record writes 403; physician-present flow passes through untouched.
  6. VN invariant: jurisdiction='VN' + verdict='auto' outside disposition ⇒ cosign, enforced in the backend regardless of row content, until a cited y sĩ scope determination exists.
  7. Regulated data: grants/envelopes ship as draft; activation requires a named physician; every gate decision lands in append-only ahp_gate_log with its plane.
  8. Frontend is advisory: every lane decision is re-made server-side before commit.
  9. Per-intended-use device flags: screening-triage, AHP diagnosis-CDSS, AHP prescribing, and patient self-screening are separately classifiable SaMD intended uses (AHP_DEVICE_INTENDED_USE); a refer-only clinic must not carry the diagnostic-CDSS registration burden.
  10. Physician-present mode is byte-identical to a deployment that never had the kit.

9. Regulatory posture (TH / VN)

  • The physician-signed standing order + referral pathway is the regulated clinical workflow; the AI is a severable recommender adjunct — that framing survives review; “the standing order is the AI’s legal protection” does not.
  • Thai FDA’s SaMD/AI guidance classifies diagnostic CDSS as a registrable device and directs that AI shall not replace clinical judgment; physician-absent + non-physician operator + drives-management is a higher-risk intended use (likely TH Class 3 for the stroke/sepsis-scope screening). There is no US-style Non-Device-CDS safe harbor here.
  • “New country = data rows” is true for engineering but each country pack is a device registration + change-control event: version-stamped, physician-approved seeds, not a config flip. Budget it that way commercially.
  • Vietnam: keep cosign_default=TRUE + no-auto until a Vietnamese legal reviewer confirms y sĩ scope against Decree 96/2023 + Circular 28/2023 (and the 2028 capacity-exam transition). The backend invariant exists so a mis-seeded pack cannot fall back to autonomous practice.
  • Patient-facing self-triage (patient-triage care setting) is a separate SaMD with its own bar — out of scope for the clinician kit.

10. Rollout (safety + enforcement gate real-patient use)

Phase Scope Gate
P0 — SHIPPED 2026-06-10 Everything in §4–§5: module + 4 migrations + TH/VN seeds (draft) + kit engine/components/adapters + guarded boot registration + unwired backend helpers + sandbox target + tests Zero behavior change; default-off both planes
P1 Red-flag net to clinical grade: validated thresholds, per-jurisdiction packs as data, total-outage backstop (hardcoded local EMS number + printed/QR referral packet) HARD GATE: clinical validation sign-off
P2 Backend enforcement wired: commitDiagnosis (new action), orderRequest sign wrap (+ activateStandingOrders fix + cosign-hold default), DB entitlement join; envelope/grant admin surfaces (registerAdminPage seam or scheme-slot) + activation compliance checklist HARD GATE: server-side re-enforcement live; no real patient before this
P3 Station wiring behind the flag: registration stamping, screening form w/ checklist, AHP workflow templates via Deployment Profile, orchestrator ahp_context projection
P4 Disposition completion: TransferRequest wiring, cosign SLA + escalation roster via AcknowledgementRequest
P5 TH market activation: Class-3 SaMD classification opinion, named physician activation flow, pilot Device registration
P6 VN pack activation (cosign-forced) + named VN legal reviewer Legal determination
P7 Hardening: offline outbox re-gating, RUDS rules for AHP anomalies, voice/face attestation binding for cosign

11. Open questions

  1. VN y sĩ scope determination (owner: VN legal reviewer) — blocks any VN auto verdict.
  2. TH device classification opinion for the screening intended-use (blocks P5).
  3. Deployment-Profile authoring UI (workflow-store gap — profiles are setup-time only today).
  4. Generic seams the platform team should own: registerAdminPage(), registerNodeType() + unknown-node fallback renderer, IPD→ConfigDrivenTable migration (all kit-agnostic).
  5. Cosign SLA economics: who staffs the remote physician pool per district, and what the cosign_sla_minutes defaults should be per jurisdiction.

12. Origin

Synthesized from three multi-agent design passes (2026-06-10): the diagnosis-gate pass (rural-ahp-gate-design), the end-to-end pathway pass (ahp-pathway-design), and the kit packaging pass (ahp-kit-packaging-design) — 39 agents total, including 6 adversarial verifiers whose corrections (numeric-only CDS, fail-open vitals util, frontend-only gates, standing_orders collision, activateStandingOrders laundering, missing commit actions, US-safe-harbor misframing, VN scope assumption) are folded into §6–§9 above.

Ask Anything