Skip to main content

Validate FHIR bundle codes against their authoritative sources: catch wrong, deprecated, and mislabeled clinical codes before they bite.

Project description

fhir-code-lint

Your FHIR test data is probably lying to you. This catches it.

fhir-code-lint validates every coded value in a FHIR R4 bundle against its authoritative source and flags the ones that disagree: wrong codes, retired codes, and displays that don't match what the code actually means.

pip install fhir-code-lint
fhir-code-lint path/to/bundles/
=== ERROR (3) ===
  [patient_07.json] MedicationRequest rxnorm:1116634 display='Tiotropium 18 MCG Inhalation Capsule'
    -> display disagrees with canonical 'ticagrelor Oral Tablet'
  ...
summary: 3 error(s), 2 warning(s) across 8 file(s)

Why this exists

During a review of a healthcare RAG prototype, a physician panelist noticed a medication card displaying a COPD inhaler while citing education material for a blood thinner. The pipeline was correct. The RxNorm code in the synthetic patient record was wrong, and every downstream system that trusted it (retrieval, display, education links) was confidently wrong with it.

The validator I wrote in response caught two more wrong medications and several deprecated codes in the same small dataset: one case from Synthea output, two in bundles served by the public HAPI FHIR test server. If your pipeline joins coded clinical data to external knowledge, check code/display consistency at ingest.

The three that motivated it

The dataset behind that story was a handful of Synthea patients plus bundles pulled from the public HAPI FHIR test server. It contained these, each verified against RxNav:

The record said RxCUI it carried RxNav says that code is
Tiotropium 18 MCG Inhalation Capsule (a COPD inhaler) 1116634 ticagrelor Oral Tablet (a blood thinner)
Morphine 1mg IV 73032 remifentanil (a different opioid)
Oxybutynin Chloride 5 MG Oral Tablet (overactive bladder) 198369 torsemide 10 MG Oral Tablet (a diuretic)

Check any of them yourself:

curl https://rxnav.nlm.nih.gov/REST/rxcui/1116634/properties.json

A physician reviewer caught the first one by accident. The validator caught the other two on purpose, plus four retired codes, the same afternoon.

What it checks

System Source of truth Mismatch severity
RxNorm RxNav properties (name + synonym) error: RxNav is canonical
SNOMED CT MedlinePlus Connect topic titles warning: titles are patient-friendly synonyms
LOINC not yet skipped in v0.1

Also flagged: missing codes, junk displays (n/a, test, ...), RxCUIs that no longer resolve (wrong or retired), and medications coded in SNOMED rather than RxNorm, which silently break RxNorm-keyed pipelines.

Resource types covered: Condition, Observation, MedicationRequest, MedicationStatement, Procedure, AllergyIntolerance, Immunization, DiagnosticReport.

flowchart LR
    A[coded value] --> B{code present?}
    B -- no --> E[error]
    B -- yes --> C{junk display?}
    C -- yes --> E
    C -- no --> D{system}
    D -- RxNorm --> R[RxNav, canonical]
    D -- "SNOMED-coded medication" --> W[warning]
    D -- SNOMED --> M[MedlinePlus, synonyms]
    R -- unknown code --> E
    R -- tokens disjoint --> E
    M -- no topic --> W
    M -- tokens disjoint --> W

Matching, honestly described

Displays and canonical names are compared as sets of lower-cased alphabetic tokens with dose-form stopwords removed ("Metformin 500 MG Oral Tablet" reduces to {metformin}). Disjoint token sets are a finding. This deliberately favors false negatives over false positives: it will not flag "Cholesterol Levels" against "Hyperlipidemia" as an error (that mismatch is a warning from a non-canonical source), but it will catch tiotropium wearing a ticagrelor code, which is the failure mode that matters.

CI usage

fhir-code-lint data/fhir/ --format json > findings.json   # machine-readable
fhir-code-lint data/fhir/ --strict                        # warnings also fail

Exit codes: 0 clean, 1 findings at failing severity, 2 usage error. Lookups are cached per run and rate-limited (--delay, default 0.1s) to be polite to the public NLM APIs.

Library usage

from fhir_code_lint import validate_paths, collect_paths

findings = validate_paths(collect_paths(["data/fhir/"]))
for f in findings:
    print(f.severity, f.file, f.code, f.detail)

FAQ

Does it need credentials? No. RxNav and MedlinePlus Connect are public NLM services.

Does it validate against Synthea specifically? It validates any FHIR R4 bundle. The code that motivated it was in Synthea output and two more came from the public HAPI test server, which is why generated and shared test data should both be linted: statistical validity and coding validity are different properties.

Why is LOINC skipped? Deterministic LOINC validation needs the LOINC table license flow. Planned, with no date attached.

License

Apache 2.0.

Built by William Garrow.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fhir_code_lint-0.1.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fhir_code_lint-0.1.0-py3-none-any.whl (16.7 kB view details)

Uploaded Python 3

File details

Details for the file fhir_code_lint-0.1.0.tar.gz.

File metadata

  • Download URL: fhir_code_lint-0.1.0.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for fhir_code_lint-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b18cbbc0f7dcbf190aa818747ec3a66a50660d00746ad6cb4501344677010854
MD5 c66ed2121953cbb6721bf44d6c4145ec
BLAKE2b-256 c9093092ce62c9938b4ad93d31f918558de27007cc9532f031932c5737bc0169

See more details on using hashes here.

Provenance

The following attestation bundles were made for fhir_code_lint-0.1.0.tar.gz:

Publisher: publish.yml on WilliamGarrow/fhir-code-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fhir_code_lint-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: fhir_code_lint-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for fhir_code_lint-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6186d4c2dbbba1dffd0468aff7b57be79620f84bb10d4baf4638447a67f2eab9
MD5 0b5505049b1eeb7c3a38978af542c4e7
BLAKE2b-256 5783ad01a4dc5e557d6d1d9b69cedfe8a32177511076529bcd6a6097ce9143f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for fhir_code_lint-0.1.0-py3-none-any.whl:

Publisher: publish.yml on WilliamGarrow/fhir-code-lint

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page