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.5.tar.gz (107.3 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.5-py3-none-any.whl (79.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: aimedicalcoding-0.3.5.tar.gz
  • Upload date:
  • Size: 107.3 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.5.tar.gz
Algorithm Hash digest
SHA256 50af5a56e8dd6a1227acfcd04c216d54502edbfe9135e7f21564c57d4e4f3814
MD5 f1272560c46d7b830ba9cb5b821b4d81
BLAKE2b-256 3aeb6e98745ab49c57eeac564fb27c86c4652d8950b381a98a727b3c4f136600

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for aimedicalcoding-0.3.5-py3-none-any.whl
Algorithm Hash digest
SHA256 219fab2433b28e80a384e3b3d0ab0a5b948c75106ff6f8f2435b7b4251a3fc64
MD5 52f9c34c1ed18910ca2c7140012bd0d3
BLAKE2b-256 c9c3a3fa24565f0824f302295ff329cfe75c3e05b92897ee8544b26d40015dc1

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