Skip to content

AllergyIntolerance Profile

Canonical../StructureDefinition/nexus-emr-core-allergyintolerance
Statusdraft · 1.26.0
BaseAllergyIntolerance (constraint)
SourceFSH · JSON

Defines the Nexus EMR standard for AllergyIntolerance resources, based on CA-Core+. Includes instance-specific Nexus EMR identifiers, references via identifier (CII E01).

FSH

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

// NexusEmrCoreAllergyIntolerance.fsh


Alias: $NexusEmrCoreAllergyIntolerance = ../StructureDefinition/nexus-emr-core-allergyintolerance


// Profile Definition
Profile:        NexusEmrCoreAllergyIntolerance
Parent:         AllergyIntolerance
Id:             nexus-emr-core-allergyintolerance
Title:          "AllergyIntolerance Profile"
Description:    "Defines the Nexus EMR standard for AllergyIntolerance resources, based on CA-Core+. Includes instance-specific Nexus EMR identifiers, references via identifier (CII E01)."
* ^url = $NexusEmrCoreAllergyIntolerance // Canonical URL from alias
* ^status = #draft
* insert NoResourceNoteExtension

// THE effective date for this profile (see the EffectiveDate / NoEffectiveDate
// RuleSets): one date concept per resource, declared here rather than guessed.
* insert EffectiveDate([[onsetDateTime | recordedDate]])

// Salient fields — the reader/render surface (see SalientField + the Salient RuleSet).
* insert Salient(code, code, coded)
* insert Salient(clinical_status, clinicalStatus, code)
* insert Salient(verification_status, verificationStatus, code)
* insert Salient(type, type, code)
* insert Salient(criticality, criticality, code)
* insert Salient(reaction, [[reaction.first().manifestation.first().text | reaction.first().manifestation.first().coding.first().display]], string)
* insert Salient(recorded_date, recordedDate, dateTime)

// SMART on FHIR exposure -- this resource is reachable by an app, and these are the
// permissions each operation costs (see SmartExposed + the SmartExposed RuleSet).
* insert SmartExposed(allergyintolerance)

// --- Element Constraints ---

* . ^short      = "Nexus EMR Allergy Intolerance Core"
* . ^definition = """
  Represents patient allergies and intolerances, capturing key details
  like substance, reaction, severity, and clinical status using standard
  Nexus EMR references. Uses instance-specific identifiers for tracking
  and cross-referencing.
  """

* 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."

* text 0..1 MS // Narrative element with human-readable summary
* text ^short = "Narrative"
* text ^definition = "A human-readable narrative summary of the AllergyIntolerance resource for human interpretation."

* id 0..1

* identifier 0..* MS
* identifier.system 1..1
* identifier.value 1..1

* identifier ^short = "Business identifier(s) for allergyintolerance"
* identifier ^definition = """
Unique identifiers for the allergyintolerance.

A business identifier is recommended but not required (identifier is 0..*). Where available, carry the source-system identifier using the most appropriate system URI. No specific canonical system is mandated.

A source system's own identifier SHOULD be carried alongside this one, flagged `use = secondary`. See [Carrying the raw code](../guide/patterns/raw-codes.md).
"""


* type 0..1
* type ^short = "Whether the reaction is immune-mediated (allergy) or not (intolerance)"
* category ^short = "The kind of substance the reaction is to"
* criticality ^short = "The risk that a future exposure causes serious harm"

// Code: Substance related to the allergy/intolerance.
* code 1..1 MS
* code ^short = "Allergen/substance with raw code support"
* code ^definition = """
The substance or class of substances that the patient has an allergy or intolerance to.

REQUIRED: text element for human readability
RECOMMENDED, for a DRUG allergen: an ingredient-level code in the slices below. CCDD is
preferred; where the source has no CCDD -- in practice most legacy EMR allergy lists -- the
Health Canada DPD active-ingredient group number is the one to carry, with ATC for class-level
records. That code is the only thing making an allergen joinable to a medication record;
without it an allergy and a prescription share nothing but free text.
OPTIONAL: SNOMED CT for non-drug allergens (foods, environmental substances) and for
substance classes
OPTIONAL: Raw EMR allergen codes for source system preservation

Nexus EMR does not mandate a specific coding system for allergens.
The text element ensures human readability even when coded values are not recognized.

Where this code was mapped from a source system's own vocabulary, carry the raw coding alongside the mapped one, flagged `userSelected = true`. See [Carrying the raw code](../guide/patterns/raw-codes.md).
"""
* code.text 1..1 MS
* code.text ^short = "Human-readable allergen description (required)"
* code.text ^definition = "Plain text representation of the allergen, required for all entries"

