---
cuppy_hub: aws
cuppy_path: examples/healthcare-master-data/03-data-model-and-fhir.md
cuppy_section: docs
cuppy_doc: J8ZS-SF0G
cuppy_doc_uuid: 74c85daa-6888-46b1-bb7e-2e40e6599c92
cuppy_permalink: https://cuppy.io/aws/d/J8ZS-SF0G
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/examples/healthcare-master-data/03-data-model-and-fhir
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: ba73c76610b2229b5be6a2cbb9c836b172d73ff0f14506abec4b0d144d5c7cf7
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# The master data model and FHIR interoperability

A healthcare master needs two data models, not one. FHIR is the interoperability model — the shape
in which clinical systems exchange information. The master model is the governance shape, carrying
survivorship decisions, provenance per attribute and the crosswalk. Conflating them is the most
common design error in this pattern.

## What HealthLake gives you

AWS describes it as "a HIPAA eligible service for storing, analyzing, and sharing health data in the
cloud using the Fast Healthcare Interoperability Resources (FHIR) R4 specification". The version is
stated unambiguously and repeatedly: FHIR R4.

The capabilities that matter for a master:

| Capability | What the documentation states |
| --- | --- |
| Transactional FHIR server | "FHIR APIs for standard resource validation, SMART on FHIR authorization" |
| Import | Native import "from an Amazon S3 bucket to an HealthLake data store" |
| Export | "Bulk data FHIR API export capabilities" |
| Chronological patient view | "creates a complete view of each patient's medical history" |
| Integrated NLP | Extracts entities and PHI, "stored as native FHIR R4 resources" |
| SQL access | NLP entities "accessed through FHIR R4 APIs or Amazon Athena (SQL)" |
| Duplicate detection | "automatically detects and links duplicate records (Preview)" |
| Legacy conversion | Data Transformation Agent for C-CDA and CSV (Preview) |

The standards conformance claim is specific, and it is the sentence that matters for any 21st
Century Cures Act discussion: HealthLake "supports customer conformity to the latest ONC and CMS
regulatory standards including: HL7 FHIR R4 APIs, FHIR Bulk Data Access, US Core IG STU, HL7 SMART
App Launch Framework IG, OAuth 2.0, and OpenID Connect."

Two constraints belong next to that list. The clinical disclaimer is explicit — HealthLake "is not a
substitute for professional medical advice, diagnosis, or treatment" and "should only be used in
patient care or clinical scenarios after review by trained medical professionals applying sound
medical judgment". And the two most MDM-relevant features, resource matching and the Data
Transformation Agent, are both labelled Preview, which raises the eligibility question set out in
document 04.

## Mapping the four master entities

| Master entity | FHIR R4 resource | Natural identifiers |
| --- | --- | --- |
| Patient | `Patient` | MRN per assigning system, SSN, member id |
| Provider | `Practitioner`, plus `PractitionerRole` | NPI, DEA, state licence |
| Organisation | `Organization` | NPI type 2, tax id, CCN |
| Location | `Location` | Facility id, NPI where applicable |

FHIR's `identifier` element is the reason HealthLake's matching works at all, and its semantics line
up exactly with how healthcare identity actually behaves. HealthLake distinguishes "globally unique
identifiers" such as an NPI or SSN, "matched on value", from "scoped identifiers" such as an MRN,
which are "only unique within a namespace (a hospital, a state, a manufacturer)" and are "matched on
the combination of the identifying system and value, so that MRN `456` at one hospital is never
linked to MRN `456` at another". It also normalises identifier system URIs, treating an SSN under
`http://hl7.org/fhir/sid/us-ssn` and `urn:oid:2.16.840.1.113883.4.1` as the same identifier.

Design consequence: get the `identifier.system` URIs right at ingestion. A per-source registry of
assigning authority to canonical system URI is small, dull and completely load-bearing. If two
hospitals' MRNs arrive under the same system URI, you will create false links that no amount of
downstream logic can repair.

When HealthLake finds a match it "creates a FHIR `Linkage` resource that points to the linked
resources and leaves the originals unchanged". A `Linkage` is a first-class FHIR resource, so
consumers that understand FHIR can traverse it. It also means the duplicates remain visible, which
is correct for clinical safety and inconvenient for naive consumers — another reason the master
needs its own resolved view.

## The master schema outside FHIR

FHIR has no construct for "this attribute came from the registration system, was last confirmed on
this date, won under this survivorship rule, and was overridden by this steward". That lives in
Aurora PostgreSQL. A minimal but sufficient shape:

```
master_entity
  master_id            uuid   PK   -- yours, minted here, never a vendor match ID
  entity_type          text        -- patient | provider | organization | location
  status               text        -- active | merged_into | retired
  merged_into          uuid        -- set when a steward merges two masters
  created_at, updated_at, version  -- version increments on any attribute change

master_attribute
  master_id            uuid   FK
  attribute_name       text        -- family_name, birth_date, primary_address ...
  attribute_value      jsonb
  source_system        text        -- which source won
  source_record_id     text        -- the record in that source
  raw_object_key       text        -- the immutable S3 object it came from
  survivorship_rule    text        -- which rule selected this value
  confidence           numeric
  effective_from       timestamptz
  steward_override     boolean
  PK (master_id, attribute_name, effective_from)

crosswalk
  master_id            uuid   FK
  source_system        text
  source_record_id     text
  match_id             text        -- the Entity Resolution match ID for this run
  match_rule           text        -- rule number, or null for ML
  confidence           numeric     -- 0.0-1.0 for ML matches
  linkage_id           text        -- the HealthLake Linkage, where one exists
  first_seen, last_seen timestamptz
  PK (source_system, source_record_id)
  -- current state only; per-run history lives in an append-only journal (see document 06)

steward_decision
  decision_id          uuid   PK
  decision_type        text        -- merge | split | attribute_override | reject_match
  subject_master_ids   uuid[]
  payload              jsonb
  decided_by           text        -- the federated identity, never a shared account
  decided_at           timestamptz
  rationale            text
```

