← Building agentic patient workflows

Part 7 of 18 · 6 min read

Patient identity and FHIR connectors

Identity mapping, SMART authorization, FHIR R4 appointment contracts and write reconciliation.

Arunank Sharan · Kuzushi Labs ·

In this article

The first connector question I would ask a clinic is not which API it uses. It is which system is allowed to say who a patient is. A wrong identity can make every later step look technically successful while attaching the appointment or message to the wrong person.

Our patient service already creates records, generates an MRN and checks for possible duplicates. The inspected project also exposes healthcare resource modules. That establishes a useful domain model; it does not establish a certified EHR integration. In this chapter I develop a reference FHIR R4 connector around that foundation, using synthetic resource identifiers.

Keep an identifier's assigning system

Suppose two clinics both have a patient numbered 10042. That number alone is not globally meaningful. I would store the local patient ID alongside an external identifier's system, value, tenant and source connection:

CREATE TABLE external_patient_identifiers (
  tenant_id uuid NOT NULL,
  connection_id uuid NOT NULL,
  local_patient_id uuid NOT NULL,
  identifier_system text NOT NULL,
  identifier_value text NOT NULL,
  verified_at timestamptz,
  UNIQUE (tenant_id, connection_id, identifier_system, identifier_value)
);

This is a proposed migration. It must also have a tenant-scoped foreign key to the local patient table. If one external system reassigns identifiers or supports merged records, the connector needs that system's merge and history rules rather than overwriting the mapping silently.

A name and telephone number can help a coordinator find candidates. They should not silently become a merge key. Shared family numbers, changed names and missing birth dates make that shortcut unreliable. I would return a bounded candidate set, show only the fields the staff member may see and require a verified selection when ambiguity remains.

An authenticated connector resolves a scoped patient identity before preparing and reconciling a FHIR write.

Start by negotiating the actual server contract

I would inspect the server's CapabilityStatement, supported FHIR version, resource profiles, search parameters and allowed interactions before implementing writes. A server exposing Appointment does not necessarily support conditional create, transactions or the same scheduling workflow as another vendor.

The reference pins FHIR R4, version 4.0.1. I would keep that version explicit in the adapter and contract fixtures. HL7 defines the Appointment resource; local profiles and vendor workflows may add requirements.

{
  "resourceType": "Appointment",
  "identifier": [{
    "system": "https://example.org/prm/appointments",
    "value": "appt-001"
  }],
  "status": "booked",
  "start": "2026-10-01T09:00:00Z",
  "end": "2026-10-01T09:30:00Z",
  "slot": [{"reference": "Slot/slot-001"}],
  "participant": [
    {"actor": {"reference": "Patient/patient-001"}, "status": "accepted"},
    {"actor": {"reference": "Practitioner/practitioner-001"}, "status": "accepted"}
  ]
}

This body assumes the appointment and participants have already been accepted by the authoritative scheduling workflow. It is not a universal instruction to bypass a vendor's booking procedure. Some systems require a proposed appointment, a vendor operation or a transaction involving a slot. The adapter must encode that agreement.

Separate authentication from workflow authorization

For an interactive EHR launch, SMART supplies a standard authorization and launch-context mechanism. I would pin the integration to the agreed SMART version, verify the issuer and audience, validate launch state, keep tokens server-side where the deployment supports it and request only the required resource access. SMART App Launch 2.2 and its scope model provide the protocol details.

An OAuth scope permitting an Appointment write is still broader than permission to execute this particular patient's booking. The PRM service should check tenant membership, staff role, connection, selected patient and the approved proposal before calling the connector. A model-generated tenant_id or bearer token must never select those boundaries.

Make a timeout a first-class result

The difficult moment is a connection failure after transmitting the booking request. I would return a typed outcome rather than a boolean:

from dataclasses import dataclass
from typing import Literal

@dataclass(frozen=True)
class WriteOutcome:
    status: Literal["succeeded", "rejected", "unknown"]
    business_id: str
    external_id: str | None = None
    external_version: str | None = None
    error_code: str | None = None

The stable business ID is created before sending. If the response disappears, the reconciliation worker searches the configured EHR connection for that identifier. Zero matches means the operation still needs an explicit retry policy; one match can be checked against the intended patient and slot; multiple matches require investigation.

Where the server supports conditional create, If-None-Exist can express that identifier query. HL7 specifies different outcomes for zero, one or multiple matches. That facility must be tested against the vendor; it should not be assumed from a generic HTTP client. For updates, an agreed version check such as If-Match can detect concurrent modification. FHIR's HTTP specification describes both mechanisms.

Keep the connector narrow

The model-facing operation should be appointment.prepare or appointment.confirm, not “make an arbitrary FHIR request.” A general URL-and-JSON tool would expose too much authority and make it difficult to reason about resource scope.

body = fhir_appointment(
    patient="patient-001",
    practitioner="practitioner-001",
    slot="slot-001",
    start="2026-10-01T09:00:00Z",
    end="2026-10-01T09:30:00Z",
    business_id="appt-001",
)

The downloadable lab's fhir_appointment builds this restricted body and rejects invalid references and time intervals. It does not send HTTP, validate a complete vendor profile or reserve a real slot. Those are separate integration tests.

What I would require before a pilot

I would record contract fixtures for an ambiguous patient search, expired credentials, insufficient scope, slot conflict, conditional-create replay, version conflict, malformed OperationOutcome and a timeout after the server accepted the write. Every fixture should assert the resulting local state and the next permitted action.

For the first connector pilot, I would select one clinic, one EHR connection, one appointment type and one reconciliation owner. Read-only patient lookup should work before writes are enabled. The acceptance demonstration is a booking made through the PRM, visible in the EHR, replayed without duplication and then reconciled after a deliberately lost response. That is a much stronger integration claim than showing that an API endpoint returned JSON.

The implementation kit includes the resource builder and offline boundary tests. It contains no patient records from the client.