Skip to main content

Detect missing/broken clinical codes (LOINC / SNOMED CT / RxNorm / ICD-10-CM / CVX) in CCDA and FHIR documents, with recovery evidence

Project description

aimedicalcoding

Find every missing or broken standard clinical code in a CCDA or FHIR document — with the evidence needed to fix it.

aimedicalcoding parses a clinical document (CCDA XML or FHIR R4 Bundle JSON), inspects every coded clinical item — labs, vitals, problems, medications, immunizations, allergies, procedures, social history — and reports the ones whose standard terminology code (LOINC · SNOMED CT · RxNorm · ICD-10-CM · CVX) is missing, local-only, crosswalkable, or malformed. Each finding (a gap) is emitted as a self-contained JSON record carrying the classification, the document facts, and a ready-made recovery plan (search terms, axis filters, crosswalk candidates, check-digit corrections).

Items that are already correctly coded produce no output — the result is a work list, not an inventory.


Installation

pip install aimedicalcoding

Requires Python 3.10+. The only runtime dependency is lxml.


Quick start (CLI)

The input format is auto-detected — the same command works for CCDA XML and FHIR JSON, and the output schema is identical for both.

# human-readable gap report (card view)
aimedicalcoding patient.xml

# FHIR R4 Bundle — same flags, same output
aimedicalcoding bundle.json --table

# counts only
aimedicalcoding patient.xml --summary --table

# machine-readable JSON, written to a file as UTF-8
aimedicalcoding patient.xml --out gaps.json

# restrict to one terminology and/or certain contexts
aimedicalcoding patient.xml --system loinc --contexts result,vital

If the entry point is not on your PATH, python -m aimedicalcoding <file> [flags] is equivalent.

CLI flags

Flag Meaning
--system loinc | snomed | rxnorm | icd10cm | cvx | all (default all)
--contexts comma-separated contexts to include, e.g. result,vital,problem
--table compact one-line-per-gap table
--json print the JSON report to stdout
-o, --out FILE write the JSON report to FILE as UTF-8 (implies --json)
--summary counts only
--include-narrative also report narrative-table-only items (CCDA; off by default)
--apply-dict expand lab abbreviations (BUN → urea nitrogen) in search terms
--json-full dump the complete internal representation (debugging)

Quick start (Python)

from aimedicalcoding import extract_gaps

result = extract_gaps("patient.xml")        # CCDA — auto-detected
result = extract_gaps("bundle.json")        # FHIR R4 Bundle — auto-detected
result = extract_gaps(raw_bytes,            # bytes / str / path / file-like
                      systems=("loinc", "snomed", "rxnorm", "icd10cm", "cvx"))

len(result)           # number of gaps
result.by_target      # {"LOINC": 17, "SNOMED-CT": 30, "RxNorm": 4, ...}
result.by_pattern     # {"LOCAL_CODE_ONLY": 15, "NULLFLAVOR": 32, ...}
result.gaps           # list[dict] — one record per (item, missing terminology)
result.to_json()      # the same JSON the CLI emits

Lower-level access — parse to the intermediate representation without gap analysis:

from aimedicalcoding.pipeline import parse_document

statements = parse_document(open("bundle.json", "rb").read())
for s in statements:
    print(s.context, s.raw_text, [(c.system, c.code) for c in s.codings])

How it works

The library is built on two independent axes that meet at a format-agnostic intermediate representation (IR):

 FORMAT axis (adapters)              TERMINOLOGY axis (extractors)
   CCDA XML  ──┐                       LOINC      SNOMED CT
   FHIR R4  ───┤──►  ClinicalStatement ──►  RxNorm   ICD-10-CM   CVX
               │         (the IR)
   (auto-detected)                    (identical for every input format)
  1. Detect — XML with a <ClinicalDocument> root → CCDA adapter; JSON with "resourceType": "Bundle" → FHIR adapter.
  2. Parse to IR — the adapter walks the document and emits one ClinicalStatement per clinical item: every coding found on the item (root code plus all translations / coding[] entries, each canonicalized by system), the human labels, the value (quantity or coded answer), specimen, panel membership, timestamps.
  3. Extract gaps — each terminology extractor examines the statements relevant to it (labs/vitals for LOINC, problems/procedures/reactions for SNOMED, medications for RxNorm, diagnoses for ICD-10-CM, immunizations for CVX), validates any code present (check digits, format), and classifies everything else into a gap pattern.
  4. Serialize — one JSON record per (item × missing terminology), identical schema for CCDA and FHIR input.

What each adapter understands

CCDA: document/section/entry structure, <code> + <translation> scan-and-identify, nullFlavor, <originalText> reference resolution into the narrative, organizer → panel linkage, specimen, interpretation and reference ranges, narrative-table fallback for sections without structured entries.

FHIR R4 Bundle: routing by resource type + Observation.category (with section-tag fallback), CodeableConcept.coding[] as primary + translations, valueQuantity / valueCodeableConcept / valueString / dataAbsentReason, hasMember / DiagnosticReport.result panel linkage (children inherit context and specimen), medicationReference → Medication.code and specimen → Specimen.type resolution across the Bundle, per-reaction allergy statements, and normalization of "unknown" null-marker strings.


Gap patterns

Each record's pattern says why the code is missing — which determines how to fix it:

Pattern What's wrong Recovery route
LOCAL_CODE_ONLY a local/in-house/CPT code, no standard code crosswalk local → target, else text search
NULLFLAVOR code explicitly declared unknown (nullFlavor, "unknown", dataAbsentReason) resolve the recovered label
DISPLAY_ONLY only a display name, no code and no absence marker resolve the label
NARRATIVE_ONLY exists only as narrative table text (CCDA, opt-in) text search, low priority
BAD_CODE right shape, fails validation (LOINC Luhn · SNOMED Verhoeff · ICD format) check-digit / format correction (suggested fix included)
ICD_ONLY ICD present, SNOMED missing official ICD ↔ SNOMED map
SNOMED_ONLY SNOMED present, ICD-10-CM missing official SNOMED → ICD-10-CM map
NDC_ONLY NDC present, RxNorm/CVX missing NDC ↔ RxNorm / CDC NDC ↔ CVX table
RXNORM_ONLY RxNorm present, target missing (vaccine → CVX; drug allergen → SNOMED) CVX ↔ RxNorm map / RxNorm ↔ SNOMED via UMLS
MULTUM_ONLY / FDB_ONLY / MEDISPAN_ONLY proprietary drug vocabulary, RxNorm missing vendor ↔ RxNorm via UMLS

Output

The JSON envelope:

{
  "source": "patient.xml",
  "source_format": "ccda",              // or "fhir" — auto-detected
  "targets_checked": ["LOINC", "SNOMED-CT", "RxNorm", "ICD-10-CM", "CVX"],
  "gap_count": 4,
  "by_pattern": { "LOCAL_CODE_ONLY": 1, "NULLFLAVOR": 3 },
  "by_target":  { "LOINC": 1, "SNOMED-CT": 3 },
  "gaps": [ ... ]
}

Each gap record separates four concerns — the classification, the facts, the plan, and the (pending) answer:

{
  "id": 1,
  "target":  { "system": "LOINC", "slot": "code" },   // what's missing, and in which slot
  "pattern": "LOCAL_CODE_ONLY",                        // why
  "section": { "code": "30954-2", "name": "Results", "system": "LOINC" },

  "observation": {                                     // the facts, verbatim
    "code": {
      "display_text": "LIPID PANEL",
      "original_text": "LIPID PANEL",
      "codings": [
        { "code": null,    "system": "LOINC", "system_raw": "http://loinc.org" },
        { "code": "80061", "system": "CPT",   "system_raw": "http://www.ama-assn.org/go/cpt" }
      ]
    },
    "value": null,
    "qualifiers": { "specimen": { "value": "Bld", "confidence": "low" }, "...": "..." }
  },

  "recovery": {                                        // the plan
    "search_terms": ["LIPID PANEL", "lipid panel"],
    "match_key": "component:LIPID PANEL | specimen:Bld | property:? | scale:? | method:-",
    "crosswalk": { "from_system": "http://www.ama-assn.org/go/cpt", "from_code": "80061" },
    "suggested_action": "Try (source_system, local_code) crosswalk FIRST; ..."
  },

  "provenance": { "location": { "type": "fhirpath", "path": "..." } },
  "resolution": { "status": "pending", "code": null, "...": "..." }  // filled by your recovery step
}

Reading it: this lab was coded with CPT 80061 but its LOINC slot is empty (code: null — the source wrote a null marker). The record hands you the CPT crosswalk candidate, the text to search with, and the LOINC axis hints (specimen/property/scale) to constrain candidates — everything a crosswalk-database or LLM step needs to fill resolution.


Scope and status

  • Emits gap detections + recovery evidence. It does not itself query terminology databases or LLMs — resolution is intentionally left pending for your downstream step.
  • Terminologies checked today: LOINC, SNOMED CT, RxNorm, ICD-10-CM, CVX. CPT / HCPCS / ICD-10-PCS are planned.
  • Validation included: LOINC Luhn check digits, SNOMED Verhoeff check digits (incl. long-format extension SCTIDs), ICD-10-CM format rules, CVX shape.
  • Code-system identification covers HL7 OIDs, FHIR URIs, urn:oid: forms and free-text names for 20+ vocabularies (Epic/local OIDs are recognized as local codes, HL7 structural vocabularies are excluded from gap detection).

License

MIT — © 2026 Trove Health.

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

aimedicalcoding-0.3.11.tar.gz (110.9 kB view details)

Uploaded Source

Built Distribution

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

aimedicalcoding-0.3.11-py3-none-any.whl (83.0 kB view details)

Uploaded Python 3

File details

Details for the file aimedicalcoding-0.3.11.tar.gz.

File metadata

  • Download URL: aimedicalcoding-0.3.11.tar.gz
  • Upload date:
  • Size: 110.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for aimedicalcoding-0.3.11.tar.gz
Algorithm Hash digest
SHA256 b8a48f3a5d586626672c1127aea2ca38d4292ace44d3eaed379216478792dfb4
MD5 55ec73b98a87406960e3c35fd19c6c74
BLAKE2b-256 3d90f59c20464a0f45c8fe1466d941dd38e24e76e1e2d6db07e9b4c9dede178d

See more details on using hashes here.

File details

Details for the file aimedicalcoding-0.3.11-py3-none-any.whl.

File metadata

File hashes

Hashes for aimedicalcoding-0.3.11-py3-none-any.whl
Algorithm Hash digest
SHA256 18a57fb33ffd54a05e39d7ead3d649ae5a6130827015d13375dddb1d63e71e72
MD5 7b00a259e535f8891fb78a232b99e2e6
BLAKE2b-256 834a009050a97864fb3cf05c44c1a0af03e4eb00227f4751b095934716b5bd1d

See more details on using hashes here.

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