Skip to content

Validation ​

BabelFHIR-TS generates runtime validators that evaluate FHIRPath constraints from StructureDefinitions. This page covers what the validators check, their limitations, and how they compare to external FHIR validators.

What Validators Check ​

The generated validate() methods DO check:

  • FHIRPath constraints from StructureDefinition invariants
  • Cardinality rules (min/max occurrences)
  • Required fields from profiles
  • Pattern constraints (patternCodeableConcept, patternCoding)
  • Fixed values (top-level scalar fields, meta.profile)
  • Prohibited fields (max=0)
  • Slice validation for extension URL, pattern/value, and exists-extension discriminators
  • Required ValueSet bindings (inline code validation when ValueSet is resolved)
  • Bundle reference resolution (checks that bundled/contained references resolve)
  • Extension structural rules (ext-1, url required, empty object check)
  • Data type correctness (string, number, boolean, etc.)

What Validators Don't Check ​

The generated validators DO NOT check:

  • Terminology validation for non-required bindings or large ValueSets without --tx-server
  • Cross-resource reference resolution (checking that references to external resources exist)
  • Complex discriminator types (type, position) — only pattern/value, extension URL, and exists-extension discriminators supported
  • Cross-resource business rules — application-specific logic

TIP

For comprehensive conformance testing, use the official HL7 FHIR Validator.

Runtime Validator Options ​

Every generated validate() function accepts an optional ValidatorOptions object as a second parameter. This enables runtime terminology validation, reference resolution, and debugging:

ts
import { validateUSCorePatientProfile } from "hl7.fhir.us.core-generated/USCorePatientProfile";

const result = await validateUSCorePatientProfile(myPatient, {
  terminologyUrl: "https://tx.fhir.org/r4",
  fhirServerUrl: "https://hapi.fhir.org/baseR4",
  httpHeaders: {
    "https://tx.fhir.org/r4": "Bearer my-token",
  },
  traceFn: (value, label) => console.log(`[trace] ${label}:`, value),
});

Available Options ​

OptionTypeDescription
terminologyUrlstringTerminology server URL for memberOf() evaluation. When set, validators make HTTP calls to $validate-code for ValueSet bindings that couldn't be resolved at generation time.
fhirServerUrlstringFHIR server URL for resolve() evaluation. When set, validators resolve external references by fetching from this server.
httpHeadersRecord<string, string>HTTP headers keyed by server URL. Use for authentication with terminology or FHIR servers.
signalAbortSignalCancels long-running async evaluations (e.g., terminology lookups).
traceFn(value: any, label: string) => voidDebug callback invoked for every FHIRPath trace() call. Useful for diagnosing constraint evaluation.

INFO

Without terminologyUrl, validators skip memberOf() checks and report no errors for unresolvable ValueSet bindings. Without fhirServerUrl, resolve() expressions are not evaluated.

Continuous Validation Pipeline ​

Every pull request runs two independent CI pipelines that validate generated code against real-world FHIR Implementation Guides. For each IG, the pipeline:

  1. Downloads the FHIR package from a registry
  2. Generates TypeScript interfaces, validators, and classes
  3. Compiles the output with tsc (zero errors required)
  4. Generates empty() and random() test resources for every profile
  5. Validates those resources against two external FHIR validators

Tested Implementation Guides (30 packages) ​

CategoryImplementation GuidePackageFHIR
USUS Corehl7.fhir.us.core@8.0.0R4
USQI-Corehl7.fhir.us.qicore@6.0.0R4
USmCODEhl7.fhir.us.mcode@4.0.0R4
USSDOH Clinical Carehl7.fhir.us.sdoh-clinicalcare@2.2.0R4
USNDH (National Directory)hl7.fhir.us.ndh@1.0.0R4
USCARIN BBhl7.fhir.us.carin-bb@2.1.0R4
USCQF Measureshl7.fhir.us.cqfmeasures@4.0.0R4
USPhysical Activityhl7.fhir.us.physical-activity@1.0.0R4
DaVinciPAShl7.fhir.us.davinci-pas@2.0.1R4
DaVinciCDexhl7.fhir.us.davinci-cdex@2.1.0R4
DaVinciPDexhl7.fhir.us.davinci-pdex@2.1.0R4
DaVinciDTRhl7.fhir.us.davinci-dtr@2.1.0R4
DaVinciAlertshl7.fhir.us.davinci-alerts@1.0.0R4
DaVinciDEQMhl7.fhir.us.davinci-deqm@4.0.0R4
DaVinciDrug Formularyhl7.fhir.us.davinci-drug-formulary@2.1.0R4
UniversalIPShl7.fhir.uv.ips@2.0.0R4
UniversalSMART App Launchhl7.fhir.uv.smart-app-launch@2.2.0R4
UniversalSDC (Structured Data Capture)hl7.fhir.uv.sdc@3.0.0R4
UniversalGenomics Reportinghl7.fhir.uv.genomics-reporting@3.0.0R4
UniversalCPG (Clinical Practice Guidelines)hl7.fhir.uv.cpg@2.0.0R4
DEISiK Basisde.gematik.isik-basismodul@4.0.3R4
DEISiK Medikationde.gematik.isik-medikation@4.0.1R4
DEKBV eRezeptkbv.ita.erp@1.1.1R4
DEDE Basisprofilde.basisprofil.r4@1.5.0R4
CHCH Core (Switzerland)ch.fhir.ig.ch-core@5.0.0R4
AUAU Core (Australia)hl7.fhir.au.core@1.0.0R4
IHEPIXmihe.iti.pixm@3.0.4R4
IHEMHDihe.iti.mhd@4.2.2R4
R5AE Research (R5)hl7.fhir.uv.ae-research-ig@1.0.1R5
R5eMedicinal Product (R5)hl7.fhir.uv.emedicinal-product-info@1.0.0R5

