Skip to content

Nexus EMR Item Consent Profile Core Profile

Canonical../StructureDefinition/nexus-emr-core-item-consent
Statusdraft · 1.26.0
BaseConsent (constraint)
SourceFSH · JSON

Patient-level consent for one catalogue item — whether the clinic may email the patient, text the patient, or use an AI scribe during their visit. Recorded manually by clinic staff from what the patient tells them, not captured through a patient-facing flow.

FSH

The authored source of truth: resources/StructureDefinition-nexus-emr-core-item-consent.fsh (edit on GitHub). One FSH file may define several related artifacts.

// NexusEmrCoreItemConsent.fsh
// Patient-level consent for a single catalogue item (Email, SMS, AI Scribe), recorded
// manually by clinic staff. One Consent resource per (patient, item), updated in place;
// earlier decisions are the resource's own version history.
//
// Sibling of NexusEmrCoreAppConsent (per-app consent, Device actor). Every Consent profile in this
// guide now states its decision in the SAME element -- `provision.type` on the root provision --
// so a consumer reads one element whatever family it holds.
//
// THIS PROFILE USED TO BE THE EXCEPTION, and the exception was a hazard. It carried the decision in
// `status` alone: `active` meant the patient said yes, `rejected` meant no. That was defensible
// while item consent was the only privacy Consent in the store. It stopped being defensible when
// the privacy directive arrived, because an `active` directive means the patient said NO -- so one
// element meant opposite things across two families that share a resource type, a scope, and a
// search. A consumer reading `status` without first discriminating on `category` read a lockbox as
// permission.
//
// The fix is not a better warning. `status` returns to what R4 defines it as -- whether the record
// is operative -- and the decision moves to `provision.type`, where the other families already
// carried it. `rejected` is no longer written: it is an R4 LIFECYCLE code meaning a party rejected
// the consent, and using it to carry a patient's answer was the root of the divergence.

Alias: $NexusEmrCoreItemConsent = ../StructureDefinition/nexus-emr-core-item-consent
Alias: $NexusConsentItemCS = ../CodeSystem/nexus-consent-item
Alias: $NexusConsentItemsVS = ../ValueSet/nexus-consent-items
Alias: $NexusConsentFamilyCS = ../CodeSystem/nexus-consent-family
Alias: $NexusConsentFamiliesVS = ../ValueSet/nexus-consent-families

// No raw-identifier expectation on this profile -- and since 1.21.0, none anywhere else in the IG
// either: the `*-has-raw-identifier` invariants were removed from all 34 profiles that carried
// them. The reason is still worth keeping for THIS profile specifically, because it is a different
// reason. Elsewhere carrying a source identifier remains the right thing to do and is simply no
// longer checked. An item consent has no source system at all: it is born here, when staff record
// what the patient said, so a source identifier could only be satisfied by inventing one. The
// resource carrying no identifier at all is complete.

// The two invariants below close the gap between what this profile's prose requires and what its
// cardinalities and bindings can express. Both are warnings on purpose: they surface a writer that
// has drifted from the model without blocking a later phase that needs room to move.

Invariant:     item-consent-status-is-defined
Description:   "SHOULD carry one of the two status codes this profile gives meaning: active (the record is operative) or entered-in-error (retracted). The DECISION is `provision.type`, not `status` — recommended, not enforced"
Severity:      #warning
Expression:    "status = 'active' or status = 'entered-in-error'"

Invariant:     item-consent-performer-is-attributable
Description:   "SHOULD name who made the decision and carry their display text, since the decision-maker is shown to staff from performer.display (recommended, not enforced)"
Severity:      #warning
Expression:    "performer.exists() and performer.display.exists()"

// ===========================================================================
// Profile Definition
// ===========================================================================
Profile:        NexusEmrCoreItemConsent
Parent:         Consent
Id:             nexus-emr-core-item-consent
Title:          "Nexus EMR Item Consent Profile Core"
Description:    """
Patient-level consent for one catalogue item — whether the clinic may email the patient, text
the patient, or use an AI scribe during their visit. Recorded manually by clinic staff from
what the patient tells them, not captured through a patient-facing flow.

One Consent resource exists per (patient, item) and is updated in place as the patient changes
their mind. `category` identifies the item and is the lookup key; **`provision.type` carries the
decision** -- `permit` is Provided, `deny` is Declined -- and `status` says only whether the record
is operative.

This profile used to carry its answer in `status` alone. That made an active item consent mean
permission while an active privacy directive means a restriction in force, across resources sharing
a resource type, a scope and a search. Every Consent profile in this guide now states its decision
in the same element.
"""
// THE effective date for this profile (see the EffectiveDate / NoEffectiveDate
// RuleSets): one date concept per resource, declared here rather than guessed.
* insert EffectiveDate([[dateTime]])