// Drug-allergen slots. Same posture as Condition's icd10ca slice and Procedure's cci slice: the
// IG declares the SLOT and the system URI and does not enumerate codes. Every slice is optional
// -- most allergen records carry text alone, and many allergens are not drugs at all.
//
// These exist so a drug allergy has a JOIN KEY to a medication, and they are the SAME ingredient-
// level systems the three medication profiles slice (see the MedicationCoding RuleSet). An
// allergen coded with CCDD, ATC or the DPD active-ingredient group can therefore be matched
// against a MedicationRequest, MedicationStatement or MedicationDispense coded the same way; the
// join has to be sliced on both sides or it does nothing. Matching on `code.text` is not a
// substitute for that; it is string comparison over clinician-typed free text.
//
// ONE OF THE THREE IS DELIBERATELY NOT THE SAME SYSTEM AS THE MEDICATION PROFILES USE, and the
// asymmetry is the point. Health Canada's active-ingredient group number is ten digits and
// decomposes as [2 digits: how many active ingredients][5 digits: which active-ingredient
// group][3 digits: which STRENGTH group]. The full ten-digit number (AIGN,
// http://terminology.hl7.org/CodeSystem/v3-hc-aign) therefore names a drug AT A STRENGTH, which
// is right on a prescription and wrong here: an allergy is to an ingredient, not to a milligram,
// and an allergen carrying a strength-specific code would fail to match the same drug dispensed
// at a different strength. So this profile binds the five-digit middle portion instead (AIGC,
// http://terminology.hl7.org/CodeSystem/v3-hc-aigc), which is exactly the active-ingredient
// composition with strength dropped.
//
// TO MATCH THE TWO, take positions 3 through 7 of a medication's AIGN and compare that to an
// allergen's AIGC. They are different systems and different code lengths; comparing them as
// equal strings never matches, which is a silent miss rather than an error.
//
// THERE IS DELIBERATELY NO `din` SLICE, and the omission is a decision rather than an oversight.
// A DIN identifies one manufacturer's marketed product, and a patient is not allergic to a
// product -- they are allergic to what is in it. DIN-to-DIN comparison misses the same drug from
// another manufacturer and misses a different salt of the same ingredient, so a DIN on an allergen
// would look like a join key while silently failing the cases the join exists for. Allergen
// matching is true at ingredient level or it is not true. The slicing is open, so a source that
// records a DIN against an allergy may still carry it as an unsliced coding; it simply does not
// get a named slot that would imply it is matchable.
* code.coding ^slicing.discriminator[0].type = #value
* code.coding ^slicing.discriminator[0].path = "system"
* code.coding ^slicing.rules = #open
* code.coding ^slicing.description = "Slice by code system: ingredient-level drug allergen codes"
* code.coding contains ccdd 0..* MS and atc 0..* MS and aigc 0..* MS
* code.coding[ccdd].system = $CCDD (exactly)
* code.coding[ccdd].code 1..1
* code.coding[ccdd] ^short = "CCDD coding -- the ingredient-level drug allergen (system slot only; codes not enumerated)"
* code.coding[ccdd] ^definition = "Canadian Clinical Drug Data Set code for a drug allergen. Ingredient level, which is the level at which matching an allergen against a medication is meaningful. Preferred over ATC where the source has it."
* code.coding[atc].system = $ATC (exactly)
* code.coding[atc].code 1..1
* code.coding[atc] ^short = "ATC coding -- coarser ingredient-level allergen grouping (system slot only; codes not enumerated)"
* code.coding[atc] ^definition = "WHO Anatomical Therapeutic Chemical code for a drug allergen. Ingredient level but coarser than CCDD, and useful where the allergy is recorded against a drug class rather than a single ingredient."
* code.coding[aigc].system 1..1
* code.coding[aigc].system = $HcAigc (exactly)
* code.coding[aigc].code 1..1
* code.coding[aigc].display 0..1
* code.coding[aigc] ^short = "DPD active-ingredient group CODE (AIGC, 5 digits) -- strength dropped, which is what an allergy means"
* code.coding[aigc] ^definition = "Health Canada Drug Product Database active-ingredient group code for a drug allergen: the five-digit middle portion of the ten-digit active-ingredient group number, naming the active-ingredient composition with strength dropped. That is the level at which an allergy is true -- a patient is allergic to an ingredient, not to a strength -- and it is why this slice binds a different system from the medication profiles, which carry the full ten-digit number (v3-hc-aign) because a prescription does have a strength. To match an allergen against a medication, compare this code to positions 3 through 7 of the medication's number; the two are not equal as strings. Reach for it when CCDD is absent, in practice most legacy EMR medication and allergy lists where a DIN and a line of free text is all that exists."