Firely SDK Validator ​

The first pipeline validates generated resources using the Firely SDK Validator — NuGet package Firely.Fhir.Validation.R4 (v3.x), running on the Firely .NET SDK Hl7.Fhir.R4 (v6.x).

These are two separate packages on separate version lines, and the report shows both. A validator version of 3.x alongside an SDK version of 6.x is expected — it does not mean the SDK is out of date.

The pipeline always validates against the newest release of each, matching how it always runs the newest HL7 Java Validator. FirelyValidator.csproj declares major-bounded floating ranges (3.*, 6.*) and carries no lock file, so every CI restore re-resolves them. The exact versions a given run used are recorded in the report header.

Known Firely SDK Issues

52 profiles are excluded from Firely validation due to schema loading or parser bugs in the Firely SDK, or to defects in the profiles themselves. See Firely SDK Exclusions below.

HL7 Java Validator ​

The second pipeline validates using the official HL7 FHIR Validator (v6.10.3), the reference implementation for FHIR conformance checking.

Known HL7 Validator Issues

27 profiles are excluded from HL7 validation due to terminology server limitations, profile resolution failures, missing snapshots, or cross-profile validation issues. See HL7 Java Validator Exclusions below.

INFO

Terminology validation requires a tx server. The pipeline uses --tx-server https://tx.fhir.org/r4 during generation to expand ValueSets and produce valid codes.

📊 Full Parity Report

Firely SDK Exclusions ​

52 profiles are excluded from Firely validation statistics because they fail for reasons outside the generated code: bugs in the Firely SDK Validator's profile/schema loading or parser, or a discriminator in the published profile that no instance can satisfy.

Discriminator Loading Failures ​

The Firely SDK cannot evaluate complex discriminator expressions in certain StructureDefinitions (fixed[x]/pattern[x]/resolve() errors). 15 profiles across 4 IGs:

PackageProfilesError Pattern
ISiK BasisISiKOrganisation, ISiKOrganisationFachabteilung, ISiKAngehoeriger, ISiKPersonImGesundheitsberuf, ISiKPatientdiscriminator should have a 'fixed[x]', 'pattern[x]' or binding element
IPSCompositionUvIps, DiagnosticReportUvIps, BundleUvIpsdiscriminator path 'resolve()' — FHIR R4 spec explicitly supports this
CH CoreCHCorePractitioner, CHCorePractitionerEPR, CHCorePatient, CHCorePatientEPRFailed to load 'ch-core-address' — compound discriminator on Extension
DaVinci PASPASClaimBase, PASClaim, PASClaimUpdate, PASClaimInquiry, PASRequestBundle, PASInquiryRequestBundleFailed to load — discriminator evaluation failure

Parser Ordering Bug ​

The Firely SDK reports false element ordering errors (coding vs text) when parsing against certain profiles. The same resources validate without errors against base FHIR resource types. 5 profiles:

PackageProfiles
ISiK BasisISiKDiagnose, ISiKProzedur
DaVinci PASPASEncounter, PASDeviceRequest, PASTask

resolve() Discriminators ​

FHIR R4 lists resolve() as a legal discriminator path, and eight profiles use it to slice a reference by the profile its target conforms to. Firely rejects the path and abandons the schema, then validates against the base resource type. 8 profiles:

PackageProfilesElement
IPSCompositionUvIps, DiagnosticReportUvIpsComposition.section:sectionMedications.entry, DiagnosticReport.result
Physical ActivityPAConditionLowPA, PADiagnosticReport, PAGoalCondition.evidence.detail, DiagnosticReport.result, Goal.addresses
SDOHSDOHCCCondition, SDOHCCTaskForPatientCondition.evidence.detail, Task.partOf
mCODETNMStageGroupObservation.hasMember ($this.resolve())

Snapshot Generator Crash ​

Firely's snapshot generator raises Internal error ... (ElementMatcher.constructChoiceTypeMatch): choice type of diff does not occur in snap for the NDH Practitioner profiles, naming the same path on both sides of the comparison. 3 profiles: NdhPractitioner, NdhNdApiPractitioner, NdhPnLdApiPractitioner.