// SMART on FHIR exposure -- READ-ONLY. Exposure is per resource TYPE and Consent also carries
// app-consent, which is the record authorizing the app itself; see that profile for why write
// and delete are withheld from the type as a whole.
* insert SmartExposedReadOnly(consent)

* ^url = $NexusEmrCoreItemConsent
* ^status = #draft
* obeys item-consent-status-is-defined and item-consent-performer-is-attributable

* . ^short      = "Patient consent for one catalogue item (Email | SMS | AI Scribe) Core"
* . ^definition = """
Records the patient's current answer for a single consent item, as recorded by clinic staff.

**One resource per (patient, item), updated in place.** There is never more than one Consent
for a given patient and item. When the patient changes their mind, the same resource is updated
— `status` flips, `dateTime` is re-stamped, `performer` is set to whoever recorded the change —
rather than a second Consent being created. The decision history is therefore the resource's
FHIR version history: read prior versions through `_history` to see what was decided before,
by whom, and with what reason and evidence attached.

**The decision lives in `provision.type`.** `permit` is Provided, `deny` is Declined. This is the
same element, carrying the same meaning, as every other Consent profile in this guide:

| `provision.type` | `status`            | Meaning                                                    |
|------------------|---------------------|------------------------------------------------------------|
| `permit`         | `active`            | Provided — the patient consented to this item              |
| `deny`           | `active`            | Declined — the patient did not consent, or withdrew        |
| *(any)*          | `entered-in-error`  | The record was created in error and must be ignored        |
| *(no Consent)*   | —                   | Not Recorded — nobody has asked, or nobody wrote it down   |

`deny` covers both an initial decline and a revocation of a previously provided consent, so a
consumer checks exactly one value to know the item is not permitted; it never needs to distinguish
"said no" from "said yes then changed their mind". That distinction is recoverable from version
history, and from the status-reason extension when staff recorded one.

Transitions between `permit` and `deny` are expected and legal in both directions, any number of
times. `status` stays `active` throughout: the record is operative whichever way the answer points.

**The gate reads `provision.type`.** A consumer deciding whether it may act on an item MUST permit
the behaviour only where a Consent exists, `status` is `active`, and `provision.type` is `permit`.
Everything else withholds it: no Consent at all, `deny`, `entered-in-error`, and a root provision
that states no type. Consent is opt-in, so silence is never a yes.

A gate written on `status` alone permits a declined item, because a declined item consent is
`active`. The answer is not in `status`, and this profile does not write `rejected`.

**Absence is meaningful, and it is not a refusal.** No Consent for a (patient, item) pair means Not
Recorded. It withholds the behaviour exactly as `deny` does, and it is shown to staff distinctly,
because a patient who has never been asked is a prompt to ask. `entered-in-error` reads the same way
on both counts: unanswered to a reader, no permission to a gate.

**Finding an item consent.** Search by patient and item coding:

```
GET Consent?patient={id}&category=../CodeSystem/nexus-consent-item|{code}
```

At most one resource comes back. That same search is also the WRITE path: a conditional update
against it creates-or-updates in one call, with no prior read. See `identifier` for the form.
"""

* meta.source 0..1 MS
* meta.source ^short = "Identifies EMR instance & pipeline version the resource came from"
* meta.source ^definition = "A URI that identifies the EMR pipeline and version from which this resource originated.  This tells you which EMR instance (recommend using the instance identifier), and the version of the pipeline code/transformations."

// ===========================================================================
// Identification
// ===========================================================================