Four properties make this schema do its job.

**`master_id` is yours.** The Entity Resolution glossary defines a match ID only as "the ID
generated by AWS Entity Resolution and applied to each matched record set ... part of the matching
workflow metadata". It is run output. `master_id` is the identifier consumers see, and the crosswalk
is what reconciles the two on every run.

**Provenance is per attribute, not per record.** A golden patient routinely has a surname from the
EHR and an address from registration. Auditing "where did this value come from" only works at
attribute granularity, with `raw_object_key` pointing back into the Object Lock bucket.

**`master_attribute` is append-only.** `effective_from` in the key gives history for free, which is
both the audit trail and the ability to answer "what did we believe on this date".

**`steward_decision` is an input, not a log.** It is replayed after every match run, which is what
stops reprocessing from reversing human judgement. See document 05.

Keep the source's own `Unique ID` discipline in mind on the way in: Entity Resolution requires "a
unique identifier that you designate ... assigned to each row of input data", unique within a table,
"maximum Unique ID length is 38 for a matching workflow". A composite of source system and record
id can exceed 38 characters; hash it if so.

## Which store answers which question

| Question | Store | Why |
| --- | --- | --- |
| Who is this patient, resolved? | Aurora | The golden record with provenance |
| What is this patient's clinical history? | HealthLake | FHIR R4, chronological |
| Which source records map to this master? | Aurora crosswalk | The mapping is governance data |
| Give me a FHIR Bundle for an external app | HealthLake | FHIR APIs, SMART on FHIR |
| How many duplicates did we resolve last month? | Athena over S3 | Cheap analytics over match output |
| What changed in the master, and when? | Aurora history | Append-only attribute versions |

Both directions of flow are legitimate. HealthLake can be a *source* — its `Patient` resources are
input to the curated zone and then to matching. It can also be a *consumer* — the resolved master
writes back a canonical `Patient` plus `Linkage` resources. A mature deployment does both, and the
order matters: match in the resolve account, then write the resolved view to HealthLake, so the FHIR
store reflects governed decisions rather than competing with them.

## Unstructured text

Both HealthLake's integrated NLP and Comprehend Medical can mine free text. HealthLake's is the
better fit when the text already lives in the FHIR store, because extracted entities "are stored as
native FHIR R4 resources within a HealthLake data store". Comprehend Medical is the better fit in
the pipeline, before data lands, and offers ontology linking: `InferICD10CM` links conditions to
ICD-10-CM codes (the page names "the 2021 version"), `InferRxNorm` links medications to RxNorm, and
`InferSNOMEDCT` links concepts to SNOMED CT.

Its `DetectPHI` operation detects the entity types `AGE`, `DATE`, `NAME`, `PHONE_OR_FAX`, `EMAIL`,
`ID`, `URL`, `ADDRESS` and `PROFESSION`, all under the category `PROTECTED_HEALTH_INFORMATION` and
each with a confidence score; document 04 covers what that does and does not amount to under HIPAA.

For a master specifically, the realistic use is narrow: recovering demographics from unstructured
registration notes to improve match coverage, and detecting PHI that arrived in a field where it
should not be. It is English-only — Comprehend Medical "only detects medical entities in English
language (US-EN) texts" — and its confidence scores need a threshold you choose and defend.

## Where FHIR is the wrong model

FHIR does model organisational structure, and getting this wrong is an easy way to lose a healthcare
audience. The R4 specification states that "The Organization resource often exists as a hierarchy of
organization resources, using the _part-of_ property to provide the association of the child to its
parent organization", and `Organization.partOf` is defined as "The organization of which this
organization forms a part". A separate `OrganizationAffiliation` resource exists alongside it.

The real limitation is narrower and more interesting, and the cardinality is the evidence: `partOf`
is **0..1**, one parent per organisation, with no element for effective dating. A health system
needs several simultaneous rollups over the same organisations — legal entity, billing and TIN,
service-line reporting — each with dates on it, so that "this rollup was true until April" is
answerable. That is what an MDM hierarchy manager supplies and FHIR does not.

What FHIR genuinely has no concept of is a survivorship rule, a trust score or a steward. `Linkage`
records that two resources are related; it does not express which attribute of which resource
prevails. None of that is a defect — FHIR is an exchange format, not a governance model.

The practical division follows. Master lookups belong in Aurora, because the governance data they
need — provenance, survivorship rationale, steward decisions, the crosswalk — exists only there.
HealthLake serves exchange: FHIR APIs, Bulk Data export, SMART on FHIR. Its cost shape is worth
knowing when sizing that role: a standing $0.27 per data store hour, roughly $197 per month per
store, plus storage, with FHIR queries beyond 3,500 per hour charged at $0.015 per 10,000 on the
Standard tier.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
