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 vaccine product, CVX missing CVX ↔ RxNorm map
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.3.tar.gz (107.5 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.3-py3-none-any.whl (79.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aimedicalcoding-0.3.3.tar.gz
  • Upload date:
  • Size: 107.5 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.3.tar.gz
Algorithm Hash digest
SHA256 965ca356b7d0e23bf5e9c9de0d899f4d726cd6b6d905435548dec532a0511ff0
MD5 bc5ab7852016eccf81e3aeae3f16729a
BLAKE2b-256 1552e5e7b5bdee0314908cbf82d3511fe94efb479aabd9a2d6d45f2228f86e12

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for aimedicalcoding-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 7db276eb39fd68d589ca516276be6a03ecd3cd0d92a01169d0ddac9914fb3ab6
MD5 86cef821edcf6767dd3999da9b06c3b4
BLAKE2b-256 7c0c078c69004b4fd679fcbe169686d2f59432b39c8836b671a0284652327e21

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