* identifier 0..* MS
* identifier.system 1..1
* identifier.value 1..1
* identifier ^short = "Business identifier(s) for item consent"
* identifier ^definition = """
Unique identifiers for the item consent.

Unlike the profiles describing resources converted from a source EMR, this one expects no
source-system identifier: an item consent is created here, when staff record the patient's answer,
so there is no upstream record for it to point back at. A resource carrying no identifier at all is
complete.

**The singleton is stated by the search, not by a computed key.** Exactly one Consent exists per
(patient, item), and the patient and the item are both modelled elements, so a writer addresses the
pair directly and creates-or-updates in a single call:

  PUT Consent?patient={id}&category=../CodeSystem/nexus-consent-item|ai-scribe

The server matches the existing resource and adds a version, or creates the resource when there is
no match — which is exactly the intended lifecycle, and means two staff saving the same item at the
same moment produce two versions of one resource rather than two competing resources. It is the same
search a reader uses to find an item consent, so a reader and a writer name the pair the same way,
and there is no second value to keep in step with it.

**A retired identifier appears on older records.** `NamingSystem/nexus-item-consent-identifier`
carried a value composed as `{patient-logical-id}:{item-code}`, and it is retired. The patient half
is `Resource.id` — the address a resource has on one server — so a re-ingest, a store migration or a
copy between deployments leaves a recorded value naming an id that no longer exists, and the
consequence is quiet: a conditional update keyed on a stale value matches nothing and creates a
second resource for a pair that must have exactly one, while a writer that reads the value back as a
precondition stops writing for that patient instead, with no error that names the cause. Read such a
value as what it is, a key computed at write time. Do not recompute one, match on one, or use one as
a precondition, and do not write new ones.
"""

// ===========================================================================
// The decision
// ===========================================================================

* status 1..1 MS
* status ^short = "active (the record is operative) | entered-in-error"
* status ^definition = """
Required field in FHIR R4 Consent. Whether this record is operative — **not** what the patient
decided.

- `active` — the record stands. Read `provision.type` for the answer.
- `entered-in-error` — the record was created in error. Ignore it; it is not a decision.

Absence of any Consent for a (patient, item) pair means **Not Recorded** — no answer has been
written down. Show it distinctly from a `deny`; gate it the same, under the rule stated on the
resource itself.

**`rejected` is not written by this profile, and this is a change.** Earlier releases used it for a
declined or withdrawn item, which made `status` carry the decision. R4 defines `rejected` as a
lifecycle state — a party rejected the consent — and borrowing it for a patient's answer meant one
element meant opposite things across the Consent families sharing this store: an `active` item
consent was permission, an `active` privacy directive is a restriction in force. A reader that
checked `status` first read a lockbox as permission.

The decision is now `provision.type`, which is where the other families already carried it. One
element, one meaning, whatever family a consumer is holding.

The remaining R4 consent-state codes (`draft`, `proposed`, `inactive`, `rejected`) carry no meaning
in this profile and are not written by the EMR; a reader that meets one treats it as Not Recorded,
and `item-consent-status-is-defined` warns on it, since the binding alone cannot express that
narrowing.
"""
* status from http://hl7.org/fhir/ValueSet/consent-state-codes (required)

// The reason a status was recorded travels with the status it explains, so a historical version
// read yields that version's reason.
* extension contains
    ConsentStatusReasonExtension named statusReason 0..1 MS
* extension[statusReason] ^short = "Free-text reason for the current status (typically on revocation)"
* extension[statusReason] ^definition = """
Optional free text explaining why the consent is in its current status — most often what the
patient said when withdrawing a consent they had previously provided.

Always optional, on every status. Absence carries no meaning, and consumers must render a status
change with no reason as a complete record. The reason is deliberately uncoded free text so it
keeps the patient's own words.
"""

* scope 1..1 MS
* scope ^short = "patient-privacy - Privacy consent"
* scope ^definition = "Required field in FHIR R4 Consent. For this item consent use case, always set to 'patient-privacy' as we're dealing with patient privacy consent for clinic-recorded consent items."
* scope from http://hl7.org/fhir/ValueSet/consent-scope (required)
* scope ^fixedCodeableConcept.coding[0].system = "http://terminology.hl7.org/CodeSystem/consentscope"
* scope ^fixedCodeableConcept.coding[0].code = #patient-privacy
* scope ^fixedCodeableConcept.coding[0].display = "Privacy Consent"

// ===========================================================================
// Which item this consent is about, and which family it belongs to
// ===========================================================================