Unmatchable Discriminators in the Profile ​

Six profiles slice on a value discriminator whose slice sets no fixed[x], pattern[x] or binding, so no instance can ever satisfy it. Firely refuses to build the schema and validates the resource against its base type instead, reporting only the base type's required elements. The defect is in the published StructureDefinition, not in the validator:

PackageProfilesDiscriminator
CPGCHFBodyWeight, CHFO2Sat, CHFPotassiumObservation.category slice
AU CoreAUCorePathologyResultObservation.category:specificDiscipline.coding.code
SDOHSDOHCCObservationRaceOMB, SDOHCCObservationEthnicityOMBObservation.component:*Description.value[x]
DaVinci PASPASClaimBase, PASClaim, PASClaimInquiry, PASClaimUpdateClaim.careTeam:OverallClaimMember — value discriminator on a boolean
ISiK BasisISiKAngehoerigerRelatedPerson.name:Name — pattern discriminator with no pattern
IHE MHDFindDocumentReferencesResponseBundle.entry:DocumentReference — discriminator always succeeds
AU CoreAUCoreBloodPressureObservation.component:SystolicBP.code — coding sliced to more than one choice

TIP

Both validators exclude profiles independently. Some profiles (e.g. ISiKOrganisation, ISiKPatient) are excluded from both pipelines for different reasons.

Keeping the Exclusion List Honest ​

Every exclusion records a diagnosis, and diagnoses go stale: the validator ships a fix, or an earlier harness bug turns out to have been the real cause. A stale entry is worse than no entry, because the report reads as parity while the check is simply not happening.

scripts/audit-parity-exclusions.mjs replays a run's recorded comparisons through the harness's own comparator, once with each exclusion applied and once without, and sorts every entry into obsolete (passes without it — delete), load-bearing (passes only with it — keep), ineffective (fails either way — the reason no longer describes what happens) or unmeasured.

The re-check protocol ​

  1. Audit at least two runs. Three entries read obsolete against a single run, were deleted, and failed in the next one.
  2. A run count does not settle a terminology-dependent finding. Those same three then read obsolete across two consecutive runs and were still live, because whether the HL7 validator reports them depends on what the terminology server answers that day. Entries like that carry intermittent: true and the audit refuses to propose them for deletion. Flag an entry that behaves this way rather than deleting it a second time.
  3. Measure profile-level entries deliberately. Run the pipeline with PARITY_AUDIT_EXCLUSIONS=1, which switches every curated exclusion off, so the suppressed comparisons actually run. Those numbers are diagnostic only and must never be published as the pipeline's own.
  4. Check the version spread. The audit prints how many entries are pinned to each validator version. Anything older than the version in the run report has not been re-verified since.
  5. Deleting is the risky direction. Keeping a live entry costs one tolerated field; deleting one costs a red pipeline. When in doubt, keep it and re-audit.

Profile-level exclusions come back unmeasured by construction: suppressing the profile suppresses the comparison, so the artifacts cannot say whether the exclusion is still needed. Those have to be audited by re-running with them disabled. Their validatorVersion is deliberately left at the version they were last verified against, so the gap between that and the version in the report is the staleness signal.

HL7 Java Validator Exclusions ​

27 profiles are excluded from HL7 validation statistics due to terminology server limitations, profile resolution failures, missing snapshots, cross-profile validation, or discriminator evaluation failures.

Profile Resolution Failures ​

The HL7 validator cannot resolve profile references needed for slicing evaluation. 7 profiles in ISiK Basis:

ProfileError Pattern
ISiKOrganisation, ISiKOrganisationFachabteilungUnable to resolve profile (identifier-bsnr)
ISiKAllergieUnvertraeglichkeitUnable to resolve profile (CodingASK)
ISiKPersonImGesundheitsberufUnable to resolve profile (identifier-efn)
ISiKAbrechnungsfallUnable to resolve profile (identifier-abrechnungsnummer)
ISiKPatientUnable to resolve profile (identifier-kvid-10)
ISiKProzedurUnable to resolve profile (CodingOPS)

Missing Snapshots ​

The HL7 validator reports that certain StructureDefinitions have no snapshot, preventing validation. 3 profiles in ISiK Basis:

ProfileError Pattern
ISiKDiagnose, ISiKVersicherungsverhaeltnisSelbstzahler, ISiKVersicherungsverhaeltnisGesetzlichhas no snapshot - validation is ...

Slicing Evaluation Failure ​

The HL7 validator cannot match discriminators for $this-based slicing. 1 profile:

ProfileError Pattern
ISiKAngehoerigerCould not match any discriminators ($this) for slice RelatedPerson.name:Name

Environment Errors ​

The HL7 validator reported internal errors unrelated to the generated resource content. 6 profiles:

PackageProfiles
ISiK BasisISiKBerichtBundle, ISiKPatientMergeSubscription, ISiKLebensZustand, ISiKCodeSystem
IPSBundleUvIps
DaVinci PASPASTask

Released under the ISC License.