Skip to content

Terminology

Free text is where clinical data starts — "aspirin", "type 2 diabetes", "blood pressure" — but FHIR resources need codings. healthchain.terminology is the seam for mapping text to codes: a small protocol any terminology service can implement, and an in-process lookup that works offline with zero setup.

The demo catalog is not a terminology service

LocalCodeLookup's bundled catalog covers a few dozen common concepts so demos, tests, and generated projects run without network access or licenses. For real coverage, put a proper terminology service behind the same TerminologyLookup protocol.

Quick Start

from healthchain.terminology import LocalCodeLookup

lookup = LocalCodeLookup()

# Every query word must match — strengths and brands narrow results
results = lookup.search("metoprolol 25")
print(results[0].code, results[0].display)
# 866427 24 HR metoprolol succinate 25 MG Extended Release Oral Tablet

# Restrict to one code system
from healthchain.terminology import ICD10CM
condition = lookup.search("hypertension", system=ICD10CM)[0]
print(condition.code)  # I10

Results are Coding models (code / display / system), ranked tightest-match-first, and serialize with model_dump() — so a lookup can be returned directly from an agent tool. The point of handing a model a lookup tool is that it uses a returned code instead of inventing one.

The catalog

The bundled demo catalog ships license-free code systems only:

System Constant Contents
RxNorm RXNORM Common medications with strengths
LOINC LOINC Common labs and vital signs
ICD-10-CM ICD10CM Common conditions

(No SNOMED CT — its license doesn't permit redistribution in an open-source package.)

Replace it with your own formulary or code list:

from healthchain.terminology import Coding, LocalCodeLookup, RXNORM

site_lookup = LocalCodeLookup(catalog=[
    Coding(code="197361", display="amLODIPine 5 MG Oral Tablet", system=RXNORM),
    {"code": "314076", "display": "lisinopril 10 MG Oral Tablet", "system": RXNORM},
])

A local catalog is data your application may want to walk, not just probe — to build a mention lexicon for an extractor, render a picker, or export. LocalCodeLookup is iterable and exposes the full catalog via entries():

lexicon = {coding.display.lower() for coding in site_lookup}
all_codings = site_lookup.entries()
len(site_lookup)  # catalog size

(Enumeration is a capability of local lookups, not part of the TerminologyLookup protocol — a remote terminology service generally can't list itself.)

The protocol

Anything with a search(text, system) -> list[Coding] method is a TerminologyLookup — the protocol is runtime-checkable, and structural, so implementations don't need to inherit anything:

from healthchain.terminology import Coding, TerminologyLookup

class MyTerminologyService:
    """e.g. backed by a FHIR $lookup endpoint or a hosted MCP server."""

    def search(self, text: str, system: str | None = None) -> list[Coding]:
        ...

assert isinstance(MyTerminologyService(), TerminologyLookup)

Write code against the protocol, and swap LocalCodeLookup for a real service when you deploy.

Putting it together

Look up a code, build the resource, validate it — the write loop an agent (or you) runs:

from healthchain.fhir import create_medication_statement, validate_resource
from healthchain.terminology import LocalCodeLookup, RXNORM

lookup = LocalCodeLookup()

coding = lookup.search("acetaminophen 325", system=RXNORM)[0]
medication = create_medication_statement(
    subject="Patient/123",
    status="active",
    code=coding.code,
    display=coding.display,
    system=coding.system,
)
report = validate_resource(medication)
assert report.valid