* category 1..* MS
* category ^short = "The consent item this resource records (Email | SMS | AI Scribe), plus the family code"
* category ^definition = """
Required field in FHIR R4 Consent. This profile carries two codings, in two slices discriminated by
code system, and neither substitutes for the other.

**`item` says WHICH item the patient answered.** It is the key consumers look the resource up by:

```
GET Consent?patient={id}&category=../CodeSystem/nexus-consent-item|{code}
```

**`family` says this Consent is an item consent.** Three Consent families share this store, one
`GET Consent?patient={id}` returns them mixed, and all three carry `scope = patient-privacy` — so
`category` is the only element that separates them, and every family in this guide carries the
family code. Select this family with:

```
GET Consent?patient={id}&category=../CodeSystem/nexus-consent-family|item
```

The slicing is open, so further categories may be carried alongside these two — a jurisdictional or
workflow classification, say. No other category identifies the item or the family, and a consumer
MUST NOT infer either from one.
"""

* category ^comment = """
Both codings sit outside HL7's consent-category value set on purpose. That value set classifies
consent *documents* (research consent, advance directive, privacy policy acknowledgement) and has
no concept for a per-item communication preference, nor for this guide's own family axis, which is
the case an extensible binding exists to cover. Terminology-aware validators may note that the base
extensible binding is unmet on these resources; that note is expected, not a defect.
"""

// Slice by code system: the item code and the family code each come from a Nexus CodeSystem of
// their own, so the system is what tells the two apart.
* category ^slicing.discriminator[0].type = #pattern
* category ^slicing.discriminator[0].path = "coding.system"
* category ^slicing.rules = #open
* category ^slicing.description = "Slice category by code system: the item code, and the family code"

* category contains
    item 1..1 MS and
    family 1..1 MS

* category[item] ^short = "The consent item (single coding from the Nexus consent-item CodeSystem)"
* category[item] ^definition = """
The catalogue item this Consent answers, as a single coding from the Nexus consent-item
CodeSystem. The concept definition in that CodeSystem is the standard description shown to staff
and patients when the item is presented for a decision.
"""
* category[item] ^comment = """
Slice membership is discriminated by `coding.system`, so the item coding MUST use the Nexus
consent-item system. The binding is extensible rather than required because clinic-defined items
are expected in a later phase and will be published in tenant-scoped CodeSystems; when they land,
this profile gains a slice for them rather than any code being smuggled into the `item` slice
under a foreign system.
"""
* category[item].coding 1..1
* category[item].coding.system 1..1
* category[item].coding.system = $NexusConsentItemCS (exactly)
* category[item].coding.code 1..1
* category[item].coding.display 0..1
* category[item] from $NexusConsentItemsVS (extensible)

* category[family] ^short = "Fixed: this Consent is an item consent"
* category[family] ^definition = """
The family this Consent belongs to, fixed to `item`. It tells a consumer which interpretation
contract governs the resource in its hand before the resource is interpreted, which is the point of
carrying it on a searchable element: a mixed result can be partitioned in the search rather than
after it.

It says WHAT this decision governs and nothing about how to read it. The decision is
`provision.type` in every family, so a reader never needs the family code to answer the dangerous
question — it answers the cheap one.
"""
* category[family] ^comment = """
The binding is required, not extensible: the value of this element is that a consumer can enumerate
the families it knows how to interpret and refuse the ones it does not. A family minted under a
deployment's own system would arrive looking like a known one, which is the failure the element
exists to prevent.
"""
* category[family].coding 1..1
* category[family].coding.system 1..1
* category[family].coding.system = $NexusConsentFamilyCS (exactly)
* category[family].coding.code 1..1
* category[family].coding.code = #item (exactly)
* category[family] from $NexusConsentFamiliesVS (required)

// ===========================================================================
// Who and when
// ===========================================================================

* patient 1..1 MS
* patient only Reference(NexusEmrCorePatient)
* patient ^short = "The patient whose consent this is"

* dateTime 1..1 MS
* dateTime ^short = "When THIS status was recorded"
* dateTime ^definition = """
When the current status was recorded — re-stamped on every status change, not fixed at the first
decision. Reading a historical version yields the dateTime of that version's status, so the
sequence of dateTimes across versions is the decision timeline.
"""

