Patient Intake Resolver
Patient intake workflow resolver: registries, resolver contract, deployment profiles.
Status: Proposed · Phase 0 architectural commitment Scope: Patient intake / registration across jurisdictions, facility types, and entry channels. The same contract extends to discharge, transfer, and perioperative workflows in later phases. Audience: Engineering, integration partners, regulators.
1. Problem
medOS-ultra targets deployments across Thailand, Japan, Philippines, and the United States (near-term), across facility types ranging from single-doctor clinics through EMRAM Stage 5 hospitals and Japanese nursing homes. The combinatorial space is:
N jurisdictions × M facility types × K entry channels × L regulatory frameworks
If each combination is a hand-built workflow, the system collapses under its own maintenance cost. The architecture must make adding a jurisdiction, a facility type, or an entry channel an additive operation — registering components, not editing templates. This document specifies the contract and schemas that make that property hold.
The architecture is grounded in existing healthcare standards:
- FHIR R4
PlanDefinition/ActivityDefinitionas the projection format for workflow interop (not the storage format — internal storage remains the ReactFlow graph + a separate metadata layer). - HL7v2 ADT messages as outputs of workflow completion, not inputs.
ADT^A04,ADT^A05,ADT^A01, etc. are emitted deterministically from workflow state. - IHE PAM actor model (Patient Identity Source, Patient Encounter Manager, Patient Encounter Consumer) for integration conformance.
- IHE PIX / PDQm for cross-channel identity reconciliation.
- HIMSS EMRAM stages as first-class metadata on templates and steps, enabling per-facility maturity gating.
1.1 What this design builds on (existing infrastructure)
This architecture is not a greenfield design. Approximately 70 % of the infrastructure already exists in the repo, and the design extends it rather than replacing it. Before reading further, the following are already in place and referenced throughout the document:
| Exists today | Location | Role in this design |
|---|---|---|
BlockEnum — node type vocabulary |
web/src/common/components/medical/builder/main-flow-editor/components/workflow/types.ts |
Is the step type registry. Not replaced by a new table. |
workflow_templates table |
Backend schema | Extended with kind, emram_minimum, steps, fhir_plan_definition, template_hash. Existing reactflow_graph stays. |
workflow_template_bindings table with scope_type IN ('clinic', 'sub_clinic', 'location') |
infrastructure/medbase/migrations/007_intake_workflow_template_resolution.sql |
Extended with channel_id + context-filter columns. Existing resolution chain widens. |
| Existing resolver logic | workflowTemplate.service.ts (backend) |
Extracted into a pure library implementing the contract in §3. Existing service becomes a thin wrapper. |
WorkflowSession.location_id + ResolveTemplateRequest { workflowKind, clinicId, subClinicId, locationId } |
web/**/configurable-intake/types.ts |
Input signature extended with channel_id, encounter_class, patient_class, role. |
Location entity with type, physicalType, operationalStatus |
Location.ts, FHIR Location controller at services/public-api/.../fhir/resources/location.controller.ts |
Used for physical-location steps. Not repurposed for entry channels. |
department_queues.location_id |
existing schema | Used by enqueue step. |
| HL7v2 parser | services/interoperability/.../modules/hl7v2/hl7v2-parser.service.ts |
Currently discards MSH-4 — known bug. §9 emission layer restores it on the out-bound side. |
| FHIR infrastructure (CapabilityStatement, transformers, subscriptions) | services/public-api/.../modules/fhir/ |
Hosts the §8 PlanDefinition / ActivityDefinition projection. |
| Built-in registration templates | web/src/templates/workflows/registration/{basic,vitals-first,qr-code,api-integration}-registration.json |
First templates to migrate through the resolver — the correctness gate in Phase 1. |
| Market Pack system | infrastructure/market-packs/medos-{japan,philippines,thailand}/ |
Extended to seed adapter rows (§4.2) and deployment profiles (§15). |
What the design adds that does not exist today:
- One new table:
channel_registry. Nothing in the codebase models entry channels (LINE, QR kiosk, web portal) with capabilities and a public slug. This is the only genuinely new entity in the core schema. - Adapter sub-registration for BlockEnum. Today each BlockEnum entry has one implementation. The design changes this to one type → many jurisdiction-scoped and channel-scoped implementations, resolved at bind time.
- Pure resolver library extracted from
workflowTemplate.service.ts, with the full contract in §3. - Content-addressed
resolved_plan_store— new table, but it is a projection cache, not a new domain entity. - FHIR PlanDefinition projection on template save (§8), new endpoint, reuses existing FHIR infra.
- HL7 ADT emission from workflow completion (§9), new outbound path, sits next to the existing parser.
- Deployment Profile catalog (§15) — new table, setup-time only, does not affect runtime.
Everything else in the design is an extension of something that already works. When §4 refers to “the Step Type Registry,” read “BlockEnum.” When it refers to “the Template Registry,” read “the existing workflow_templates table with new columns.”
2. Architectural invariants
These seven properties are non-negotiable. Any change that violates one is a breaking change.
- Pure resolver.
resolve(input, context)is a pure function. Same inputs always produce the same plan. - Total resolution. The resolver never returns a partial plan. Either every required step has an adapter, or the call returns a non-ok status with explicit gaps.
- Deterministic tiebreakers. When multiple adapters match a step, the resolver picks by
(channel_requirement specificity, adapter version, content hash)in that order. No implicit ordering. - Audit trace as output. Every plan carries
resolution_log— one entry per step, recording which adapter was chosen and why others were rejected. Regulatory evidence, not debug logging. - Content-addressed, immutable plans. A resolved plan is keyed by
hash(ResolutionInput) + registry_snapshot_id. Plans are never mutated or invalidated. New snapshots produce new plans; in-flight encounters continue on the plan that resolved them. - Ranked fallback adapters per step. Every resolved step carries a ranked list of adapters, not a single pick. Runtime selects the highest-ranked healthy adapter. This handles operational degradation (NHSO API down) — it does not handle jurisdiction mismatch, which is solved by template selection.
- Regulatory effective-date tracking. Every plan stamp records the regulation versions in effect at resolution time. A mid-encounter regulation change forces re-resolution only when the amendment is tagged
requires_reresolution: true.
3. The resolver contract
3.1 Input
interface ResolutionInput {
// Who and where
tenant_id: UUID; // carries jurisdiction + regulatory profile
scope: BindingScope; // { kind: 'clinic'|'subclinic'|'service_point', id: UUID }
channel_id: UUID; // references channel_registry
// What
template_id: UUID; // references workflow_template
workflow_kind: WorkflowKind; // 'patient-intake' | 'discharge' | ...
// Context (all nullable; adapters that don't care ignore them)
encounter_class?: EncounterClass; // AMB | EMER | IMP | PRENC
patient_class?: PatientClass; // new | returning | pediatric | vip | foreign
role?: Role; // clerk | nurse | self-service | triage-nurse
}
interface ResolutionContext {
as_of: Timestamp;
registry_snapshot_id: UUID; // immutable snapshot of adapter_catalog + BlockEnum metadata
feature_flags: FlagSet;
}
Nullable context fields are the forward-compatibility hedge. Adapters that do not reference them continue to resolve identically; adapters that later need them (pediatric consent, foreign-patient templates) can be introduced without migrating existing bindings.
3.2 Output
interface ResolutionResult {
status: 'ok' | 'missing_adapter' | 'ambiguous' | 'emram_insufficient' | 'context_unsatisfiable';
plan?: ResolvedPlan;
gaps?: ResolutionGap[]; // populated when status != 'ok'
resolution_log: ResolutionDecision[];
stamp: ResolutionStamp;
}
interface ResolvedPlan {
graph: DAG<ResolvedStep>; // nodes are ResolvedStep; edges encode control flow
context_schema: WorkflowContextSchema; // accumulated requires/produces across the graph
}
interface ResolvedStep {
node_id: string; // stable id from the template graph
step_type: StepTypeRef; // e.g. 'identify' | 'consent' | 'coverage'
adapters: AdapterRef[]; // ranked, highest-preference first
config_per_adapter: Record<AdapterRef, ValidatedConfig>;
requires: ContextFieldSchema[];
produces: ContextFieldSchema[];
compensate?: AdapterRef; // eagerly resolved counterpart
on_failure: 'compensate' | 'retry' | 'skip' | 'abort';
sla_budget_ms?: number;
escalation?: EscalationPolicy;
skip_when?: ConditionExpr; // evaluated at runtime against accumulated context
}
interface ResolutionStamp {
resolver_version: string;
registry_snapshot_id: UUID;
input_hash: string; // SHA-256 of canonicalized ResolutionInput
resolved_at: Timestamp;
regulatory_effective_dates: Record<RegulationId, VersionStamp>;
// e.g. { "TH.PDPA": { version: "2024-08", effective_from: "2024-08-01", requires_reresolution: false },
// "TH.NHSO_API": { version: "v3.2", effective_from: "2024-06-15", requires_reresolution: false } }
}
3.3 Typed workflow context (the dataflow model)
The resolver does not emit explicit {producer, consumer} dataflow edges. Each adapter declares a schema of what it requires from the accumulated workflow context and what it contributes back:
interface AdapterSchema {
requires: ContextFieldSchema[]; // typed fields needed before this adapter runs
produces: ContextFieldSchema[]; // typed fields added on success
}
Example adapter declarations:
// ThaiD OAuth authenticate adapter
{
step_type: 'authenticate',
requires: [{ name: 'patient_ref', type: 'PatientReference' }],
produces: [
{ name: 'identity_verification', type: 'VerificationResult' },
{ name: 'national_id', type: 'ThaiNationalID' }
]
}
// NHSO coverage adapter
{
step_type: 'coverage',
requires: [
{ name: 'patient_ref', type: 'PatientReference' },
{ name: 'identity_verification', type: 'VerificationResult' },
{ name: 'national_id', type: 'ThaiNationalID' }
],
produces: [{ name: 'coverage', type: 'CoverageResource[]' }]
}
At bind time the resolver walks the template DAG and, for each step, verifies that every field in requires is produced by some path-ancestor step. If not, the binding is rejected with an exact error:
CoverageAdapter:nhso-v3requiresidentity_verification: VerificationResultbut no upstream step in templatebasic-intake-thproduces it.
This gives the same safety guarantee as explicit dataflow wiring, derived from graph topology + adapter schemas, with zero dataflow-declaration maintenance surface.
At runtime, each adapter receives a WorkflowContext object from which it reads its declared requires and to which the engine appends its declared produces on success. Adapters remain unit-testable: construct a context containing the required fields and invoke the adapter in isolation.
4. Registry model (three extensions, one new table)
The design refers to four registries conceptually, but only one of them is a new database table. The other three are extensions of existing artifacts:
| Registry | Physical form | New table? |
|---|---|---|
| 4.1 Step Type Registry | BlockEnum (TypeScript, extended with metadata per entry) |
No |
| 4.2 Adapter Catalog | New table referencing BlockEnum by canonical name | Yes (the catalog rows are new; the concept is a sub-registration on BlockEnum) |
| 4.3 Channel Registry | New table | Yes — the only genuinely new domain entity |
| 4.4 Template Registry | Existing workflow_templates with added columns |
No |
4.1 Step Type Registry — BlockEnum extended with metadata
The step type vocabulary is BlockEnum, already defined at web/src/common/components/medical/builder/main-flow-editor/components/workflow/types.ts. It is the canonical, closed set of workflow node types and is extended per entry with the metadata the resolver needs, co-located with the existing NODES_INITIAL_DATA / NODES_EXTRA_DATA structures:
// Extension shape, per BlockEnum entry
interface BlockMetadata {
canonical_name: string; // stable id used by adapter_catalog.block_type
description: LocalizedString;
produces_fhir?: FhirResourceType[];
compensatable: boolean; // whether compensate:<name> implementations are expected
}
Phase 1 adds or confirms the following entries in BlockEnum and maps each to a canonical name used by the adapter catalog:
| BlockEnum / canonical name | Purpose |
|---|---|
identify |
Establish who the patient is |
authenticate |
Prove it (cryptographic / biometric / document) |
consent |
Obtain regulatory consent |
demographics |
Collect / confirm personal data |
coverage |
Verify insurance / entitlement |
triage |
Clinical assessment and acuity |
enqueue |
Route to service point |
handoff |
Transfer between channel / system / actor |
compensate |
Rollback partial state on failure |
compensate is a first-class entry. Each compensatable step registers a companion compensate:<step> adapter in the catalog: identify creates a Patient resource → compensate:identify voids it; coverage creates a Coverage resource → compensate:coverage voids it via the payer API or marks it invalid locally.
No step_type_registry table. The vocabulary is code; the catalog rows reference it by canonical name.
4.2 Adapter Catalog — sub-registration on BlockEnum
This is where scale lives. Today each BlockEnum entry has one implementation; the catalog changes that to one type → many jurisdiction-scoped and channel-scoped implementations, resolved at bind time. The rows are new; the concept is “BlockEnum with many implementations per entry.”
CREATE TABLE adapter_catalog (
id UUID PRIMARY KEY,
block_type TEXT NOT NULL, -- references BlockEnum canonical_name (§4.1)
jurisdiction TEXT NOT NULL, -- ISO 3166-1 alpha-2
channel_requirements JSONB NOT NULL, -- subset of channel.capabilities required
channel_preferences JSONB NOT NULL DEFAULT '{}',-- soft preferences for priority calculation
encounter_class_filter TEXT[], -- NULL = any, array = restricted
patient_class_filter TEXT[], -- NULL = any
role_filter TEXT[], -- NULL = any
implementation_ref TEXT NOT NULL, -- canonical URL → concrete implementation module / ActivityDefinition
version TEXT NOT NULL, -- semver; immutable once published
config_schema JSONB NOT NULL, -- JSON Schema for deploy-time config
adapter_schema JSONB NOT NULL, -- { requires: [...], produces: [...] }
priority INTEGER NOT NULL DEFAULT 100,
regulation_refs JSONB NOT NULL DEFAULT '[]',-- ['TH.PDPA@2024-08']
market_pack_id UUID NOT NULL REFERENCES market_pack(id),
published_at TIMESTAMPTZ NOT NULL,
deprecated_at TIMESTAMPTZ, -- soft-delete; old plans still reference it
UNIQUE (block_type, jurisdiction, implementation_ref, version)
);
CREATE INDEX idx_adapter_resolve
ON adapter_catalog (block_type, jurisdiction, priority DESC)
WHERE deprecated_at IS NULL;
block_type is not a foreign key to a table — it is a canonical name that must match a BlockEnum entry. Validation runs at Market Pack publish time: a catalog row whose block_type has no matching BlockEnum entry is rejected before merge.
Adapter versions are immutable. A bug fix publishes a new row with a bumped version and a higher priority. The old row stays queryable for plans that already reference it. This is what makes content-addressed plans sound.
Existing BlockEnum implementations become “default” adapters. Phase 2 migration inserts one row per existing BlockEnum entry into adapter_catalog with jurisdiction = '*' (universal) and priority = 50 (low, so jurisdiction-specific rows override). This keeps existing templates working unchanged while the sub-registration model rolls out.
4.3 Channel Registry — the only new domain table
This is the one genuinely new domain entity in the design. Nothing in the codebase models entry channels with capabilities and a public slug. The earlier internal exploration considered extending Location with is_virtual + slug columns; that is rejected because a LINE bot or WhatsApp channel is categorically not a Location — it is an ingress path, not a place of care. Location stays for physical places; channels get their own table.
Modern intake starts in channels that HL7v2 did not anticipate:
CREATE TABLE channel_registry (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
clinic_id UUID NOT NULL,
type TEXT NOT NULL, -- line_oa | whatsapp | sms | qr_kiosk | walk_in_counter | web_portal | mobile_app
capabilities JSONB NOT NULL, -- { has_screen, has_camera, has_card_reader, is_attended, can_oauth, needs_network, supports_biometric }
public_slug TEXT NOT NULL, -- tenant-scoped, immutable once published
endpoint_config JSONB NOT NULL, -- channel-specific credentials / webhook URLs
msh3_code TEXT NOT NULL, -- HL7 MSH-3 Sending Application identifier
fhir_endpoint_ref UUID, -- → FHIR Endpoint resource
status TEXT NOT NULL, -- active | paused | retired
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (tenant_id, public_slug)
);
A LINE channel in Bangkok and a LINE channel in Tokyo share the same capabilities shape but resolve to different adapter sets because the tenant’s jurisdiction differs.
4.4 Template Registry — extending existing workflow_templates
The workflow_templates table already exists and is the write model behind the ReactFlow visual editor. No replacement. Phase 1 adds columns:
ALTER TABLE workflow_templates ADD COLUMN kind TEXT NOT NULL DEFAULT 'patient-intake';
ALTER TABLE workflow_templates ADD COLUMN emram_minimum SMALLINT NOT NULL DEFAULT 0;
ALTER TABLE workflow_templates ADD COLUMN steps JSONB NOT NULL DEFAULT '[]';
ALTER TABLE workflow_templates ADD COLUMN fhir_plan_definition JSONB; -- materialized on save
ALTER TABLE workflow_templates ADD COLUMN template_hash TEXT; -- content hash, drives cache keys
-- reactflow_graph stays as the write model (existing visual editor format)
The steps array holds metadata the ReactFlow graph does not carry: skip_when conditions, timeouts, escalation policies, on_failure behavior, and EMRAM stage attribution per step. The four existing built-in registration templates (web/src/templates/workflows/registration/{basic,vitals-first,qr-code,api-integration}-registration.json) are the first migration targets — their JSON shape gets a steps array and the rest of the metadata inferred from their existing structure.
5. Binding — extending the existing workflow_template_bindings table
The workflow_template_bindings table already exists (infrastructure/medbase/migrations/007_intake_workflow_template_resolution.sql) and already supports scope_type IN ('clinic', 'sub_clinic', 'location'). Phase 1 extends it with channel_id and the four context-filter columns, plus the resolved_plan_hash reference:
ALTER TABLE workflow_template_bindings
ADD COLUMN channel_id UUID REFERENCES channel_registry(id), -- NULL = applies to all channels at scope
ADD COLUMN encounter_class TEXT, -- NULL = any
ADD COLUMN patient_class TEXT, -- NULL = any
ADD COLUMN role TEXT, -- NULL = any
ADD COLUMN resolved_plan_hash TEXT; -- reference into plan store
Resolution precedence widens to include channel, most specific first:
(channel + encounter_class + patient_class + role)
→ (channel + any context)
→ (service_point / location)
→ (subclinic)
→ (clinic)
→ (tenant)
→ (global default)
Plans are stored in a separate content-addressed object store. The binding holds only resolved_plan_hash. A new snapshot yields a new hash, hence a new plan row. Old plans remain intact for in-flight encounters.
CREATE TABLE resolved_plan_store (
hash TEXT PRIMARY KEY, -- hash(input) + snapshot_id
plan JSONB NOT NULL,
stamp JSONB NOT NULL,
resolution_log JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
6. Execution model
Prerequisite check. The current workflow engine in packages/medical-kit/src/medical-worklist/workflowState.ts must be verified as a DAG executor, not a sequence runner. If it linearizes the graph, the resolver’s DAG output has no consumer and Phase 1 is blocked on engine work. This verification is the first Phase 1 task.
Assuming the engine is DAG-capable (or once it is made so):
6.1 Adapter invocation
Each node in the resolved DAG is executed when all predecessors have completed and any skip_when has been evaluated. Execution per step:
- Evaluate
skip_whenagainst currentWorkflowContext. If true, mark skipped and continue. - Iterate the ranked
adapterslist:- Call the adapter’s health probe (if defined). On unhealthy, continue to the next.
- Invoke the adapter with
(context, config). On success, appendproducesto the context. - On failure, evaluate
on_failure:retry→ retry per adapter’s retry policy, then fall through to the next adapter.compensate→ halt forward progress, invoke the compensation saga (§6.2).skip→ mark the step skipped, continue forward.abort→ terminate the workflow with failure.
- If no adapter in the ranked list succeeds, treat as
on_failure.
Fallback chains handle operational degradation only. Primary coverage adapter is NHSO, fallback is Social Security, final fallback is cash-pay manual entry. They do not handle jurisdiction mismatch — a Japanese tourist at a Thai clinic is resolved to a different template (opd-intake-foreign-patient), not to Thai adapters with Japanese fallbacks. Operational resilience and jurisdiction mismatch are solved by different mechanisms.
6.2 Compensation saga
When a step fails with on_failure: compensate, the engine:
- Halts forward execution.
- Walks completed steps in reverse topological order.
- For each completed step that has a
compensateadapter resolved, invokes it with the same context. - Records each compensation attempt in the execution log, success or failure.
- Terminates the workflow in
compensatedstate.
Compensation adapters are resolved eagerly at bind time, not lazily at failure time. This is deliberate: a failing system should not be discovering it cannot clean up after itself. If a step has no compensation adapter available for the jurisdiction, the binding is rejected at template-bind time.
6.3 Human-task escalation
Steps with role: clerk | nurse | triage-nurse and an sla_budget_ms set carry an escalation policy. The engine starts a timer on step entry; on expiry, the policy fires (page supervisor, reroute to alternate role, emit an alert to the queue dashboard). Escalation policy is adapter-level, not engine-level, because escalation semantics vary by jurisdiction (unionized staff, regulated nurse-patient ratios).
7. Plan continuity and regulatory re-resolution
A registration session can span hours — LINE pre-registration at 08:00, counter arrival at 11:00. Between those two moments the registry snapshot may advance: a new adapter version, a new regulation effective date.
Default behavior: continue on the plan that was resolved at session start. The stamp records registry_snapshot_id and regulatory_effective_dates. The engine references this plan through to completion even if the registry has moved on.
Forced re-resolution. A regulation version entry may carry a requires_reresolution: true flag. Thai PDPA amendments that change consent scope qualify; minor adapter version bumps do not. When such an amendment becomes effective mid-session:
- The engine detects the delta against the plan’s
regulatory_effective_dates. - The engine re-resolves against the current snapshot with the original
ResolutionInput. - Completed steps whose adapter is unchanged are marked as already satisfied; the new plan resumes from the first step with a changed adapter.
- The patient is notified in-channel that terms have been updated and affirmative re-consent may be required.
- The old plan stays in
resolved_plan_storefor audit.
This is how we handle semi-frequent Thai PDPA amendments and Japanese APPI revisions without either ignoring them (compliance failure) or re-resolving every in-flight encounter on every change (operational chaos).
8. FHIR projection
On template save, the engine materializes a FHIR PlanDefinition into workflow_templates.fhir_plan_definition. This projection is read-only and never written by external systems — they push Market Packs (adapter registry rows), not PlanDefinitions.
Projection shape:
PlanDefinition.action[]— one per template step.action.definitionCanonical→ the ActivityDefinition URL derived from the step’s primary adapter.action.condition[]→skip_whenexpressions translated to FHIRPath.action.participant[]→rolemapped to FHIR Practitioner / Patient / Device roles.useContextentries:program→{ "coding": [{ "code": "EMRAM", "display": "Stage 4" }] }jurisdiction→ ISO 3166-1 codeworkflow-task→workflow_kind(patient-intake, discharge, …)
ActivityDefinitions are materialized from adapter_catalog rows. Each adapter’s implementation_ref is its canonical URL. Jurisdiction and regulation compliance carry through as useContext entries.
The endpoint lives at services/public-api/src/modules/fhir/resources/plan-definition.controller.ts and hooks into the existing FHIR CapabilityStatement.
9. HL7 ADT emission
Workflow completion deterministically produces an ADT message. This replaces any code path that emits ADT from other triggers.
| Workflow kind + encounter_class | ADT trigger |
|---|---|
patient-intake + AMB (walk-in) |
ADT^A04 (Register a Patient) |
patient-intake + PRENC (pre-reg) |
ADT^A05 (Pre-admit) |
patient-intake + EMER |
ADT^A04 with PV1-2 = E |
patient-intake + IMP (direct admit) |
ADT^A01 (Admit / Visit Notification) |
ltc-admission (Tokuyou) |
ADT^A01 with facility-type coding in PV1 |
Field mapping:
- MSH-3 (Sending Application) ←
channel.msh3_code— closes the parser gap. - MSH-4 (Sending Facility) ←
tenant.facility_code— closes the parser gap. This is a critical tenant-routing value and must not be discarded. - PV1-2 (Patient Class) ←
encounter_class. - PV1-3 (Assigned Patient Location) ← resolved
Locationreference from theenqueuestep. - PID-3 (Patient Identifier List) ← Patient identifiers from the
identifystep, including PIX-resolved cross-references.
Emission lives in services/interoperability/.../modules/hl7v2/emitter/ (new), alongside the existing parser.
10. Identity reconciliation (PIX / PDQm hook)
The identify step is where cross-channel identity reconciliation happens. Flow:
- LINE pre-registration produces an identifier set:
{ LINE-user-id, OTP-verified-phone }. - Patient arrives at counter with national ID card.
- Counter staff scans card; the
identifystep at the counter receives the card identifier. - The adapter calls PIX Manager (
services/interoperability/.../modules/pix/— Phase 5) with the card identifier. - PIX returns cross-references, including the LINE pre-reg identifier.
- The engine merges the two sessions; the LINE pre-registration’s partial context (demographics, consent draft) becomes the starting context for the counter workflow.
The adapter schema enforces that every identifier set produced upstream is typed:
produces: [{ name: 'patient_identifiers', type: 'Identifier[]' }]
PIX cross-referencing is a separate adapter invoked by the identify step when configured, not a hard-coded engine behavior.
11. Phase plan
Phase 1 — structural commitment. (Weeks 1–4)
The actual delta from what exists today. Each item either extends something in the repo or adds one of the small number of new tables.
Engine and resolver:
- Verify DAG execution capability in
packages/medical-kit/src/medical-worklist/workflowState.ts. Rewrite the runner if it linearizes. This is the first task; the rest is blocked on it. - Extract the existing resolve logic from
workflowTemplate.service.tsinto a pure library implementing the §3 contract. The existing service becomes a thin wrapper. Full input signature (channel_id,encounter_class,patient_class,role,ResolutionContext) wired through — adapters that do not use the new fields ignore nulls. - Resolver returns the full
ResolutionResultshape withresolution_logandstamppopulated. Fallback lists may be single-element in Phase 1, but the shape is correct. - Bind-time validation of
requires/producesacross the template DAG.
Schema changes — additive only:
BlockEnummetadata extension (code change atweb/src/common/components/medical/builder/main-flow-editor/components/workflow/types.tsand associated NODES_*DATA structures). No schema.adapter_catalogtable created, seeded with one default row per existing BlockEnum entry (jurisdiction = '*',priority = 50). Thailand-specific adapters seeded directly via migration (Market Pack tooling comes in Phase 2).channel_registrytable created, seeded withwalk_in_counter+line_oachannels for the Thailand sandbox tenant.workflow_template_bindingsextended withchannel_id+ context-filter columns +resolved_plan_hash. Existing rows keep working (NULL = any).workflow_templatesextended withkind,emram_minimum,steps,fhir_plan_definition,template_hash. Existingreactflow_graphstays.resolved_plan_storetable created (new).
Templates and UI:
- Four existing registration templates (
web/src/templates/workflows/registration/*.json) re-expressed through the resolver with equivalent runtime output to today’s behavior. This is the correctness gate. If the resolver cannot reproduce today’s behavior for these four templates, the architecture has not landed. - Registration templates page (
/workflow-store) gets a channel selector alongside the existing clinic / sub-clinic selector. BackendResolveTemplateRequestalready acceptslocationId; Phase 1 extends it withchannelId. - One hard-coded Deployment Profile inline:
hospital-general × TH.
Out of Phase 1: Market Pack extraction of adapters, FHIR PlanDefinition projection, ADT emission fixes, PIX, other profiles. These are Phase 2–5.
Phase 1.5 — Deployment Profile extraction. (Week 5)
- Extract the hard-coded profile into the
deployment_profiletable. - Add URL seeding (
?profile=...&market=...). - Add the setup validator.
- Still one profile.
Phase 2 — Market Pack extraction, Japan introduction. (Weeks 6–9)
- Extract Thailand adapters from hand-seeded rows into a Market Pack format.
- Build the Japan Market Pack as the test of “adding a country = adding rows.”
- Introduce the
ltc-tokuyou × JPprofile. LTC admission template for Tokuyou ships here.
Phase 2.5 — Second jurisdiction for the same profile. (Weeks 10–11)
- Introduce the
hospital-general × PHprofile. - Validates profile reuse across jurisdictions. Adding rows, not code.
Phase 3 — FHIR projection. (Weeks 12–15)
PlanDefinitionmaterialization on template save.ActivityDefinitionendpoints for adapters.- EMRAM
useContexttagging. - Published
/fhir/PlanDefinitionendpoint. - Introduce the
longevity-clinic-premiumprofile, initially TH, then US and JP once the longevity-addon Market Pack exists.
Phase 4 — ADT emission. (Weeks 16–19)
- Deterministic ADT output from workflow completion.
- MSH-3, MSH-4, PV1-2, PV1-3 mapping.
- Fix the parser’s MSH-4 drop.
- One-way emission only; inbound ADT stays on the existing ingest path.
- Introduce the
ltc-snf × USprofile — the US regulatory onramp (MDS / PDPM / HIPAA-strict).
Phase 5 — PIXm and cross-channel reconciliation. (Weeks 20–23)
- PIX Manager service.
- LINE pre-reg → counter-arrival merge.
- PDQm queries for cross-facility lookup within a tenant.
The measure of success at each phase is: adding the next profile is additive, not editive. The moment a new profile forces a resolver-contract change or a registry-schema change, the architecture has failed and needs review.
12. Non-goals (explicit deferrals)
- Inbound FHIR PlanDefinition writes. External systems push Market Packs, not PlanDefinitions. The resolver is our write path.
- Custom workflow scripting.
skip_when/requiredconditions are a minimal declarative expression language over context and channel capabilities. No general scripting. - Dynamic adapter loading. Adapters are registry rows dispatched by a single workflow engine. No plugin runtime. Scale is “more rows,” not “more deployed services.”
- BPMN-level orchestration. No compensation of compensation, no timers across sessions, no long-running sagas beyond a single registration. If we outgrow PlanDefinition’s expressiveness, we layer — we do not replace.
- Cross-tenant identity reconciliation. PIX operates within a tenant. Cross-tenant is a separate architectural problem with a separate consent model.
13. Testing strategy
The resolver’s seven invariants translate directly into property-based tests:
- Purity:
resolve(input, ctx) === resolve(input, ctx)over 10⁴ random inputs. - Totality: For every input that returns
status: ok, the plan covers every required step. For every input that returnsstatus != ok, thegapsarray is non-empty and points to real missing adapters. - Deterministic tiebreakers: For inputs with multiple matching adapters at equal priority, the same adapter is always chosen.
- Audit completeness:
resolution_log.length === plan.graph.node_count. - Content addressability: Identical inputs + identical snapshot → identical
stamp.input_hash. - Fallback ranking: For every step with multiple candidate adapters, the ranked list matches the documented tiebreaker order.
- Regulatory stamp completeness: Every regulation referenced by any resolved adapter appears in
stamp.regulatory_effective_dates.
The binding-time validation test is the correctness gate: take the four existing Thai templates, bind them against the Phase 1 channel set, and assert that (a) every binding resolves successfully and (b) the runtime behavior of the resolved plans is equivalent to the pre-refactor behavior. Equivalence is measured against a corpus of recorded patient registrations.
14. Open questions
- DAG executor status. Unverified. First Phase 1 task.
- Role taxonomy.
clerk | nurse | self-service | triage-nurseis a starting set. Vajira’s staffing model likely has more granularity. emram_minimumon templates vsemram_stageon steps. We currently specify both. One may subsume the other after Phase 3.- Plan eviction policy for
resolved_plan_store. In-flight encounters pin plans; long-completed plans can be archived. Retention must satisfy the longest audit requirement across jurisdictions — likely 7 years. - Runtime re-resolution triggers beyond regulatory changes. Tenant configuration changes (new insurance contract) may also warrant re-resolution for in-flight encounters. Behavior TBD.
- Facility dimension for multi-facility tenants. A single Japanese tenant may run a hospital and a Tokuyou facility (
JAPAN-NURSING-HOME.mdtreats these as completely independent systems). Todayclinic_idis the closest to a facility proxy. Do we need an explicitfacility_id/facility_typedimension on the resolver input and onchannel_registry, or is thetenant → clinichierarchy sufficient once each facility is modeled as its own tenant? Decision needed before Phase 2 introducesltc-tokuyou. - Location entity cleanup. The existing
Location.tsentity carries billing fields (serviceCharge,foodCharge) alongside place-of-care concepts. This is messy and will become an obstacle as Location gets referenced more heavily byenqueuesteps and ADT PV1-3 emission. Separate PR, not blocking, but should be scheduled before Phase 4 ADT emission work.
15. Deployment Profile Catalog
15.1 Concept
Market Packs are jurisdiction-scoped — they seed adapters for Thailand, Japan, Philippines, United States. But a Thailand Market Pack alone does not tell you whether the facility is Human Longevity Bangkok or Vajira Hospital. Those two deployments share every adapter in the Thailand Market Pack (ThaiD, PDPA consent, Thai demographics) and share almost no workflow templates. One does cash-pay concierge intake with whole-body diagnostics; the other does NHSO-funded OPD / IPD with ER and surgery.
Deployment Profile is the missing concept: a curated bundle of (facility_type × supported_jurisdictions) that specifies templates, clinic structure, role taxonomy, feature flags, and default bindings for a recognizable kind of healthcare operation. It is a setup-time artifact, not a runtime resolution input.
| Layer | Scope | Example |
|---|---|---|
| Market Pack | jurisdiction | Thailand (adapters for ThaiD, PDPA, NHSO, TH pathology) |
| Deployment Profile | facility type | longevity-clinic-premium (templates, clinics, roles for Human Longevity–style operations) |
| Tenant Configuration | specific deployment | “Human Longevity Bangkok” = longevity-clinic-premium × Thailand + tenant overrides |
A profile declares the Market Packs it needs. Some profiles are single-jurisdiction by nature (ltc-tokuyou is Japan-only; the workflow is built around 介護保険 and 要介護 certification, which do not exist elsewhere). Others are multi-jurisdiction (longevity clinics operate the same way in Bangkok, Los Angeles, and Tokyo — same service shape, different regulatory adapter sets).
15.2 Profile × Jurisdiction Catalog
Phase 1 catalog:
| Profile ID | Facility Type | Supported Jurisdictions | Required Market Packs | Example Customers |
|---|---|---|---|---|
longevity-clinic-premium |
Longevity / wellness | TH, US (CA), JP | base + jurisdiction + longevity-addon | Human Longevity (Bangkok / LA / Tokyo) |
ltc-tokuyou |
Japanese nursing home | JP only | japan + ltc-jp | 特別養護老人ホーム facilities |
ltc-snf |
US skilled nursing | US only | us + ltc-us | Medicare-certified SNFs |
hospital-general |
General hospital | TH, PH | jurisdiction base | Vajira (TH), Makati Med / St. Luke’s (PH) |
(longevity-clinic-premium, US) and (longevity-clinic-premium, JP) pair the same profile with different Market Packs. (ltc-tokuyou, TH) is rejected at application time — the profile’s supported_jurisdictions does not include TH, and the ltc-jp Market Pack’s kaigo_hoken adapters will not resolve against a TH tenant anyway.
15.3 Schema
CREATE TABLE deployment_profile (
id TEXT PRIMARY KEY, -- 'longevity-clinic-premium'
display_name JSONB NOT NULL, -- bilingual
description JSONB NOT NULL, -- bilingual
facility_type TEXT NOT NULL, -- 'longevity-clinic' | 'nursing-home' | 'general-hospital' | 'community-clinic'
supported_jurisdictions TEXT[] NOT NULL, -- ['TH', 'US', 'JP']
required_market_packs TEXT[] NOT NULL, -- ['base', '{jurisdiction}', 'longevity-addon']
template_set UUID[] NOT NULL, -- references workflow_templates
default_bindings JSONB NOT NULL, -- array of binding specs to apply post-install
clinic_seed JSONB NOT NULL, -- clinic/sub-clinic/service-point tree
role_taxonomy JSONB NOT NULL, -- role IDs this profile introduces
feature_flags JSONB NOT NULL, -- feature enablement map
sample_patients JSONB, -- demo data for UAT deployments
emram_target SMALLINT, -- suggested starting EMRAM stage (nullable)
channel_seed JSONB NOT NULL, -- default channel_registry entries
published_at TIMESTAMPTZ NOT NULL,
deprecated_at TIMESTAMPTZ
);
Profile application at tenant setup runs a validator first, then writes rows into the tenant’s scope: templates, bindings, clinics, channels. The write is a single transaction — either the full profile applies or none of it does. Subsequent changes (adding a clinic, renaming a role) happen on the tenant’s own rows and do not drift the profile.
15.4 The four profiles
15.4.1 longevity-clinic-premium
Supported jurisdictions: TH, US (California specifically — wellness-clinic classification differs by state), JP.
Required Market Packs: base, {jurisdiction}, longevity-addon (new Market Pack for longevity-specific adapters: research-enrollment consent, genomics counseling acknowledgement, membership-renewal billing).
Template set:
| Template | Purpose | Notable steps |
|---|---|---|
longevity-intake |
First-visit comprehensive intake | identify → authenticate → demographics → research-consent (APPI / PDPA / HIPAA by jurisdiction) → lifestyle-assessment → membership-verification → enqueue-assessment-center |
diagnostic-panel-booking |
Schedule whole-body MRI, genomics, advanced bloodwork | identify → coverage (self-pay) → diagnostic-order → appointment-scheduling |
consultation-wellness |
Preventive-medicine MD visit | identify → triage (wellness-coordinator) → consultation (wellness-md) → care-plan-draft |
report-delivery |
Deliver personalized longevity report to patient + family | identify → consent-share-with-family → report-generation → report-review-session |
membership-renewal |
Subscription renewal | identify → coverage (membership-billing adapter) → renewal-confirmation |
Clinic seed:
- Assessment Center (diagnostics intake and reception)
- Imaging (MRI, CT, DEXA)
- Labs (phlebotomy + partner-lab handoff)
- Consultation Rooms (wellness physicians)
- Executive Concierge (VIP-tier handling)
Role taxonomy: wellness-coordinator, concierge, wellness-md, phlebotomist, imaging-tech, genomics-counselor.
Feature flags:
{
"membership": true, // subscription billing active
"research-enrollment": true, // data contribution to longevity cohort
"insurance-eligibility": false, // cash-pay primary; no NHSO/Medicare step
"pharmacy-retail": false, // no retail pharmacy
"supplement-prescribing": true, // nutraceutical orders
"hl7-adt-emit": false, // not a hospital; no public-health ADT
"concierge-routing": true, // VIP path routing
"research-cohort-export": true // gated FHIR export for cohort studies
}
EMRAM target: null. Longevity clinics are not typically EMRAM-tracked. Clinical records are still stored with full audit, but the maturity model does not apply.
Jurisdiction-specific variations (applied via Market Pack, not profile):
- TH: PDPA + TFDA medical wellness facility classification. Research consent uses the PDPA Section 26 scientific-research basis.
- US (CA): HIPAA + CLIA for lab handoffs + California-specific wellness clinic rules (CDPH). Research consent under Common Rule.
- JP: APPI + 診療所 (diagnostic clinic) classification. Research consent under the Ethical Guidelines for Medical and Health Research Involving Human Subjects.
15.4.2 ltc-tokuyou (JP only)
Supported jurisdictions: JP only.
Required Market Packs: base, japan, ltc-jp (kaigo_hoken adapters, 要介護 certification, care-plan assessment tooling).
Template set:
| Template | Purpose | Notable steps |
|---|---|---|
admission-tokuyou |
Multi-week admission to long-term care | identify (MyNumber or 保険証) → authenticate → consent (APPI + LTC facility agreement) → care-level-intake (requires 要介護 certification from municipality) → family-agreement → room-assignment |
care-plan-assessment |
Initial care plan within 14 days of admission | resident-review → ADL-assessment → care-plan-draft (care-manager) → family-review → plan-activation |
care-plan-quarterly-review |
Quarterly care plan refresh | chart-review → updated-ADL → revised-plan → family-confirmation |
daily-cares-log |
Daily caregiver / nurse documentation | resident-check → vitals → meals → ADL-log → notable-events |
family-communication |
Updates, visitation scheduling | family-identity → communication-log → visit-scheduling |
end-of-life-planning |
Advance directives, palliative transition | resident-capacity-assessment → advance-directive (with family) → palliative-orders |
discharge-to-family / discharge-to-hospital / death-in-place |
Three discharge paths | context-specific assessment → ADT emission → kaigo settlement |
Clinic seed (small-group unit model, typical for modern Tokuyou):
- Admission office
- Care units (ユニット型 — small groups of ~10 residents each)
- Rehabilitation room
- Nursing station
- Dining / communal area
- Palliative care room
- Family meeting room
Role taxonomy: care-manager (ケアマネージャー), nurse (看護師), caregiver (介護職員), physical-therapist (理学療法士), facility-director (施設長), social-worker (生活相談員), physician (嘱託医 — visiting contracted MD).
Feature flags:
{
"kaigo-hoken": true,
"care-level-certification": true,
"long-stay-records": true, // residents live here; no discharge per visit
"family-portal": true,
"insurance-eligibility": false, // kaigo handles this in the care-level step
"hl7-adt-emit": true, // transfers to hospitals
"ssmix2-export": true, // Japanese EHR data exchange standard
"advance-directive": true
}
EMRAM target: 2–3. LTC facilities are not the primary EMRAM audience; CMS and Japanese MHLW care about different data.
15.4.3 ltc-snf (US only)
Supported jurisdictions: US only.
Required Market Packs: base, us, ltc-us (MDS 3.0 forms, PDPM calculator, Medicare benefit tracker, CMS reporting hooks).
Template set:
| Template | Purpose | Notable steps |
|---|---|---|
admission-snf |
Medicare-compliant SNF admission | identify → authenticate → HIPAA-consent → medicare-eligibility → 3-day-qualifying-stay-verification → PDPM-intake → room-assignment |
mds-assessment-admission |
MDS 3.0 on admission (5-day, 14-day, 30-day) | resident-review → MDS-items-entry → interdisciplinary-review → submission-to-CMS |
pdpm-billing-calculation |
Derive payment from MDS | MDS-lookup → PDPM-category-calculation → HIPPS-code-generation → claim-prepare |
care-plan-care-conference |
Interdisciplinary team meeting | IDT-scheduling → MDS-review → care-plan-draft → resident-family-review |
therapy-order-management |
PT / OT / speech therapy orders | order-entry → minutes-tracking → RUG-PDPM-impact |
discharge-planning |
Medicare-compliant discharge | discharge-assessment → safe-discharge-verification → post-discharge-care-arrangement → OBRA-discharge-MDS |
medicare-100-day-tracking |
Benefit window monitoring | daily-eligibility-check → benefit-days-remaining → financial-counseling-trigger |
Role taxonomy: mds-coordinator, director-of-nursing, charge-nurse, cna, physical-therapist, occupational-therapist, speech-therapist, social-worker, medical-director, activities-director.
Feature flags:
{
"mds-3.0": true,
"pdpm-billing": true,
"medicare-benefit-tracking": true,
"cms-reporting": true, // MDS submission, QAPI, staffing reports
"hipaa-strict": true,
"hl7-adt-emit": true, // hospital transfers
"rai-process": true,
"obra-compliance": true
}
EMRAM target: variable. CMS prioritizes MDS / PDPM compliance over EMRAM stages.
15.4.4 hospital-general (TH, PH)
Supported jurisdictions: TH, PH (with room for ID, VN, MY later under the same profile — Southeast Asian general-hospital shape is remarkably consistent at the workflow level).
Required Market Packs: base, {jurisdiction}.
Template set:
| Template | Purpose |
|---|---|
opd-intake-basic |
Walk-in outpatient registration |
opd-intake-pre-registered |
Patient who pre-registered via LINE / web |
opd-intake-foreign-patient |
Foreign / tourist patient (different auth + consent + payment template entirely) |
ed-triage |
Emergency department triage |
ipd-admission-ed |
Emergency → inpatient admission |
ipd-admission-elective |
Scheduled surgical admission |
ipd-transfer-intra |
Ward-to-ward transfer |
ipd-discharge |
Discharge planning + medication reconciliation |
surgical-preop |
Pre-operative workup |
pharmacy-dispense-opd |
Outpatient pharmacy dispensing |
pharmacy-dispense-ipd |
Inpatient pharmacy ward-delivery |
cashier-billing |
Patient financial counseling + payment |
Clinic seed:
- Outpatient clinics (specialty set: internal medicine, pediatrics, OB/GYN, surgery, ENT, ophthalmology, dentistry, cardiology, orthopedics, dermatology — configurable per tenant)
- Emergency Department
- Inpatient wards (general, ICU, pediatric, maternity, surgical, medical)
- Operating theaters
- Pharmacy (OPD + IPD)
- Laboratory
- Imaging (radiology, ultrasound, CT, MRI if EMRAM ≥ 4)
- Cashier / billing counter
Role taxonomy: registration-clerk, triage-nurse, ward-nurse, charge-nurse, physician (with specialty subtypes), resident (enabled only in teaching-mode), pharmacist, cashier, medical-coder, case-manager.
Feature flags:
{
"insurance-eligibility": true,
"pharmacy-retail": true,
"pharmacy-ipd-unit-dose": true,
"hl7-adt-emit": true,
"coder-workflow": true, // RCM claim coding
"teaching-mode": false, // Vajira-style overlay, enabled per tenant
"emergency-department": true,
"operating-theater": true
}
EMRAM target: 3–5 for Vajira-tier hospitals, 2–3 for community hospitals. Tenant-set at application time.
Jurisdiction-specific variations (applied via Market Pack, not profile):
- TH (Vajira): NHSO + Social Security + private insurance + cash. Thai PDPA. MOPH reporting endpoints. Thai + English. Optional
teaching-modeoverlay for the university-affiliated teaching workflow. - PH: PhilHealth case rates + private insurance + self-pay (significant out-of-pocket share). Philippine Data Privacy Act. DOH reporting endpoints. Tagalog + English. Senior-citizen and PWD mandatory-discount logic in
coverageadapter.
The profile is shared. Only the Market Pack changes. This is the test of the architecture: a PH hospital and a TH hospital share 90% of their workflow shape because the facility type is the same; jurisdiction drives the 10% that differs at the adapter level.
15.5 Setup flow and URL seeding
The existing URL seeding (?market=japan) extends to profile-aware seeding:
?profile=longevity-clinic-premium&market=thailand
?profile=ltc-tokuyou&market=japan
?profile=hospital-general&market=philippines
?profile=hospital-general&market=thailand&variant=teaching # Vajira's overlay
The setup flow becomes:
- Profile + Jurisdiction selection (gallery UI or URL). User picks from the catalog. Invalid pairs (
ltc-tokuyou× non-JP) are visually disabled with a one-line explanation. - Market Pack resolution. Setup service looks up
profile.required_market_packsand applies them in order. If a required Market Pack is not installed on this instance, setup fails with a clear error. - Profile validation against the chosen jurisdiction — see §15.6.
- Template installation. Templates from
profile.template_setare written toworkflow_templatesin the tenant scope. - Clinic seed. Clinics / sub-clinics / service-points written per
profile.clinic_seed. - Channel seed. Default channels from
profile.channel_seedwritten tochannel_registry. Ahospital-generalprofile seedswalk_in_counterper clinic;longevity-clinic-premiumadditionally seedsweb_portalfor membership access. - Default bindings applied. Templates bound to clinics per
profile.default_bindings. - Resolver pre-computes plans. For each binding, the resolver produces and stores the resolved plan. Any bind-time validation failure aborts the setup transaction with the specific gap reported.
- Sample data (optional). If the UAT flag is set,
profile.sample_patientsis loaded for demo.
The ?profile=...&market=... URL seeding is the automated path for Vercel UAT deployments — it lets the sandbox spin up with a chosen profile without manual setup. Production deployments use the same underlying apply-profile transaction through the admin UI.
15.6 Profile application validation
Before any writes, the setup service runs a pre-flight validator:
- Profile supports jurisdiction.
market ∈ profile.supported_jurisdictionsor reject. - Required Market Packs are installed on this instance and at compatible versions.
- Every template in
profile.template_setresolves cleanly against the chosen jurisdiction’s adapters — run the resolver for each default binding and confirmstatus: ok. This is the gap-finder from the resolver design applied at profile apply time. If Template X requires acoverageadapter for jurisdiction Y that the combined Market Packs do not provide, setup fails with the specific missing adapter, not a runtime surprise. - Role taxonomy does not clash with tenant-existing roles (only matters for profile migration / upgrade, not fresh install).
- Feature flags are compatible with instance-wide licensing and legal constraints (e.g.,
research-enrollment: truerequires an IRB-approved research tenant).
All five checks pass → setup proceeds in a single transaction. Any failure → nothing is written. This matters because half-applied profiles create the exact class of incoherent deployments this catalog is designed to prevent.
15.7 Composition with the registry architecture
The Deployment Profile layer sits cleanly on top of the registry model:
┌────────────────────────────────────────────────┐
│ Deployment Profile Catalog │ ← setup-time (§15)
│ (longevity-clinic-premium, ltc-tokuyou, …) │
└──────────────────┬─────────────────────────────┘
│ applies
┌──────────────────▼─────────────────────────────┐
│ Tenant Scope │
│ · clinic_seed → clinics table │
│ · channel_seed → channel_registry (§4.3) │
│ · template_set → workflow_templates (§4.4)│
│ · default_bindings → workflow_template_bindings│
└──────────────────┬─────────────────────────────┘
│ references
┌──────────────────▼─────────────────────────────┐
│ Adapter Catalog (§4.2) ← Market Pack seeded │
│ BlockEnum + metadata (§4.1) (in code) │
└──────────────────┬─────────────────────────────┘
│ consumed by
┌──────────────────▼─────────────────────────────┐
│ Resolver (§3) │
│ returns ResolvedPlan for each binding │
└────────────────────────────────────────────────┘
No runtime path changes. The resolver is unaware of Deployment Profile as a concept — it sees templates, bindings, adapters, and channels like before. The profile is pure setup-time composition.
15.8 Success criterion for the catalog
The architecture has succeeded when adding the next profile is an additive operation:
- New row in
deployment_profile. - New Market Pack if a new jurisdiction (new rows in
adapter_catalog). - Zero code changes to the resolver, the registries, or the runtime engine.
Any proposal to add a profile that cannot be expressed this way is a signal that the resolver contract or registry schema needs revisiting — not a signal to special-case the profile.
Appendix A — Glossary
| Term | Definition |
|---|---|
| Adapter | A concrete implementation of one BlockEnum entry, scoped by jurisdiction and channel capabilities. Catalogued in adapter_catalog. |
| Adapter Catalog | Table of adapter implementations, one-to-many per BlockEnum entry. Does not replace BlockEnum; sub-registers against it. |
| Binding | A rule that associates a template with a scope (tenant / clinic / subclinic / service-point) and optional context. |
| Channel | An entry channel through which a patient reaches the workflow (LINE, walk-in counter, kiosk, web portal, etc.). |
| Deployment Profile | A curated bundle of templates, clinic structure, roles, and feature flags for a facility type. |
| Market Pack | A jurisdiction-scoped bundle of adapters, seed data, and regulatory configuration. |
| Plan | The output of the resolver — an immutable, content-addressed DAG of resolved steps. |
| ResolutionInput | The deterministic input to the resolver (tenant, scope, channel, template, optional context). |
| ResolutionContext | The temporal + snapshot state that makes the resolver pure (as-of timestamp, registry snapshot, feature flags). |
| Resolver | The pure function that turns (ResolutionInput, ResolutionContext) into a ResolvedPlan. |
| Stamp | The provenance record attached to every plan: resolver version, snapshot, input hash, regulatory effective dates. |
| Step type | An entry in the canonical closed vocabulary (identify, authenticate, consent, …). Implemented as BlockEnum in code, not as a database table. |
| Template | An ordered DAG of step types with conditions, timeouts, and EMRAM attributions. Stored as ReactFlow graph + metadata, projected to FHIR PlanDefinition. |
Appendix B — Standards references
- FHIR R4 — HL7 FHIR Release 4.
PlanDefinition,ActivityDefinition,Task,Encounter,Patient,Coverage,Consent. - HL7v2 — ADT event triggers A01, A04, A05, A08, A28, A31.
- IHE PAM — Patient Administration Management integration profile.
- IHE PIX / PDQm — Patient Identifier Cross-referencing and Patient Demographics Query for mobile.
- HIMSS EMRAM — Electronic Medical Record Adoption Model, Stages 0–7.
- SS-MIX2 — Japanese standardized structured medical information exchange.
- MDS 3.0 / PDPM — Minimum Data Set / Patient-Driven Payment Model (US SNF).
- RAI — Resident Assessment Instrument (US LTC).
- PDPA — Thailand Personal Data Protection Act.
- APPI — Japan Act on the Protection of Personal Information.
- HIPAA — US Health Insurance Portability and Accountability Act.
Last updated: 2026-04-16. Revision history tracked via git.