medOS ultra

Patient Intake Resolver

Patient intake workflow resolver: registries, resolver contract, deployment profiles.

39 min read diagramsUpdated 2026-04-15docs/architecture/patient-intake-resolver.md

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 / ActivityDefinition as 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:

  1. 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.
  2. 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.
  3. Pure resolver library extracted from workflowTemplate.service.ts, with the full contract in §3.
  4. Content-addressed resolved_plan_store — new table, but it is a projection cache, not a new domain entity.
  5. FHIR PlanDefinition projection on template save (§8), new endpoint, reuses existing FHIR infra.
  6. HL7 ADT emission from workflow completion (§9), new outbound path, sits next to the existing parser.
  7. 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.

  1. Pure resolver. resolve(input, context) is a pure function. Same inputs always produce the same plan.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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-v3 requires identity_verification: VerificationResult but no upstream step in template basic-intake-th produces 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:

  1. Evaluate skip_when against current WorkflowContext. If true, mark skipped and continue.
  2. Iterate the ranked adapters list:
    • Call the adapter’s health probe (if defined). On unhealthy, continue to the next.
    • Invoke the adapter with (context, config). On success, append produces to 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.
  3. 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:

  1. Halts forward execution.
  2. Walks completed steps in reverse topological order.
  3. For each completed step that has a compensate adapter resolved, invokes it with the same context.
  4. Records each compensation attempt in the execution log, success or failure.
  5. Terminates the workflow in compensated state.

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:

  1. The engine detects the delta against the plan’s regulatory_effective_dates.
  2. The engine re-resolves against the current snapshot with the original ResolutionInput.
  3. Completed steps whose adapter is unchanged are marked as already satisfied; the new plan resumes from the first step with a changed adapter.
  4. The patient is notified in-channel that terms have been updated and affirmative re-consent may be required.
  5. The old plan stays in resolved_plan_store for 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_when expressions translated to FHIRPath.
  • action.participant[]role mapped to FHIR Practitioner / Patient / Device roles.
  • useContext entries:
    • program{ "coding": [{ "code": "EMRAM", "display": "Stage 4" }] }
    • jurisdiction → ISO 3166-1 code
    • workflow-taskworkflow_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 Location reference from the enqueue step.
  • PID-3 (Patient Identifier List) ← Patient identifiers from the identify step, 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:

  1. LINE pre-registration produces an identifier set: { LINE-user-id, OTP-verified-phone }.
  2. Patient arrives at counter with national ID card.
  3. Counter staff scans card; the identify step at the counter receives the card identifier.
  4. The adapter calls PIX Manager (services/interoperability/.../modules/pix/ — Phase 5) with the card identifier.
  5. PIX returns cross-references, including the LINE pre-reg identifier.
  6. 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.ts into 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 ResolutionResult shape with resolution_log and stamp populated. Fallback lists may be single-element in Phase 1, but the shape is correct.
  • Bind-time validation of requires / produces across the template DAG.

Schema changes — additive only:

  • BlockEnum metadata extension (code change at web/src/common/components/medical/builder/main-flow-editor/components/workflow/types.ts and associated NODES_*DATA structures). No schema.
  • adapter_catalog table 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_registry table created, seeded with walk_in_counter + line_oa channels for the Thailand sandbox tenant.
  • workflow_template_bindings extended with channel_id + context-filter columns + resolved_plan_hash. Existing rows keep working (NULL = any).
  • workflow_templates extended with kind, emram_minimum, steps, fhir_plan_definition, template_hash. Existing reactflow_graph stays.
  • resolved_plan_store table 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. Backend ResolveTemplateRequest already accepts locationId; Phase 1 extends it with channelId.
  • 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_profile table.
  • 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 × JP profile. LTC admission template for Tokuyou ships here.

Phase 2.5 — Second jurisdiction for the same profile. (Weeks 10–11)

  • Introduce the hospital-general × PH profile.
  • Validates profile reuse across jurisdictions. Adding rows, not code.

Phase 3 — FHIR projection. (Weeks 12–15)

  • PlanDefinition materialization on template save.
  • ActivityDefinition endpoints for adapters.
  • EMRAM useContext tagging.
  • Published /fhir/PlanDefinition endpoint.
  • Introduce the longevity-clinic-premium profile, 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 × US profile — 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 / required conditions 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:

  1. Purity: resolve(input, ctx) === resolve(input, ctx) over 10⁴ random inputs.
  2. Totality: For every input that returns status: ok, the plan covers every required step. For every input that returns status != ok, the gaps array is non-empty and points to real missing adapters.
  3. Deterministic tiebreakers: For inputs with multiple matching adapters at equal priority, the same adapter is always chosen.
  4. Audit completeness: resolution_log.length === plan.graph.node_count.
  5. Content addressability: Identical inputs + identical snapshot → identical stamp.input_hash.
  6. Fallback ranking: For every step with multiple candidate adapters, the ranked list matches the documented tiebreaker order.
  7. 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-nurse is a starting set. Vajira’s staffing model likely has more granularity.
  • emram_minimum on templates vs emram_stage on 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.md treats these as completely independent systems). Today clinic_id is the closest to a facility proxy. Do we need an explicit facility_id / facility_type dimension on the resolver input and on channel_registry, or is the tenant → clinic hierarchy sufficient once each facility is modeled as its own tenant? Decision needed before Phase 2 introduces ltc-tokuyou.
  • Location entity cleanup. The existing Location.ts entity carries billing fields (serviceCharge, foodCharge) alongside place-of-care concepts. This is messy and will become an obstacle as Location gets referenced more heavily by enqueue steps 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-mode overlay 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 coverage adapter.

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:

  1. 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.
  2. Market Pack resolution. Setup service looks up profile.required_market_packs and applies them in order. If a required Market Pack is not installed on this instance, setup fails with a clear error.
  3. Profile validation against the chosen jurisdiction — see §15.6.
  4. Template installation. Templates from profile.template_set are written to workflow_templates in the tenant scope.
  5. Clinic seed. Clinics / sub-clinics / service-points written per profile.clinic_seed.
  6. Channel seed. Default channels from profile.channel_seed written to channel_registry. A hospital-general profile seeds walk_in_counter per clinic; longevity-clinic-premium additionally seeds web_portal for membership access.
  7. Default bindings applied. Templates bound to clinics per profile.default_bindings.
  8. 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.
  9. Sample data (optional). If the UAT flag is set, profile.sample_patients is 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:

  1. Profile supports jurisdiction. market ∈ profile.supported_jurisdictions or reject.
  2. Required Market Packs are installed on this instance and at compatible versions.
  3. Every template in profile.template_set resolves cleanly against the chosen jurisdiction’s adapters — run the resolver for each default binding and confirm status: ok. This is the gap-finder from the resolver design applied at profile apply time. If Template X requires a coverage adapter for jurisdiction Y that the combined Market Packs do not provide, setup fails with the specific missing adapter, not a runtime surprise.
  4. Role taxonomy does not clash with tenant-existing roles (only matters for profile migration / upgrade, not fresh install).
  5. Feature flags are compatible with instance-wide licensing and legal constraints (e.g., research-enrollment: true requires 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.

Ask Anything