* performer 0..1 MS
* performer only Reference(NexusEmrCorePatient or NexusEmrCorePractitioner or NexusEmrCorePractitionerRole)
* performer ^short = "Who made the consent decision (the patient, or the staff member who recorded it)"
* performer ^definition = """
Who made this consent decision: the patient themselves, or the named staff member who recorded
it on the patient's behalf.

This is the display source for "who made it" wherever the decision is shown. It is not the audit
value — the server-stamped UpdatedBy meta extension remains the tamper-evident record of who
wrote the resource, and the two can legitimately differ (staff records what the patient decided).
Writers MUST set performer on every status change so the displayed attribution tracks the
decision rather than lagging on whoever happened to record the previous one.

The element stays `0..1` so a historical version written before this profile existed still
conforms; `item-consent-performer-is-attributable` carries the requirement as a warning instead.
`display` is must-support for the same reason the requirement exists at all: attribution is
rendered from the display text, so a reference without one shows the reader nothing, and
resolving it after the fact is not always possible — a patient-made decision references the
Patient, which a reader holding only consent permission may not be able to read.
"""

* performer.display 0..1 MS
* performer.display ^short = "How the decision-maker is named to the reader"

* policy 1..* MS
* policy.uri 1..1
* policy ^short = "The policy this consent is given under"
* policy ^definition = """
A URI identifying the policy under which this consent is recorded -- the custodian's published
privacy policy, or the statutory provision that governs the decision.

R4's `ppc-1` invariant requires either `policy` or `policyRule`; this element satisfies it.

**`policyRule` is not used, and the reason is a correction.** Earlier releases fixed `policyRule`
to `consentpolicycodes#cric`. That code is 45 CFR 46 -- the United States Common Rule for
informed consent in human-subjects research. It is not a communication-preference policy, not a
data-sharing policy, and not Canadian. It was never the right value here, and the code system it
comes from contains no Canadian concept at all: its 27 codes are US federal and state instruments
plus three European ones. Rather than substitute another wrong code, this profile names the actual
governing policy by URI, which is both truthful and machine-readable.
"""

// ===========================================================================
// Evidence
// ===========================================================================

* sourceReference 0..1 MS
* sourceReference only Reference(NexusEmrCoreDocumentReference)
* sourceReference ^short = "Evidence document for THIS status change, if one was captured"
* sourceReference ^definition = """
Optional supporting document for the current status — a scanned signed consent or withdrawal
form, for instance.

The reference belongs to the status it was recorded with. Because the resource is updated in
place, each stored version keeps its own sourceReference, so evidence attached to an earlier
decision stays with that version in history instead of being overwritten by the next one.
"""

// Evidence must be a DocumentReference, never an inline attachment: routing it through
// DocumentReference means DocumentReference read permission gates access to the document
// itself. The reason text is an extension precisely so it stays readable to Consent readers.
//
// Attachment is removed from source[x] rather than only zeroed out as a type slice. A 0..0 slice
// on an open type slicing does not stop an inline attachment — the validator reads it as an
// element matching no slice and lets it through — so the type itself has to go.
* source[x] only Reference(NexusEmrCoreDocumentReference)

// ===========================================================================
// Reserved
// ===========================================================================

* provision 1..1 MS
* provision ^short = "The decision: permit (Provided) | deny (Declined)"
* provision ^definition = """
Carries the patient's answer for this item.

This element used to be reserved and unused, with the decision in `status`. It is now where the
answer lives, matching every other Consent profile in this guide.
"""

* provision.type 1..1 MS
* provision.type ^short = "permit (Provided) | deny (Declined or withdrawn)"
* provision.type ^definition = """
The patient's answer. `permit` means the clinic may do the thing this item names; `deny` means it
may not, whether the patient declined from the start or withdrew a consent they had given.

**Required on the root provision.** R4's element definition says `type` is "not permitted in root
rule", and this guide does not follow that reading -- IHE Privacy Consent on FHIR and Ontario's
Provincial Consent Override Interface both require it there, and a consent whose base rule states no
decision states none where a reader looks first. Every Consent profile in this guide takes the same
convention, so one rule reads all of them.
"""
* provision.type from http://hl7.org/fhir/ValueSet/consent-provision-type (required)

* provision.provision 0..*
* provision.provision ^short = "Reserved for future per-purpose granularity"
* provision.provision ^definition = """
Left unconstrained and unused. Reserved for a future phase needing per-purpose granularity within a
single item -- consenting to email for appointment reminders but not for lab results, say. Those
would be nested exceptions to the root decision, which is the shape R4 intends and the shape the
privacy-directive profile already uses for its own exceptions.
"""