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.
Metadata
Release files for fhir-code-lint 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fhir_code_lint-0.1.0.tar.gz | 13.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fhir_code_lint-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.2 kB
Release files / fhir_code_lint-0.1.0.tar.gz
| Download URL | fhir_code_lint-0.1.0.tar.gz |
|---|---|
| Size | 13.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b18cbbc0f7dcbf190aa818747ec3a66a50660d00746ad6cb4501344677010854
|
|
BLAKE2b-256 checksum How to use checksums |
c9093092ce62c9938b4ad93d31f918558de27007cc9532f031932c5737bc0169
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.
Transparency logRelease files / fhir_code_lint-0.1.0-py3-none-any.whl
| Download URL | fhir_code_lint-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6186d4c2dbbba1dffd0468aff7b57be79620f84bb10d4baf4638447a67f2eab9
|
|
BLAKE2b-256 checksum How to use checksums |
5783ad01a4dc5e557d6d1d9b69cedfe8a32177511076529bcd6a6097ce9143f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.
Transparency logSecurity for fhir-code-lint 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fhir_code_lint-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.