CLI and integration
Map one observation:
python -m loinc_mapper map `
--name "LDL" `
--unit "mg/dL" `
--specimen serum `
--source-laboratory "Wellspan Health" `
--observation-domain LAB_RESULT `
--loinc-class-hint CHEM `
--panel-context "lipid panel" `
--panel-context-source official_panel_heading `
--panel-context-confidence 0.98 `
--context-aliases config/axis_context_aliases.json `
--panel-index config/panel_context_index.2.82.json `
--safety-policy config/axis_safety_policy.json `
--umls-path assets/umls/2026AA `
--scispacy-model en_core_sci_md `
--vector-index assets/loinc/2.82/terms.faiss `
--vector-metadata assets/loinc/2.82/terms.json
The single-row command accepts --source-laboratory because reviewed local
mapping evidence is intentionally scoped to the performing laboratory. Batch
and report inputs carry the same source_laboratory field in each row.
--context-aliases is the versioned JSON bundle used for reviewed axis
surfaces and method-family compatibility. Pass the same file to every worker;
it is not a clinician-entered mapping file.
Map a CSV batch with a raw_name column:
python -m loinc_mapper map-batch `
--input observations.csv `
--output mapped.jsonl `
--umls-path assets/umls/2026AA `
--scispacy-model en_core_sci_md `
--vector-index assets/loinc/2.82/terms.faiss `
--vector-metadata assets/loinc/2.82/terms.json
Map a raw OCR report. This command executes row extraction before the same required mapping pipeline and writes one JSON object per extracted row:
python -m loinc_mapper map-report `
--input report.txt `
--output mapped.jsonl `
--umls-path assets/umls/2026AA `
--scispacy-model en_core_sci_md `
--vector-index assets/loinc/2.82/terms.faiss
Build release artifacts
The CSV release remains immutable source data. Compile it once into the worker artifact used at runtime:
python -m loinc_mapper build-catalog `
--output assets/loinc/2.82/catalog_v5.sqlite3 `
--release 2.82
Build the catalog-wide SapBERT sidecar after installing the production package (FAISS is a required dependency):
python -m loinc_mapper build-vectors `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--output assets/loinc/2.82/terms.faiss `
--metadata assets/loinc/2.82/terms.json `
--aliases-per-code 4 `
--batch-size 128
IndexFlatIP is exact inner-product search over normalized vectors, which is
cosine similarity. The metadata pins the LOINC release and SapBERT model. Do
not mix a vector sidecar with a different release or model. The runtime loads
the index once per worker and reuses cached query embeddings/hits; it does not
re-encode the full catalog for each row. Every non-verified-registry row still
executes UMLS, validation, ranking, and the confidence gate independently.
Build the panel-context artifact locally from the clinician-approved defaults
and pinned PanelsAndForms.csv after every panel-input or LOINC release
change:
python -m loinc_mapper build-panel-index `
--panel-defaults config/lab_panel_default_specimens.csv `
--panels-and-forms data/Loinc_2.82/Loinc_2.82/AccessoryFiles/PanelsAndForms/PanelsAndForms.csv `
--aliases config/panel_context_aliases.2.82.json `
--output config/panel_context_index.2.82.json `
--release 2.82
The output records parent-panel membership and component codes. It is not a registry of aliases to parent panel LOINCs; a component row must still map to its individual result code.
Build canonical code-level vectors from an already-built index without another full SapBERT encoding pass:
python -m loinc_mapper build-code-vectors `
--index assets/loinc/2.82/terms.faiss `
--metadata assets/loinc/2.82/terms.json
Optional official-class shards are derived from the same global index. They are a benchmarked latency optimization, not a separate LOINC database:
python -m loinc_mapper build-vector-shards `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--index assets/loinc/2.82/terms.faiss `
--metadata assets/loinc/2.82/terms.json `
--output assets/loinc/2.82/shards
Compact UMLS Serving Index
prepare-umls and upgrade-umls retain the raw licensed source needed for
audit/rebuilds. Build the compact runtime artifact after source validation:
python -m loinc_mapper build-umls-serving `
--asset-path assets/umls/2026AA `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--output assets/umls/2026AA/serving/umls_loinc.sqlite3
This keeps aliases only when their CUI bridges to an active local LOINC code.
It can take time and disk during the explicit build; do not invoke it at
request time. Once present, runtime uses serving_manifest.json and avoids
rehashing raw UMLS during worker startup.
Profiles and Worker Batches
Stage flags live in .env; see .env.example. Production refuses disabled
evidence stages. Use PIPELINE_PROFILE=evaluation only for comparisons; its
results are experimental and cannot create Elation payloads.
python -m loinc_mapper evaluate `
--input evaluation/gold/holdout.csv `
--details evaluation/gold/profile_matrix.json `
--profile-matrix `
--umls-path assets/umls/2026AA
The matrix reports full pipeline, safe fast path, no-UMLS, no-FAISS, no-UMLS/no-FAISS, and deterministic-only diagnostics, with metrics and stage p50/p95 timings.
For the main fax project, send a versioned MappingBatch instead of starting
models for each fax:
python -m loinc_mapper run-worker `
--input batch.json `
--output batch-result.json `
--umls-path assets/umls/2026AA
The batch preserves report boundaries while mapping all observations through
one warm Mapper.map_many() call. See Deployment for the
payload schema and Cloud Run Job settings.
Run the deployment canaries against the same registry and configuration bundle
that the worker will load. The command maps synthetic rows in one map_many()
batch and exits nonzero if any unit-grammar canary (UNIT_GRAMMAR_CANARIES)
does not reach its canonical form, or if the Insulin, ALT, AST, or CMP
glucose check does not return its expected active code, through the ordinary
path (mapped) or a certified force_exact entry of the loaded snapshot
(force_mapped). The report also carries safety_policy_version. Pass the
registry the worker loads: with the legacy mapping_registry.json the AST
row abstains on margin because LOINC's Part alias AST (antimicrobial
susceptibility testing) makes every susceptibility term an exact-alias
candidate; the production snapshot carries a verified AST entry.
python -m loinc_mapper runtime-canaries `
--registry config/mapping_registry.order_sets.20260827.json `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--panel-index config/panel_context_index.2.82.json `
--safety-policy config/axis_safety_policy.json `
--umls-path assets/umls/2026AA `
--vector-index assets/loinc/2.82/terms.faiss `
--vector-metadata assets/loinc/2.82/terms.json
This is a deployment-integrity check, not a clinical accuracy claim and not a replacement for the expert-reviewed holdout evaluation.
Operator Tools
Four scripts under tools/ support performance, accuracy and publication
work on the real artifact bundle. They accept the same data arguments as the
CLI (--umls-path, --catalog, --registry, --vector-index, …).
tools/benchmark_map_many.pymaps the de-identified 55-row fixture throughmap_many, prints batch timings, counters and per-stage p50/p95, and with--compare <previous.json>exits 1 when any mapping identity changed. See Performance.--check-expectations(2026-09-25) exits 1 when a fixture row misses itsexpected_status,expected_loincorexpected_primary_outcome;tests/fixtures/round_j_probe_rows.20260925.jsoncarries the round-J probe rows (the 09-25 reports' wrong codes and abstains as printed facts with synthetic values) for that check. Fixture rows may also carryvalue,specimen,method,scale,panel_context,report_section_idandrepair_operations.tools/facts_replay.py <facts.json> <out.json>(2026-09-25) maps everylab_analyterow of a de-identified facts export from the application (names, units, value shapes, headings; no values or identifiers), synthesising a value from each shape, and writes per-row evidence (status, code, confidence, margin, primary outcome, released clues, coverage, groups, candidates, trace) for tracing a production run offline;--sections pagegives heading-less rows a per-page section id.tools/replay_review_cases.py <directory>rebuilds the observations stored in the main project's review-case JSON files, maps them once, and prints a de-identified before/after table (analyte name, unit, stored status/code, new status/code, new primary outcome). It writes nothing and never prints values, identifiers, document references or file names; run it on the operator's machine only.tools/export_example_units.pyregenerates the three release-derived unit artifacts: the fixture of every distinctEXAMPLE_UCUM_UNITS/EXAMPLE_UNITSsurface on active laboratory terms (with the display/UCUM pairs of single-example terms),config/unit_release_vocabulary.json(annotation tokens, display-word aliases, display-form alignments the parser loads), and the allowlist of surfaces the grammar still cannot parse, asserted bytests/test_units_loinc_coverage.py. Run it with--catalogafter a LOINC release change and with--from-fixtureafter a unit-grammar change; the allowlist may only shrink.python -m loinc_mapper build-panel-indexcompiles the panel context index from the clinician defaults, the release'sPanelsAndForms.csvand the reviewed aliases file, plus three optional inputs:--order-set-catalog(the main project's Elation order-set export; heading surfaces of the practice's panel orders),--registry(the active snapshot; decides%member twins and resolves vendor-definition components) and--vendor-definitions(official vendor result definitions). See Panel context.tools/registry_constraint_report.py --registry <snapshot> [--output <csv>]lists every specimen and method constraint of a snapshot and classifies it as a restatement of the approved term's own axis, a discriminator (another approval of the same folded name targets another code) or a scope limit, for the clinician to decide which to keep. It never edits a registry. See Clinician-governed learning.tools/registry_lint.py --registry <snapshot> [--output <csv>] [--fuzzy] [--catalog <sqlite>] [--panel-index <json>]lists pairs of approvals whose surfaces meet (the same normalized text, the closed fold, the joined surface, and with--fuzzythe bounded fuzzy lookup) with different targets, each side's scope and required units, and classifies the pairconflictorscoped(a laboratory, a panel or disjoint required units keep it apart). A force entry that names another code than an approval of the same surface is listed as meetingforce. Read-only; the list is for the clinician. See Clinician-governed learning.tools/recertify_fast_path.py --registry <snapshot> --output <registry.json> [--report-output <json>]strict-replays every clinician universal approval without fast-path certification (its own surface, first required unit and constraints, with no laboratory and at an unseen one, through the full pipeline and with its fast path on) and certifies it only when all four map its target. It writes a new active snapshot whose manifest names the parent and the report hash; the operator publishes it. Loads the bundle twice, one after the other; run it alone.tools/compile_clinician_approvals.py --cases <directory> --decisions <json> --reviewer-id … --reviewer-name … --output <registry.json>turns a clinician's decisions on stored review cases into a finalized active registry snapshot through the same contract as the main project's publish endpoint:validate_review_decision,compile_registry_snapshot, a full replay and a fast-path replay on the bundle,finalize_registry_snapshot.--facts <json>replaces--caseswith a list of de-identified mapping fact rows (name, unit, specimen, laboratory, panel heading with its source and confidence, panel LOINC hint, report section; the allow-list isDEIDENTIFIED_FACT_ROW_FIELDS), each becoming a synthetic case with an abstain result, so a round can be prepared from a report's printed names. The decisions file holds analyte name, optional unit, target code, action (approve_universalorapprove_source_specific), rationale, optionalcorrected_fields/clear_fieldsand, for a universal approval, an optionalrequired_panel(a panel index record key the case must resolve to through its heading or hint; the mapper's index checks it); cases are loaded with synthetic ids and without report references, so the snapshot carries no patient-linked identifier, and a case whose stored result verified an input repair is loaded under its repairedmapping_name, the surface the runtime looks up. An alias whose surfaces collide with an active or earlier entry in the same scope and point to another code is skipped and reported, never overwritten; the same label approved to the same code under a second panel is a second entry. The tool writes the registry and its manifest locally only; promotion is the operator's action. See Clinician-governed learning.
Clinician Review and Registry Snapshots
The current production review path is storage-free in this package and clinician-friendly in the main project. It validates decisions and creates immutable candidate snapshots; the main project owns reviewer authentication, the UI, durable review events, Cloud Storage, and Cloud Run orchestration. See Clinician-governed learning for the full contract.
The old JSONL queue commands remain available for local diagnostics and older
integrations, but import-reviews is compatibility-only and never enables the
registry fast path. New clinical approvals should use the ReviewCase CSV
workflow below.
Create review cases in the main project with build_review_case, then export
them to a doctor-friendly CSV:
python -m loinc_mapper export-review-csv `
--cases review_cases.jsonl `
--output clinician_review_template.csv
Validate the completed CSV without modifying an active registry:
python -m loinc_mapper validate-review-csv `
--cases review_cases.jsonl `
--input clinician_reviewed.csv `
--registry config/mapping_registry.json `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--output review_validation.json
Compile only fully valid approvals into a candidate snapshot:
python -m loinc_mapper compile-review-snapshot `
--cases review_cases.jsonl `
--input clinician_reviewed.csv `
--registry config/mapping_registry.json `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--registry-output candidate_registry.json `
--manifest-output candidate_manifest.json
With --collect an approval that cannot be published never stops the others: every
approval that passes is compiled and the rest are returned, each with its reason, in
the manifest's returned_decisions; the printed summary carries
returned_decision_count.
The candidate has registry_status=candidate, so a worker refuses to load it.
The main project must finalize it before publishing an active snapshot. For
normal authorized clinician publication, use
PublicationPolicy.CLINICIAN_FAST_REPLAY with a mapper factory configured by
PipelineSettings.clinician_review_replay(). That runs one batched exact
registry replay over changed reviewed rows and does not load UMLS, ScispaCy,
FAISS, or SapBERT. PublicationPolicy.STRICT_REPLAY retains original/unseen
and fast-equivalence replay for scheduled release audits. There is no
clinician-controlled fast-path switch. A restricted, server-authorized
approve_force_exact / force_exact action exists for exceptional operations:
it matches only the exact lossless label, produces force_mapped, bypasses
clinical validation by design, is excluded from normal metrics, and cannot
file to Elation unless the deployment separately enables
IS_FORCE_MAPPED_ELATION_ON=true. It must never be exposed as an ordinary
clinician UI action.
Export uncertain rows with their complete candidate explanation for local diagnostics or a legacy migration:
python -m loinc_mapper review-queue `
--input observations.csv `
--output review_queue.jsonl `
--umls-path assets/umls/2026AA
For an existing legacy JSONL queue only, import explicit approvals:
python -m loinc_mapper import-reviews `
--input review_queue.jsonl `
--output config/mapping_registry.reviewed.json
Finalized registry snapshots change retrieval in newly launched jobs. They do not silently retrain SapBERT. A feature ranker can be trained later from detailed evaluation records, then compared against the champion with the precision and dangerous-false-positive gate before deployment:
python -m loinc_mapper train-ranker `
--input evaluation/stress_test/production_detailed_report.json `
--output assets/rankers/adarank-compatible.json
Audit the existing registry before deciding which local records are genuinely universal. This is read-only; it never promotes a source-specific mapping:
python -m loinc_mapper audit-registry-scopes `
--registry config/mapping_registry.json `
--output evaluation/registry_scope_audit.json
Export a two-layer registry for a new integration without changing the legacy runtime file:
python -m loinc_mapper export-layered-registry `
--registry config/mapping_registry.json `
--output config/mapping_registry.layered.json
Cluster unresolved rows for review. The output contains recurrence counts, source laboratories, axis facts, and a suggested review scope. It never writes an active mapping:
python -m loinc_mapper cluster-review-queue `
--input review_queue.jsonl `
--output evaluation/review_clusters.json
Build a MIMIC review set
MIMIC-IV v3.1 is handled as a real-world candidate corpus first. Its local
itemid labels are not automatically trusted as LOINC gold labels:
python -m loinc_mapper prepare-mimic `
--labevents path/to/labevents.csv `
--d-labitems path/to/d_labitems.csv `
--output evaluation/mimic/candidates.csv `
--mapping-template evaluation/mimic/item_mapping_template.csv
After expert review of the template:
python -m loinc_mapper build-mimic-gold `
--candidates evaluation/mimic/candidates.csv `
--mapping evaluation/mimic/item_mapping_template.reviewed.csv `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--output evaluation/mimic/gold.csv
The preparation step excludes patient, encounter, specimen, timestamp, and provider identifiers from its output. Keep the downloaded source and derived files under the PhysioNet agreement and outside Git.
Audit labels before evaluation
Before interpreting accuracy, audit whether the input name and expected LOINC code agree on method, specimen, duration, and unit dimension:
python -m loinc_mapper audit-labels `
--input evaluation/stress_test/stress_test_output_v2.csv `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--output evaluation/stress_test/stress_test_output_v2_label_audit.json
Rows such as HDL Cholesterol (Calc) expected as methodless 2085-9 are
flagged rather than silently relabeled. A clinical reviewer must decide
whether Calc is a real method, a local panel suffix, or an incorrect label.
Audit the Toward Health screening seed files before registry promotion:
python -m loinc_mapper audit-screening `
--catalog assets/loinc/2.82/catalog_v5.sqlite3 `
--input data/initial_labs_data_temp/toward_health_screening_loinc_cpt_mapping.csv `
data/initial_labs_data_temp/toward_health_screening_specialty_questionable.csv `
data/initial_labs_data_temp/toward_health_screening_top500.csv `
--output evaluation/screening/toward_health_screening_audit.json
This command is review-only. It reports blank, missing, inactive,
non-laboratory, exact-name, and release-name-different rows; it never writes
entries into config/mapping_registry.json.
Evaluation, report, and CSV batch mapping use map_many. This shares safe
infrastructure such as repeated UMLS lookups and SapBERT query embeddings; it
does not merge rows or share a clinical decision. Each result retains its own
candidate list, six-axis evidence, unit decision, confidence, and provenance.
The Python boundary is:
from loinc_mapper import LabObservation, build_mapper
mapper = build_mapper(
core_path,
rich_path,
common_names_path,
registry_path,
map_to_path,
umls_path,
"en_core_sci_md",
"cambridgeltl/SapBERT-from-PubMedBERT-fulltext",
vector_index_path="assets/loinc/2.82/terms.faiss",
vector_metadata_path="assets/loinc/2.82/terms.json",
)
result = mapper.map(LabObservation(raw_name="Hb A1C", unit="%"))
For category-aware routing, pass observation_domain, loinc_class_hint,
and optional panel_context in the LabObservation. LAB_RESULT is the hard
laboratory scope. LOINC class and panel context are advisory retrieval/ranking
evidence, so an incorrect section label cannot reject a safe clinical sibling.
See Category-aware routing and
Universal multi-lab mapping.
Production construction requires umls_path, scispacy_model,
sapbert_model, and the pinned FAISS vector sidecar. The CLI auto-detects the
default sidecar, but explicit paths are recommended for reproducible runs.
If the sidecar is missing, production construction fails instead of silently
falling back to direct encoder reranking. map-report runs OCR row extraction
and emits mapping results with accepted Elation payloads.
The CLI uses the generated schema-6 catalog_v5.sqlite3 automatically when it
is present. Pass --catalog explicitly in deployment scripts so the selected
artifact is visible and reproducible. See Deployment for the
complete worker integration sequence and Maintenance for
release refresh procedures.
For the active Elation test catalog, use the append-only Dr Tro snapshot
command and supporting audit workflow in
Order-set registry snapshot. The snapshot command
is the production bootstrap path; it preserves the parent registry and writes
one new immutable-compatible artifact. Pass
--panel-components config/order_set_panel_components.20260827.json to append
the reviewed scalar CMP, CBC, thyroid, HbA1c/eAG, MPV, and RDW-SD result
aliases. Panel parents remain context-only. The audit/draft/review files are
optional diagnostics and are not required to build the approved snapshot.