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.4.tar.gz (107.8 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.4-py3-none-any.whl (80.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aimedicalcoding-0.3.4.tar.gz
  • Upload date:
  • Size: 107.8 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.4.tar.gz
Algorithm Hash digest
SHA256 39565f06448d4f6e5f2fd9d97fe8be4d08b755e4a36770dc471ce06d7e940177
MD5 4161f37e388991a62d6d182348b605bc
BLAKE2b-256 b9499994eb2aeda1d4362616c41b5ccb88af7f3671b2d0b33cdc0fb8eb7cbb91

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for aimedicalcoding-0.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 67cfe61cb44dbe7850c556eaedb4b49cc5491a8bd1a8a8693dfd3ce2ed07ef67
MD5 6e76234afcc3f1e6050ef02302104288
BLAKE2b-256 bd1ef881fe07a6be8cd220b131530446a7392000a13f42c7a943348f401faa34

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