// 0..1, NOT 1..1, AND THE BASE INVARIANTS ARE WHY. R4 carries a matched pair on this resource:
// ait-1 says clinicalStatus SHALL be present unless verificationStatus is `entered-in-error`, and
// ait-2 says it SHALL NOT be present when it is. A flat 1..1 keeps the first and CONTRADICTS the
// second, which makes a retracted allergy unrepresentable: the one shape that must drop
// clinicalStatus is the one shape the profile would have required it on.
//
// That is not hypothetical here. Retiring a chart entry is a suggested-resource task carrying a
// prototype whose verificationStatus is `entered-in-error`, and the allergy case is the worked
// example this specification leads with. Under 1..1 the guide's own example could not conform to
// the guide's own profile, and nothing caught it because the prototype did not declare the
// profile -- it now does.
//
// Nothing is lost by relaxing it. ait-1 already requires clinicalStatus on every allergy that is
// not retracted, which is what 1..1 was reaching for, and it states the exception rather than
// colliding with it.
* clinicalStatus 0..1 MS
* clinicalStatus ^short = "Whether the allergy is still current"
* clinicalStatus ^definition = "The clinical status of the allergy or intolerance. Used to distinguish active allergies from historical or resolved ones. Present on every allergy except a retracted one: R4's ait-1 requires it unless `verificationStatus` is `entered-in-error`, and ait-2 forbids it when that is the status."
* clinicalStatus ^comment = "This is a modifier element - absence of an active status means the allergy should not trigger alerts. Absence because the entry is `entered-in-error` means the allergy was never true and must not be surfaced at all, which is a different statement from `inactive` or `resolved`."
* clinicalStatus from http://hl7.org/fhir/ValueSet/allergyintolerance-clinical (required)

* recordedDate 1..1 MS
* recordedDate ^short = "Date allergy was first recorded"
* recordedDate ^definition = "The date on which the allergy or intolerance was first recorded in this system. This may differ from when the allergy first occurred."
* recordedDate ^comment = "This represents when the allergy was documented, not necessarily when it was first experienced by the patient."

* asserter 0..1

* reaction.description 0..1
* reaction.severity ^short = "How severe the reaction event was overall"


// Patient: Reference MUST use identifier. Maps to CII subject.
* patient 1..1 MS
* patient ^short = "Who the allergy or intolerance is for"
* patient ^definition = "The patient who has the allergy or intolerance. Must reference using Nexus EMR patient identifier."
* patient ^comment = "References should use identifier-based references following Nexus EMR patterns."

// Encounter: Optional reference, MUST use identifier if present.
* encounter 0..1 MS
* encounter ^short = "Encounter when allergy was recorded"
* encounter ^definition = "The encounter during which this allergy or intolerance was recorded. Optional but helpful for clinical context."
* encounter ^comment = "Linking to encounter provides clinical context about when and where the allergy was documented."


// Note: THE RESOURCE'S CLINICAL COMMENTS, and nothing else. Unprofiled until now, which left every
// reader to guess both what the element was for and whether its attribution could be relied on.
//
// PLAIN ANNOTATION, DELIBERATELY -- NOT THE TASK NOTE PROFILE. `Task.note` is typed to Task Note
// because a task is a conversation handed between parties and a turn with no author is a statement
// in the record with nobody behind it. A comment on an allergy or intolerance is a different animal: a remark
// about the record, not a turn addressed to someone, and requiring an author and a time on every
// one of them would make unattributed comments -- including every comment arriving from a source
// system that never carried the fields -- non-conformant for attribution nothing reads. The
// Resource Note extension is likewise not used here: it exists for resources R4 gives NO note
// element, and this resource has one.
* note 0..* MS
* note ^short = "Clinical comments on the allergy or intolerance -- one whole comment per repeat"
* note ^definition = """
Free-text clinical comment about an allergy or intolerance: what a person wanted to say about it that no other
element carries.

**One whole comment per repeat.** `note.text` is the comment in full, and a consumer renders it
verbatim.

**`note.text` is not a carrier for other fields.** A consumer MUST NOT parse it for prefixes,
markers, keys or any other in-band convention, and a producer MUST NOT encode in it a value that
belongs to an element or an extension of its own. Anything with its own editing lifecycle -- one
box, edited and cleared on its own -- gets its own address, as an element of this resource or as an
extension declared by this guide.
"""
* note ^comment = "Attribution is OPTIONAL here, unlike on a task. `author[x]` and `time` keep their base R4 cardinality: a producer that has them SHOULD write them, a consumer displays them when present, and no consumer may require them -- comments reaching this element from a source system routinely carry neither. This is why the element is a plain `Annotation` rather than the [Task Note](../fhir/StructureDefinition-nexus-emr-task-note.md) profile, which requires both because a task's turns must each say who said it. Where both are absent, the comment is attributable only to the resource's own provenance."