Catalog PyPI
PACKAGE EXPLORERView in registry

aimedicalcoding

PyPI

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

ccdaclinical-terminologycvxfhiricd-10-cminteroperabilityloincmedical-codingrxnormsnomed
Versions collected1
Direct dependencies4From release 0.3.17
Last observed21 Oct, 11:17 UTC

README

Version 0.3.17 documentation

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

FlagMeaning
--systemloinc | snomed | rxnorm | icd10cm | cvx | all (default all)
--contextscomma-separated contexts to include, e.g. result,vital,problem
--tablecompact one-line-per-gap table
--jsonprint the JSON report to stdout
-o, --out FILEwrite the JSON report to FILE as UTF-8 (implies --json)
--summarycounts only
--include-narrativealso report narrative-table-only items (CCDA; off by default)
--apply-dictexpand lab abbreviations (BUN → urea nitrogen) in search terms
--json-fulldump 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:

PatternWhat's wrongRecovery route
LOCAL_CODE_ONLYa local/in-house/CPT code, no standard codecrosswalk local → target, else text search
NULLFLAVORcode explicitly declared unknown (nullFlavor, "unknown", dataAbsentReason)resolve the recovered label
DISPLAY_ONLYonly a display name, no code and no absence markerresolve the label
NARRATIVE_ONLYexists only as narrative table text (CCDA, opt-in)text search, low priority
BAD_CODEright shape, fails validation (LOINC Luhn · SNOMED Verhoeff · ICD format)check-digit / format correction (suggested fix included)
ICD_ONLYICD present, SNOMED missingofficial ICD ↔ SNOMED map
SNOMED_ONLYSNOMED present, target missing (ICD-10-CM; or LOINC on a lab/functional item)SNOMED → ICD-10-CM / SNOMED → LOINC map
CPT_ONLYCPT present, no LOINCCPT → LOINC crosswalk
NDC_ONLYNDC present, RxNorm/CVX missingNDC ↔ RxNorm / CDC NDC ↔ CVX table
RXNORM_ONLYRxNorm present, target missing (vaccine → CVX; drug allergen → SNOMED)CVX ↔ RxNorm map / RxNorm ↔ SNOMED via UMLS
MULTUM_ONLY / FDB_ONLY / MEDISPAN_ONLYproprietary drug vocabulary, RxNorm missingvendor ↔ 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.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.3.1717 Jul 20264

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 11:17 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:f0d1afdded2f487422c837c65563c292e9013d440d118977b1e9ed961aff5910