@cosyte/terminology 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3466 @@
1
+ /**
2
+ * The immutable FHIR value types the engine operates over: {@link Coding} and
3
+ * {@link CodeableConcept}.
4
+ *
5
+ * These mirror the FHIR R4 datatypes of the same names (the interchange lingua franca the whole
6
+ * ecosystem speaks), reduced to the fields the terminology operations need. Every instance the
7
+ * engine hands back is deep-frozen, so a translated result is safe to share across a pipeline
8
+ * without defensive copying — the immutability discipline the parsers follow.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * A single coded value — a `code` drawn from a code `system`, optionally versioned and displayed.
14
+ *
15
+ * Models FHIR R4 `Coding`. `system` is the code system's **canonical URI** (e.g.
16
+ * `http://loinc.org`), not a mnemonic or OID — use {@link ../systems/resolve.resolveSystem} to
17
+ * canonicalize before constructing one. `system` is optional because a raw wire code sometimes
18
+ * arrives without one; the engine surfaces that rather than inventing a system.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { coding } from "@cosyte/terminology";
23
+ *
24
+ * const c = coding({ system: "http://loinc.org", code: "2160-0", display: "Creatinine" });
25
+ * c.code; // => "2160-0"
26
+ * ```
27
+ */
28
+ interface Coding {
29
+ /** The code system's canonical URI (e.g. `http://snomed.info/sct`). Optional when unknown. */
30
+ readonly system?: string;
31
+ /** The symbol in the code system's syntax. Required — a coding without a code is meaningless. */
32
+ readonly code: string;
33
+ /** The code system version this code is drawn from, when known (mappings are release-scoped). */
34
+ readonly version?: string;
35
+ /** The human-readable label. Carried verbatim from the source; never fabricated by the engine. */
36
+ readonly display?: string;
37
+ }
38
+ /**
39
+ * A concept described by one or more {@link Coding}s plus optional free text — FHIR R4
40
+ * `CodeableConcept`. Included so the value layer is complete for downstream consumers even though
41
+ * Phase 1's {@link ../conceptmap/translate.translate} operates on a single {@link Coding}.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * import { codeableConcept } from "@cosyte/terminology";
46
+ *
47
+ * const cc = codeableConcept({
48
+ * coding: [{ system: "http://loinc.org", code: "2160-0" }],
49
+ * text: "Serum creatinine",
50
+ * });
51
+ * cc.coding[0].code; // => "2160-0"
52
+ * ```
53
+ */
54
+ interface CodeableConcept {
55
+ /** The codings for this concept, in order of preference. Frozen and never empty-by-fabrication. */
56
+ readonly coding: readonly Coding[];
57
+ /** Plain-text representation of the concept, when supplied. */
58
+ readonly text?: string;
59
+ }
60
+ /**
61
+ * Construct a deep-frozen {@link Coding}.
62
+ *
63
+ * The returned object is `Object.freeze`d so it cannot be mutated in place — parsed/translated
64
+ * values are immutable by default. Optional fields are only present when supplied (no `undefined`
65
+ * keys), so structural equality checks stay clean.
66
+ *
67
+ * @param init - The coding fields; `code` is required.
68
+ * @returns A frozen {@link Coding}.
69
+ * @example
70
+ * ```ts
71
+ * import { coding } from "@cosyte/terminology";
72
+ *
73
+ * const frozen = coding({ system: "http://loinc.org", code: "2160-0" });
74
+ * Object.isFrozen(frozen); // => true
75
+ * ```
76
+ */
77
+ declare function coding(init: Coding): Coding;
78
+ /**
79
+ * Construct a deep-frozen {@link CodeableConcept} (its `coding` array and every member frozen).
80
+ *
81
+ * @param init - The concept fields; `coding` is required (may be empty).
82
+ * @returns A frozen {@link CodeableConcept}.
83
+ * @example
84
+ * ```ts
85
+ * import { codeableConcept } from "@cosyte/terminology";
86
+ *
87
+ * const cc = codeableConcept({ coding: [{ code: "2160-0" }] });
88
+ * Object.isFrozen(cc.coding); // => true
89
+ * ```
90
+ */
91
+ declare function codeableConcept(init: CodeableConcept): CodeableConcept;
92
+
93
+ /**
94
+ * The engine's **stable diagnostic + fatal code registries** and its typed error.
95
+ *
96
+ * `@cosyte/terminology` is an engine, not a lenient wire-format parser, so its stable codes are
97
+ * **diagnostics** (typed, surfaced outcomes a consumer branches on) and **fatals** (thrown on
98
+ * structurally unusable input), rather than the parsers' "Tier-2 tolerance warnings". The
99
+ * discipline is identical: the code strings are part of the public contract — consumers narrow on
100
+ * them, so renaming or removing one is a **breaking change**, and the full set is snapshotted via
101
+ * `sortedCodeSet` (`@cosyte/test-utils`) as a reviewable tripwire.
102
+ *
103
+ * **Value-free by construction.** A diagnostic carries a stable code plus, at most, a
104
+ * code + system + version — **never** a surrounding patient identifier or clinical narrative. A
105
+ * code *in patient context* can be PHI (roadmap §7), so messages describe *structure* (a resource
106
+ * path, a code system), never echo arbitrary input *values*.
107
+ *
108
+ * @packageDocumentation
109
+ */
110
+ /**
111
+ * Stable **diagnostic codes** — typed, surfaced, non-throwing outcomes.
112
+ *
113
+ * These are not errors: they are first-class results a caller inspects (an unmapped translation,
114
+ * an unrecognized code system). `key === value` so `Object.values(...)` yields the snapshot set.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { DIAGNOSTIC_CODES } from "@cosyte/terminology";
119
+ *
120
+ * DIAGNOSTIC_CODES.TERM_TRANSLATE_UNMAPPED; // => "TERM_TRANSLATE_UNMAPPED"
121
+ * ```
122
+ */
123
+ declare const DIAGNOSTIC_CODES: {
124
+ /**
125
+ * A `$translate` found no target for the source code. A **first-class outcome, never an error** —
126
+ * the source is surfaced as `unmapped`, never replaced with a guessed target (the never-fabricate
127
+ * invariant).
128
+ */
129
+ readonly TERM_TRANSLATE_UNMAPPED: "TERM_TRANSLATE_UNMAPPED";
130
+ /**
131
+ * A code-system identifier (URI, OID, or mnemonic) was not recognized by the identity resolver.
132
+ * Surfaced as a typed `unknown`, never coerced to a guessed canonical URI.
133
+ */
134
+ readonly TERM_SYSTEM_UNRECOGNIZED: "TERM_SYSTEM_UNRECOGNIZED";
135
+ /**
136
+ * An RRF (pipe-delimited RxNorm/UMLS release) row was structurally unusable — too few columns to
137
+ * reach a configured field, or a missing code. The row is **skipped and surfaced** as a load
138
+ * warning (liberal on load), never kept as a partially-parsed concept.
139
+ */
140
+ readonly TERM_RRF_MALFORMED_ROW: "TERM_RRF_MALFORMED_ROW";
141
+ /**
142
+ * A CSV (RFC-4180, e.g. LOINC `Loinc.csv`) row was structurally unusable — too few fields to reach
143
+ * a configured column. The row is **skipped and surfaced** as a load warning, never partial.
144
+ */
145
+ readonly TERM_CSV_MALFORMED: "TERM_CSV_MALFORMED";
146
+ /**
147
+ * A fixed-width (e.g. ICD-10-CM order file) row was structurally unusable — too short to contain
148
+ * the configured code field, or a missing code. Skipped and surfaced, never partial.
149
+ */
150
+ readonly TERM_FIXED_WIDTH_MALFORMED: "TERM_FIXED_WIDTH_MALFORMED";
151
+ /**
152
+ * A concept inside a FHIR `CodeSystem` resource was unusable — not an object, or missing its
153
+ * required `code`. Skipped and surfaced (the whole resource still loads); `line` is `0` since a
154
+ * JSON concept is not line-addressable.
155
+ */
156
+ readonly TERM_FHIR_CONCEPT_MALFORMED: "TERM_FHIR_CONCEPT_MALFORMED";
157
+ /**
158
+ * A `$lookup`/`$validate-code` found no concept for the code in the loaded release. A **first-class
159
+ * typed outcome, never a guess** — the engine returns `unknown`/`valid: false`, never a fabricated
160
+ * display or a coerced `valid: true` (the never-fabricate invariant, applied to code identity).
161
+ */
162
+ readonly TERM_CODE_UNKNOWN: "TERM_CODE_UNKNOWN";
163
+ /**
164
+ * A resolved concept is **deprecated / no longer current** (e.g. LOINC `STATUS = DEPRECATED`, or a
165
+ * FHIR concept flagged `inactive`). Surfaced on the concept's status so a caller never presents a
166
+ * non-current code as clean; the concept is still *found* (a lookup succeeds), it is just flagged.
167
+ */
168
+ readonly TERM_CONCEPT_DEPRECATED: "TERM_CONCEPT_DEPRECATED";
169
+ /**
170
+ * A resolved concept is a **classification header, not a billable/valid leaf** (e.g. an ICD-10-CM
171
+ * order-file row with the position-15 flag `0`). Surfaced on the concept's status: a header code is
172
+ * found but is **not valid for claim submission**, and must never be presented as billable.
173
+ */
174
+ readonly TERM_CONCEPT_HEADER_NOT_BILLABLE: "TERM_CONCEPT_HEADER_NOT_BILLABLE";
175
+ /**
176
+ * A ValueSet `compose` part could **not be expanded** — an intensional `include`/`exclude` whose
177
+ * code system was not supplied, an unresolvable referenced value set, or a `filter` operator the
178
+ * engine does not implement. Surfaced as a typed outcome that marks the expansion **incomplete**;
179
+ * the engine **never** fabricates a member and **never** returns a silently-empty "no members"
180
+ * answer for a part it could not compute (roadmap §4.4 — a false "not a member" is a clinical error).
181
+ */
182
+ readonly TERM_VALUESET_CANNOT_EXPAND: "TERM_VALUESET_CANNOT_EXPAND";
183
+ /**
184
+ * A **pre-computed** `ValueSet.expansion` is **incomplete** — its `total` exceeds the number of
185
+ * `contains` entries, or it is flagged too-costly (the VSAC `$expand` >1200-code truncation hazard,
186
+ * roadmap §4.4). The engine **never treats a truncated expansion as complete membership**: a code
187
+ * absent from a truncated expansion is `undetermined`, never a confident "not a member".
188
+ */
189
+ readonly TERM_VALUESET_EXPANSION_TRUNCATED: "TERM_VALUESET_EXPANSION_TRUNCATED";
190
+ /**
191
+ * A UCUM unit expression is **not valid** — it does not parse against the UCUM grammar, or it
192
+ * names an atom absent from the vendored UCUM table. A **first-class typed outcome, never an
193
+ * error and never a guess**: {@link validateUcum} returns `{ valid: false, reason }`, never a
194
+ * coerced or "nearest" unit (the never-fabricate invariant, applied to units — roadmap §4.3). The
195
+ * `reason` is value-free (a grammar fault or an unknown-atom shape), never PHI.
196
+ */
197
+ readonly TERM_UCUM_INVALID: "TERM_UCUM_INVALID";
198
+ /**
199
+ * A crosswalk source code has an **authored No-Map** — the steward declares it has no valid target
200
+ * in the other classification (a GEM `NoDx`/`NoPCS` sentinel, or a SNOMED complex-map `447638001`
201
+ * "cannot be classified" category). A **first-class typed outcome, never an error and never a
202
+ * guess** (the never-fabricate invariant, applied to crosswalks — roadmap §4.1). Distinct from
203
+ * {@link TERM_CROSSWALK_UNMAPPED}: the source *is* in the map, and the map says "no target".
204
+ */
205
+ readonly TERM_CROSSWALK_NO_MAP: "TERM_CROSSWALK_NO_MAP";
206
+ /**
207
+ * A crosswalk source code is **absent from the map entirely** — not an authored No-Map, simply not
208
+ * present. Surfaced as a typed outcome (never a fabricated target, never a silent success), and
209
+ * kept distinct from {@link TERM_CROSSWALK_NO_MAP} so a caller can tell "the steward said no target"
210
+ * from "this code was not in the file".
211
+ */
212
+ readonly TERM_CROSSWALK_UNMAPPED: "TERM_CROSSWALK_UNMAPPED";
213
+ /**
214
+ * A SNOMED→ICD-10-CM complex-map group's decision needs **patient context** (an `IFA` age band or
215
+ * gender) the caller did not supply. The engine surfaces the candidate rules + advice and **refuses
216
+ * to pick a branch** it lacks the data for (roadmap §4.1) — never a silently-chosen target.
217
+ */
218
+ readonly TERM_CROSSWALK_CONTEXT_REQUIRED: "TERM_CROSSWALK_CONTEXT_REQUIRED";
219
+ /**
220
+ * A GEM file line was structurally unusable — not three whitespace-delimited fields, or a flag
221
+ * field that is not a 5-digit code. The row is **skipped and surfaced** as a load warning (liberal
222
+ * on load), never kept as a partially-parsed entry.
223
+ */
224
+ readonly TERM_GEM_MALFORMED_ROW: "TERM_GEM_MALFORMED_ROW";
225
+ /**
226
+ * A SNOMED complex-map RF2 refset row was structurally unusable — missing its
227
+ * `referencedComponentId`/`mapGroup`/`mapPriority`. Skipped and surfaced, never partial.
228
+ */
229
+ readonly TERM_COMPLEX_MAP_MALFORMED_ROW: "TERM_COMPLEX_MAP_MALFORMED_ROW";
230
+ /**
231
+ * A RxNorm RRF row (`RXNCONSO` / `RXNREL` / `RXNSAT`) was structurally unusable — too few columns to
232
+ * reach a required field, or a missing `RXCUI`/NDC value. The row is **skipped and surfaced** as a
233
+ * load warning (liberal on load), never kept as a partial concept/edge. Rows that are simply *not of
234
+ * interest* (a non-`RXNORM` atom, an atom-level relationship, a non-`NDC` attribute) are skipped
235
+ * **silently** — they are expected, not faults.
236
+ */
237
+ readonly TERM_RXNORM_MALFORMED_ROW: "TERM_RXNORM_MALFORMED_ROW";
238
+ /**
239
+ * A RxNorm graph navigation was asked about an `RXCUI` **absent from the loaded release** — a
240
+ * first-class typed outcome, never a fabricated concept and never an empty "success" (the
241
+ * never-fabricate invariant, applied to the drug graph — roadmap §4.2). Distinct from a *present*
242
+ * concept that simply has no edge of the requested relationship (that is a found result with empty
243
+ * targets).
244
+ */
245
+ readonly TERM_RXNORM_UNKNOWN_RXCUI: "TERM_RXNORM_UNKNOWN_RXCUI";
246
+ /**
247
+ * An NDC could **not be resolved** to any `RXCUI` in the loaded RxNorm release — a typed, surfaced
248
+ * absence, never a guessed `RXCUI`. NDC↔RXCUI is many:1 and temporal (roadmap §4.2), so a resolution
249
+ * is always release-scoped; an NDC not present in the loaded release is this diagnostic.
250
+ */
251
+ readonly TERM_RXNORM_NDC_UNMAPPED: "TERM_RXNORM_NDC_UNMAPPED";
252
+ };
253
+ /**
254
+ * A value from {@link DIAGNOSTIC_CODES} — the type consumers narrow a diagnostic's `code` against.
255
+ */
256
+ type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[keyof typeof DIAGNOSTIC_CODES];
257
+ /**
258
+ * Stable **fatal codes** — thrown (as a {@link TerminologyError}) when input cannot be loaded into
259
+ * a usable model at all. Always thrown, never swallowed. `key === value`, part of the contract.
260
+ *
261
+ * @example
262
+ * ```ts
263
+ * import { FATAL_CODES } from "@cosyte/terminology";
264
+ *
265
+ * FATAL_CODES.TERM_CONCEPTMAP_MALFORMED; // => "TERM_CONCEPTMAP_MALFORMED"
266
+ * ```
267
+ */
268
+ declare const FATAL_CODES: {
269
+ /**
270
+ * A `ConceptMap` resource was structurally unusable — wrong `resourceType`, or a group/element
271
+ * missing a required field. Thrown rather than silently loading a partial, misleading map.
272
+ */
273
+ readonly TERM_CONCEPTMAP_MALFORMED: "TERM_CONCEPTMAP_MALFORMED";
274
+ /**
275
+ * A CodeSystem *source* was structurally unusable — a FHIR resource with the wrong `resourceType`,
276
+ * or a delimited source whose configured code/display column is absent from the header. Thrown
277
+ * rather than silently loading an empty or mis-keyed release. (Malformed individual **rows** are
278
+ * skipped-and-surfaced load warnings, not fatals — only an unusable *source* is fatal.)
279
+ */
280
+ readonly TERM_CODESYSTEM_MALFORMED: "TERM_CODESYSTEM_MALFORMED";
281
+ /**
282
+ * A `ValueSet` resource was structurally unusable — wrong `resourceType`, or a `compose`/`expansion`
283
+ * whose required shape (an `include` that is not an object, a `contains` entry missing its `code`)
284
+ * is corrupt. Thrown rather than silently loading a partial value set that would bind *some* codes
285
+ * and quietly drop others. (A `filter` operator the engine does not implement is **not** fatal — it
286
+ * is the surfaced `TERM_VALUESET_CANNOT_EXPAND` diagnostic at expansion time.)
287
+ */
288
+ readonly TERM_VALUESET_MALFORMED: "TERM_VALUESET_MALFORMED";
289
+ /**
290
+ * A crosswalk map *source* was structurally unusable — e.g. a SNOMED complex-map RF2 refset with
291
+ * empty content or a header missing a required column. Thrown rather than silently loading an empty
292
+ * or mis-keyed map. (Malformed individual **rows** are skipped-and-surfaced warnings, not fatals.)
293
+ */
294
+ readonly TERM_CROSSWALK_MALFORMED: "TERM_CROSSWALK_MALFORMED";
295
+ /**
296
+ * An **inversion** of a directional crosswalk was requested — a forbidden operation. The GEMs and
297
+ * the SNOMED→ICD-10-CM map are authoritative in **one direction only**; a forward map is not the
298
+ * inverse of the backward map (roadmap §4.1). Thrown by {@link ../crosswalk/gems.invertGem} so the
299
+ * never-invert refusal is a first-class, discoverable contract rather than a silent absence.
300
+ */
301
+ readonly TERM_MAP_NOT_INVERTIBLE: "TERM_MAP_NOT_INVERTIBLE";
302
+ };
303
+ /**
304
+ * A value from {@link FATAL_CODES} — the type carried by a thrown {@link TerminologyError}.
305
+ */
306
+ type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
307
+ /**
308
+ * The engine's typed error. Carries a stable {@link FatalCode} so callers branch on `err.code`
309
+ * without string-matching the message. Thrown only for {@link FATAL_CODES} conditions.
310
+ *
311
+ * The `message` is **value-free**: it names the structural fault (a resource path, a missing
312
+ * field) and never echoes input *values*.
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * import { loadConceptMap, TerminologyError, FATAL_CODES } from "@cosyte/terminology";
317
+ *
318
+ * try {
319
+ * loadConceptMap({ resourceType: "Patient" });
320
+ * } catch (err) {
321
+ * (err as TerminologyError).code === FATAL_CODES.TERM_CONCEPTMAP_MALFORMED; // => true
322
+ * }
323
+ * ```
324
+ */
325
+ declare class TerminologyError extends Error {
326
+ /** The stable fatal code. Branch on this, not on {@link message}. */
327
+ readonly code: FatalCode;
328
+ /**
329
+ * @param code - The stable {@link FatalCode} describing the fault.
330
+ * @param message - A **value-free** structural description (path + fault; never an input value).
331
+ */
332
+ constructor(code: FatalCode, message: string);
333
+ }
334
+
335
+ /**
336
+ * Minimal, zero-dependency navigation helpers for **untrusted FHIR JSON** (`unknown` inputs
337
+ * decoded from a consumer-supplied `ConceptMap`/`CodeSystem`/`ValueSet`).
338
+ *
339
+ * The engine never trusts the shape of its inputs: every accessor here narrows an `unknown`
340
+ * to a concrete type or returns `undefined`, so a malformed resource degrades to a typed
341
+ * diagnostic (see {@link ../conceptmap/load}) rather than a `TypeError` deep in a getter. This
342
+ * is the terminology-engine analogue of a parser's lenient tokenizer — liberal on load.
343
+ *
344
+ * These helpers are **value-free**: they read structure, never log or echo field *values*, so a
345
+ * code-in-context (which can be PHI, per the roadmap's PHI posture) never flows into a message.
346
+ *
347
+ * @packageDocumentation
348
+ */
349
+ /**
350
+ * Narrow an `unknown` to a plain JSON object (a non-null, non-array `object`).
351
+ *
352
+ * @param value - The value to test.
353
+ * @returns `true` when `value` is a non-null, non-array object.
354
+ * @example
355
+ * ```ts
356
+ * import { isJsonObject } from "@cosyte/terminology";
357
+ *
358
+ * isJsonObject({ resourceType: "ConceptMap" }); // => true
359
+ * ```
360
+ */
361
+ declare function isJsonObject(value: unknown): value is Record<string, unknown>;
362
+ /**
363
+ * Read a **string** property from an untrusted object, or `undefined` if absent/not a string.
364
+ *
365
+ * @param obj - The source object (may be any `unknown`).
366
+ * @param key - The property name to read.
367
+ * @returns The string value, or `undefined` when the key is missing or not a string.
368
+ * @example
369
+ * ```ts
370
+ * import { getString } from "@cosyte/terminology";
371
+ *
372
+ * getString({ url: "http://loinc.org" }, "url"); // => "http://loinc.org"
373
+ * ```
374
+ */
375
+ declare function getString(obj: unknown, key: string): string | undefined;
376
+ /**
377
+ * Read an **array** property from an untrusted object as a `readonly unknown[]`, or `undefined`.
378
+ *
379
+ * The elements are left as `unknown` — callers narrow each one. A non-array (including a missing
380
+ * key) yields `undefined`, which callers treat as "no entries" rather than an error.
381
+ *
382
+ * @param obj - The source object (may be any `unknown`).
383
+ * @param key - The property name to read.
384
+ * @returns The array (elements untyped), or `undefined` when the key is missing or not an array.
385
+ * @example
386
+ * ```ts
387
+ * import { getArray } from "@cosyte/terminology";
388
+ *
389
+ * getArray({ group: [{ source: "a" }] }, "group")?.length; // => 1
390
+ * ```
391
+ */
392
+ declare function getArray(obj: unknown, key: string): readonly unknown[] | undefined;
393
+ /**
394
+ * Read a **boolean** property from an untrusted object, or `undefined` if absent/not a boolean.
395
+ *
396
+ * A non-boolean (including a missing key, or the strings `"true"`/`"false"`) yields `undefined` —
397
+ * the accessor never coerces, so a caller can distinguish "explicitly `false`" from "absent".
398
+ *
399
+ * @param obj - The source object (may be any `unknown`).
400
+ * @param key - The property name to read.
401
+ * @returns The boolean value, or `undefined` when the key is missing or not a boolean.
402
+ * @example
403
+ * ```ts
404
+ * import { getBoolean } from "@cosyte/terminology";
405
+ *
406
+ * getBoolean({ inactive: true }, "inactive"); // => true
407
+ * ```
408
+ */
409
+ declare function getBoolean(obj: unknown, key: string): boolean | undefined;
410
+ /**
411
+ * Read a **number** property from an untrusted object, or `undefined` if absent/not a finite number.
412
+ *
413
+ * @param obj - The source object (may be any `unknown`).
414
+ * @param key - The property name to read.
415
+ * @returns The number value, or `undefined` when the key is missing or not a finite number.
416
+ * @example
417
+ * ```ts
418
+ * import { getNumber } from "@cosyte/terminology";
419
+ *
420
+ * getNumber({ valueInteger: 3 }, "valueInteger"); // => 3
421
+ * ```
422
+ */
423
+ declare function getNumber(obj: unknown, key: string): number | undefined;
424
+
425
+ /**
426
+ * The **code-system identity registry** — the grounded OID ↔ canonical-URI ↔ mnemonic facts the
427
+ * ecosystem uses.
428
+ *
429
+ * A code-system *identity* (the triple of canonical URI, OID, and HL7 v2 mnemonic that all name
430
+ * the same terminology) is a **published fact**, not copyrighted content — so it is encoded here,
431
+ * cited firsthand. This is the license line the roadmap draws: identities are facts we may encode;
432
+ * the code *lists themselves* (SNOMED/CPT/LOINC content) are BYO and never bundled.
433
+ *
434
+ * **Every identity below is grounded firsthand against a primary source** (verified 2026-07-21):
435
+ *
436
+ * - The FHIR R4 terminology-systems registry — `https://hl7.org/fhir/R4/terminologies-systems.html`
437
+ * — for LOINC, SNOMED CT, RxNorm, UCUM, CPT, NDC, CVX (canonical URI + OID), and for the HL7 v2
438
+ * table URI/OID patterns (`http://terminology.hl7.org/CodeSystem/v2-[X]` ↔
439
+ * `2.16.840.1.113883.12.[X]`).
440
+ * - The FHIR R4 ICD page — `https://hl7.org/fhir/R4/icd.html` — for ICD-10-CM
441
+ * (`http://hl7.org/fhir/sid/icd-10-cm` ↔ `2.16.840.1.113883.6.90`).
442
+ * - HL7 Table 0396 (coding-system mnemonics) — the THO `v2-0396` code system — for the mnemonics
443
+ * (`LN`, `SCT`, `I10C`, `RXNORM`, `UCUM`, `C4`, `NDC`, `CVX`).
444
+ *
445
+ * Identities that could not be pinned to a primary source firsthand this pass are **omitted**, not
446
+ * guessed (roadmap: "do not guess an identity") — notably ICD-9-CM and ICD-10-PCS, whose OID
447
+ * assignments were ambiguous across sources.
448
+ *
449
+ * @packageDocumentation
450
+ */
451
+ /**
452
+ * A resolved code-system identity — the canonical URI plus the alternate identifiers that name the
453
+ * same system.
454
+ */
455
+ interface SystemIdentity {
456
+ /** The canonical URI — the single value the engine treats as the system's identity. */
457
+ readonly url: string;
458
+ /** The registered OID (bare, no `urn:oid:` prefix), when the system has one. */
459
+ readonly oid?: string;
460
+ /** A short human-readable name for diagnostics/logs (value-free — a system name, never a value). */
461
+ readonly name: string;
462
+ /** HL7 v2 Table 0396 mnemonic(s) and other accepted short aliases for this system. */
463
+ readonly mnemonics: readonly string[];
464
+ }
465
+ /**
466
+ * The seed identity table. Order is irrelevant; lookup indexes are built from it in
467
+ * {@link ../systems/resolve}. Each entry is one firsthand-grounded system (see the module doc).
468
+ */
469
+ declare const SYSTEM_IDENTITIES: readonly SystemIdentity[];
470
+ /**
471
+ * The OID root under which every HL7 v2 table code system is registered:
472
+ * `2.16.840.1.113883.12.[table]` — grounded against the FHIR R4 terminology-systems registry.
473
+ * The table number is appended with no leading zeros (e.g. table 0203 → `…12.203`).
474
+ */
475
+ declare const HL7_V2_OID_ROOT = "2.16.840.1.113883.12";
476
+ /**
477
+ * The canonical URI prefix for HL7 v2 table code systems in HL7 Terminology (THO):
478
+ * `http://terminology.hl7.org/CodeSystem/v2-[XXXX]`, where `[XXXX]` is the zero-padded 4-digit
479
+ * table id — grounded against the FHIR R4 terminology-systems registry.
480
+ */
481
+ declare const HL7_V2_URL_PREFIX = "http://terminology.hl7.org/CodeSystem/v2-";
482
+
483
+ /**
484
+ * The **code-system identity / canonical-URI resolver** — `resolveSystem`.
485
+ *
486
+ * Given a code system named any of the ways the ecosystem names it — a canonical URI, an OID (bare
487
+ * or `urn:oid:`-prefixed), or an HL7 v2 mnemonic (`LN`, `SCT`, …) — it returns the one canonical
488
+ * {@link SystemIdentity}. This is the piece `@cosyte/transform` pins so a `Coding` off any parser
489
+ * (HL7 v2 CE/CWE, C-CDA, FHIR) can be canonicalized to a single system URI before translation.
490
+ *
491
+ * **Never guesses.** An identifier that matches nothing known returns a typed
492
+ * `{ unknown: true, input }` — never a fabricated URI (the never-fabricate invariant, applied to
493
+ * system identity).
494
+ *
495
+ * @packageDocumentation
496
+ */
497
+
498
+ /** The typed "not recognized" outcome from {@link resolveSystem} — surfaced, never a guess. */
499
+ interface UnknownSystem {
500
+ /** Discriminant: the identifier was not recognized. */
501
+ readonly unknown: true;
502
+ /** The identifier as supplied, echoed back so the caller can surface/log it. */
503
+ readonly input: string;
504
+ }
505
+ /** The result of {@link resolveSystem}: a resolved {@link SystemIdentity} or a typed unknown. */
506
+ type ResolveSystemResult = SystemIdentity | UnknownSystem;
507
+ /**
508
+ * Resolve a code-system identifier to its canonical {@link SystemIdentity}.
509
+ *
510
+ * Accepts, in order of attempt: a canonical URI (case-insensitive), a `v2-XXXX` mnemonic or a
511
+ * v2 table URI/OID (resolved structurally), a bare or `urn:oid:`-prefixed OID, or an HL7 v2
512
+ * Table 0396 mnemonic. Whitespace is trimmed. An empty or unrecognized identifier yields a typed
513
+ * {@link UnknownSystem} — the resolver **never** invents a URI.
514
+ *
515
+ * @param id - The identifier (canonical URI, OID, `urn:oid:` OID, or mnemonic).
516
+ * @returns The resolved {@link SystemIdentity}, or `{ unknown: true, input }`.
517
+ * @example
518
+ * ```ts
519
+ * import { resolveSystem } from "@cosyte/terminology";
520
+ *
521
+ * resolveSystem("2.16.840.1.113883.6.1"); // => { url: "http://loinc.org", … }
522
+ * resolveSystem("SCT"); // => { url: "http://snomed.info/sct", … }
523
+ * resolveSystem("http://example.com/nope"); // => { unknown: true, input: "http://example.com/nope" }
524
+ * ```
525
+ */
526
+ declare function resolveSystem(id: string): ResolveSystemResult;
527
+ /**
528
+ * Type guard: did {@link resolveSystem} fail to recognize the identifier?
529
+ *
530
+ * @param result - A {@link resolveSystem} result.
531
+ * @returns `true` when the result is a typed {@link UnknownSystem}.
532
+ * @example
533
+ * ```ts
534
+ * import { resolveSystem, isUnknownSystem } from "@cosyte/terminology";
535
+ *
536
+ * isUnknownSystem(resolveSystem("nope")); // => true
537
+ * ```
538
+ */
539
+ declare function isUnknownSystem(result: ResolveSystemResult): result is UnknownSystem;
540
+
541
+ /**
542
+ * The types for the ConceptMap `$translate` engine — the loaded {@link ConceptMap} model, the
543
+ * normalized {@link Relationship} enum, and the fail-safe {@link TranslateResult}.
544
+ *
545
+ * The engine targets **FHIR R4** ConceptMap input (`element.target.equivalence`), but carries an
546
+ * internal, version-neutral {@link Relationship} model that maps cleanly to **both** R4
547
+ * `equivalence` and R5 `relationship` (the enum was renamed and re-valued between versions). Each
548
+ * match surfaces both the normalized relationship and the verbatim R4 token it came from, so no
549
+ * information is dropped and a consumer can render either vocabulary.
550
+ *
551
+ * @packageDocumentation
552
+ */
553
+
554
+ /**
555
+ * The verbatim **FHIR R4** `ConceptMap.group.element.target.equivalence` value, carried through
556
+ * untouched so nothing the map author asserted is lost. (R4 enum:
557
+ * `relatedto | equivalent | equal | wider | subsumes | narrower | specializes | inexact |
558
+ * unmatched | disjoint`.)
559
+ */
560
+ type R4Equivalence = "relatedto" | "equivalent" | "equal" | "wider" | "subsumes" | "narrower" | "specializes" | "inexact" | "unmatched" | "disjoint";
561
+ /**
562
+ * The engine's **normalized, version-neutral relationship** between a source and a target concept —
563
+ * the FHIR **R5** vocabulary, which the R4 `equivalence` tokens fold into cleanly. Surfaced on every
564
+ * match alongside the verbatim R4 token.
565
+ *
566
+ * - `equivalent` ← R4 `equivalent` / `equal`
567
+ * - `source-is-narrower-than-target` ← R4 `wider` / `subsumes` (the source is-a the target)
568
+ * - `source-is-broader-than-target` ← R4 `narrower` / `specializes` (the target is-a the source)
569
+ * - `related-to` ← R4 `relatedto` / `inexact`
570
+ * - `not-related-to` ← R4 `disjoint` / `unmatched`
571
+ */
572
+ type Relationship = "equivalent" | "source-is-narrower-than-target" | "source-is-broader-than-target" | "related-to" | "not-related-to";
573
+ /**
574
+ * One target concept a {@link translate} call produced, with its relationship to the source and
575
+ * the verbatim R4 equivalence token. Never fabricated — every field is drawn from the loaded map.
576
+ */
577
+ interface TranslateMatch {
578
+ /** The target {@link Coding} exactly as declared in the map (frozen). */
579
+ readonly target: Coding;
580
+ /** The normalized, version-neutral relationship (R5 vocabulary). */
581
+ readonly relationship: Relationship;
582
+ /** The verbatim FHIR R4 `equivalence` token this match was declared with. */
583
+ readonly equivalence: R4Equivalence;
584
+ /** Author's free-text comment on this mapping, when present (carried verbatim, e.g. map advice). */
585
+ readonly comment?: string;
586
+ }
587
+ /**
588
+ * The FHIR R4 `group.unmapped.mode` an unmapped source fell through to — the map author's declared
589
+ * fallback behavior. Surfaced (never silently acted on): the caller decides whether to trust a
590
+ * fallback.
591
+ *
592
+ * - `provided` — echo the source code as the target (the map declares no real mapping).
593
+ * - `fixed` — a single fixed fallback code the map declares for *all* unmapped sources.
594
+ * - `other-map` — consult the referenced ConceptMap (which this engine does not auto-follow).
595
+ * - `none` — no `group.unmapped` directive applied (plain no-match, or an explicit `unmatched`).
596
+ */
597
+ type UnmappedMode = "provided" | "fixed" | "other-map" | "none";
598
+ /** Provenance of a translation — which map (and which group's systems) produced it. */
599
+ interface MapProvenance {
600
+ /** The ConceptMap's canonical `url`, when declared. */
601
+ readonly conceptMapUrl?: string;
602
+ /** The ConceptMap's business `version`, when declared (mappings are release-scoped). */
603
+ readonly conceptMapVersion?: string;
604
+ /** The source code system of the group that matched (or was consulted), when known. */
605
+ readonly sourceSystem?: string;
606
+ /** The target code system of the group that matched, when known. */
607
+ readonly targetSystem?: string;
608
+ }
609
+ /** A successful translation: one or more matches, never empty (an empty result is `unmapped`). */
610
+ interface TranslateMatched {
611
+ /** Discriminant: the source mapped. */
612
+ readonly unmapped: false;
613
+ /** The target concepts, in declared order. Non-empty and frozen. */
614
+ readonly matches: readonly TranslateMatch[];
615
+ /** Where this translation came from. */
616
+ readonly provenance: MapProvenance;
617
+ }
618
+ /**
619
+ * A **fail-safe unmapped** translation — the never-fabricate outcome. The source is surfaced as-is;
620
+ * **no target is ever guessed**. Any `group.unmapped` fallback the author declared is reported via
621
+ * {@link UnmappedMode} (and `fixedTarget`/`otherMapUrl`) for the caller to accept or reject — the
622
+ * engine does not silently substitute it.
623
+ */
624
+ interface TranslateUnmapped {
625
+ /** Discriminant: the source did not map. */
626
+ readonly unmapped: true;
627
+ /** The stable diagnostic code for an unmapped translation. */
628
+ readonly code: "TERM_TRANSLATE_UNMAPPED";
629
+ /** The map author's declared fallback mode (or `none`). */
630
+ readonly mode: UnmappedMode;
631
+ /** The original source {@link Coding}, surfaced untouched. */
632
+ readonly source: Coding;
633
+ /** For `mode: "fixed"`, the author's fixed fallback coding — reported, not auto-applied. */
634
+ readonly fixedTarget?: Coding;
635
+ /** For `mode: "other-map"`, the referenced map's URL — reported, not auto-followed. */
636
+ readonly otherMapUrl?: string;
637
+ /** Where the (failed) translation was attempted. */
638
+ readonly provenance: MapProvenance;
639
+ }
640
+ /** The result of {@link translate}: a matched result or a typed {@link TranslateUnmapped}. */
641
+ type TranslateResult = TranslateMatched | TranslateUnmapped;
642
+ /**
643
+ * A single `group.unmapped` directive from a loaded ConceptMap group (FHIR R4).
644
+ */
645
+ interface ConceptMapUnmapped {
646
+ /** The fallback mode. */
647
+ readonly mode: UnmappedMode;
648
+ /** For `fixed`, the fixed target code. */
649
+ readonly code?: string;
650
+ /** For `fixed`, the fixed target display. */
651
+ readonly display?: string;
652
+ /** For `other-map`, the canonical URL of the map to consult. */
653
+ readonly url?: string;
654
+ }
655
+ /** One `group.element.target` — a candidate target for a source concept (FHIR R4). */
656
+ interface ConceptMapTarget {
657
+ /** The target code (absent for a pure `unmatched` assertion). */
658
+ readonly code?: string;
659
+ /** The target display, carried verbatim when present. */
660
+ readonly display?: string;
661
+ /** The verbatim R4 equivalence token. */
662
+ readonly equivalence: R4Equivalence;
663
+ /** Author's comment (e.g. steward map advice), carried verbatim. */
664
+ readonly comment?: string;
665
+ }
666
+ /** One `group.element` — a source concept and its candidate targets (FHIR R4). */
667
+ interface ConceptMapElement {
668
+ /** The source code. */
669
+ readonly code?: string;
670
+ /** The source display. */
671
+ readonly display?: string;
672
+ /** The candidate targets. */
673
+ readonly target: readonly ConceptMapTarget[];
674
+ }
675
+ /** One `group` — a source-system/target-system pair with its element mappings (FHIR R4). */
676
+ interface ConceptMapGroup {
677
+ /** The group's source code system (canonical URI), when declared. */
678
+ readonly source?: string;
679
+ /** The group's source system version, when declared. */
680
+ readonly sourceVersion?: string;
681
+ /** The group's target code system (canonical URI), when declared. */
682
+ readonly target?: string;
683
+ /** The group's target system version, when declared. */
684
+ readonly targetVersion?: string;
685
+ /** The element mappings. */
686
+ readonly element: readonly ConceptMapElement[];
687
+ /** The `group.unmapped` fallback directive, when declared. */
688
+ readonly unmapped?: ConceptMapUnmapped;
689
+ }
690
+ /**
691
+ * A loaded, immutable ConceptMap — the subset of the FHIR R4 `ConceptMap` resource the
692
+ * `$translate` engine needs. Produced by {@link loadConceptMap}; every field frozen.
693
+ */
694
+ interface ConceptMap {
695
+ /** The resource's canonical `url`, when declared. */
696
+ readonly url?: string;
697
+ /** The resource's business `version`, when declared. */
698
+ readonly version?: string;
699
+ /** The map's `sourceUri`/`sourceCanonical` scope, when declared. */
700
+ readonly sourceScope?: string;
701
+ /** The map's `targetUri`/`targetCanonical` scope, when declared. */
702
+ readonly targetScope?: string;
703
+ /** The translation groups. */
704
+ readonly group: readonly ConceptMapGroup[];
705
+ }
706
+
707
+ /**
708
+ * {@link loadConceptMap} — validate and normalize an untrusted FHIR R4 `ConceptMap` JSON resource
709
+ * into the immutable {@link ConceptMap} model the `$translate` engine runs over.
710
+ *
711
+ * **Conservative on load** (unlike a wire parser's lenient posture): a ConceptMap drives a clinical
712
+ * translation, so a structurally unusable map is a typed **fatal**
713
+ * ({@link FATAL_CODES.TERM_CONCEPTMAP_MALFORMED}), never a silently partial map that would translate
714
+ * *some* codes and quietly drop others. Fault messages are **value-free** — they name the resource
715
+ * *path* and the fault, never echo a code or display *value*.
716
+ *
717
+ * @packageDocumentation
718
+ */
719
+
720
+ /**
721
+ * Load an untrusted FHIR R4 `ConceptMap` JSON value into an immutable {@link ConceptMap}.
722
+ *
723
+ * Accepts the standard resource shape: a top-level object with `resourceType: "ConceptMap"` and an
724
+ * optional `group[]`. `source[x]`/`target[x]` scopes are read from either the `Uri` or `Canonical`
725
+ * variant. The result is deep-frozen. Anything structurally unusable throws a
726
+ * {@link TerminologyError} carrying {@link FATAL_CODES.TERM_CONCEPTMAP_MALFORMED}.
727
+ *
728
+ * @param json - The untrusted resource (typically `JSON.parse` output — hence `unknown`).
729
+ * @returns The immutable, validated {@link ConceptMap}.
730
+ * @throws {TerminologyError} `TERM_CONCEPTMAP_MALFORMED` when the resource is not a usable ConceptMap.
731
+ * @example
732
+ * ```ts
733
+ * import { loadConceptMap } from "@cosyte/terminology";
734
+ *
735
+ * const map = loadConceptMap({
736
+ * resourceType: "ConceptMap",
737
+ * url: "http://example.org/cm/gender",
738
+ * group: [
739
+ * {
740
+ * source: "http://hl7.org/fhir/administrative-gender",
741
+ * target: "http://terminology.hl7.org/CodeSystem/v2-0001",
742
+ * element: [{ code: "male", target: [{ code: "M", equivalence: "equivalent" }] }],
743
+ * },
744
+ * ],
745
+ * });
746
+ * map.group.length; // => 1
747
+ * ```
748
+ */
749
+ declare function loadConceptMap(json: unknown): ConceptMap;
750
+
751
+ /**
752
+ * {@link translate} — the ConceptMap `$translate` engine, modeled on the FHIR
753
+ * `ConceptMap/$translate` operation.
754
+ *
755
+ * Given a source {@link Coding} and a loaded {@link ConceptMap}, it returns the declared target
756
+ * code(s) with their FHIR relationship and the map's provenance — or, when the source does not map,
757
+ * a **typed {@link TranslateUnmapped}**. This slice births the invariants every later phase
758
+ * inherits:
759
+ *
760
+ * 1. **Never fabricate.** An unmapped source yields a surfaced `unmapped` result — never a guessed
761
+ * target. Every target returned is drawn verbatim from the map; the engine invents nothing.
762
+ * 2. **Never invert.** Translation reads the map in its authored direction only: a source coding is
763
+ * matched against `group.element.code` (the source side) and **never** against target codes. A
764
+ * directional map therefore cannot be run backwards — reverse translation requires an explicit
765
+ * inverse map (a later phase), it is never synthesized here.
766
+ *
767
+ * @packageDocumentation
768
+ */
769
+
770
+ /**
771
+ * Translate a source {@link Coding} through a loaded {@link ConceptMap}.
772
+ *
773
+ * Matches the coding's `code` against each applicable group's `element.code` (the **source** side —
774
+ * never targets, so the map is never inverted) and returns every declared target for it. A target
775
+ * with an `unmatched` equivalence, or with no `code`, is treated as "no target" — the FHIR way of
776
+ * asserting a non-mapping — and does not count as a match. When nothing matches, the map author's
777
+ * `group.unmapped` directive (if any) is *reported* via the result's {@link UnmappedMode} but never
778
+ * silently applied.
779
+ *
780
+ * @param sourceCoding - The source concept to translate.
781
+ * @param map - A {@link ConceptMap} from {@link loadConceptMap}.
782
+ * @returns A {@link TranslateResult} — matched targets, or a typed unmapped outcome.
783
+ * @example
784
+ * ```ts
785
+ * import { loadConceptMap, translate } from "@cosyte/terminology";
786
+ *
787
+ * const map = loadConceptMap({
788
+ * resourceType: "ConceptMap",
789
+ * group: [
790
+ * {
791
+ * source: "http://hl7.org/fhir/administrative-gender",
792
+ * target: "http://terminology.hl7.org/CodeSystem/v2-0001",
793
+ * element: [{ code: "male", target: [{ code: "M", equivalence: "equivalent" }] }],
794
+ * },
795
+ * ],
796
+ * });
797
+ * const r = translate(
798
+ * { system: "http://hl7.org/fhir/administrative-gender", code: "male" },
799
+ * map,
800
+ * );
801
+ * r.unmapped; // => false
802
+ * ```
803
+ */
804
+ declare function translate(sourceCoding: Coding, map: ConceptMap): TranslateResult;
805
+
806
+ /**
807
+ * The types for the **CodeSystem load layer** and the FHIR `$lookup` / `$validate-code` operations
808
+ * (roadmap Phase 2): the immutable {@link CodeSystem} model, the per-concept {@link ConceptStatus},
809
+ * and the discriminated {@link CodeSystemSource} union the loaders accept.
810
+ *
811
+ * The model is **content-agnostic**: it is fed a code system's *release* (a consumer-supplied RRF,
812
+ * CSV, fixed-width file, or FHIR `CodeSystem` JSON) and answers the two identity questions. It ships
813
+ * **zero copyrighted content** — every concept comes from the caller's data (roadmap §5, the BYO-data
814
+ * posture). The FHIR operation shapes are grounded firsthand on the FHIR R4 Terminology Module
815
+ * (`https://hl7.org/fhir/R4/codesystem-operation-lookup.html`,
816
+ * `https://hl7.org/fhir/R4/codesystem-operation-validate-code.html`).
817
+ *
818
+ * @packageDocumentation
819
+ */
820
+
821
+ /**
822
+ * A concept's normalized activity — whether it is a clean, current, usable code, or carries a
823
+ * steward status that a caller must not ignore. `active` is the boolean convenience derived from it.
824
+ *
825
+ * - `active` — current and usable.
826
+ * - `deprecated` — no longer current (LOINC `DEPRECATED`; a FHIR `inactive`/`deprecated` concept).
827
+ * - `discouraged` / `trial` — LOINC lifecycle states (usable, but not settled).
828
+ * - `obsolete` — withdrawn (RxNorm `SUPPRESS = O`).
829
+ * - `suppressed` — editor-suppressed (RxNorm/UMLS `SUPPRESS = Y`/`E`).
830
+ * - `header` — a classification header, **not a billable/valid leaf** (ICD-10-CM flag `0`).
831
+ * - `unknown` — a status token the mapper did not recognize; carried verbatim, never guessed clean.
832
+ */
833
+ type ConceptActivity = "active" | "deprecated" | "discouraged" | "trial" | "obsolete" | "suppressed" | "header" | "unknown";
834
+ /**
835
+ * The status flags a `$lookup`/`$validate-code` carries on a concept — the safety surface that stops
836
+ * a non-current or non-billable code from being presented as clean (roadmap §4.4). Present only when
837
+ * the loaded release supplied status information; the engine never fabricates a status.
838
+ */
839
+ interface ConceptStatus {
840
+ /** The normalized {@link ConceptActivity}. */
841
+ readonly activity: ConceptActivity;
842
+ /** Convenience: `activity === "active"`. A caller can gate on this without a switch. */
843
+ readonly active: boolean;
844
+ /** For classification systems (ICD-10-CM): is this a billable/valid leaf? A header is `false`. */
845
+ readonly billable?: boolean;
846
+ /** The steward's raw status token, carried verbatim (e.g. `"DEPRECATED"`, `"O"`, `"0"`). */
847
+ readonly raw?: string;
848
+ /** A stable {@link DiagnosticCode} naming the concern, when the concept is not clean-active. */
849
+ readonly code?: DiagnosticCode;
850
+ }
851
+ /**
852
+ * One property value on a concept — models FHIR R4 `$lookup` `property` ("one or more properties
853
+ * that contain additional information about the code, including status"). Carried verbatim from the
854
+ * release; never fabricated.
855
+ */
856
+ interface Property {
857
+ /** Identifies the property (FHIR `property.code`), e.g. `"shortDescription"`, `"TTY"`, `"parent"`. */
858
+ readonly code: string;
859
+ /** The property value (FHIR `property.value[x]`), reduced to the primitive kinds the readers emit. */
860
+ readonly value: string | boolean | number;
861
+ /** Optional human-readable rendering of the value (FHIR `property.description`). */
862
+ readonly description?: string;
863
+ }
864
+ /**
865
+ * One loaded, immutable concept — the subset of a `CodeSystem.concept` the operations need.
866
+ */
867
+ interface Concept {
868
+ /** The code, exactly as it appears in the release. */
869
+ readonly code: string;
870
+ /** The preferred display, carried verbatim. Absent when the release supplied none (never invented). */
871
+ readonly display?: string;
872
+ /** The formal definition, when the release supplied one. */
873
+ readonly definition?: string;
874
+ /** The concept's status flags, when the release carried status information. */
875
+ readonly status?: ConceptStatus;
876
+ /** Additional properties, in load order. Frozen; may be empty, never fabricated. */
877
+ readonly properties: readonly Property[];
878
+ }
879
+ /**
880
+ * A surfaced, non-fatal load warning — a malformed row that was **skipped**, reported with its line
881
+ * number and a **value-free** structural reason (never the row's content). Liberal on load.
882
+ */
883
+ interface LoadWarning {
884
+ /** The stable diagnostic code (`TERM_RRF_MALFORMED_ROW` / `_CSV_MALFORMED` / `_FIXED_WIDTH_MALFORMED`). */
885
+ readonly code: DiagnosticCode;
886
+ /** The 1-based source line the fault was found on (positional context; value-free). */
887
+ readonly line: number;
888
+ /** A value-free description of the structural fault (never echoes a field value). */
889
+ readonly detail: string;
890
+ }
891
+ /**
892
+ * A loaded, immutable code-system release — the queryable model {@link loadCodeSystem} produces and
893
+ * {@link lookup} / {@link validateCode} run over. Concepts are keyed by code for O(1) identity; the
894
+ * map is exposed `readonly` and every concept is frozen.
895
+ */
896
+ interface CodeSystem {
897
+ /** The code system's canonical URI, when supplied by the source. */
898
+ readonly url?: string;
899
+ /** The release version, when supplied — mappings and displays are release-scoped. */
900
+ readonly version?: string;
901
+ /** A short human-readable name, when supplied. */
902
+ readonly name?: string;
903
+ /** The number of loaded concepts. */
904
+ readonly count: number;
905
+ /** The concepts, keyed by `code`. Read-only; every value frozen. */
906
+ readonly concepts: ReadonlyMap<string, Concept>;
907
+ /** The skipped-row warnings surfaced during load, in line order. Frozen; may be empty. */
908
+ readonly warnings: readonly LoadWarning[];
909
+ }
910
+ /** A successful {@link lookup}: the concept's details plus its release provenance. */
911
+ interface LookupResult {
912
+ /** Discriminant: the code was found. */
913
+ readonly found: true;
914
+ /** The code, echoed. */
915
+ readonly code: string;
916
+ /** The preferred display, when the release carried one (never fabricated). */
917
+ readonly display?: string;
918
+ /** The formal definition, when present. */
919
+ readonly definition?: string;
920
+ /** The concept's status flags, when present. */
921
+ readonly status?: ConceptStatus;
922
+ /** The concept's properties, in load order (frozen, may be empty). */
923
+ readonly properties: readonly Property[];
924
+ /** The code system's canonical URI, when known. */
925
+ readonly system?: string;
926
+ /** The release version, when known. */
927
+ readonly version?: string;
928
+ }
929
+ /** A **fail-safe unknown** {@link lookup} — the never-fabricate outcome; no display is ever guessed. */
930
+ interface LookupUnknown {
931
+ /** Discriminant: the code was not found. */
932
+ readonly found: false;
933
+ /** The stable diagnostic code for an unknown code. */
934
+ readonly code: "TERM_CODE_UNKNOWN";
935
+ /** The code that was looked up, echoed so the caller can surface it. */
936
+ readonly input: string;
937
+ }
938
+ /** The result of {@link lookup}: a found concept or a typed {@link LookupUnknown}. */
939
+ type LookupOutcome = LookupResult | LookupUnknown;
940
+ /**
941
+ * The result of {@link validateCode} — models FHIR R4 `$validate-code`'s `result` boolean. A found
942
+ * concept is `valid: true` **carrying its status** (a deprecated or header code validates as present
943
+ * but is flagged, never presented clean); an absent code is `valid: false`, never a guessed `true`.
944
+ */
945
+ interface ValidateCodeResult {
946
+ /** FHIR `result`: true if the code is a valid member of the code system. */
947
+ readonly valid: boolean;
948
+ /** The concept's status flags, when the code was found and the release carried status. */
949
+ readonly status?: ConceptStatus;
950
+ /** For an invalid code, the stable diagnostic (`TERM_CODE_UNKNOWN`); absent when valid. */
951
+ readonly code?: DiagnosticCode;
952
+ }
953
+ /** A mapper from a raw steward status token to a normalized {@link ConceptActivity}. Overridable. */
954
+ type StatusMapper = (raw: string) => ConceptActivity;
955
+ /** A half-open `[start, end)` slice of a fixed-width line (0-based char indices; `end` optional). */
956
+ interface FieldSlice {
957
+ /** The 0-based start char index (inclusive). */
958
+ readonly start: number;
959
+ /** The 0-based end char index (exclusive). Omit to slice to end of line. */
960
+ readonly end?: number;
961
+ }
962
+ /** One extra RRF column to capture as a {@link Property}. */
963
+ interface RrfPropertyColumn {
964
+ /** The property code to emit. */
965
+ readonly name: string;
966
+ /** The 0-based column index. */
967
+ readonly column: number;
968
+ }
969
+ /** The column mapping for an {@link RrfSource} (0-based column indices into the pipe-split row). */
970
+ interface RrfColumnMap {
971
+ /** The column holding the concept code (e.g. RxNorm `RXCUI` = 0, or `CODE` = 13). */
972
+ readonly code: number;
973
+ /** The column holding the display string (e.g. `STR` = 14). */
974
+ readonly display: number;
975
+ /** The column holding a status/suppress token (e.g. `SUPPRESS` = 16), when present. */
976
+ readonly status?: number;
977
+ /** Extra columns to surface as properties. */
978
+ readonly properties?: readonly RrfPropertyColumn[];
979
+ }
980
+ /**
981
+ * An **RRF** (Rich Release Format — pipe-delimited RxNorm/UMLS release) source. One parameterized
982
+ * reader serves both: RxNorm `RXNCONSO`/`RXNSAT` and UMLS `MRCONSO`/`MRSAT` share the RRF shape
983
+ * (pipe-delimited, one trailing pipe per line). BYO — the consumer supplies the file.
984
+ */
985
+ interface RrfSource {
986
+ /** Discriminant. */
987
+ readonly format: "rrf";
988
+ /** The raw RRF file content. */
989
+ readonly content: string;
990
+ /** Which columns hold the code/display/status/properties. */
991
+ readonly columns: RrfColumnMap;
992
+ /** The code system's canonical URI, attached to the loaded release. */
993
+ readonly url?: string;
994
+ /** A human-readable name. */
995
+ readonly name?: string;
996
+ /** The release version (RRF carries none inline — supply it so displays stay release-scoped). */
997
+ readonly version?: string;
998
+ /** Override the default status interpretation of the `status` column's raw token. */
999
+ readonly statusMap?: StatusMapper;
1000
+ }
1001
+ /** The column mapping for a {@link CsvSource} (header names, matched against the header row). */
1002
+ interface CsvColumnMap {
1003
+ /** The header name of the code column (e.g. LOINC `LOINC_NUM`). */
1004
+ readonly code: string;
1005
+ /** The header name of the display column (e.g. LOINC `LONG_COMMON_NAME`). */
1006
+ readonly display: string;
1007
+ /** The header name of a status column (e.g. LOINC `STATUS`), when present. */
1008
+ readonly status?: string;
1009
+ /** Header names to surface as properties. */
1010
+ readonly properties?: readonly string[];
1011
+ }
1012
+ /**
1013
+ * An **RFC-4180 CSV** source (quoted fields, embedded commas/quotes/newlines) — e.g. LOINC's
1014
+ * `Loinc.csv`. The one release format that needs real quote handling; hand-rolled, zero-dep. BYO.
1015
+ */
1016
+ interface CsvSource {
1017
+ /** Discriminant. */
1018
+ readonly format: "csv";
1019
+ /** The raw CSV file content. */
1020
+ readonly content: string;
1021
+ /** Which header-named columns hold the code/display/status/properties. */
1022
+ readonly columns: CsvColumnMap;
1023
+ /** The code system's canonical URI. */
1024
+ readonly url?: string;
1025
+ /** A human-readable name. */
1026
+ readonly name?: string;
1027
+ /** The release version. */
1028
+ readonly version?: string;
1029
+ /** Override the default status interpretation of the `status` column's raw token. */
1030
+ readonly statusMap?: StatusMapper;
1031
+ }
1032
+ /** The billable-flag descriptor for a {@link FixedWidthFieldMap}. */
1033
+ interface BillableFlag {
1034
+ /** The 0-based char index of the flag. */
1035
+ readonly at: number;
1036
+ /** The character value that means "billable/valid leaf" (ICD-10-CM: `"1"`; `"0"` is a header). */
1037
+ readonly billableValue: string;
1038
+ }
1039
+ /** One extra fixed-width field to capture as a {@link Property}. */
1040
+ interface FixedWidthPropertyField {
1041
+ /** The property code to emit. */
1042
+ readonly name: string;
1043
+ /** The field slice. */
1044
+ readonly slice: FieldSlice;
1045
+ }
1046
+ /** The field mapping for a {@link FixedWidthSource} (0-based char slices). */
1047
+ interface FixedWidthFieldMap {
1048
+ /** The slice holding the concept code. */
1049
+ readonly code: FieldSlice;
1050
+ /** The slice holding the display string. */
1051
+ readonly display: FieldSlice;
1052
+ /** The billable/header flag (ICD-10-CM position-15), when the format has one. */
1053
+ readonly billable?: BillableFlag;
1054
+ /** A slice holding a status token, when the format has one (alternative to `billable`). */
1055
+ readonly status?: FieldSlice;
1056
+ /** Extra slices to surface as properties. */
1057
+ readonly properties?: readonly FixedWidthPropertyField[];
1058
+ }
1059
+ /**
1060
+ * A **fixed-width** source (slice by column) — e.g. the ICD-10-CM order file. Fully parameterized:
1061
+ * the consumer supplies the field slices (see {@link ICD10CM_ORDER_FILE_FIELDS} for the documented
1062
+ * ICD-10-CM preset, which they confirm against their release's README — the byte offsets are a
1063
+ * flagged open question in the roadmap). BYO.
1064
+ */
1065
+ interface FixedWidthSource {
1066
+ /** Discriminant. */
1067
+ readonly format: "fixed-width";
1068
+ /** The raw fixed-width file content. */
1069
+ readonly content: string;
1070
+ /** Which char slices hold the code/display/billable/status/properties. */
1071
+ readonly fields: FixedWidthFieldMap;
1072
+ /** The code system's canonical URI. */
1073
+ readonly url?: string;
1074
+ /** A human-readable name. */
1075
+ readonly name?: string;
1076
+ /** The release version. */
1077
+ readonly version?: string;
1078
+ /** Override the default status interpretation of the `status` slice's raw token. */
1079
+ readonly statusMap?: StatusMapper;
1080
+ }
1081
+ /**
1082
+ * A **FHIR R4 `CodeSystem`** JSON source (native, license-clean model). The consumer supplies the
1083
+ * untrusted resource; nested `concept` hierarchies are flattened, and the standard `inactive` /
1084
+ * `deprecated` concept properties are read into {@link ConceptStatus}.
1085
+ */
1086
+ interface FhirCodeSystemSource {
1087
+ /** Discriminant. */
1088
+ readonly format: "fhir";
1089
+ /** The untrusted FHIR `CodeSystem` resource (typically `JSON.parse` output — hence `unknown`). */
1090
+ readonly resource: unknown;
1091
+ }
1092
+ /** The discriminated union {@link loadCodeSystem} accepts. */
1093
+ type CodeSystemSource = RrfSource | CsvSource | FixedWidthSource | FhirCodeSystemSource;
1094
+
1095
+ /**
1096
+ * {@link loadCodeSystem} — the single entry point that dispatches a {@link CodeSystemSource} to the
1097
+ * right reader (RRF / CSV / fixed-width / FHIR) and assembles the immutable {@link CodeSystem}.
1098
+ *
1099
+ * The engine is **content-agnostic and license-clean**: it loads a *consumer-supplied* release and
1100
+ * ships **zero** copyrighted content (roadmap §5). The loaded model is deep-frozen and keyed by code
1101
+ * for O(1) {@link lookup}/{@link validateCode}.
1102
+ *
1103
+ * @packageDocumentation
1104
+ */
1105
+
1106
+ /**
1107
+ * Load a consumer-supplied code-system release into an immutable, queryable {@link CodeSystem}.
1108
+ *
1109
+ * Dispatches on `source.format`:
1110
+ * - `"rrf"` — pipe-delimited RxNorm/UMLS release (one parameterized reader for both).
1111
+ * - `"csv"` — RFC-4180 CSV (e.g. LOINC `Loinc.csv`), with real quote handling.
1112
+ * - `"fixed-width"` — slice-by-column order files (e.g. ICD-10-CM; see `ICD10CM_ORDER_FILE_FIELDS`).
1113
+ * - `"fhir"` — a native FHIR R4 `CodeSystem` JSON resource.
1114
+ *
1115
+ * **Liberal on load:** a malformed *row* is skipped and surfaced in `warnings` (never a crash, never a
1116
+ * partial concept). **Conservative on structure:** an unusable *source* (wrong FHIR `resourceType`, or
1117
+ * a CSV whose configured columns are absent from the header) throws a typed `TerminologyError`
1118
+ * carrying `TERM_CODESYSTEM_MALFORMED`.
1119
+ *
1120
+ * @param source - The release descriptor (a discriminated {@link CodeSystemSource}).
1121
+ * @returns The immutable, deep-frozen {@link CodeSystem}.
1122
+ * @throws {TerminologyError} `TERM_CODESYSTEM_MALFORMED` when the source is structurally unusable.
1123
+ * @example
1124
+ * ```ts
1125
+ * import { loadCodeSystem } from "@cosyte/terminology";
1126
+ *
1127
+ * const cs = loadCodeSystem({
1128
+ * format: "fhir",
1129
+ * resource: {
1130
+ * resourceType: "CodeSystem",
1131
+ * url: "http://example.org/cs",
1132
+ * concept: [{ code: "A", display: "Alpha" }],
1133
+ * },
1134
+ * });
1135
+ * cs.count; // => 1
1136
+ * ```
1137
+ */
1138
+ declare function loadCodeSystem(source: CodeSystemSource): CodeSystem;
1139
+
1140
+ /**
1141
+ * {@link lookup} and {@link validateCode} — the FHIR R4 CodeSystem `$lookup` and `$validate-code`
1142
+ * operations over a loaded {@link CodeSystem}.
1143
+ *
1144
+ * Grounded firsthand on FHIR R4 (`https://hl7.org/fhir/R4/codesystem-operation-lookup.html`,
1145
+ * `https://hl7.org/fhir/R4/codesystem-operation-validate-code.html`):
1146
+ *
1147
+ * - `$lookup` returns the concept's `display` (*"The preferred display for this concept"*) and its
1148
+ * `property` values (*"additional information about the code, including status"*).
1149
+ * - `$validate-code` returns `result` (*"True if the concept details supplied are valid"*).
1150
+ *
1151
+ * Both honor the **never-fabricate invariant** born in Phase 1, now applied to code identity: an
1152
+ * unknown code yields a typed `unknown` / `valid: false` — **never** a fabricated display and
1153
+ * **never** a guessed `valid: true`. A found-but-non-current concept (deprecated, or an ICD-10-CM
1154
+ * header) validates as present **but carries its status flag**, so a caller never treats it as clean.
1155
+ *
1156
+ * @packageDocumentation
1157
+ */
1158
+
1159
+ /**
1160
+ * `$lookup`: resolve a code to its display, definition, status, and properties in a loaded release.
1161
+ *
1162
+ * @param cs - The loaded {@link CodeSystem}.
1163
+ * @param code - The code to look up.
1164
+ * @returns A {@link LookupResult} when found, or a typed {@link LookupUnknown} — never a guess.
1165
+ * @example
1166
+ * ```ts
1167
+ * import { loadCodeSystem, lookup } from "@cosyte/terminology";
1168
+ *
1169
+ * const cs = loadCodeSystem({
1170
+ * format: "fhir",
1171
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "A", display: "Alpha" }] },
1172
+ * });
1173
+ * const r = lookup(cs, "A");
1174
+ * r.found === true && r.display; // => "Alpha"
1175
+ * ```
1176
+ */
1177
+ declare function lookup(cs: CodeSystem, code: string): LookupOutcome;
1178
+ /**
1179
+ * `$validate-code`: is `code` a valid member of the loaded code system?
1180
+ *
1181
+ * A found code is `valid: true`, **carrying its status** (a deprecated or header code is present but
1182
+ * flagged — never silently clean). An absent code is `valid: false` with `TERM_CODE_UNKNOWN`, never a
1183
+ * guessed `true`.
1184
+ *
1185
+ * @param cs - The loaded {@link CodeSystem}.
1186
+ * @param code - The code to validate.
1187
+ * @returns A {@link ValidateCodeResult}.
1188
+ * @example
1189
+ * ```ts
1190
+ * import { loadCodeSystem, validateCode } from "@cosyte/terminology";
1191
+ *
1192
+ * const cs = loadCodeSystem({
1193
+ * format: "fhir",
1194
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "A", display: "Alpha" }] },
1195
+ * });
1196
+ * validateCode(cs, "A").valid; // => true
1197
+ * validateCode(cs, "ZZZ").valid; // => false
1198
+ * ```
1199
+ */
1200
+ declare function validateCode(cs: CodeSystem, code: string): ValidateCodeResult;
1201
+
1202
+ /**
1203
+ * The **RRF** (Rich Release Format) reader — the pipe-delimited layer shared by RxNorm (`RXNCONSO`/
1204
+ * `RXNSAT`) and UMLS (`MRCONSO`/`MRSAT`). One parameterized reader serves both: they share the RRF
1205
+ * shape — fields separated by `|`, with **one trailing pipe per line** (grounded firsthand on the
1206
+ * UMLS Reference Manual, `https://www.ncbi.nlm.nih.gov/books/NBK9685/`: *"values … are separated by
1207
+ * vertical bars (|)"* and each row ends with *"a vertical bar and line termination"*).
1208
+ *
1209
+ * **Liberal on load** (roadmap §6): a row too short to reach a configured column, or with an empty
1210
+ * code, is **skipped and surfaced** as a `TERM_RRF_MALFORMED_ROW` warning — never kept as a partial
1211
+ * concept, never a crash. RRF reserves `|` as the delimiter (fields never contain a literal pipe), so
1212
+ * a plain split is exact.
1213
+ *
1214
+ * @packageDocumentation
1215
+ */
1216
+
1217
+ /**
1218
+ * Split one RRF line into its fields, dropping the single reserved trailing pipe.
1219
+ *
1220
+ * @param line - One RRF line (no line terminator).
1221
+ * @returns The fields, in column order.
1222
+ * @example
1223
+ * ```ts
1224
+ * import { parseRrfLine } from "@cosyte/terminology";
1225
+ *
1226
+ * parseRrfLine("314076|ENG|lisinopril 10 MG|"); // => ["314076", "ENG", "lisinopril 10 MG"]
1227
+ * ```
1228
+ */
1229
+ declare function parseRrfLine(line: string): readonly string[];
1230
+ /**
1231
+ * Parse an {@link RrfSource} into concepts (first row per code wins) and skipped-row warnings.
1232
+ *
1233
+ * A code that recurs across rows (RRF repeats a code once per atom/attribute) keeps its **first**
1234
+ * occurrence — a documented v1 limitation: preferred-atom selection (`TS`/`STT`/`ISPREF`) and
1235
+ * attribute merging are later work. The result is data only; {@link loadCodeSystem} freezes it.
1236
+ *
1237
+ * @param source - The RRF source.
1238
+ * @returns The parsed concepts (by code) and the load warnings.
1239
+ * @example
1240
+ * ```ts
1241
+ * import { parseRrf } from "@cosyte/terminology";
1242
+ *
1243
+ * const { concepts } = parseRrf({
1244
+ * format: "rrf",
1245
+ * content: "1|Aspirin|",
1246
+ * columns: { code: 0, display: 1 },
1247
+ * });
1248
+ * concepts.get("1")?.display; // => "Aspirin"
1249
+ * ```
1250
+ */
1251
+ declare function parseRrf(source: RrfSource): {
1252
+ concepts: Map<string, Concept>;
1253
+ warnings: LoadWarning[];
1254
+ };
1255
+
1256
+ /**
1257
+ * The **RFC-4180 CSV** reader — the one release format that needs real quote handling (LOINC's
1258
+ * `Loinc.csv`). Fields may be quoted with `"`; inside a quoted field a literal quote is escaped as
1259
+ * `""`, and commas and newlines are data. Hand-rolled, zero-dep (roadmap §1: the CSV surface is small
1260
+ * and well-specified enough to hand-roll rather than pull a dependency).
1261
+ *
1262
+ * **Liberal on load:** a data row with too few fields to reach a configured column is **skipped and
1263
+ * surfaced** as a `TERM_CSV_MALFORMED` warning; an unterminated final quote is surfaced too — never a
1264
+ * crash. A configured code/display header that is **absent from the header row** is a *fatal*
1265
+ * (`TERM_CODESYSTEM_MALFORMED`): the source config does not match the file, so nothing loads cleanly.
1266
+ *
1267
+ * @packageDocumentation
1268
+ */
1269
+
1270
+ /**
1271
+ * Parse RFC-4180 CSV content into rows of fields (the `.rows` of {@link parseCsvRows}). Lenient: a
1272
+ * stray mid-field quote is literal, so a malformed quote never silently swallows subsequent rows.
1273
+ *
1274
+ * @param content - The raw CSV text.
1275
+ * @returns The rows, each an array of field strings (untrimmed).
1276
+ * @example
1277
+ * ```ts
1278
+ * import { parseCsv } from "@cosyte/terminology";
1279
+ *
1280
+ * parseCsv('a,b\n"x,y","z""q"'); // => [["a", "b"], ["x,y", 'z"q']]
1281
+ * ```
1282
+ */
1283
+ declare function parseCsv(content: string): string[][];
1284
+ /**
1285
+ * Parse a {@link CsvSource} into concepts and skipped-row warnings.
1286
+ *
1287
+ * @param source - The CSV source.
1288
+ * @returns The parsed concepts (by code) and the load warnings.
1289
+ * @throws {TerminologyError} `TERM_CODESYSTEM_MALFORMED` when the file has no header row, or a
1290
+ * configured `code`/`display`/`status`/property column is absent from the header.
1291
+ * @example
1292
+ * ```ts
1293
+ * import { parseCsvSource } from "@cosyte/terminology";
1294
+ *
1295
+ * const { concepts } = parseCsvSource({
1296
+ * format: "csv",
1297
+ * content: "CODE,NAME\n2160-0,Creatinine",
1298
+ * columns: { code: "CODE", display: "NAME" },
1299
+ * });
1300
+ * concepts.get("2160-0")?.display; // => "Creatinine"
1301
+ * ```
1302
+ */
1303
+ declare function parseCsvSource(source: CsvSource): {
1304
+ concepts: Map<string, Concept>;
1305
+ warnings: LoadWarning[];
1306
+ };
1307
+
1308
+ /**
1309
+ * The **fixed-width** reader — slice a line by column, for order-file layouts like the ICD-10-CM
1310
+ * order file (`icd10cm_order_YYYY.txt`). Fully parameterized: the consumer supplies the field slices,
1311
+ * so the *engine* is content-agnostic and the byte offsets live in the caller's config, not baked in.
1312
+ *
1313
+ * The documented ICD-10-CM preset is {@link ICD10CM_ORDER_FILE_FIELDS} — the position-15 flag
1314
+ * (0-based char index 14) is `1` for a **billable/valid** code and `0` for a **header** (grounded on
1315
+ * the CMS/CDC order-file layout). Per the roadmap's flagged open question (§10 #4), a consumer should
1316
+ * confirm the exact offsets against their release's README before relying on the preset — which is
1317
+ * exactly why the reader takes the slices as a parameter.
1318
+ *
1319
+ * **Liberal on load:** a line too short to contain the code slice, or with an empty code, is skipped
1320
+ * and surfaced as a `TERM_FIXED_WIDTH_MALFORMED` warning.
1321
+ *
1322
+ * @packageDocumentation
1323
+ */
1324
+
1325
+ /**
1326
+ * Extract and trim a {@link FieldSlice} from a line (`end` omitted slices to end of line).
1327
+ *
1328
+ * @param line - The source line.
1329
+ * @param slice - The half-open `[start, end)` slice.
1330
+ * @returns The trimmed field text (empty when the slice is entirely past the line's end).
1331
+ * @example
1332
+ * ```ts
1333
+ * import { sliceField } from "@cosyte/terminology";
1334
+ *
1335
+ * sliceField("00001 A00 1 Cholera", { start: 6, end: 13 }); // => "A00"
1336
+ * ```
1337
+ */
1338
+ declare function sliceField(line: string, slice: FieldSlice): string;
1339
+ /**
1340
+ * The documented **ICD-10-CM order file** field layout (0-based char indices).
1341
+ *
1342
+ * - `code` — the ICD-10-CM code (no decimal point).
1343
+ * - `billable` — position-15 flag at index 14: `"1"` = billable/valid leaf, `"0"` = header.
1344
+ * - `display` — the long description (to end of line).
1345
+ * - property `shortDescription` — the 60-char short description.
1346
+ *
1347
+ * Grounded on the CMS/CDC order-file layout; **verify against your release's README** before relying
1348
+ * on the exact offsets (roadmap §10 #4 — the CDC PDF resisted authoritative decode). The reader takes
1349
+ * the layout as a parameter precisely so this preset is a starting point, not a hardcoded assumption.
1350
+ */
1351
+ declare const ICD10CM_ORDER_FILE_FIELDS: FixedWidthFieldMap;
1352
+ /**
1353
+ * Parse a {@link FixedWidthSource} into concepts and skipped-row warnings.
1354
+ *
1355
+ * @param source - The fixed-width source.
1356
+ * @returns The parsed concepts (by code) and the load warnings.
1357
+ * @example
1358
+ * ```ts
1359
+ * import { parseFixedWidth } from "@cosyte/terminology";
1360
+ *
1361
+ * const { concepts } = parseFixedWidth({
1362
+ * format: "fixed-width",
1363
+ * content: "A00 1 Cholera",
1364
+ * fields: { code: { start: 0, end: 3 }, display: { start: 6 } },
1365
+ * });
1366
+ * concepts.get("A00")?.display; // => "Cholera"
1367
+ * ```
1368
+ */
1369
+ declare function parseFixedWidth(source: FixedWidthSource): {
1370
+ concepts: Map<string, Concept>;
1371
+ warnings: LoadWarning[];
1372
+ };
1373
+
1374
+ /**
1375
+ * The **FHIR R4 `CodeSystem`** JSON loader — the native, license-clean model. Reads the untrusted
1376
+ * resource into the engine's {@link Concept} map, flattening nested `concept` hierarchies and mapping
1377
+ * the standard concept properties to {@link ConceptStatus}.
1378
+ *
1379
+ * Grounded firsthand on FHIR R4 (`https://hl7.org/fhir/R4/codesystem.html` and the concept-properties
1380
+ * code system, `https://hl7.org/fhir/R4/codesystem-concept-properties.html`): a concept has
1381
+ * `code`/`display`/`definition`/`designation`/`property`/nested `concept`; the base property
1382
+ * **`inactive`** (boolean) is *"True if the concept is not considered active — e.g. not a valid
1383
+ * concept any more"*, **`deprecated`** (dateTime) marks a deprecation, and a `status` string property
1384
+ * is common in real systems.
1385
+ *
1386
+ * **Conservative on load:** the wrong `resourceType` is a *fatal* (`TERM_CODESYSTEM_MALFORMED`); a
1387
+ * concept missing its required `code` is skipped and surfaced (a malformed row, not a whole-file
1388
+ * fatal).
1389
+ *
1390
+ * @packageDocumentation
1391
+ */
1392
+
1393
+ /**
1394
+ * Parse a {@link FhirCodeSystemSource} into concepts, a resolved url/version/name, and warnings.
1395
+ *
1396
+ * @param source - The FHIR CodeSystem source.
1397
+ * @returns The parsed concepts (by code), the load warnings, and the resource metadata.
1398
+ * @throws {TerminologyError} `TERM_CODESYSTEM_MALFORMED` when the resource is not a FHIR `CodeSystem`.
1399
+ * @example
1400
+ * ```ts
1401
+ * import { parseFhirCodeSystem } from "@cosyte/terminology";
1402
+ *
1403
+ * const { concepts } = parseFhirCodeSystem({
1404
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "A", display: "Alpha" }] },
1405
+ * format: "fhir",
1406
+ * });
1407
+ * concepts.get("A")?.display; // => "Alpha"
1408
+ * ```
1409
+ */
1410
+ declare function parseFhirCodeSystem(source: FhirCodeSystemSource): {
1411
+ concepts: Map<string, Concept>;
1412
+ warnings: LoadWarning[];
1413
+ url?: string;
1414
+ version?: string;
1415
+ name?: string;
1416
+ };
1417
+
1418
+ /**
1419
+ * Status interpretation shared by the loaders — the {@link defaultStatusMapper} that turns a raw
1420
+ * steward status token into a normalized {@link ConceptActivity}, and {@link makeStatus} which
1421
+ * assembles the frozen {@link ConceptStatus} (attaching the stable diagnostic code for the concerns
1422
+ * the roadmap names: deprecated and header-not-billable).
1423
+ *
1424
+ * The mapper is deliberately **conservative**: it recognizes only the tokens grounded in the format
1425
+ * docs (LOINC `STATUS` lifecycle values, RxNorm/UMLS `SUPPRESS` values — roadmap §4.4/§11), and maps
1426
+ * everything else to `unknown` — a non-active state carrying the raw token, **never** a guessed
1427
+ * clean-active. Every loader lets the consumer override it for a release with bespoke semantics.
1428
+ *
1429
+ * @packageDocumentation
1430
+ */
1431
+
1432
+ /**
1433
+ * The default raw-token → {@link ConceptActivity} mapping, case-insensitive.
1434
+ *
1435
+ * Recognized (grounded in the format docs): `""`/`N`/`ACTIVE` → active; `DEPRECATED` → deprecated;
1436
+ * `DISCOURAGED`; `TRIAL`; `O`/`OBSOLETE` → obsolete; `Y`/`E`/`SUPPRESSED` → suppressed. Anything else
1437
+ * → `unknown` (surfaced with its raw token, never coerced to active).
1438
+ *
1439
+ * @param raw - The steward's raw status token.
1440
+ * @returns The normalized activity.
1441
+ * @example
1442
+ * ```ts
1443
+ * import { defaultStatusMapper } from "@cosyte/terminology";
1444
+ *
1445
+ * defaultStatusMapper("DEPRECATED"); // => "deprecated"
1446
+ * defaultStatusMapper("N"); // => "active"
1447
+ * ```
1448
+ */
1449
+ declare const defaultStatusMapper: StatusMapper;
1450
+ /**
1451
+ * Assemble a frozen {@link ConceptStatus} from a normalized activity, carrying the raw token and (for
1452
+ * classification systems) the billable flag, and attaching the stable diagnostic code for the two
1453
+ * concerns the roadmap names — `TERM_CONCEPT_DEPRECATED` and `TERM_CONCEPT_HEADER_NOT_BILLABLE`.
1454
+ *
1455
+ * @param activity - The normalized activity.
1456
+ * @param raw - The verbatim steward token, if any (omitted from the result when empty).
1457
+ * @param billable - The billable flag for a classification system, if applicable.
1458
+ * @returns A frozen {@link ConceptStatus}.
1459
+ * @example
1460
+ * ```ts
1461
+ * import { makeStatus } from "@cosyte/terminology";
1462
+ *
1463
+ * makeStatus("deprecated", "DEPRECATED").code; // => "TERM_CONCEPT_DEPRECATED"
1464
+ * ```
1465
+ */
1466
+ declare function makeStatus(activity: ConceptActivity, raw?: string, billable?: boolean): ConceptStatus;
1467
+
1468
+ /**
1469
+ * The types for the **ValueSet binding layer** (roadmap Phase 3): the immutable {@link ValueSet}
1470
+ * model, the `compose` component/filter shapes, the {@link ExpansionContext} that supplies the loaded
1471
+ * code systems and referenced value sets, and the fail-safe {@link ExpandResult} /
1472
+ * {@link ValueSetMembership} outcomes.
1473
+ *
1474
+ * The model is **content-agnostic and license-clean**: a `ValueSet` names codes by system URI and is
1475
+ * expanded over **consumer-supplied** {@link CodeSystem} releases — the engine ships **zero** encoded
1476
+ * value-set content (SNOMED/CPT/full-UMLS/VSAC value sets are strictly BYO, roadmap §5). The FHIR
1477
+ * operation shapes are grounded firsthand on the FHIR R4 Terminology Module
1478
+ * (`https://hl7.org/fhir/R4/valueset.html`, `https://hl7.org/fhir/R4/valueset-operation-expand.html`,
1479
+ * `https://hl7.org/fhir/R4/valueset-operation-validate-code.html`).
1480
+ *
1481
+ * @packageDocumentation
1482
+ */
1483
+
1484
+ /**
1485
+ * A FHIR R4 `ValueSet.compose.include.filter` operator — the subset the engine implements plus the
1486
+ * ones it explicitly does not. Grounded on the FHIR `filter-operator` code system
1487
+ * (`https://hl7.org/fhir/R4/valueset-filter-operator.html`).
1488
+ *
1489
+ * **Implemented:** `is-a` / `descendent-of` / `is-not-a` (subsumption over the loaded release's
1490
+ * hierarchy), `=` / `in` / `not-in` / `exists` (property predicates). **Not implemented** (surfaced as
1491
+ * a typed {@link DiagnosticCode.TERM_VALUESET_CANNOT_EXPAND}, never silently mis-expanded): `regex`,
1492
+ * `generalizes`. Anything unrecognized is treated as not-implemented — never as "matches nothing".
1493
+ */
1494
+ type FilterOperator = "=" | "is-a" | "descendent-of" | "is-not-a" | "regex" | "in" | "not-in" | "generalizes" | "exists";
1495
+ /** One `ValueSet.compose.include.filter` — a `property op value` predicate over a code system. */
1496
+ interface ConceptSetFilter {
1497
+ /** The concept property the predicate is on (e.g. `"concept"` for the code hierarchy, or a code). */
1498
+ readonly property: string;
1499
+ /** The filter operator. */
1500
+ readonly op: FilterOperator;
1501
+ /** The comparison value (for `in`/`not-in`, a comma-separated list; for `is-a`, an anchor code). */
1502
+ readonly value: string;
1503
+ }
1504
+ /** One explicit `ValueSet.compose.include.concept` — an enumerated code (extensional). */
1505
+ interface ConceptRef {
1506
+ /** The code, drawn verbatim from the value set. */
1507
+ readonly code: string;
1508
+ /** The display, when the value set supplied one (carried verbatim, never fabricated). */
1509
+ readonly display?: string;
1510
+ }
1511
+ /**
1512
+ * One `ValueSet.compose.include` / `.exclude` component — a `ConceptSet`. Selects codes by an explicit
1513
+ * `concept` list, by `filter`s over a `system`, by referencing other value sets (`valueSet`), or a
1514
+ * combination (the combination is an **intersection**, per FHIR).
1515
+ */
1516
+ interface ConceptSetComponent {
1517
+ /** The code system these codes are drawn from (canonical URI), when the component names one. */
1518
+ readonly system?: string;
1519
+ /** The code system version to pin the selection to, when declared. */
1520
+ readonly version?: string;
1521
+ /** An explicit, enumerated code list (extensional). Mutually exclusive with {@link filter} in FHIR. */
1522
+ readonly concept?: readonly ConceptRef[];
1523
+ /** Intensional filters over {@link system}. Each code must satisfy **all** filters. */
1524
+ readonly filter?: readonly ConceptSetFilter[];
1525
+ /** Canonical URLs of other value sets; a code must be a member of **every** one (intersection). */
1526
+ readonly valueSet?: readonly string[];
1527
+ }
1528
+ /** A `ValueSet.compose` — the intensional/extensional definition of membership. */
1529
+ interface ValueSetCompose {
1530
+ /** The `include` components (their union, minus every {@link exclude}, is the membership). */
1531
+ readonly include: readonly ConceptSetComponent[];
1532
+ /** The `exclude` components — codes removed from the include union. */
1533
+ readonly exclude: readonly ConceptSetComponent[];
1534
+ }
1535
+ /** One `ValueSet.expansion.contains` entry from a **pre-computed** expansion. */
1536
+ interface ExpansionContains {
1537
+ /** The code system URI, when the entry names one. */
1538
+ readonly system?: string;
1539
+ /** The code. Required — a `contains` entry without a code is a malformed expansion. */
1540
+ readonly code: string;
1541
+ /** The display, carried verbatim when present. */
1542
+ readonly display?: string;
1543
+ /** The code system version, when the entry pins one. */
1544
+ readonly version?: string;
1545
+ }
1546
+ /**
1547
+ * A **pre-computed** `ValueSet.expansion` — a cached membership snapshot, used as-is (extensional).
1548
+ * The engine never re-derives it; it only checks whether it is **complete** (roadmap §4.4 — a
1549
+ * truncated expansion must never read as full membership).
1550
+ */
1551
+ interface ValueSetExpansion {
1552
+ /** The server-reported total membership, when present (FHIR `expansion.total`). */
1553
+ readonly total?: number;
1554
+ /** The snapshot members, in expansion order (frozen). */
1555
+ readonly contains: readonly ExpansionContains[];
1556
+ /**
1557
+ * Derived at load: the expansion is **incomplete**. True when `total` exceeds `contains.length`, or
1558
+ * the `http://hl7.org/fhir/StructureDefinition/valueset-toocostly` extension flagged it too-costly
1559
+ * (the VSAC >1200-code truncation hazard). A truncated expansion never reads as complete membership.
1560
+ */
1561
+ readonly truncated: boolean;
1562
+ }
1563
+ /**
1564
+ * A loaded, immutable `ValueSet` — the subset of the FHIR R4 `ValueSet` resource the binding layer
1565
+ * needs. Produced by {@link loadValueSet}; every field deep-frozen.
1566
+ */
1567
+ interface ValueSet {
1568
+ /** The resource's canonical `url`, when declared (the key referenced value sets resolve against). */
1569
+ readonly url?: string;
1570
+ /** The resource's business `version`, when declared. */
1571
+ readonly version?: string;
1572
+ /** A short human-readable name, when declared. */
1573
+ readonly name?: string;
1574
+ /** The intensional/extensional `compose`, when the resource defines membership by composition. */
1575
+ readonly compose?: ValueSetCompose;
1576
+ /** A **pre-computed** `expansion`, when the resource carries a cached membership snapshot. */
1577
+ readonly expansion?: ValueSetExpansion;
1578
+ }
1579
+ /**
1580
+ * The data an {@link expand}/{@link validateCodeInValueSet} call draws on: the loaded
1581
+ * {@link CodeSystem} releases (for intensional `filter`/`system` includes) and any referenced
1582
+ * {@link ValueSet}s (for `compose.include.valueSet`), each keyed by its **canonical URL**.
1583
+ *
1584
+ * The engine makes **no network call** and holds no bundled content: everything intensional resolves
1585
+ * through this map, and a system or value set the caller did not supply becomes a typed
1586
+ * {@link DiagnosticCode.TERM_VALUESET_CANNOT_EXPAND}, never a fabricated member (roadmap §2, §4.4).
1587
+ */
1588
+ interface ExpansionContext {
1589
+ /** Loaded code-system releases, keyed by canonical URI (e.g. `"http://loinc.org"`). */
1590
+ readonly codeSystems?: ReadonlyMap<string, CodeSystem>;
1591
+ /** Referenced value sets, keyed by canonical URL, for `compose.include.valueSet`. */
1592
+ readonly valueSets?: ReadonlyMap<string, ValueSet>;
1593
+ }
1594
+ /** A surfaced expansion concern — a part that could not be expanded, or a truncated snapshot. */
1595
+ interface ExpansionDiagnostic {
1596
+ /** The stable code (`TERM_VALUESET_CANNOT_EXPAND` / `TERM_VALUESET_EXPANSION_TRUNCATED`). */
1597
+ readonly code: DiagnosticCode;
1598
+ /** A **value-free** structural description of the concern (never echoes a patient value). */
1599
+ readonly detail: string;
1600
+ /** The code system / value set URI the concern is about (a published identity — not PHI). */
1601
+ readonly system?: string;
1602
+ }
1603
+ /**
1604
+ * The result of {@link expand}: the flattened membership plus an honest **completeness** signal.
1605
+ *
1606
+ * `complete` is `false` whenever any part could not be fully computed (a missing code system, an
1607
+ * unresolved referenced value set, an unimplemented `filter` op, or a truncated pre-computed
1608
+ * expansion). A `false` here is the never-fabricate contract in action: the `contains` set is a
1609
+ * **lower bound**, so it must never be read as exhaustive membership.
1610
+ */
1611
+ interface ExpandResult {
1612
+ /** Whether the expansion is exhaustive. When `false`, `contains` is a lower bound, not the whole set. */
1613
+ readonly complete: boolean;
1614
+ /** The expanded, de-duplicated members, in first-seen order (frozen; every member frozen). */
1615
+ readonly contains: readonly Coding[];
1616
+ /** The server-reported total, when the expansion came from a pre-computed snapshot. */
1617
+ readonly total?: number;
1618
+ /** The surfaced concerns, in encounter order (frozen; may be empty). */
1619
+ readonly diagnostics: readonly ExpansionDiagnostic[];
1620
+ }
1621
+ /** A definitive membership answer — the code is (or is not) a member of the value set. */
1622
+ interface ValueSetMemberDecided {
1623
+ /** Discriminant: membership was decided. */
1624
+ readonly undetermined: false;
1625
+ /** FHIR `$validate-code` `result`: true iff the code is a member of the value set. */
1626
+ readonly result: boolean;
1627
+ /** The coding that was tested, echoed. */
1628
+ readonly coding: Coding;
1629
+ }
1630
+ /**
1631
+ * A **fail-safe undetermined** membership — the never-fabricate outcome for a value set that could
1632
+ * not be fully evaluated (a missing code system, a truncated expansion). The engine refuses to guess:
1633
+ * it returns neither `true` nor `false`, so a caller can never mistake "we could not check" for "not a
1634
+ * member" (roadmap §4.4 — a false "not a member" is a clinical error).
1635
+ */
1636
+ interface ValueSetMemberUndetermined {
1637
+ /** Discriminant: membership could not be decided. */
1638
+ readonly undetermined: true;
1639
+ /** The stable diagnostic code (`TERM_VALUESET_CANNOT_EXPAND`). */
1640
+ readonly code: "TERM_VALUESET_CANNOT_EXPAND";
1641
+ /** The coding that was tested, echoed. */
1642
+ readonly coding: Coding;
1643
+ /** The surfaced reasons the check could not be completed. */
1644
+ readonly diagnostics: readonly ExpansionDiagnostic[];
1645
+ }
1646
+ /** The result of {@link validateCodeInValueSet}: a decided membership or a typed undetermined. */
1647
+ type ValueSetMembership = ValueSetMemberDecided | ValueSetMemberUndetermined;
1648
+
1649
+ /**
1650
+ * {@link loadValueSet} — validate and normalize an untrusted FHIR R4 `ValueSet` JSON resource into
1651
+ * the immutable {@link ValueSet} model the `$expand` / `$validate-code` binding operations run over.
1652
+ *
1653
+ * **Conservative on load** (like {@link ../conceptmap/load.loadConceptMap}, unlike a lenient wire
1654
+ * parser): a value set *binds* whether a code is allowed in a clinical slot, so a structurally
1655
+ * unusable resource is a typed **fatal** ({@link FATAL_CODES.TERM_VALUESET_MALFORMED}) rather than a
1656
+ * silently partial value set that would admit *some* codes and quietly drop others. A `filter`
1657
+ * operator the engine does not implement is **not** fatal — the value set loads, and the gap surfaces
1658
+ * at expansion time as a {@link DIAGNOSTIC_CODES.TERM_VALUESET_CANNOT_EXPAND} diagnostic. Fault
1659
+ * messages are **value-free**: they name the resource *path* and the fault, never echo a code value.
1660
+ *
1661
+ * @packageDocumentation
1662
+ */
1663
+
1664
+ /**
1665
+ * Load an untrusted FHIR R4 `ValueSet` JSON value into an immutable {@link ValueSet}.
1666
+ *
1667
+ * Accepts the standard resource shape: `resourceType: "ValueSet"` with an optional intensional
1668
+ * `compose` (`include`/`exclude` of `system`/`concept`/`filter`/`valueSet`) and/or a **pre-computed**
1669
+ * `expansion` (`contains`, with `total` and the `valueset-toocostly` extension read into a derived
1670
+ * `truncated` flag). The result is deep-frozen. Anything structurally unusable throws a
1671
+ * {@link TerminologyError} carrying {@link FATAL_CODES.TERM_VALUESET_MALFORMED}.
1672
+ *
1673
+ * @param json - The untrusted resource (typically `JSON.parse` output — hence `unknown`).
1674
+ * @returns The immutable, validated {@link ValueSet}.
1675
+ * @throws {TerminologyError} `TERM_VALUESET_MALFORMED` when the resource is not a usable ValueSet.
1676
+ * @example
1677
+ * ```ts
1678
+ * import { loadValueSet } from "@cosyte/terminology";
1679
+ *
1680
+ * const vs = loadValueSet({
1681
+ * resourceType: "ValueSet",
1682
+ * url: "http://example.org/vs/colors",
1683
+ * compose: {
1684
+ * include: [{ system: "http://example.org/cs", concept: [{ code: "red" }, { code: "green" }] }],
1685
+ * },
1686
+ * });
1687
+ * vs.compose?.include.length; // => 1
1688
+ * ```
1689
+ */
1690
+ declare function loadValueSet(json: unknown): ValueSet;
1691
+
1692
+ /**
1693
+ * {@link expand} — the FHIR R4 ValueSet `$expand` operation over a loaded {@link ValueSet}.
1694
+ *
1695
+ * Grounded firsthand on FHIR R4 (`https://hl7.org/fhir/R4/valueset-operation-expand.html`,
1696
+ * `https://hl7.org/fhir/R4/valueset.html#compose`):
1697
+ *
1698
+ * - A **pre-computed** `expansion` is used as-is (extensional), and its completeness is checked — a
1699
+ * truncated snapshot is flagged, never treated as full membership (roadmap §4.4).
1700
+ * - An intensional `compose` is expanded over **consumer-supplied** {@link ../codesystem/types.CodeSystem}
1701
+ * releases: the union of `include` components minus the union of `exclude` components. Each component
1702
+ * selects by explicit `concept`, by `filter` (subsumption + property predicates), by the whole
1703
+ * `system`, and/or by intersecting referenced value sets.
1704
+ *
1705
+ * **Never fabricate.** A part that cannot be computed — a missing code system, an unresolved
1706
+ * referenced value set, an unimplemented `filter` operator — yields a typed
1707
+ * {@link ../common/diagnostics.DIAGNOSTIC_CODES.TERM_VALUESET_CANNOT_EXPAND} and marks the result
1708
+ * `complete: false`, so the `contains` set is an explicit **lower bound**, never a silently-empty or
1709
+ * fabricated membership.
1710
+ *
1711
+ * @packageDocumentation
1712
+ */
1713
+
1714
+ /**
1715
+ * `$expand`: compute the flattened membership of a {@link ValueSet} over the supplied code systems.
1716
+ *
1717
+ * @param vs - The loaded value set.
1718
+ * @param ctx - The loaded code systems / referenced value sets the intensional parts resolve against.
1719
+ * @returns An {@link ExpandResult} — the `contains` members plus an honest `complete` flag. When
1720
+ * `complete` is `false`, `contains` is a lower bound (never treat it as exhaustive membership).
1721
+ * @example
1722
+ * ```ts
1723
+ * import { loadValueSet, loadCodeSystem, expand } from "@cosyte/terminology";
1724
+ *
1725
+ * const cs = loadCodeSystem({
1726
+ * format: "fhir",
1727
+ * resource: {
1728
+ * resourceType: "CodeSystem",
1729
+ * url: "http://example.org/cs",
1730
+ * concept: [{ code: "a" }, { code: "b" }, { code: "c" }],
1731
+ * },
1732
+ * });
1733
+ * const vs = loadValueSet({
1734
+ * resourceType: "ValueSet",
1735
+ * compose: { include: [{ system: "http://example.org/cs", concept: [{ code: "a" }, { code: "b" }] }] },
1736
+ * });
1737
+ * const result = expand(vs, { codeSystems: new Map([["http://example.org/cs", cs]]) });
1738
+ * result.complete; // => true
1739
+ * result.contains.length; // => 2
1740
+ * ```
1741
+ */
1742
+ declare function expand(vs: ValueSet, ctx?: ExpansionContext): ExpandResult;
1743
+
1744
+ /**
1745
+ * {@link validateCodeInValueSet} — the FHIR R4 ValueSet `$validate-code` operation (value-set
1746
+ * binding): is a {@link Coding} a member of a {@link ValueSet}?
1747
+ *
1748
+ * Grounded firsthand on FHIR R4 (`https://hl7.org/fhir/R4/valueset-operation-validate-code.html`): a
1749
+ * binding decides whether a code is *allowed* in a slot. The engine returns a **decided** membership
1750
+ * (`result: true`/`false`) **only when it can prove it** — a code found in a computable `include` and
1751
+ * not removed by any computable `exclude` is a definite member; a code absent from a fully-evaluated
1752
+ * value set is a definite non-member. When any relevant part cannot be evaluated (a missing code
1753
+ * system, a truncated pre-computed expansion, an unimplemented `filter`), the answer is a typed
1754
+ * {@link ValueSetMemberUndetermined} — **never** a fabricated `false` (roadmap §4.4: a false "not a
1755
+ * member" is a clinical error).
1756
+ *
1757
+ * @packageDocumentation
1758
+ */
1759
+
1760
+ /**
1761
+ * `$validate-code` (ValueSet): is `coding` a member of `valueSet`?
1762
+ *
1763
+ * @param coding - The code to test (system + code).
1764
+ * @param valueSet - The loaded value set.
1765
+ * @param ctx - The loaded code systems / referenced value sets the intensional parts resolve against.
1766
+ * @returns A decided {@link ValueSetMembership} (`result: true`/`false`), or a typed
1767
+ * {@link ValueSetMemberUndetermined} when membership could not be proven — never a fabricated `false`.
1768
+ * @example
1769
+ * ```ts
1770
+ * import { loadValueSet, validateCodeInValueSet } from "@cosyte/terminology";
1771
+ *
1772
+ * const vs = loadValueSet({
1773
+ * resourceType: "ValueSet",
1774
+ * compose: { include: [{ system: "http://example.org/cs", concept: [{ code: "a" }, { code: "b" }] }] },
1775
+ * });
1776
+ * const r = validateCodeInValueSet({ system: "http://example.org/cs", code: "a" }, vs);
1777
+ * r.undetermined === false && r.result; // => true
1778
+ * ```
1779
+ */
1780
+ declare function validateCodeInValueSet(coding: Coding, valueSet: ValueSet, ctx?: ExpansionContext): ValueSetMembership;
1781
+
1782
+ /**
1783
+ * Subsumption + `compose.include.filter` evaluation — the shared machinery {@link ./expand.expand}
1784
+ * and {@link ./validate.validateCodeInValueSet} both draw on.
1785
+ *
1786
+ * **Subsumption** is read from the loaded {@link CodeSystem}'s **`parent`** concept-properties
1787
+ * (`http://hl7.org/fhir/concept-properties#parent`) — which the FHIR loader synthesizes from a nested
1788
+ * `concept` hierarchy and carries verbatim from an explicit `parent` property. This is deliberately
1789
+ * the release's **own** hierarchy (roadmap: "hierarchy/`$subsumes` minimal; deep subsumption is
1790
+ * Phase 5"); a code system that encodes no parent edges has no descendants beyond identity, and that
1791
+ * is treated as complete — not guessed deeper.
1792
+ *
1793
+ * **Filter support is explicit.** `is-a` / `descendent-of` / `is-not-a` / `=` / `in` / `not-in` /
1794
+ * `exists` are implemented; `regex` / `generalizes` / anything unrecognized report `supported: false`
1795
+ * so the caller surfaces a typed {@link DIAGNOSTIC_CODES.TERM_VALUESET_CANNOT_EXPAND} rather than
1796
+ * silently mis-including or dropping codes.
1797
+ *
1798
+ * @packageDocumentation
1799
+ */
1800
+
1801
+ /**
1802
+ * The filters whose operator the engine does **not** implement (`regex` / `generalizes` / unknown) —
1803
+ * a non-empty result means the caller must surface a typed cannot-expand, never mis-expand.
1804
+ *
1805
+ * @param filters - The component's filters.
1806
+ * @returns The subset with an unimplemented operator (empty when all are supported).
1807
+ * @example
1808
+ * ```ts
1809
+ * import { unsupportedOps } from "@cosyte/terminology";
1810
+ *
1811
+ * unsupportedOps([{ property: "concept", op: "is-a", value: "x" }]).length; // => 0
1812
+ * ```
1813
+ */
1814
+ declare function unsupportedOps(filters: readonly ConceptSetFilter[]): readonly ConceptSetFilter[];
1815
+ /** An immutable ancestor index over a {@link CodeSystem}'s `parent` edges. */
1816
+ interface Subsumption {
1817
+ /** All (transitive) ancestor codes of `code`, cycle-safe. Excludes `code` itself. */
1818
+ ancestorsOf(code: string): ReadonlySet<string>;
1819
+ }
1820
+ /**
1821
+ * Build a cycle-safe ancestor index from a loaded {@link CodeSystem}'s `parent` concept-properties.
1822
+ *
1823
+ * @param cs - The loaded code system.
1824
+ * @returns A {@link Subsumption} answering transitive-ancestor queries.
1825
+ * @example
1826
+ * ```ts
1827
+ * import { loadCodeSystem, buildSubsumption } from "@cosyte/terminology";
1828
+ *
1829
+ * const cs = loadCodeSystem({
1830
+ * format: "fhir",
1831
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "p", concept: [{ code: "c" }] }] },
1832
+ * });
1833
+ * buildSubsumption(cs).ancestorsOf("c").has("p"); // => true
1834
+ * ```
1835
+ */
1836
+ declare function buildSubsumption(cs: CodeSystem): Subsumption;
1837
+ /**
1838
+ * True when `code` is-a `anchor` — the same code, or a (transitive) descendant of it.
1839
+ *
1840
+ * @param sub - The subsumption index (from {@link buildSubsumption}).
1841
+ * @param code - The candidate code.
1842
+ * @param anchor - The ancestor code to test against.
1843
+ * @returns Whether `code` is `anchor` or a descendant of it.
1844
+ * @example
1845
+ * ```ts
1846
+ * import { loadCodeSystem, buildSubsumption, isA } from "@cosyte/terminology";
1847
+ *
1848
+ * const cs = loadCodeSystem({
1849
+ * format: "fhir",
1850
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "p", concept: [{ code: "c" }] }] },
1851
+ * });
1852
+ * isA(buildSubsumption(cs), "c", "p"); // => true
1853
+ * ```
1854
+ */
1855
+ declare function isA(sub: Subsumption, code: string, anchor: string): boolean;
1856
+ /** The outcome of evaluating a single filter against a single concept. */
1857
+ interface FilterMatch {
1858
+ /** Whether the concept satisfies the filter (meaningful only when {@link supported}). */
1859
+ readonly matched: boolean;
1860
+ /** Whether the engine implements this filter operator; `false` ⇒ the caller must surface it. */
1861
+ readonly supported: boolean;
1862
+ }
1863
+ /**
1864
+ * Evaluate one {@link ConceptSetFilter} against one {@link Concept}, using the release's hierarchy.
1865
+ *
1866
+ * The `concept` filter property (and the hierarchy operators regardless of property) reads the code
1867
+ * itself / its subsumption; other properties read the concept's `property` values. An unimplemented
1868
+ * operator returns `{ supported: false }` — never a coerced `matched`.
1869
+ *
1870
+ * @param concept - The concept under test.
1871
+ * @param filter - The filter predicate.
1872
+ * @param sub - The subsumption index for the concept's code system.
1873
+ * @returns A {@link FilterMatch}.
1874
+ * @example
1875
+ * ```ts
1876
+ * import { loadCodeSystem, buildSubsumption, matchesFilter } from "@cosyte/terminology";
1877
+ *
1878
+ * const cs = loadCodeSystem({
1879
+ * format: "fhir",
1880
+ * resource: { resourceType: "CodeSystem", concept: [{ code: "p", concept: [{ code: "c" }] }] },
1881
+ * });
1882
+ * const dog = cs.concepts.get("c");
1883
+ * dog && matchesFilter(dog, { property: "concept", op: "is-a", value: "p" }, buildSubsumption(cs)).matched; // => true
1884
+ * ```
1885
+ */
1886
+ declare function matchesFilter(concept: Concept, filter: ConceptSetFilter, sub: Subsumption): FilterMatch;
1887
+
1888
+ /**
1889
+ * Types for the **UCUM unit layer** (roadmap Phase 4): the parsed unit AST, the reduced
1890
+ * (canonical) dimensional form, and the public {@link UcumValidation} result.
1891
+ *
1892
+ * The layer is **recognition + validation + representation canonicalization only** — it answers
1893
+ * "is this a valid UCUM expression" and "are these two expressions the same unit", and it
1894
+ * canonicalizes a unit's *representation*. It performs **no magnitude conversion** (`mg/dL` →
1895
+ * `mmol/L` needs an analyte's molar mass — a clinical computation the engine refuses, roadmap
1896
+ * §2/§4.3). The tables are the vendored, verbatim UCUM `ucum-essence.xml` (roadmap §5), transformed
1897
+ * to an in-memory model at runtime; the engine ships **no derivative** of the UCUM table.
1898
+ *
1899
+ * @packageDocumentation
1900
+ */
1901
+ /** A UCUM metric prefix (e.g. `k` = kilo, factor `1e3`), from the essence `<prefix>` table. */
1902
+ interface UcumPrefix {
1903
+ /** The case-sensitive prefix symbol (e.g. `"k"`, `"da"`, `"Ki"`). */
1904
+ readonly code: string;
1905
+ /** The multiplicative factor (e.g. `1000` for `k`, `1024` for `Ki`). */
1906
+ readonly factor: number;
1907
+ }
1908
+ /**
1909
+ * A UCUM unit atom, from the essence `<base-unit>` / `<unit>` table. An atom is a symbol like `m`,
1910
+ * `L`, `[in_i]`, `10*`, or `Cel`, **before** any prefix or exponent is applied.
1911
+ */
1912
+ interface UcumAtom {
1913
+ /** The case-sensitive atom symbol (the essence `Code`, e.g. `"m"`, `"[iU]"`, `"m[Hg]"`). */
1914
+ readonly code: string;
1915
+ /** Whether a metric prefix may attach to this atom (essence `isMetric`). Only metric atoms prefix. */
1916
+ readonly metric: boolean;
1917
+ /** A **base** unit (one of the 7: `m s g rad K C cd`) — its own dimension, factor 1. */
1918
+ readonly base: boolean;
1919
+ /** A base unit's dimension key (its own `code`), or `undefined` for derived atoms. */
1920
+ readonly dim?: string;
1921
+ /**
1922
+ * A **special** unit (essence `isSpecial`, e.g. `Cel`, `B`, `[pH]`) — defined by a non-linear
1923
+ * function, so it has **no** linear factor/dimension and is never reduced to one. Compared only by
1924
+ * identity of its normalized representation (never claimed equal to a linear unit).
1925
+ */
1926
+ readonly special: boolean;
1927
+ /**
1928
+ * An **arbitrary** unit (essence `isArbitrary`, e.g. `[IU]`, `[arb'U]`) — not commensurable with
1929
+ * any other unit. Modeled as its own dimension axis keyed by the atom `code`, so `[IU]/[IU]`
1930
+ * reduces to unity but `[IU]` is never equated with a mass or another arbitrary unit.
1931
+ */
1932
+ readonly arbitrary: boolean;
1933
+ /**
1934
+ * The linear definition: `1 <atom> = value × <unit>` (essence `<value value Unit>`), for derived
1935
+ * atoms. `undefined` for base units (they define the dimensions) and for special units (non-linear).
1936
+ */
1937
+ readonly value?: {
1938
+ readonly factor: number;
1939
+ readonly unit: string;
1940
+ };
1941
+ }
1942
+ /** The parsed UCUM essence table — the engine's in-memory unit model, built lazily and cached. */
1943
+ interface UcumEssence {
1944
+ /** Prefix symbols → prefix, longest symbols first (for greedy matching). */
1945
+ readonly prefixes: readonly UcumPrefix[];
1946
+ /** Atom symbols → atom, longest symbols first (for greedy longest-match). */
1947
+ readonly atoms: readonly UcumAtom[];
1948
+ /** Atom lookup by exact `code`. */
1949
+ readonly atomByCode: ReadonlyMap<string, UcumAtom>;
1950
+ /** Prefix lookup by exact `code`. */
1951
+ readonly prefixByCode: ReadonlyMap<string, UcumPrefix>;
1952
+ }
1953
+ /** One parsed component of a unit term: a simple unit (prefix?+atom) with an optional exponent. */
1954
+ interface SimpleUnitNode {
1955
+ readonly kind: "simple";
1956
+ /** The applied prefix, when present. */
1957
+ readonly prefix?: UcumPrefix;
1958
+ /** The resolved atom. */
1959
+ readonly atom: UcumAtom;
1960
+ /** The integer exponent (default 1). Negative for the denominator side. */
1961
+ readonly exponent: number;
1962
+ }
1963
+ /** A numeric factor component (a bare integer, e.g. the `4` in `4.[pi]`). */
1964
+ interface FactorNode {
1965
+ readonly kind: "factor";
1966
+ /** The integer value. */
1967
+ readonly value: number;
1968
+ }
1969
+ /** A parenthesised sub-term or an annotation-only component. */
1970
+ interface GroupNode {
1971
+ readonly kind: "group";
1972
+ /** The parsed inner term. */
1973
+ readonly term: UnitNode;
1974
+ }
1975
+ /** A component that carries **only** an annotation (e.g. `{cells}`), dimensionless and inert. */
1976
+ interface AnnotationNode {
1977
+ readonly kind: "annotation";
1978
+ }
1979
+ /** A component: a simple unit, a factor, a group, or an annotation — each optionally exponentiated. */
1980
+ type ComponentNode = SimpleUnitNode | FactorNode | GroupNode | AnnotationNode;
1981
+ /**
1982
+ * A parsed UCUM term: an ordered list of components, each combined into the product with the given
1983
+ * sign (+1 for `.` / the leading operand, -1 for the `/` denominator).
1984
+ */
1985
+ interface UnitNode {
1986
+ readonly kind: "term";
1987
+ /** The components and their operator sign, in source order. */
1988
+ readonly factors: ReadonlyArray<{
1989
+ readonly sign: 1 | -1;
1990
+ readonly node: ComponentNode;
1991
+ }>;
1992
+ }
1993
+ /**
1994
+ * A unit reduced to base dimensions: a scalar `factor` times a map of dimension axis → exponent.
1995
+ * Dimension keys are the seven UCUM base symbols (`m s g rad K C cd`) plus `arb:<code>` axes for
1996
+ * arbitrary units. Two `linear` forms are the **same unit** iff equal dims and equal factor.
1997
+ */
1998
+ interface LinearReduction {
1999
+ readonly kind: "linear";
2000
+ /** The accumulated scalar factor in base units. */
2001
+ readonly factor: number;
2002
+ /** Dimension axis → non-zero exponent (zero exponents omitted). */
2003
+ readonly dims: Readonly<Record<string, number>>;
2004
+ }
2005
+ /**
2006
+ * A **non-linear** (special) reduction — the expression involves a special unit (`Cel`, `B`, …) and
2007
+ * cannot be reduced to a scalar×dimension form. Carried by its normalized representation `form`;
2008
+ * two special reductions are the same unit iff their `form`s are identical (never equated with a
2009
+ * linear unit — the never-fabricate posture applied to units).
2010
+ */
2011
+ interface SpecialReduction {
2012
+ readonly kind: "special";
2013
+ /** The normalized expression string (prefixes/atoms/exponents; annotations stripped). */
2014
+ readonly form: string;
2015
+ }
2016
+ /** The reduced canonical form of a unit expression. */
2017
+ type Reduction = LinearReduction | SpecialReduction;
2018
+ /**
2019
+ * The result of {@link validateUcum}: a valid unit with its canonical descriptor, or a typed
2020
+ * invalid carrying the stable {@link TERM_UCUM_INVALID} diagnostic and a value-free reason.
2021
+ */
2022
+ type UcumValidation = {
2023
+ /** The expression is a well-formed UCUM unit over known atoms. */
2024
+ readonly valid: true;
2025
+ /**
2026
+ * A stable, normalized **descriptor** of the unit's canonical form — equal iff the units are
2027
+ * the same (see {@link ucumEqual}). Note: this is a comparison/debug descriptor, **not**
2028
+ * guaranteed to be a re-parseable UCUM expression (UCUM cannot express arbitrary numeric
2029
+ * factors as literals).
2030
+ */
2031
+ readonly canonical: string;
2032
+ } | {
2033
+ /** The expression is not a valid UCUM unit. */
2034
+ readonly valid: false;
2035
+ /** The stable diagnostic code — never a guessed "nearest" unit (the never-fabricate rule). */
2036
+ readonly code: "TERM_UCUM_INVALID";
2037
+ /** A **value-free** structural reason (a grammar fault or an unknown atom; never PHI). */
2038
+ readonly reason: string;
2039
+ };
2040
+
2041
+ /**
2042
+ * The public **UCUM** surface (roadmap Phase 4): {@link validateUcum} (recognition + a canonical
2043
+ * descriptor) and {@link ucumEqual} (are two expressions the *same unit*).
2044
+ *
2045
+ * Both are **recognition / validation / representation-canonicalization only** — never magnitude
2046
+ * conversion (roadmap §2/§4.3). The never-fabricate posture holds: an unparseable or unknown unit is
2047
+ * a typed {@link TERM_UCUM_INVALID} outcome carrying a value-free reason, **never** a guessed
2048
+ * "nearest" unit; and a special (non-linear) unit is never claimed equal to a linear one.
2049
+ *
2050
+ * @packageDocumentation
2051
+ */
2052
+
2053
+ /**
2054
+ * Validate a UCUM unit expression and, when valid, return its canonical descriptor.
2055
+ *
2056
+ * Validation **is** parsing against the UCUM grammar over the known atom table: an expression is
2057
+ * valid iff it parses completely. An invalid expression returns a typed {@link TERM_UCUM_INVALID}
2058
+ * with a value-free reason — never a coerced or guessed unit.
2059
+ *
2060
+ * @param unit - The UCUM unit expression (e.g. `"mmol/L"`, `"kg.m/s2"`, `"Cel"`).
2061
+ * @returns `{ valid: true, canonical }` or `{ valid: false, code: "TERM_UCUM_INVALID", reason }`.
2062
+ * @example
2063
+ * ```ts
2064
+ * import { validateUcum } from "@cosyte/terminology";
2065
+ *
2066
+ * validateUcum("mmol/L").valid; // => true
2067
+ * const bad = validateUcum("mg/dl/"); // trailing operator
2068
+ * bad.valid; // => false
2069
+ * ```
2070
+ */
2071
+ declare function validateUcum(unit: string): UcumValidation;
2072
+ /**
2073
+ * Whether two UCUM expressions denote the **same unit** — i.e. they reduce to the same canonical
2074
+ * form (equal base dimensions *and* equal scale). This recognizes representational equivalences
2075
+ * (`N` ≡ `kg.m/s2`, `mmol/L` ≡ `mmol.L-1`, annotations inert) but is **not** magnitude conversion:
2076
+ * `mg` and `g` are different units and compare unequal.
2077
+ *
2078
+ * A unit is never equal to an invalid expression, and a special (non-linear) unit (`Cel`, `[pH]`) is
2079
+ * equal only to a structurally identical special unit — never to a linear unit (the never-fabricate
2080
+ * posture: the engine will not assert an equivalence it cannot prove).
2081
+ *
2082
+ * @param a - The first UCUM unit expression.
2083
+ * @param b - The second UCUM unit expression.
2084
+ * @returns `true` iff both are valid and denote the same unit.
2085
+ * @example
2086
+ * ```ts
2087
+ * import { ucumEqual } from "@cosyte/terminology";
2088
+ *
2089
+ * ucumEqual("N", "kg.m/s2"); // => true
2090
+ * ucumEqual("mmol/L", "mmol.L-1"); // => true
2091
+ * ucumEqual("mg", "g"); // => false
2092
+ * ```
2093
+ */
2094
+ declare function ucumEqual(a: string, b: string): boolean;
2095
+
2096
+ /**
2097
+ * The **UCUM grammar parser** (roadmap Phase 4): a hand-rolled, zero-dependency recursive-descent
2098
+ * parser from a unit expression string to a {@link UnitNode} AST, grounded firsthand on the UCUM
2099
+ * specification's syntax (base units, metric prefixes attached with no delimiter and resolved by
2100
+ * longest-match, `.` multiply / `/` divide, signed integer exponents, `{…}` inert annotations, and
2101
+ * the `10*` / `10^` powers-of-ten). Validation **is** parsing: an expression is valid iff it parses
2102
+ * completely over **known** atoms — an unknown atom yields no match and fails, never a guessed unit.
2103
+ *
2104
+ * @packageDocumentation
2105
+ */
2106
+
2107
+ /** A successful parse (the AST) or a value-free failure reason. */
2108
+ type ParseResult = {
2109
+ readonly ok: true;
2110
+ readonly node: UnitNode;
2111
+ } | ParseFailure;
2112
+ /** A parse failure carrying a value-free structural reason. */
2113
+ interface ParseFailure {
2114
+ readonly ok: false;
2115
+ /** A structural description of the fault (never echoes an input *value* beyond the offending token shape). */
2116
+ readonly reason: string;
2117
+ }
2118
+ /**
2119
+ * Parse a UCUM unit expression into an AST.
2120
+ *
2121
+ * @param unit - The unit expression (e.g. `"mmol/L"`, `"kg.m/s2"`, `"10*3/ul"`).
2122
+ * @returns The parsed {@link UnitNode}, or a typed {@link ParseFailure} with a value-free reason.
2123
+ * @example
2124
+ * ```ts
2125
+ * import { parseUcum } from "@cosyte/terminology";
2126
+ *
2127
+ * parseUcum("mmol/L").ok; // => true
2128
+ * parseUcum("m/").ok; // => false
2129
+ * ```
2130
+ */
2131
+ declare function parseUcum(unit: string): ParseResult;
2132
+
2133
+ /**
2134
+ * **Dimensional reduction** of a parsed UCUM unit (roadmap Phase 4): reduce an expression to its
2135
+ * canonical base-unit form — a scalar factor times a map of the seven UCUM base dimensions
2136
+ * (`m s g rad K C cd`), recursively substituting each atom's essence definition down to base units.
2137
+ *
2138
+ * This is **representation** canonicalization, not magnitude *conversion*: it answers "are these two
2139
+ * expressions the same unit" (`N` ≡ `kg.m/s2`, `mmol/L` ≡ `mmol.L-1`), never "what is 5 mg/dL in
2140
+ * mmol/L" (that needs an analyte's molar mass — refused, roadmap §2/§4.3). **Special** units (`Cel`,
2141
+ * `B`, `[pH]`) are non-linear and carry no factor/dimension — they reduce to an opaque normalized
2142
+ * form and are never equated with a linear unit. **Arbitrary** units (`[IU]`, `[arb'U]`) are modeled
2143
+ * as their own dimension axis, so they cancel with themselves but convert to nothing else.
2144
+ *
2145
+ * @packageDocumentation
2146
+ */
2147
+
2148
+ /**
2149
+ * Reduce a parsed UCUM unit to its canonical {@link Reduction} — a linear scalar×dimension form, or
2150
+ * an opaque `special` form for expressions involving a non-linear special unit.
2151
+ *
2152
+ * @param node - The AST from {@link parseUcum}.
2153
+ * @returns The reduced form.
2154
+ * @example
2155
+ * ```ts
2156
+ * import { parseUcum, reduce } from "@cosyte/terminology";
2157
+ *
2158
+ * const parsed = parseUcum("N");
2159
+ * if (parsed.ok) {
2160
+ * const r = reduce(parsed.node);
2161
+ * r.kind; // => "linear" (1000 × g·m·s⁻²)
2162
+ * }
2163
+ * ```
2164
+ */
2165
+ declare function reduce(node: UnitNode): Reduction;
2166
+
2167
+ /**
2168
+ * Parse the vendored, verbatim UCUM `ucum-essence.xml` (embedded as {@link UCUM_ESSENCE_XML}) into
2169
+ * the engine's in-memory unit model — the {@link UcumEssence} table of prefixes and atoms.
2170
+ *
2171
+ * Hand-rolled, **zero-dependency** scanning of a known-well-formed document (not a general XML
2172
+ * parser): the essence file is a flat, regular list of `<prefix>` / `<base-unit>` / `<unit>`
2173
+ * elements. Parsed **once**, lazily, and cached — the engine reads no file at runtime; the table is
2174
+ * a pure transform of the embedded verbatim content (a permitted UCUM use, not a derivative — see
2175
+ * `vendor/ucum/NOTICE.md`).
2176
+ *
2177
+ * @packageDocumentation
2178
+ */
2179
+
2180
+ /**
2181
+ * Load the UCUM essence table, parsing the embedded verbatim XML on first call and caching it.
2182
+ *
2183
+ * @returns The immutable in-memory {@link UcumEssence} model (7 base units, 24 prefixes, ~305 atoms).
2184
+ * @example
2185
+ * ```ts
2186
+ * import { loadUcumEssence } from "@cosyte/terminology";
2187
+ *
2188
+ * const essence = loadUcumEssence();
2189
+ * essence.atomByCode.has("m"); // => true
2190
+ * ```
2191
+ */
2192
+ declare function loadUcumEssence(): UcumEssence;
2193
+
2194
+ /**
2195
+ * The types for the **crosswalk resolvers** (roadmap Phase 5) — the CMS ICD-9↔ICD-10 **GEMs**
2196
+ * (General Equivalence Mappings) and the NLM **SNOMED CT → ICD-10-CM complex map**.
2197
+ *
2198
+ * Both are the library's highest-risk surface, and their types are built around the stewards' own
2199
+ * warnings: these maps are **approximate, 1:many, context-dependent, and non-invertible** — *"not
2200
+ * crosswalks… reference mappings"* (CMS on the GEMs), *"semi-automated"* (NLM on the SNOMED map). So
2201
+ * every result type here is a **discriminated, fail-safe outcome** carrying the steward's flags,
2202
+ * never a single collapsed target:
2203
+ *
2204
+ * - a No-Map is a typed {@link CrosswalkNoMap} — the steward said "cannot be classified", surfaced,
2205
+ * never a guessed code;
2206
+ * - a 1:many source yields the **whole** ordered candidate set, never one confident pick;
2207
+ * - a context-dependent rule with no runtime context is a typed {@link ComplexMapContextRequired} —
2208
+ * the rule + advice ride through, the engine never picks a branch it wasn't given the data for;
2209
+ * - all Map Advice / GEM flags ride through **verbatim** ("advice flags must not be swallowed",
2210
+ * roadmap §4.1);
2211
+ * - a directional map is **never inverted** (see {@link ../crosswalk/gems.invertGem}).
2212
+ *
2213
+ * @packageDocumentation
2214
+ */
2215
+
2216
+ /**
2217
+ * The **direction** of a loaded GEM file. CMS ships the forward (ICD-9→ICD-10, `…_I9gem.txt`) and
2218
+ * backward (ICD-10→ICD-9, `…_I10gem.txt`) maps as **separate, non-inverse artifacts** — a forward map
2219
+ * composed with a backward map is *"not a mirror image"* (roadmap §4.1). A `GemMap` therefore records
2220
+ * which direction it is and is only ever applied in that direction.
2221
+ */
2222
+ type GemDirection = "9-to-10" | "10-to-9";
2223
+ /**
2224
+ * The decoded **5-position flag field** of a GEM entry (CMS Dx GEM User's Guide). Each digit is a
2225
+ * steward signal a caller must not ignore; the verbatim field rides through as {@link raw}.
2226
+ *
2227
+ * Positions (left to right): `approximate | no-map | combination | scenario | choice-list`.
2228
+ */
2229
+ interface GemFlags {
2230
+ /**
2231
+ * Position 1 — **approximate** flag. `true` when the mapping is approximate (the common case: the
2232
+ * source and target are not identical in meaning); `false` when the entry is an exact identity. A
2233
+ * GEM entry is a *reference* mapping either way — never an assertion of clinical equivalence.
2234
+ */
2235
+ readonly approximate: boolean;
2236
+ /**
2237
+ * Position 2 — **no-map** flag. `true` when the source code has **no valid target** in the other
2238
+ * classification (the target field is the `NoDx`/`NoPCS` sentinel). A first-class typed outcome —
2239
+ * surfaced as a {@link CrosswalkNoMap}, never a fabricated target (roadmap §4.1).
2240
+ */
2241
+ readonly noMap: boolean;
2242
+ /**
2243
+ * Position 3 — **combination** flag. `true` when the source requires a **combination** of target
2244
+ * codes to be fully represented; the entry is one candidate within a scenario/choice-list cluster
2245
+ * (see {@link scenario} / {@link choiceList}), never a standalone 1:1 target.
2246
+ */
2247
+ readonly combination: boolean;
2248
+ /**
2249
+ * Position 4 — **scenario** number (`0` for a non-combination entry). Within a combination, all
2250
+ * entries sharing a scenario describe **one** valid way to represent the source; distinct scenarios
2251
+ * are alternative representations.
2252
+ */
2253
+ readonly scenario: number;
2254
+ /**
2255
+ * Position 5 — **choice-list** number (`0` for a non-combination entry). Within a scenario, a valid
2256
+ * cluster is built by taking **one** target from each distinct choice list.
2257
+ */
2258
+ readonly choiceList: number;
2259
+ /** The verbatim 5-character flag field, carried through untouched. */
2260
+ readonly raw: string;
2261
+ }
2262
+ /**
2263
+ * One loaded GEM entry — a source code, its (optional) target, and the decoded {@link GemFlags}. A
2264
+ * No-Map entry ({@link GemFlags.noMap}) carries **no** {@link target}: the source has no equivalent,
2265
+ * and the engine never invents one.
2266
+ */
2267
+ interface GemEntry {
2268
+ /** The source code (in the map's source classification), verbatim. */
2269
+ readonly source: string;
2270
+ /** The target code (in the map's target classification), verbatim — **absent** for a No-Map entry. */
2271
+ readonly target?: string;
2272
+ /** The decoded steward flags. */
2273
+ readonly flags: GemFlags;
2274
+ }
2275
+ /**
2276
+ * A loaded, immutable GEM map. Entries are keyed by source code (a source may have many entries — the
2277
+ * maps are 1:many). Applied **only** in its {@link direction}; never inverted.
2278
+ */
2279
+ interface GemMap {
2280
+ /** Which direction this file maps. Applied only this way. */
2281
+ readonly direction: GemDirection;
2282
+ /** The GEM version/release (e.g. `"2018"`), when supplied — mappings are release-scoped. */
2283
+ readonly version?: string;
2284
+ /** The number of loaded entries. */
2285
+ readonly count: number;
2286
+ /** Entries keyed by source code, each list in file order. Read-only; frozen. */
2287
+ readonly entries: ReadonlyMap<string, readonly GemEntry[]>;
2288
+ /** The skipped-row warnings surfaced during load, in line order. Frozen; may be empty. */
2289
+ readonly warnings: readonly GemLoadWarning[];
2290
+ }
2291
+ /**
2292
+ * A surfaced, non-fatal GEM load warning — a malformed line that was **skipped**, reported with its
2293
+ * line number and a value-free structural reason. Liberal on load (roadmap §6, the parsers' posture).
2294
+ */
2295
+ interface GemLoadWarning {
2296
+ /** The stable diagnostic code for a skipped GEM row. */
2297
+ readonly code: "TERM_GEM_MALFORMED_ROW";
2298
+ /** The 1-based source line the fault was found on. */
2299
+ readonly line: number;
2300
+ /** A value-free description of the structural fault (never echoes a field value). */
2301
+ readonly detail: string;
2302
+ }
2303
+ /** One choice list within a combination scenario — pick **one** of its targets to build a cluster. */
2304
+ interface GemChoiceList {
2305
+ /** The choice-list number ({@link GemFlags.choiceList}). */
2306
+ readonly choiceList: number;
2307
+ /** The candidate target codes for this choice list, in file order. Frozen; never empty. */
2308
+ readonly targets: readonly string[];
2309
+ }
2310
+ /**
2311
+ * One combination **scenario** — a single valid way to represent the source as a combination of
2312
+ * target codes. A valid **cluster** is one target taken from each of its {@link choiceLists}.
2313
+ */
2314
+ interface GemScenario {
2315
+ /** The scenario number ({@link GemFlags.scenario}). */
2316
+ readonly scenario: number;
2317
+ /** The choice lists composing this scenario, ordered by choice-list number. Frozen. */
2318
+ readonly choiceLists: readonly GemChoiceList[];
2319
+ }
2320
+ /**
2321
+ * A successful GEM application — one or more candidate targets for the source. The **full** candidate
2322
+ * set is surfaced (never collapsed to one); when the source is a combination, {@link combinations}
2323
+ * carries the scenario/choice-list structure needed to build valid clusters.
2324
+ */
2325
+ interface GemMatched {
2326
+ /** Discriminant: the source mapped to at least one target. */
2327
+ readonly mapped: true;
2328
+ /** The source code, echoed. */
2329
+ readonly source: string;
2330
+ /** The direction the map was applied in. */
2331
+ readonly direction: GemDirection;
2332
+ /** Every candidate entry for the source, in file order. Frozen; never empty here. */
2333
+ readonly entries: readonly GemEntry[];
2334
+ /** For a combination source, the scenario→choice-list structure. Absent for a plain 1:many source. */
2335
+ readonly combinations?: readonly GemScenario[];
2336
+ }
2337
+ /**
2338
+ * One row of the SNOMED CT → ICD-10-CM **complex/extended map** refset — the caller-supplied,
2339
+ * SNOMED-licensed content the resolver runs over (**no SNOMED content is bundled**; roadmap §5). The
2340
+ * field names paraphrase the SNOMED RF2 extended-map refset columns.
2341
+ */
2342
+ interface ComplexMapEntry {
2343
+ /** The SNOMED CT source concept id (`referencedComponentId`), verbatim. */
2344
+ readonly source: string;
2345
+ /**
2346
+ * The **map group** (1-based). Distinct groups on one source are an **AND** — the source needs a
2347
+ * target from *each* group (e.g. a manifestation code plus an etiology code). Every group resolves
2348
+ * independently.
2349
+ */
2350
+ readonly group: number;
2351
+ /**
2352
+ * The **map priority** within a group (1-based). Rules are evaluated in ascending priority; the
2353
+ * first whose {@link rule} matches the caller's context wins that group (an if-then-else chain).
2354
+ */
2355
+ readonly priority: number;
2356
+ /**
2357
+ * The **map rule**, verbatim — `"TRUE"` / `"OTHERWISE TRUE"` (unconditional fall-through), or an
2358
+ * `IFA` predicate (e.g. an age-band or gender test) evaluated against caller-supplied context.
2359
+ */
2360
+ readonly rule: string;
2361
+ /**
2362
+ * The **map advice**, verbatim (pipe-delimited in the source) — steward instructions to a human
2363
+ * (`CONSIDER LATERALITY`, `EPISODE OF CARE INFORMATION NEEDED`, `THIS IS A MANIFESTATION CODE…`).
2364
+ * Carried through untouched: advice flags must **never** be swallowed (roadmap §4.1).
2365
+ */
2366
+ readonly advice: string;
2367
+ /** The ICD-10-CM **map target** code, verbatim — **absent** for a No-Map / empty-target row. */
2368
+ readonly target?: string;
2369
+ /** The steward **map category** id (a `MAP_CATEGORIES` value, e.g. `447638001`), when supplied. */
2370
+ readonly category?: string;
2371
+ /** The map **correlation** id (SNOMED map-correlation concept), when supplied. Carried verbatim. */
2372
+ readonly correlation?: string;
2373
+ }
2374
+ /**
2375
+ * A loaded, immutable SNOMED→ICD-10-CM complex map. Entries are keyed by source concept id; within a
2376
+ * source they are ordered by (group, priority). Applied **only** SNOMED→ICD-10-CM; never inverted.
2377
+ */
2378
+ interface ComplexMap {
2379
+ /** The map version/release, when supplied — mappings are release-scoped. */
2380
+ readonly version?: string;
2381
+ /** The number of loaded rows. */
2382
+ readonly count: number;
2383
+ /** Rows keyed by SNOMED source concept id, each ordered by (group, priority). Frozen. */
2384
+ readonly entries: ReadonlyMap<string, readonly ComplexMapEntry[]>;
2385
+ /** The skipped-row warnings surfaced during load, in line order. Frozen; may be empty. */
2386
+ readonly warnings: readonly ComplexMapLoadWarning[];
2387
+ }
2388
+ /** A surfaced, non-fatal complex-map load warning — a malformed refset row that was skipped. */
2389
+ interface ComplexMapLoadWarning {
2390
+ /** The stable diagnostic code for a skipped complex-map row. */
2391
+ readonly code: "TERM_COMPLEX_MAP_MALFORMED_ROW";
2392
+ /** The 1-based source line the fault was found on. */
2393
+ readonly line: number;
2394
+ /** A value-free description of the structural fault. */
2395
+ readonly detail: string;
2396
+ }
2397
+ /**
2398
+ * The **caller-supplied patient context** an `IFA` rule is evaluated against. The resolver evaluates
2399
+ * only against what it is given: a rule needing context absent here surfaces as a
2400
+ * {@link ComplexMapContextRequired}, never a silently-picked branch (roadmap §4.1).
2401
+ */
2402
+ interface PatientContext {
2403
+ /** The patient's age **in years**, when known (for `IFA … Age …` bands). */
2404
+ readonly ageYears?: number;
2405
+ /** The patient's administrative gender, when known (for gender `IFA` rules). */
2406
+ readonly gender?: "male" | "female";
2407
+ }
2408
+ /**
2409
+ * One map group's resolution — a discriminated outcome. A group resolves to a target, a No-Map, an
2410
+ * ambiguity (candidates surfaced), or a context-required stall; it is **never** a silently-picked
2411
+ * single target when the steward flagged otherwise.
2412
+ */
2413
+ type ComplexMapGroupResult = ComplexMapGroupResolved | ComplexMapGroupNoMap | ComplexMapContextRequired;
2414
+ /** A group that resolved to a concrete ICD-10-CM target via its winning rule. */
2415
+ interface ComplexMapGroupResolved {
2416
+ /** Discriminant. */
2417
+ readonly outcome: "resolved";
2418
+ /** The 1-based map group. */
2419
+ readonly group: number;
2420
+ /** The resolved ICD-10-CM target coding (drawn verbatim from the map). */
2421
+ readonly target: Coding;
2422
+ /** The steward map category, when supplied (e.g. `447637006`). */
2423
+ readonly category?: string;
2424
+ /** The winning rule's advice, verbatim. */
2425
+ readonly advice: string;
2426
+ /** The winning rule text, verbatim (`"TRUE"`, an `IFA` predicate, …). */
2427
+ readonly rule: string;
2428
+ }
2429
+ /**
2430
+ * A group the steward marked **No-Map** (`447638001` "cannot be classified", or an empty target) — a
2431
+ * typed, surfaced outcome, never a fabricated target.
2432
+ */
2433
+ interface ComplexMapGroupNoMap {
2434
+ /** Discriminant. */
2435
+ readonly outcome: "no-map";
2436
+ /** The stable diagnostic code. */
2437
+ readonly code: "TERM_CROSSWALK_NO_MAP";
2438
+ /** The 1-based map group. */
2439
+ readonly group: number;
2440
+ /** The steward map category, when supplied (`447638001`, or absent for an empty-target row). */
2441
+ readonly category?: string;
2442
+ /** The advice on the No-Map row, verbatim. */
2443
+ readonly advice: string;
2444
+ }
2445
+ /**
2446
+ * A group whose winning path is a **context-dependent** `IFA` rule (or `447639009`/`447640006`
2447
+ * category) for which the caller supplied **no** matching context. The engine surfaces the rules +
2448
+ * advice and refuses to pick a branch (roadmap §4.1) — the never-fabricate rule for runtime context.
2449
+ */
2450
+ interface ComplexMapContextRequired {
2451
+ /** Discriminant. */
2452
+ readonly outcome: "context-required";
2453
+ /** The stable diagnostic code. */
2454
+ readonly code: "TERM_CROSSWALK_CONTEXT_REQUIRED";
2455
+ /** The 1-based map group. */
2456
+ readonly group: number;
2457
+ /** The unresolved candidate rules in this group (priority order), each with its target + advice. */
2458
+ readonly rules: readonly ComplexMapEntry[];
2459
+ }
2460
+ /** A successful complex-map application — one resolution **per map group**, in group order. */
2461
+ interface ComplexMapMatched {
2462
+ /** Discriminant: the source concept is present in the map. */
2463
+ readonly mapped: true;
2464
+ /** The SNOMED source concept id, echoed. */
2465
+ readonly source: string;
2466
+ /** One resolution per map group, ordered by group. A source needing N codes has N groups. Frozen. */
2467
+ readonly groups: readonly ComplexMapGroupResult[];
2468
+ }
2469
+ /**
2470
+ * A **No-Map** outcome — the source is present in the map but the steward declares it has no valid
2471
+ * target ("cannot be classified with available data"). A first-class typed result, **never** a
2472
+ * fabricated target (roadmap §4.1). Distinct from {@link CrosswalkUnmapped} (source absent entirely).
2473
+ */
2474
+ interface CrosswalkNoMap {
2475
+ /** Discriminant: the source did not map. */
2476
+ readonly mapped: false;
2477
+ /** Sub-discriminant: this is an authored No-Map, not a plain absence. */
2478
+ readonly noMap: true;
2479
+ /** The stable diagnostic code. */
2480
+ readonly code: "TERM_CROSSWALK_NO_MAP";
2481
+ /** The source code, echoed. */
2482
+ readonly source: string;
2483
+ /** The steward flags/advice that declared the No-Map, carried verbatim. */
2484
+ readonly entries: readonly GemEntry[];
2485
+ }
2486
+ /**
2487
+ * An **unmapped** outcome — the source code is **not present in the map at all** (distinct from an
2488
+ * authored No-Map). Surfaced as a typed result, never a guessed target and never a silent success.
2489
+ */
2490
+ interface CrosswalkUnmapped {
2491
+ /** Discriminant: the source did not map. */
2492
+ readonly mapped: false;
2493
+ /** Sub-discriminant: a plain absence, not an authored No-Map. */
2494
+ readonly noMap: false;
2495
+ /** The stable diagnostic code. */
2496
+ readonly code: "TERM_CROSSWALK_UNMAPPED";
2497
+ /** The source code, echoed. */
2498
+ readonly source: string;
2499
+ }
2500
+ /** The result of {@link ../crosswalk/gems.applyGem}: a match, an authored No-Map, or an absence. */
2501
+ type GemApplyResult = GemMatched | CrosswalkNoMap | CrosswalkUnmapped;
2502
+ /**
2503
+ * The result of {@link ../crosswalk/complex-map.applyComplexMap}: a per-group match, or a typed
2504
+ * absence when the source concept is not in the map.
2505
+ */
2506
+ type ComplexMapApplyResult = ComplexMapMatched | CrosswalkUnmapped;
2507
+
2508
+ /**
2509
+ * The CMS **ICD-9 ↔ ICD-10 GEMs** (General Equivalence Mappings) loader + applier (roadmap Phase 5).
2510
+ *
2511
+ * The GEMs are **CMS/NCHS public-domain** (US-government work) reference mappings between the ICD-9-CM
2512
+ * and ICD-10-CM diagnosis classifications. CMS is emphatic about what they are — *"GEMs are not
2513
+ * crosswalks. They are reference mappings… the raw material from which providers… derive specific
2514
+ * applied mappings"* — so this resolver treats a GEM hit as a **candidate**, never an equivalence:
2515
+ *
2516
+ * - **Approximate by default.** Position-1 flag: most entries are approximate (source and target are
2517
+ * not identical in meaning). The flag rides through; the engine asserts nothing beyond it.
2518
+ * - **No-Map is first-class.** A source with no valid target carries the `NoDx` sentinel (and the
2519
+ * position-2 flag); {@link applyGem} returns a typed {@link CrosswalkNoMap}, never a guessed code.
2520
+ * - **1:many, never collapsed.** A source with several targets returns the **whole** candidate set.
2521
+ * - **Combination clusters.** A combination source (position-3 flag) maps to a *combination* of
2522
+ * targets grouped by scenario (position 4) and choice list (position 5); the result surfaces that
2523
+ * structure so a caller can build valid clusters, never a single fabricated target.
2524
+ * - **Directional, never inverted.** The forward (9→10) and backward (10→9) files are **separate,
2525
+ * non-inverse** artifacts; {@link invertGem} refuses to synthesize one from the other.
2526
+ *
2527
+ * The engine ships **no GEM content**: the caller supplies the public-domain GEM file (BYO — roadmap
2528
+ * §2/§5). Bundling the public-domain packs is the separate content-packs phase.
2529
+ *
2530
+ * @packageDocumentation
2531
+ */
2532
+
2533
+ /**
2534
+ * A raw GEM source file, plus the direction it maps and its release version. BYO — the caller
2535
+ * supplies the public-domain CMS GEM file content.
2536
+ */
2537
+ interface GemSource {
2538
+ /** The raw GEM file content (whitespace-delimited: `source target flags`, one entry per line). */
2539
+ readonly content: string;
2540
+ /** Which direction this file maps (`"9-to-10"` for `…_I9gem.txt`, `"10-to-9"` for `…_I10gem.txt`). */
2541
+ readonly direction: GemDirection;
2542
+ /** The GEM release/version (e.g. `"2018"`), when known — mappings are release-scoped. */
2543
+ readonly version?: string;
2544
+ }
2545
+ /**
2546
+ * Load a CMS GEM file into an immutable {@link GemMap}, keyed by source code.
2547
+ *
2548
+ * Liberal on load: a structurally unusable *line* (not three whitespace-delimited fields, or a
2549
+ * malformed 5-digit flag) is **skipped and surfaced** as a {@link GemLoadWarning}, never a
2550
+ * partially-parsed entry. An unusable *source* is not possible here (an all-malformed file loads to an
2551
+ * empty map with warnings) — there is no fatal for GEM load. Ships **no** GEM content: the entries are
2552
+ * the caller's public-domain file (roadmap §5).
2553
+ *
2554
+ * @param source - The {@link GemSource} (raw content + direction + version).
2555
+ * @returns The immutable {@link GemMap}.
2556
+ * @example
2557
+ * ```ts
2558
+ * import { loadGems, applyGem } from "@cosyte/terminology";
2559
+ *
2560
+ * // Synthetic rows in the CMS GEM format (source target flags): an exact map and a No-Map.
2561
+ * const gems = loadGems({
2562
+ * direction: "9-to-10",
2563
+ * version: "2018",
2564
+ * content: "0010 A000 00000\nV290 NoDx 11000\n",
2565
+ * });
2566
+ * applyGem(gems, "0010").mapped; // => true
2567
+ * ```
2568
+ */
2569
+ declare function loadGems(source: GemSource): GemMap;
2570
+ /**
2571
+ * Apply a loaded GEM map to a source code, in the map's authored direction only.
2572
+ *
2573
+ * Returns the **full** candidate set (never collapsed to one), with each entry's steward flags; a
2574
+ * combination source additionally surfaces its scenario/choice-list structure. A source the steward
2575
+ * marked No-Map (`NoDx` sentinel) is a typed {@link CrosswalkNoMap}; a source **absent** from the map
2576
+ * is a typed {@link CrosswalkUnmapped} — the two are distinct, and neither is ever a fabricated code.
2577
+ *
2578
+ * @param map - A {@link GemMap} from {@link loadGems}.
2579
+ * @param source - The source code (in the map's source classification).
2580
+ * @returns A {@link GemApplyResult}.
2581
+ * @example
2582
+ * ```ts
2583
+ * import { loadGems, applyGem } from "@cosyte/terminology";
2584
+ *
2585
+ * const gems = loadGems({ direction: "9-to-10", content: "V290 NoDx 11000\n" });
2586
+ * const r = applyGem(gems, "V290");
2587
+ * r.mapped; // => false
2588
+ * ```
2589
+ */
2590
+ declare function applyGem(map: GemMap, source: string): GemApplyResult;
2591
+ /**
2592
+ * Refuse to invert a directional GEM map. **Always throws** {@link FATAL_CODES.TERM_MAP_NOT_INVERTIBLE}.
2593
+ *
2594
+ * The never-invert invariant made explicit and discoverable: a forward GEM (9→10) is **not** the
2595
+ * inverse of the backward GEM (10→9) — CMS ships them as separate artifacts and *"forward and
2596
+ * backward mappings are not simply the reverse"* (roadmap §4.1). To resolve the other direction, load
2597
+ * the other file with {@link loadGems}; the engine never synthesizes an inverse.
2598
+ *
2599
+ * @param map - The map an inversion was (incorrectly) requested for.
2600
+ * @returns Never — always throws.
2601
+ * @throws {TerminologyError} `TERM_MAP_NOT_INVERTIBLE`, always.
2602
+ * @example
2603
+ * ```ts runnable throws
2604
+ * import { loadGems, invertGem } from "@cosyte/terminology";
2605
+ *
2606
+ * const gems = loadGems({ direction: "9-to-10", content: "0010 A000 00000\n" });
2607
+ * invertGem(gems); // throws TERM_MAP_NOT_INVERTIBLE
2608
+ * ```
2609
+ */
2610
+ declare function invertGem(map: GemMap): never;
2611
+
2612
+ /**
2613
+ * The **SNOMED CT → ICD-10-CM complex-map** resolver (roadmap Phase 5) — a rule-based, **BYO**
2614
+ * resolver over the caller-supplied complex/extended-map refset. **No SNOMED CT content is bundled**
2615
+ * (roadmap §5): the engine ships the rule machinery; the caller supplies the map rows from their own
2616
+ * SNOMED-licensed release.
2617
+ *
2618
+ * The map is *"semi-automated"* by the steward's own statement — 1:many, context-dependent, with
2619
+ * first-class No-Map states. The resolver honours that:
2620
+ *
2621
+ * - **Map groups are an AND.** A source needing a manifestation *and* an etiology code has two groups;
2622
+ * {@link applyComplexMap} resolves each independently and returns one result per group.
2623
+ * - **Priorities are an if-then-else.** Within a group, rules run in ascending priority; the first
2624
+ * whose `IFA` predicate the caller's context satisfies wins.
2625
+ * - **Context it wasn't given, it never guesses.** If deciding a group requires an `IFA` predicate
2626
+ * (an age band, a gender) the caller left unspecified, the group is a typed
2627
+ * {@link ComplexMapContextRequired} carrying the candidate rules + advice — never a silently-picked
2628
+ * branch (roadmap §4.1).
2629
+ * - **No-Map is typed.** A `447638001` "cannot be classified" row (or an empty target) is a typed
2630
+ * No-Map, never a fabricated code.
2631
+ * - **Advice rides through verbatim.** `CONSIDER LATERALITY`, `EPISODE OF CARE INFORMATION NEEDED`,
2632
+ * manifestation-code advice — carried untouched to the caller.
2633
+ * - **Never inverted.** The map is applied SNOMED→ICD-10-CM only; the reverse is a separate artifact.
2634
+ *
2635
+ * @packageDocumentation
2636
+ */
2637
+
2638
+ /**
2639
+ * The discriminated input {@link loadComplexMap} accepts — already-structured refset rows, or a raw
2640
+ * RF2 tab-delimited extended-map refset the caller exported from their SNOMED release.
2641
+ */
2642
+ type ComplexMapInput = {
2643
+ readonly format: "rows";
2644
+ readonly rows: readonly ComplexMapEntry[];
2645
+ readonly version?: string;
2646
+ } | {
2647
+ readonly format: "rf2";
2648
+ readonly content: string;
2649
+ readonly version?: string;
2650
+ };
2651
+ /**
2652
+ * Load a SNOMED→ICD-10-CM complex map from **caller-supplied** rows or a raw RF2 refset into an
2653
+ * immutable {@link ComplexMap}, keyed by source concept and ordered by (group, priority).
2654
+ *
2655
+ * Liberal on load: a structurally unusable RF2 *row* is skipped and surfaced as a warning; only an
2656
+ * unusable *source* (empty content, header missing required columns) is a fatal
2657
+ * {@link FATAL_CODES.TERM_CROSSWALK_MALFORMED}. Ships **no** SNOMED content — the rows are the
2658
+ * caller's, under their own licence (roadmap §5).
2659
+ *
2660
+ * @param input - Structured `rows`, or a raw RF2 extended-map refset `content`.
2661
+ * @returns The immutable {@link ComplexMap}.
2662
+ * @throws {TerminologyError} `TERM_CROSSWALK_MALFORMED` when the source is structurally unusable.
2663
+ * @example
2664
+ * ```ts
2665
+ * import { loadComplexMap } from "@cosyte/terminology";
2666
+ *
2667
+ * const map = loadComplexMap({
2668
+ * format: "rows",
2669
+ * rows: [
2670
+ * { source: "195967001", group: 1, priority: 1, rule: "TRUE", advice: "ALWAYS J45.909", target: "J45.909", category: "447637006" },
2671
+ * ],
2672
+ * });
2673
+ * map.count; // => 1
2674
+ * ```
2675
+ */
2676
+ declare function loadComplexMap(input: ComplexMapInput): ComplexMap;
2677
+ /**
2678
+ * Apply a loaded SNOMED→ICD-10-CM complex map to a source concept and patient context.
2679
+ *
2680
+ * Resolves **each map group** independently (groups are an AND — a source needing multiple ICD-10-CM
2681
+ * codes has multiple groups) by running its rules in priority order against `context`. A group whose
2682
+ * decision needs `IFA` context the caller did not supply is a typed
2683
+ * {@link ComplexMapContextRequired}; a `447638001` row is a typed No-Map; every result carries the
2684
+ * steward's advice verbatim. **Never** fabricates a target, **never** inverts the map.
2685
+ *
2686
+ * @param map - A {@link ComplexMap} from {@link loadComplexMap}.
2687
+ * @param source - The SNOMED CT source concept id.
2688
+ * @param context - The caller-supplied {@link PatientContext} (age / gender). Optional; a missing
2689
+ * field that a rule needs yields a `context-required` group, never a guessed branch.
2690
+ * @returns A {@link ComplexMapApplyResult} — per-group resolutions, or a typed absence.
2691
+ * @example
2692
+ * ```ts
2693
+ * import { loadComplexMap, applyComplexMap } from "@cosyte/terminology";
2694
+ *
2695
+ * const map = loadComplexMap({
2696
+ * format: "rows",
2697
+ * rows: [
2698
+ * { source: "72098002", group: 1, priority: 1, rule: "IFA 248152002 | Female (finding) |", advice: "MAP IS CONTEXT DEPENDENT FOR GENDER", target: "O00.9", category: "447639009" },
2699
+ * { source: "72098002", group: 1, priority: 2, rule: "OTHERWISE TRUE", advice: "CANNOT BE CLASSIFIED", category: "447638001" },
2700
+ * ],
2701
+ * });
2702
+ * const r = applyComplexMap(map, "72098002", { gender: "female" });
2703
+ * r.mapped; // => true
2704
+ * ```
2705
+ */
2706
+ declare function applyComplexMap(map: ComplexMap, source: string, context?: PatientContext): ComplexMapApplyResult;
2707
+ /** The SNOMED CT source-system URI a complex-map source concept is drawn from (re-exported for docs). */
2708
+ declare const COMPLEX_MAP_SOURCE_SYSTEM = "http://snomed.info/sct";
2709
+
2710
+ /**
2711
+ * Steward-defined **map category** and **map-relationship** identifiers the crosswalk resolvers carry
2712
+ * through verbatim — the SNOMED CT complex-map `mapCategoryId` concepts and the map-correlation
2713
+ * concepts. These are **published SNOMED CT metadata identifiers** (concept ids in the SNOMED CT
2714
+ * Model Component module), encoded here as identity facts (like the code-system OID↔URI facts in
2715
+ * {@link ../systems/registry}); **no SNOMED CT content is bundled** — a caller still supplies the map
2716
+ * refset itself under their own SNOMED licence (roadmap §5, the BYO posture).
2717
+ *
2718
+ * The category on a resolved SNOMED→ICD-10-CM map row is the steward telling the caller *how much to
2719
+ * trust the target*: a `447638001` "cannot be classified" row is a **No-Map**, a `447640006`
2720
+ * "ambiguous" row must surface its candidates rather than pick one, and a `447639009`
2721
+ * "context-dependent" row needs runtime patient context. The resolver never collapses these to a
2722
+ * single confident target (roadmap §4.1).
2723
+ *
2724
+ * @packageDocumentation
2725
+ */
2726
+ /**
2727
+ * The SNOMED CT complex-map **map category** concept ids — the `mapCategoryId` column of the
2728
+ * SNOMED→ICD-10-CM extended-map refset. Encoded as published identity facts; carried through a
2729
+ * resolution verbatim so a caller sees the steward's own trust signal.
2730
+ *
2731
+ * @example
2732
+ * ```ts
2733
+ * import { MAP_CATEGORIES } from "@cosyte/terminology";
2734
+ *
2735
+ * MAP_CATEGORIES.NO_MAP; // => "447638001"
2736
+ * ```
2737
+ */
2738
+ declare const MAP_CATEGORIES: {
2739
+ /** `447637006` — "Map source concept is properly classified" (a normal, usable target). */
2740
+ readonly PROPERLY_CLASSIFIED: "447637006";
2741
+ /** `447638001` — "Map source concept cannot be classified with available data" — a **No-Map**. */
2742
+ readonly NO_MAP: "447638001";
2743
+ /** `447639009` — "Map of source concept is context dependent" (needs runtime patient context). */
2744
+ readonly CONTEXT_DEPENDENT: "447639009";
2745
+ /** `447640006` — "Source concept is ambiguous" — surface candidates, never pick one. */
2746
+ readonly AMBIGUOUS: "447640006";
2747
+ };
2748
+ /** A value from {@link MAP_CATEGORIES} — the SNOMED map-category id carried on a resolution. */
2749
+ type MapCategoryId = (typeof MAP_CATEGORIES)[keyof typeof MAP_CATEGORIES];
2750
+ /**
2751
+ * The set of {@link MAP_CATEGORIES} ids the resolver treats as a **No-Map** (a typed, surfaced
2752
+ * "cannot be classified", never a fabricated target).
2753
+ */
2754
+ declare const NO_MAP_CATEGORIES: ReadonlySet<string>;
2755
+ /**
2756
+ * The canonical code-system URI for **ICD-10-CM** — the target system of both crosswalks. A published
2757
+ * identity fact (mirrors {@link ../systems/registry.SYSTEM_IDENTITIES}); attached to a resolved
2758
+ * target {@link ../common/coding.Coding} so a translation is self-describing.
2759
+ */
2760
+ declare const ICD10CM_SYSTEM = "http://hl7.org/fhir/sid/icd-10-cm";
2761
+ /**
2762
+ * The canonical code-system URI for **ICD-9-CM** (the legacy diagnosis classification the GEMs bridge
2763
+ * to/from). A published identity fact; attached to a GEM target/source coding.
2764
+ */
2765
+ declare const ICD9CM_SYSTEM = "http://hl7.org/fhir/sid/icd-9-cm";
2766
+ /**
2767
+ * The canonical code-system URI for **SNOMED CT** — the source system of the SNOMED→ICD-10-CM complex
2768
+ * map. Recognised by URI only; **no SNOMED CT content is bundled** (roadmap §5).
2769
+ */
2770
+ declare const SNOMEDCT_SYSTEM = "http://snomed.info/sct";
2771
+
2772
+ /**
2773
+ * The types for the **RxNorm drug relationship graph** (roadmap Phase 6) — the ingredient / brand /
2774
+ * clinical-drug / dose-form graph navigated over a **caller-supplied** RxNorm RRF release.
2775
+ *
2776
+ * RxNorm is the US NLM's normalized drug nomenclature. Its concepts (`RXCUI`s) are typed by **term
2777
+ * type** ({@link TermType}: `IN` ingredient, `SCD` semantic clinical drug, `SBD` semantic branded
2778
+ * drug, `BN` brand name, `DF` dose form, …) and wired together by **directed relationships** carried
2779
+ * in `RXNREL.RRF` and labeled by a `RELA` (`has_ingredient`, `tradename_of`, `has_dose_form`,
2780
+ * `consists_of`, …). The graph these form is the whole point of this phase: resolve an `SCD` to its
2781
+ * ingredients, a branded drug to its generic, a drug to its dose form.
2782
+ *
2783
+ * **The direction convention is a documented medication-safety trap** (roadmap §4.2 / §10 Q5), and it
2784
+ * is grounded here firsthand on the NLM RxNorm Technical Documentation (§12.7) and the UMLS Reference
2785
+ * Manual: *"the direction of REL — the relationship which the SECOND concept or atom (with … RXCUI2
2786
+ * …) HAS TO the FIRST concept or atom (with … RXCUI1 …)."* So every `RXNREL` row `(RXCUI1, …, RELA,
2787
+ * RXCUI2, …)` is read **`RXCUI2 ⟶RELA⟶ RXCUI1`** and normalized at load into a
2788
+ * {@link RxNormEdge} with `subject = RXCUI2`, `predicate = RELA`, `object = RXCUI1`. Navigation only
2789
+ * ever follows **authored** edges by their predicate; the engine **never synthesizes an inverse edge**
2790
+ * (RxNorm already ships both directions of an asymmetric relationship as separate rows) and **never
2791
+ * fabricates** an `RXCUI` or a relationship absent from the loaded data — an absent concept is a typed
2792
+ * {@link RxNormUnknown}, an unresolvable NDC a typed {@link NdcUnmapped}, never a guess.
2793
+ *
2794
+ * **No RxNorm content is bundled** (roadmap §5): the engine ships the graph *machinery*; the caller
2795
+ * supplies the RRF release (the public-domain Current Prescribable Content, or the full BYO release).
2796
+ * Bundling the public-domain Prescribable pack is the separate content-packs phase (Phase 7).
2797
+ *
2798
+ * @packageDocumentation
2799
+ */
2800
+ /**
2801
+ * A RxNorm **term type** (`TTY`) — the kind of drug concept an `RXCUI` names. These are the
2802
+ * `RXNCONSO.RRF` `TTY` values relevant to the drug graph, grounded on the NLM RxNorm Technical
2803
+ * Documentation (Appendix 5). Carried verbatim on a {@link RxNormConcept}; the engine classifies but
2804
+ * never re-labels a concept.
2805
+ */
2806
+ type TermType = "IN" | "PIN" | "MIN" | "BN" | "SCDC" | "SBDC" | "SCD" | "SBD" | "SCDG" | "SBDG" | "GPCK" | "BPCK" | "DF" | "DFG" | "PSN" | "SY" | "TMSY" | "ET";
2807
+ /**
2808
+ * One RxNorm concept (`RXCUI`) as loaded from `RXNCONSO.RRF` — its normalized name and term type.
2809
+ * Built from the concept's `SAB=RXNORM` atom (the normalized form); the verbatim `RXCUI`, `TTY`, and
2810
+ * `STR` (name) ride through untouched.
2811
+ */
2812
+ interface RxNormConcept {
2813
+ /** The RxNorm concept unique identifier (`RXCUI`), verbatim. */
2814
+ readonly rxcui: string;
2815
+ /** The concept's term type ({@link TermType}), verbatim from `RXNCONSO.TTY`. */
2816
+ readonly tty: TermType;
2817
+ /** The concept's normalized name (`RXNCONSO.STR`), verbatim. */
2818
+ readonly name: string;
2819
+ /** `true` when the source atom is suppressed (`RXNCONSO.SUPPRESS` ≠ `N`) — surfaced, never hidden. */
2820
+ readonly suppressed: boolean;
2821
+ }
2822
+ /**
2823
+ * One directed relationship edge, normalized from an `RXNREL.RRF` row under the documented direction
2824
+ * convention (see the module doc): the row `(RXCUI1, …, RELA, RXCUI2, …)` becomes
2825
+ * `subject = RXCUI2`, `predicate = RELA`, `object = RXCUI1` — read **`subject ⟶predicate⟶ object`**.
2826
+ */
2827
+ interface RxNormEdge {
2828
+ /** The subject `RXCUI` — `RXCUI2` in the source row. The concept the relationship is *from*. */
2829
+ readonly subject: string;
2830
+ /** The relationship label (`RXNREL.RELA`, e.g. `has_ingredient`), verbatim. */
2831
+ readonly predicate: string;
2832
+ /** The object `RXCUI` — `RXCUI1` in the source row. The concept the relationship is *to*. */
2833
+ readonly object: string;
2834
+ }
2835
+ /**
2836
+ * A resolved **NDC → RXCUI** mapping (from an `RXNSAT.RRF` `ATN=NDC` attribute), carrying the
2837
+ * temporal {@link NdcStatus} and the **as-of release** it is valid for. NDC↔RXCUI is many:1 and
2838
+ * changes across releases (roadmap §4.2), so a resolution is never timeless — the release rides
2839
+ * through on {@link asOf}.
2840
+ */
2841
+ interface NdcResolution {
2842
+ /** Discriminant: the NDC resolved to a concept in the loaded release. */
2843
+ readonly resolved: true;
2844
+ /** The 11-digit NDC (as supplied), echoed. */
2845
+ readonly ndc: string;
2846
+ /** The `RXCUI` the NDC attribute is attached to in the loaded release. */
2847
+ readonly rxcui: string;
2848
+ /** The temporal status of the NDC ({@link NdcStatus}). */
2849
+ readonly status: NdcStatus;
2850
+ /** The release version the mapping is valid **as of**, when the graph carries one. */
2851
+ readonly asOf?: string;
2852
+ }
2853
+ /**
2854
+ * The temporal status of an NDC↔RXCUI mapping (RxNav `getNDCStatus`: `ACTIVE` / `OBSOLETE` / `ALIEN`
2855
+ * / `UNKNOWN`). An NDC found in the loaded release's `RXNSAT` attributes is `active` **as of that
2856
+ * release**; `obsolete` / `alien` come from the RxNav NDC-history data (a differential / BYO source,
2857
+ * not the base RRF concept files) and are surfaced only when the caller supplies them — the engine
2858
+ * **never fabricates** a non-current status (roadmap §4.2).
2859
+ */
2860
+ type NdcStatus = "active" | "obsolete" | "alien" | "unknown";
2861
+ /** An NDC that could not be resolved to any `RXCUI` in the loaded release — a typed absence. */
2862
+ interface NdcUnmapped {
2863
+ /** Discriminant: the NDC is not present in the loaded release. */
2864
+ readonly resolved: false;
2865
+ /** The stable diagnostic code. */
2866
+ readonly code: "TERM_RXNORM_NDC_UNMAPPED";
2867
+ /** The NDC (as supplied), echoed. */
2868
+ readonly ndc: string;
2869
+ }
2870
+ /** The result of {@link ../rxnorm/navigate.resolveNdc}: a resolution or a typed absence. */
2871
+ type NdcResult = NdcResolution | NdcUnmapped;
2872
+ /**
2873
+ * A successful graph-navigation result — the concept, the predicate followed, and the **full** set of
2874
+ * related concepts drawn verbatim from the loaded edges. `targets` may be **empty**: that is an
2875
+ * honest, data-grounded "the release has no such edge for this concept", never a fabricated relation.
2876
+ */
2877
+ interface RxNormRelated {
2878
+ /** Discriminant: the source `RXCUI` is present in the graph. */
2879
+ readonly found: true;
2880
+ /** The source `RXCUI`, echoed. */
2881
+ readonly rxcui: string;
2882
+ /** The relationship predicate(s) that were followed. */
2883
+ readonly predicates: readonly string[];
2884
+ /** The related concepts, in edge order, each drawn verbatim from the loaded graph. May be empty. */
2885
+ readonly targets: readonly RxNormConcept[];
2886
+ }
2887
+ /**
2888
+ * A typed **unknown** navigation result — the queried `RXCUI` is **absent from the loaded graph**. A
2889
+ * first-class outcome, never a fabricated concept or an empty success (the never-fabricate invariant,
2890
+ * applied to the drug graph — roadmap §4.2). Distinct from a {@link RxNormRelated} with empty
2891
+ * `targets`, which means "present, but no such relationship".
2892
+ */
2893
+ interface RxNormUnknown {
2894
+ /** Discriminant: the source `RXCUI` is not in the graph. */
2895
+ readonly found: false;
2896
+ /** The stable diagnostic code. */
2897
+ readonly code: "TERM_RXNORM_UNKNOWN_RXCUI";
2898
+ /** The queried `RXCUI`, echoed. */
2899
+ readonly rxcui: string;
2900
+ }
2901
+ /** The result of a graph-navigation query: a match (possibly empty) or a typed unknown source. */
2902
+ type RxNormNavResult = RxNormRelated | RxNormUnknown;
2903
+ /**
2904
+ * One **approximate**-match candidate (the opt-in, never-default path — roadmap §4.2). Every field is
2905
+ * marked so a caller can never mistake it for an exact identity: {@link approximate} is always `true`
2906
+ * and {@link score} is the labeled, derived similarity. The concept itself is drawn verbatim from the
2907
+ * loaded release.
2908
+ */
2909
+ interface RxNormApproximateMatch {
2910
+ /** Always `true` — this is a similarity match, never an exact code resolution. */
2911
+ readonly approximate: true;
2912
+ /** The matched concept, verbatim from the loaded release. */
2913
+ readonly concept: RxNormConcept;
2914
+ /** A derived, labeled similarity score in `[0, 1]` (token overlap). Never a clinical assertion. */
2915
+ readonly score: number;
2916
+ }
2917
+ /**
2918
+ * A surfaced, non-fatal RxNorm load warning — a malformed `RXNCONSO` / `RXNREL` / `RXNSAT` row that
2919
+ * was **skipped**, reported with its line number and a value-free structural reason (liberal on load,
2920
+ * roadmap §6). Never echoes a field value.
2921
+ */
2922
+ interface RxNormLoadWarning {
2923
+ /** The stable diagnostic code for a skipped RxNorm row. */
2924
+ readonly code: "TERM_RXNORM_MALFORMED_ROW";
2925
+ /** Which source file the fault was in. */
2926
+ readonly file: "RXNCONSO" | "RXNREL" | "RXNSAT";
2927
+ /** The 1-based source line the fault was found on. */
2928
+ readonly line: number;
2929
+ /** A value-free description of the structural fault. */
2930
+ readonly detail: string;
2931
+ }
2932
+ /**
2933
+ * A loaded, immutable RxNorm drug graph: concepts keyed by `RXCUI`, edges indexed by subject, and the
2934
+ * NDC→RXCUI attribute index — all frozen. Applied only in the authored edge direction; never inverted.
2935
+ */
2936
+ interface RxNormGraph {
2937
+ /** The release version (e.g. `"RXNORM_2026AA"`), when supplied — mappings are release-scoped. */
2938
+ readonly version?: string;
2939
+ /** Concepts keyed by `RXCUI` (first `SAB=RXNORM` atom per `RXCUI` wins). Frozen. */
2940
+ readonly concepts: ReadonlyMap<string, RxNormConcept>;
2941
+ /** Directed edges indexed by `subject` `RXCUI`, each list in file order. Frozen. */
2942
+ readonly edges: ReadonlyMap<string, readonly RxNormEdge[]>;
2943
+ /** NDC → { rxcui, status } index from `RXNSAT` `ATN=NDC` attributes. Frozen; may be empty. */
2944
+ readonly ndcs: ReadonlyMap<string, {
2945
+ readonly rxcui: string;
2946
+ readonly status: NdcStatus;
2947
+ }>;
2948
+ /** The number of loaded concepts. */
2949
+ readonly conceptCount: number;
2950
+ /** The number of loaded edges. */
2951
+ readonly edgeCount: number;
2952
+ /** The skipped-row warnings surfaced during load, in file/line order. Frozen; may be empty. */
2953
+ readonly warnings: readonly RxNormLoadWarning[];
2954
+ }
2955
+
2956
+ /**
2957
+ * The **RxNorm drug graph loader** (roadmap Phase 6) — build an immutable {@link RxNormGraph} from a
2958
+ * **caller-supplied** RxNorm RRF release (`RXNCONSO.RRF` + `RXNREL.RRF`, and optionally `RXNSAT.RRF`
2959
+ * for NDC attributes). Reuses the shared, zero-dep {@link ../codesystem/rrf.parseRrfLine} (pipe split,
2960
+ * reserved trailing pipe) — the same RRF reader the CodeSystem layer uses.
2961
+ *
2962
+ * **Column layouts, grounded firsthand** on the NLM RxNorm Technical Documentation (§ file
2963
+ * descriptions):
2964
+ * - `RXNCONSO.RRF` (18 cols): `RXCUI`(0) `LAT`(1) `TS`(2) `LUI`(3) `STT`(4) `SUI`(5) `ISPREF`(6)
2965
+ * `RXAUI`(7) `SAUI`(8) `SCUI`(9) `SDUI`(10) `SAB`(11) `TTY`(12) `CODE`(13) `STR`(14) `SRL`(15)
2966
+ * `SUPPRESS`(16) `CVF`(17). Only the **normalized** atoms (`SAB=RXNORM`) with a drug-graph `TTY`
2967
+ * become graph concepts; the first such atom per `RXCUI` wins.
2968
+ * - `RXNREL.RRF` (16 cols): `RXCUI1`(0) `RXAUI1`(1) `STYPE1`(2) `REL`(3) `RXCUI2`(4) `RXAUI2`(5)
2969
+ * `STYPE2`(6) `RELA`(7) `RUI`(8) `SRUI`(9) `SAB`(10) `SL`(11) `RG`(12) `DIR`(13) `SUPPRESS`(14)
2970
+ * `CVF`(15). Read `subject = RXCUI2`, `predicate = RELA`, `object = RXCUI1` (the direction
2971
+ * convention — see {@link ../rxnorm/rela}). Only concept-level rows (both `RXCUI`s present) with a
2972
+ * non-empty `RELA` become edges; atom-level (`RXAUI`) rows are not part of the concept graph.
2973
+ * - `RXNSAT.RRF` (13 cols): `RXCUI`(0) … `ATN`(8) `SAB`(9) `ATV`(10) `SUPPRESS`(11) `CVF`(12). Rows
2974
+ * with `ATN=NDC` index `ATV`(the NDC) → `RXCUI`, marked `active` **as of** the loaded release.
2975
+ *
2976
+ * **Liberal on load** (roadmap §6): a structurally unusable *row* (too few columns, missing a required
2977
+ * value) is **skipped and surfaced** as a `TERM_RXNORM_MALFORMED_ROW` warning, never a partial
2978
+ * concept/edge, never a crash. Rows that are simply *not of interest* (a non-`RXNORM` atom, an
2979
+ * atom-level relationship, a non-`NDC` attribute) are skipped **silently** — they are expected, not
2980
+ * faults. Ships **no** RxNorm content: every concept/edge is the caller's release (roadmap §5).
2981
+ *
2982
+ * @packageDocumentation
2983
+ */
2984
+
2985
+ /**
2986
+ * A raw RxNorm RRF release, BYO. The caller supplies the public-domain Current Prescribable Content
2987
+ * subset or the full (licensed) release; the engine bundles **no** RxNorm content (roadmap §5).
2988
+ */
2989
+ interface RxNormGraphSource {
2990
+ /** The raw `RXNCONSO.RRF` content (pipe-delimited concept/atom names). */
2991
+ readonly conso: string;
2992
+ /** The raw `RXNREL.RRF` content (pipe-delimited relationships). */
2993
+ readonly rel: string;
2994
+ /** The raw `RXNSAT.RRF` content (pipe-delimited attributes) — supply to resolve NDCs. Optional. */
2995
+ readonly sat?: string;
2996
+ /** The release version (e.g. `"RXNORM_2026AA"`), when known — mappings are release-scoped. */
2997
+ readonly version?: string;
2998
+ }
2999
+ /**
3000
+ * Load a **caller-supplied** RxNorm RRF release into an immutable {@link RxNormGraph}.
3001
+ *
3002
+ * Parses `RXNCONSO` (concepts), `RXNREL` (directed edges, normalized to the documented direction), and
3003
+ * — when supplied — `RXNSAT` (NDC attributes). Liberal on load: a structurally unusable row is a
3004
+ * skipped, surfaced {@link RxNormLoadWarning}, never partial. Ships **no** RxNorm content — the graph
3005
+ * is entirely the caller's release (roadmap §5).
3006
+ *
3007
+ * @param source - The {@link RxNormGraphSource} (RRF file contents + optional version).
3008
+ * @returns The immutable {@link RxNormGraph}.
3009
+ * @example
3010
+ * ```ts
3011
+ * import { loadRxNormGraph } from "@cosyte/terminology";
3012
+ *
3013
+ * // Synthetic rows in the RxNorm RRF wire format (no real RxNorm content is bundled).
3014
+ * const graph = loadRxNormGraph({
3015
+ * conso: "1|ENG||||||||||RXNORM|IN||lisinopril||N||\n2|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||",
3016
+ * // "SCD(2) has_ingredient IN(1)": subject=RXCUI2=2, object=RXCUI1=1.
3017
+ * rel: "1|||RN|2|||has_ingredient|||RXNORM||||||",
3018
+ * });
3019
+ * graph.conceptCount; // => 2
3020
+ * ```
3021
+ */
3022
+ declare function loadRxNormGraph(source: RxNormGraphSource): RxNormGraph;
3023
+
3024
+ /**
3025
+ * **RxNorm drug-graph navigation** (roadmap Phase 6) — resolve an `SCD` to its ingredients, a branded
3026
+ * drug to its generic, a drug to its dose form, and an NDC to its `RXCUI`, over a loaded
3027
+ * {@link RxNormGraph}. Every query is **fail-safe and never-fabricate** (roadmap §4.2):
3028
+ *
3029
+ * - a queried `RXCUI` absent from the graph is a typed {@link RxNormUnknown}, never a guessed concept;
3030
+ * - a present concept with no such relationship is a {@link RxNormRelated} with **empty** `targets` —
3031
+ * an honest, data-grounded "no such edge", distinct from "unknown concept";
3032
+ * - navigation follows only **authored** edges by their predicate; the engine **never synthesizes an
3033
+ * inverse** (RxNorm ships both directions as separate rows, so brand→generic follows the authored
3034
+ * `tradename_of` and generic→brand follows the authored `has_tradename`);
3035
+ * - an NDC not in the loaded release is a typed {@link NdcUnmapped}, and a resolution carries the
3036
+ * **as-of release** and temporal {@link ../rxnorm/types.NdcStatus} (never a timeless assertion);
3037
+ * - approximate matching is an **opt-in, explicitly labeled** path ({@link approximateMatch}), never
3038
+ * the default and never presented as an exact code resolution.
3039
+ *
3040
+ * @packageDocumentation
3041
+ */
3042
+
3043
+ /**
3044
+ * Look up a single concept by `RXCUI`.
3045
+ *
3046
+ * @param graph - A loaded {@link RxNormGraph}.
3047
+ * @param rxcui - The concept unique identifier.
3048
+ * @returns The {@link RxNormConcept}, or `undefined` when the `RXCUI` is not in the release.
3049
+ * @example
3050
+ * ```ts
3051
+ * import { loadRxNormGraph, getConcept } from "@cosyte/terminology";
3052
+ *
3053
+ * const g = loadRxNormGraph({ conso: "1|ENG||||||||||RXNORM|IN||aspirin||N||", rel: "" });
3054
+ * getConcept(g, "1")?.tty; // => "IN"
3055
+ * ```
3056
+ */
3057
+ declare function getConcept(graph: RxNormGraph, rxcui: string): RxNormConcept | undefined;
3058
+ /**
3059
+ * Follow one or more `RELA` predicates out of a concept (the generic navigation primitive). Direction
3060
+ * is the **authored** edge direction (`subject ⟶predicate⟶ object`); the engine never inverts.
3061
+ *
3062
+ * @param graph - A loaded {@link RxNormGraph}.
3063
+ * @param rxcui - The source concept.
3064
+ * @param predicate - A `RELA` name, or an array of them (any-of).
3065
+ * @returns A {@link RxNormNavResult}: the related concepts (possibly empty), or a typed unknown source.
3066
+ * @example
3067
+ * ```ts
3068
+ * import { loadRxNormGraph, relatedByRela, RELA } from "@cosyte/terminology";
3069
+ *
3070
+ * // "SCD(2) has_ingredient IN(1)": subject=RXCUI2=2, object=RXCUI1=1.
3071
+ * const g = loadRxNormGraph({
3072
+ * conso: "1|ENG||||||||||RXNORM|IN||lisinopril||N||\n2|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||",
3073
+ * rel: "1|||RN|2|||has_ingredient|||RXNORM||||||",
3074
+ * });
3075
+ * const r = relatedByRela(g, "2", RELA.HAS_INGREDIENT);
3076
+ * r.found && r.targets[0]?.name; // => "lisinopril"
3077
+ * ```
3078
+ */
3079
+ declare function relatedByRela(graph: RxNormGraph, rxcui: string, predicate: string | readonly string[]): RxNormNavResult;
3080
+ /**
3081
+ * The **ingredients** of a concept — the concepts it links to by a **direct** `has_ingredient` /
3082
+ * `has_precise_ingredient` edge in the loaded release.
3083
+ *
3084
+ * This follows the *authored, direct* edge only (never-fabricate, never a synthesized path). Note the
3085
+ * release's own topology: a clinical component (`SCDC`) links directly to its `IN`, whereas some `SCD`
3086
+ * releases route the ingredient through the component (`SCD ⟶consists_of⟶ SCDC ⟶has_ingredient⟶ IN`).
3087
+ * When a drug's ingredient is not a *direct* edge, this honestly returns found-with-empty-targets —
3088
+ * traverse {@link consistsOf} then `ingredientsOf` to reach the ingredient through the component. The
3089
+ * engine never invents the transitive edge.
3090
+ *
3091
+ * @param graph - A loaded {@link RxNormGraph}.
3092
+ * @param rxcui - The concept (a drug or a clinical/branded component).
3093
+ * @returns The directly-linked ingredient concepts (possibly empty), or a typed unknown source.
3094
+ * @example
3095
+ * ```ts
3096
+ * import { loadRxNormGraph, ingredientsOf } from "@cosyte/terminology";
3097
+ *
3098
+ * const g = loadRxNormGraph({
3099
+ * conso: "1|ENG||||||||||RXNORM|IN||lisinopril||N||\n2|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||",
3100
+ * rel: "1|||RN|2|||has_ingredient|||RXNORM||||||",
3101
+ * });
3102
+ * ingredientsOf(g, "2").found; // => true
3103
+ * ```
3104
+ */
3105
+ declare function ingredientsOf(graph: RxNormGraph, rxcui: string): RxNormNavResult;
3106
+ /**
3107
+ * The **generic** form(s) of a branded concept — an `SBD`/`BN`/`BPCK` `tradename_of` its generic. The
3108
+ * authored forward edge; the engine never inverts a `has_tradename` edge to synthesize this.
3109
+ *
3110
+ * @param graph - A loaded {@link RxNormGraph}.
3111
+ * @param rxcui - The branded concept.
3112
+ * @returns The generic concept(s) (possibly empty), or a typed unknown source.
3113
+ * @example
3114
+ * ```ts
3115
+ * import { loadRxNormGraph, genericFor } from "@cosyte/terminology";
3116
+ *
3117
+ * // "SBD(2) tradename_of SCD(1)": subject=RXCUI2=2, object=RXCUI1=1.
3118
+ * const g = loadRxNormGraph({
3119
+ * conso: "1|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||\n2|ENG||||||||||RXNORM|SBD||Zestril 10 MG Oral Tablet||N||",
3120
+ * rel: "1|||RN|2|||tradename_of|||RXNORM||||||",
3121
+ * });
3122
+ * genericFor(g, "2").found; // => true
3123
+ * ```
3124
+ */
3125
+ declare function genericFor(graph: RxNormGraph, rxcui: string): RxNormNavResult;
3126
+ /**
3127
+ * The **branded** form(s) of a generic concept — an `SCD`/`IN`/`GPCK` that `has_tradename` a brand.
3128
+ * The authored forward edge; not a synthesized inverse of `tradename_of`.
3129
+ *
3130
+ * @param graph - A loaded {@link RxNormGraph}.
3131
+ * @param rxcui - The generic concept.
3132
+ * @returns The branded concept(s) (possibly empty), or a typed unknown source.
3133
+ * @example
3134
+ * ```ts
3135
+ * import { loadRxNormGraph, brandsFor } from "@cosyte/terminology";
3136
+ *
3137
+ * const g = loadRxNormGraph({
3138
+ * conso: "1|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||\n2|ENG||||||||||RXNORM|SBD||Zestril 10 MG Oral Tablet||N||",
3139
+ * rel: "2|||RN|1|||has_tradename|||RXNORM||||||",
3140
+ * });
3141
+ * brandsFor(g, "1").found; // => true
3142
+ * ```
3143
+ */
3144
+ declare function brandsFor(graph: RxNormGraph, rxcui: string): RxNormNavResult;
3145
+ /**
3146
+ * The **dose form(s)** of a drug — the `DF`/`DFG` concepts it `has_dose_form` / `has_doseformgroup`.
3147
+ *
3148
+ * @param graph - A loaded {@link RxNormGraph}.
3149
+ * @param rxcui - The drug concept.
3150
+ * @returns The dose-form concept(s) (possibly empty), or a typed unknown source.
3151
+ * @example
3152
+ * ```ts
3153
+ * import { loadRxNormGraph, doseFormsOf } from "@cosyte/terminology";
3154
+ *
3155
+ * const g = loadRxNormGraph({
3156
+ * conso: "1|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||\n9|ENG||||||||||RXNORM|DF||Oral Tablet||N||",
3157
+ * rel: "9|||RN|1|||has_dose_form|||RXNORM||||||",
3158
+ * });
3159
+ * doseFormsOf(g, "1").found; // => true
3160
+ * ```
3161
+ */
3162
+ declare function doseFormsOf(graph: RxNormGraph, rxcui: string): RxNormNavResult;
3163
+ /**
3164
+ * The **components** a drug `consists_of` — an `SCD`/`SBD` → its `SCDC`/`SBDC` dose-form components.
3165
+ *
3166
+ * @param graph - A loaded {@link RxNormGraph}.
3167
+ * @param rxcui - The drug concept.
3168
+ * @returns The component concept(s) (possibly empty), or a typed unknown source.
3169
+ * @example
3170
+ * ```ts
3171
+ * import { loadRxNormGraph, consistsOf } from "@cosyte/terminology";
3172
+ *
3173
+ * const g = loadRxNormGraph({
3174
+ * conso: "2|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||\n3|ENG||||||||||RXNORM|SCDC||lisinopril 10 MG||N||",
3175
+ * rel: "3|||RN|2|||consists_of|||RXNORM||||||",
3176
+ * });
3177
+ * consistsOf(g, "2").found; // => true
3178
+ * ```
3179
+ */
3180
+ declare function consistsOf(graph: RxNormGraph, rxcui: string): RxNormNavResult;
3181
+ /**
3182
+ * Resolve an **NDC** (National Drug Code) to its `RXCUI` in the loaded release, carrying the temporal
3183
+ * status and the as-of release. NDC↔RXCUI is many:1 and changes across releases (roadmap §4.2), so an
3184
+ * NDC present in the release is `active` **as of that release**; an absent NDC is a typed
3185
+ * {@link NdcUnmapped}, never a guessed `RXCUI`.
3186
+ *
3187
+ * @param graph - A loaded {@link RxNormGraph} (load with `sat` supplied to populate NDC attributes).
3188
+ * @param ndc - The NDC (as it appears in the release, e.g. 11-digit).
3189
+ * @returns An {@link NdcResult} — a resolution with status + `asOf`, or a typed absence.
3190
+ * @example
3191
+ * ```ts
3192
+ * import { loadRxNormGraph, resolveNdc } from "@cosyte/terminology";
3193
+ *
3194
+ * const g = loadRxNormGraph({
3195
+ * conso: "2|ENG||||||||||RXNORM|SCD||lisinopril 10 MG Oral Tablet||N||",
3196
+ * rel: "",
3197
+ * sat: "2||||||||NDC|RXNORM|00000000000|N||",
3198
+ * version: "RXNORM_2026AA",
3199
+ * });
3200
+ * const r = resolveNdc(g, "00000000000");
3201
+ * r.resolved && r.status; // => "active"
3202
+ * ```
3203
+ */
3204
+ declare function resolveNdc(graph: RxNormGraph, ndc: string): NdcResult;
3205
+ /** Options for {@link approximateMatch}. */
3206
+ interface ApproximateMatchOptions {
3207
+ /** Minimum similarity (`0`–`1`) for a candidate to be returned. Default `0.34`. */
3208
+ readonly minScore?: number;
3209
+ /** Maximum number of candidates to return, best-first. Default `20`. */
3210
+ readonly limit?: number;
3211
+ }
3212
+ /**
3213
+ * **Opt-in, explicitly labeled** approximate name matching (roadmap §4.2) — find loaded concepts whose
3214
+ * names are token-similar to `query`. This is **never** the default resolution path and its results
3215
+ * are **never** an exact code assertion: every candidate is marked `approximate: true` with a derived
3216
+ * {@link RxNormApproximateMatch.score}. A caller opts in by calling this function explicitly.
3217
+ *
3218
+ * Coverage is not correctness (roadmap §4.2): a "no match" is an **empty** array (typed, honest),
3219
+ * distinct from a fabricated nearest guess. The engine never promotes an approximate hit to an exact
3220
+ * one.
3221
+ *
3222
+ * @param graph - A loaded {@link RxNormGraph}.
3223
+ * @param query - A free-text drug name to match against loaded concept names.
3224
+ * @param options - {@link ApproximateMatchOptions} (min score, limit).
3225
+ * @returns The labeled approximate candidates, best-first (possibly empty). Never an exact resolution.
3226
+ * @example
3227
+ * ```ts
3228
+ * import { loadRxNormGraph, approximateMatch } from "@cosyte/terminology";
3229
+ *
3230
+ * const g = loadRxNormGraph({
3231
+ * conso: "1|ENG||||||||||RXNORM|IN||lisinopril||N||",
3232
+ * rel: "",
3233
+ * });
3234
+ * approximateMatch(g, "lisinopril")[0]?.approximate; // => true
3235
+ * ```
3236
+ */
3237
+ declare function approximateMatch(graph: RxNormGraph, query: string, options?: ApproximateMatchOptions): readonly RxNormApproximateMatch[];
3238
+
3239
+ /**
3240
+ * The RxNorm **relationship (`RELA`) vocabulary** and the **direction convention** the drug graph is
3241
+ * built on — encoded here as published identity facts (like the code-system OID↔URI facts in
3242
+ * {@link ../systems/registry}), grounded firsthand on the NLM RxNorm Technical Documentation and the
3243
+ * UMLS Reference Manual. **No RxNorm content is bundled** — only the relationship *names* and the
3244
+ * *rule* for reading a row's direction (roadmap §5).
3245
+ *
3246
+ * **The direction rule (roadmap §4.2 / §10 Q5 — a documented medication-safety trap).** From the
3247
+ * RxNorm Technical Documentation (§12.7) and the UMLS Reference Manual, verbatim: *"the direction of
3248
+ * REL — the relationship which the SECOND concept or atom (with … RXCUI2 …) HAS TO the FIRST concept
3249
+ * or atom (with … RXCUI1 …)."* The doc's worked example — a row with `RXCUI1=1155862`, `RELA=isa`,
3250
+ * `RXCUI2=197589` — reads *"197589 **isa** 1155862"* (the second concept is-a the first).
3251
+ *
3252
+ * So an `RXNREL.RRF` row is read **`RXCUI2 ⟶RELA⟶ RXCUI1`**, and {@link ../rxnorm/load.loadRxNormGraph}
3253
+ * normalizes every row to `subject = RXCUI2`, `predicate = RELA`, `object = RXCUI1`. Concretely, a
3254
+ * `has_ingredient` row places the **drug** in `RXCUI2` and the **ingredient** in `RXCUI1` (the drug
3255
+ * *has* the ingredient); a `tradename_of` row places the **branded** concept in `RXCUI2` and the
3256
+ * **generic** in `RXCUI1` (the brand *is a tradename of* the generic). Getting this backwards is a
3257
+ * wrong-medication bug, so the fixtures pin it against the doc's convention.
3258
+ *
3259
+ * @packageDocumentation
3260
+ */
3261
+ /**
3262
+ * The drug-graph `RELA` relationship names, grounded on the NLM RxNorm Technical Documentation
3263
+ * (Appendix 1). Each is directional and read `subject ⟶RELA⟶ object` after normalization (see the
3264
+ * module doc). RxNorm ships both members of an inverse pair as **separate rows**, so the graph
3265
+ * traverses the authored predicate for the direction it wants — it **never synthesizes an inverse**.
3266
+ *
3267
+ * @example
3268
+ * ```ts
3269
+ * import { RELA } from "@cosyte/terminology";
3270
+ *
3271
+ * RELA.HAS_INGREDIENT; // => "has_ingredient"
3272
+ * ```
3273
+ */
3274
+ declare const RELA: {
3275
+ /** `has_ingredient` — subject (a drug/component) *has ingredient* object (an `IN`/`PIN`). */
3276
+ readonly HAS_INGREDIENT: "has_ingredient";
3277
+ /** `ingredient_of` — subject (an `IN`/`PIN`) *is an ingredient of* object (a drug/component). */
3278
+ readonly INGREDIENT_OF: "ingredient_of";
3279
+ /** `has_precise_ingredient` — subject *has precise ingredient* object (a `PIN`). */
3280
+ readonly HAS_PRECISE_INGREDIENT: "has_precise_ingredient";
3281
+ /** `precise_ingredient_of` — inverse of `has_precise_ingredient`. */
3282
+ readonly PRECISE_INGREDIENT_OF: "precise_ingredient_of";
3283
+ /** `tradename_of` — subject (a branded concept) *is a tradename of* object (its generic). */
3284
+ readonly TRADENAME_OF: "tradename_of";
3285
+ /** `has_tradename` — subject (a generic concept) *has tradename* object (a branded concept). */
3286
+ readonly HAS_TRADENAME: "has_tradename";
3287
+ /** `has_dose_form` — subject (a drug) *has dose form* object (a `DF`). */
3288
+ readonly HAS_DOSE_FORM: "has_dose_form";
3289
+ /** `dose_form_of` — subject (a `DF`) *is the dose form of* object (a drug). */
3290
+ readonly DOSE_FORM_OF: "dose_form_of";
3291
+ /** `has_doseformgroup` — subject *has dose-form group* object (a `DFG`). */
3292
+ readonly HAS_DOSE_FORM_GROUP: "has_doseformgroup";
3293
+ /** `doseformgroup_of` — inverse of `has_doseformgroup`. */
3294
+ readonly DOSE_FORM_GROUP_OF: "doseformgroup_of";
3295
+ /** `consists_of` — subject (an `SCD`/`SBD`) *consists of* object (its `SCDC`/`SBDC` component(s)). */
3296
+ readonly CONSISTS_OF: "consists_of";
3297
+ /** `constitutes` — subject (a component) *constitutes* object (the drug). Inverse of `consists_of`. */
3298
+ readonly CONSTITUTES: "constitutes";
3299
+ /** `contains` — subject (a pack) *contains* object (a member drug). */
3300
+ readonly CONTAINS: "contains";
3301
+ /** `contained_in` — subject (a drug) *is contained in* object (a pack). Inverse of `contains`. */
3302
+ readonly CONTAINED_IN: "contained_in";
3303
+ /** `has_quantified_form` — subject *has quantified form* object. */
3304
+ readonly HAS_QUANTIFIED_FORM: "has_quantified_form";
3305
+ /** `quantified_form_of` — inverse of `has_quantified_form`. */
3306
+ readonly QUANTIFIED_FORM_OF: "quantified_form_of";
3307
+ /** `isa` — subject *is a* object (hierarchical, e.g. `SCD isa SCDG`). */
3308
+ readonly ISA: "isa";
3309
+ /** `inverse_isa` — subject *is a parent of* object. Inverse of `isa`. */
3310
+ readonly INVERSE_ISA: "inverse_isa";
3311
+ };
3312
+ /** A value from {@link RELA} — a drug-graph relationship name. */
3313
+ type RelaName = (typeof RELA)[keyof typeof RELA];
3314
+ /**
3315
+ * The authored **inverse pairs** of the drug-graph `RELA`s — a published fact (RxNorm Appendix 1),
3316
+ * used only to *document* and *validate* that a caller is traversing a real relationship. The graph
3317
+ * **never** uses this to synthesize a missing edge: RxNorm ships both directions, so
3318
+ * {@link ../rxnorm/navigate} follows whichever authored predicate points the way it wants to go.
3319
+ *
3320
+ * @example
3321
+ * ```ts
3322
+ * import { RELA_INVERSE, RELA } from "@cosyte/terminology";
3323
+ *
3324
+ * RELA_INVERSE[RELA.HAS_INGREDIENT]; // => "ingredient_of"
3325
+ * ```
3326
+ */
3327
+ declare const RELA_INVERSE: Readonly<Record<string, string>>;
3328
+ /** The canonical code-system URI for **RxNorm** — a published identity fact, attached to graph codings. */
3329
+ declare const RXNORM_SYSTEM = "http://www.nlm.nih.gov/research/umls/rxnorm";
3330
+
3331
+ /**
3332
+ * The RxNorm **term-type (`TTY`) vocabulary** relevant to the drug graph — the normal-form ladder and
3333
+ * the cross-link kinds — encoded as published identity facts (grounded firsthand on the NLM RxNorm
3334
+ * Technical Documentation, Appendix 5). **No RxNorm content is bundled** (roadmap §5): these are the
3335
+ * *labels*, not the drug list.
3336
+ *
3337
+ * The RxNorm normal-form graph runs `IN → SCDC → SCD → SBD` (ingredient → clinical-drug component →
3338
+ * semantic clinical drug → semantic branded drug), with `BN` (brand name) and `DF` (dose form) as
3339
+ * cross-links (roadmap §4.2).
3340
+ *
3341
+ * @packageDocumentation
3342
+ */
3343
+
3344
+ /**
3345
+ * The drug-graph term types (`RXNCONSO.TTY`), each mapped to its short gloss. Encoded as an identity
3346
+ * fact; a concept's `TTY` is carried verbatim on its {@link ../rxnorm/types.RxNormConcept}, never
3347
+ * re-labeled.
3348
+ *
3349
+ * @example
3350
+ * ```ts
3351
+ * import { TERM_TYPES } from "@cosyte/terminology";
3352
+ *
3353
+ * TERM_TYPES.SCD; // => "Semantic Clinical Drug"
3354
+ * ```
3355
+ */
3356
+ declare const TERM_TYPES: Readonly<Record<TermType, string>>;
3357
+ /**
3358
+ * Narrow a raw `RXNCONSO.TTY` string to a known {@link TermType}, or `undefined` when it is not a
3359
+ * drug-graph term type this engine models (the concept is then skipped at load, liberal-on-load —
3360
+ * never coerced to a wrong type).
3361
+ *
3362
+ * @param tty - The raw `TTY` value from an `RXNCONSO` row.
3363
+ * @returns The narrowed {@link TermType}, or `undefined` when unrecognized.
3364
+ * @example
3365
+ * ```ts
3366
+ * import { asTermType } from "@cosyte/terminology";
3367
+ *
3368
+ * asTermType("SCD"); // => "SCD"
3369
+ * asTermType("ZZZ"); // => undefined
3370
+ * ```
3371
+ */
3372
+ declare function asTermType(tty: string): TermType | undefined;
3373
+
3374
+ /**
3375
+ * Public entry point for **`@cosyte/terminology`** — a zero-dependency, developer-focused
3376
+ * **terminology engine** for US healthcare code systems.
3377
+ *
3378
+ * Unlike its sibling `@cosyte/*` packages, this is **not a wire-format parser**: it mirrors the
3379
+ * **FHIR Terminology Module** (`$translate`, `$lookup`, `$validate-code`, `$expand`, …), operating
3380
+ * over **consumer-supplied** FHIR resources. It ships the *engine*, never copyrighted terminology
3381
+ * *content* (SNOMED/CPT/full-LOINC/UMLS are strictly BYO); code-system *identities* (OID ↔ URI) are
3382
+ * published facts, encoded here and cited firsthand.
3383
+ *
3384
+ * **Phase 1** ships exactly the surface `@cosyte/transform` pins:
3385
+ *
3386
+ * - {@link resolveSystem} — the code-system identity / canonical-URI resolver (mnemonic | OID → URI).
3387
+ * - {@link loadConceptMap} + {@link translate} — the ConceptMap `$translate` engine, with the
3388
+ * **never-fabricate / never-invert** invariants: an unmapped source is a typed
3389
+ * {@link TranslateUnmapped}, never a guessed target; a directional map is never run backwards.
3390
+ *
3391
+ * **Phase 2** adds the CodeSystem load layer and the FHIR `$lookup` / `$validate-code` operations:
3392
+ *
3393
+ * - {@link loadCodeSystem} — load a **consumer-supplied** release (RRF / CSV / fixed-width / FHIR
3394
+ * `CodeSystem` JSON) into an immutable model. Ships the engine, **never** copyrighted content.
3395
+ * - {@link lookup} — code → display + properties, carrying status (deprecated / header-not-billable).
3396
+ * - {@link validateCode} — is a code a valid member of the system, with its status flags.
3397
+ *
3398
+ * The never-fabricate invariant holds here too: an unknown code is a typed `unknown` / `valid: false`,
3399
+ * never a fabricated display or a guessed `valid: true`.
3400
+ *
3401
+ * **Phase 3** adds the ValueSet binding layer — FHIR `$expand` / `$validate-code` over a `compose`:
3402
+ *
3403
+ * - {@link loadValueSet} — load a **consumer-supplied** FHIR `ValueSet` (intensional `compose` and/or
3404
+ * a pre-computed `expansion`) into an immutable model.
3405
+ * - {@link expand} — flatten membership over the supplied {@link CodeSystem}s (`include`/`exclude`,
3406
+ * explicit `concept` lists, `is-a`/property `filter`s, referenced value sets), with an honest
3407
+ * `complete` flag: an unresolvable part is a typed `TERM_VALUESET_CANNOT_EXPAND`, never a guess.
3408
+ * - {@link validateCodeInValueSet} — binding membership, returning a **decided** `result` only when
3409
+ * proven and a typed `undetermined` otherwise (a truncated expansion never reads as complete).
3410
+ *
3411
+ * **Phase 4** adds the **UCUM** unit layer — a hand-rolled UCUM grammar parser + validation +
3412
+ * representation canonicalization (recognition only, **no** magnitude conversion):
3413
+ *
3414
+ * - {@link validateUcum} — is a string a valid UCUM unit; if so, its canonical descriptor. An
3415
+ * invalid unit is a typed `TERM_UCUM_INVALID`, never a guessed "nearest" unit.
3416
+ * - {@link ucumEqual} — do two expressions denote the *same unit* (`N` ≡ `kg.m/s2`), by reducing
3417
+ * both to base dimensions. Not magnitude conversion (`mg/dL` → `mmol/L` is refused).
3418
+ * - {@link parseUcum} / {@link reduce} / {@link loadUcumEssence} — the underlying grammar parser,
3419
+ * dimensional reducer, and the in-memory model of the vendored, verbatim UCUM table.
3420
+ *
3421
+ * **Phase 5** adds the **crosswalk resolvers** — the never-fabricate/never-invert invariant applied
3422
+ * to the published, directional reference maps (roadmap §4.1, the safety crown):
3423
+ *
3424
+ * - {@link loadGems} + {@link applyGem} — the CMS **ICD-9↔ICD-10 GEMs** (public-domain reference
3425
+ * mappings), honoring the steward's Approximate / No-Map / Combination (scenario→choice-list) flags.
3426
+ * A No-Map source is a typed {@link CrosswalkNoMap}; a 1:many source returns the full candidate set.
3427
+ * - {@link loadComplexMap} + {@link applyComplexMap} — the NLM **SNOMED CT → ICD-10-CM complex map**
3428
+ * (BYO, SNOMED-licensed content — **zero bundled**), evaluating Map Group / Priority / `IFA` Rule /
3429
+ * Advice / Category against caller-supplied {@link PatientContext}; a rule needing context the
3430
+ * caller lacks is a typed {@link ComplexMapContextRequired}, never a guessed branch.
3431
+ * - {@link invertGem} — the never-invert refusal made a first-class, thrown contract
3432
+ * ({@link FATAL_CODES.TERM_MAP_NOT_INVERTIBLE}).
3433
+ *
3434
+ * **Phase 6** adds the **RxNorm drug relationship graph** — ingredient / brand / clinical-drug /
3435
+ * dose-form navigation over a **caller-supplied** RxNorm RRF release (roadmap §4.2):
3436
+ *
3437
+ * - {@link loadRxNormGraph} — load `RXNCONSO` (concepts, typed by `TTY`), `RXNREL` (directed `RELA`
3438
+ * edges, normalized to the documented `RXCUI2 ⟶RELA⟶ RXCUI1` direction), and optionally `RXNSAT`
3439
+ * (NDC attributes) into an immutable graph. Ships **no** RxNorm content — BYO release (roadmap §5).
3440
+ * - {@link ingredientsOf} / {@link genericFor} / {@link brandsFor} / {@link doseFormsOf} /
3441
+ * {@link consistsOf} / {@link relatedByRela} — graph navigation following **authored** edges only
3442
+ * (the engine never synthesizes an inverse). An absent `RXCUI` is a typed {@link RxNormUnknown}.
3443
+ * - {@link resolveNdc} — NDC → `RXCUI` carrying the temporal status and the as-of release; an absent
3444
+ * NDC is a typed {@link NdcUnmapped}, never a guess.
3445
+ * - {@link approximateMatch} — the **opt-in, explicitly labeled** similarity path (never the default,
3446
+ * never an exact code assertion).
3447
+ *
3448
+ * Deferred to later phases: the bundleable public-domain content packs, including the RxNorm Current
3449
+ * Prescribable Content pack and a GEM pack (the content-packs phase, Phase 7). The GEMs and the
3450
+ * Prescribable subset are public-domain, so a caller may supply them today (BYO); SNOMED/CPT/full-UMLS
3451
+ * content stays BYO permanently (a licensing wall — roadmap §5).
3452
+ *
3453
+ * @packageDocumentation
3454
+ */
3455
+ /**
3456
+ * Library version string, synced with `package.json#version` at release time.
3457
+ *
3458
+ * @example
3459
+ * ```ts
3460
+ * import { VERSION } from "@cosyte/terminology";
3461
+ * typeof VERSION; // => "string"
3462
+ * ```
3463
+ */
3464
+ declare const VERSION = "0.0.0";
3465
+
3466
+ export { type AnnotationNode, type ApproximateMatchOptions, type BillableFlag, COMPLEX_MAP_SOURCE_SYSTEM, type CodeSystem, type CodeSystemSource, type CodeableConcept, type Coding, type ComplexMap, type ComplexMapApplyResult, type ComplexMapContextRequired, type ComplexMapEntry, type ComplexMapGroupNoMap, type ComplexMapGroupResolved, type ComplexMapGroupResult, type ComplexMapInput, type ComplexMapLoadWarning, type ComplexMapMatched, type ComponentNode, type Concept, type ConceptActivity, type ConceptMap, type ConceptMapElement, type ConceptMapGroup, type ConceptMapTarget, type ConceptMapUnmapped, type ConceptRef, type ConceptSetComponent, type ConceptSetFilter, type ConceptStatus, type CrosswalkNoMap, type CrosswalkUnmapped, type CsvColumnMap, type CsvSource, DIAGNOSTIC_CODES, type DiagnosticCode, type ExpandResult, type ExpansionContains, type ExpansionContext, type ExpansionDiagnostic, FATAL_CODES, type FactorNode, type FatalCode, type FhirCodeSystemSource, type FieldSlice, type FilterMatch, type FilterOperator, type FixedWidthFieldMap, type FixedWidthPropertyField, type FixedWidthSource, type GemApplyResult, type GemChoiceList, type GemDirection, type GemEntry, type GemFlags, type GemLoadWarning, type GemMap, type GemMatched, type GemScenario, type GemSource, type GroupNode, HL7_V2_OID_ROOT, HL7_V2_URL_PREFIX, ICD10CM_ORDER_FILE_FIELDS, ICD10CM_SYSTEM, ICD9CM_SYSTEM, type LinearReduction, type LoadWarning, type LookupOutcome, type LookupResult, type LookupUnknown, MAP_CATEGORIES, type MapCategoryId, type MapProvenance, NO_MAP_CATEGORIES, type NdcResolution, type NdcResult, type NdcStatus, type NdcUnmapped, type ParseFailure, type ParseResult, type PatientContext, type Property, type R4Equivalence, RELA, RELA_INVERSE, RXNORM_SYSTEM, type Reduction, type RelaName, type Relationship, type ResolveSystemResult, type RrfColumnMap, type RrfPropertyColumn, type RrfSource, type RxNormApproximateMatch, type RxNormConcept, type RxNormEdge, type RxNormGraph, type RxNormGraphSource, type RxNormLoadWarning, type RxNormNavResult, type RxNormRelated, type RxNormUnknown, SNOMEDCT_SYSTEM, SYSTEM_IDENTITIES, type SimpleUnitNode, type SpecialReduction, type StatusMapper, type Subsumption, type SystemIdentity, TERM_TYPES, type TermType, TerminologyError, type TranslateMatch, type TranslateMatched, type TranslateResult, type TranslateUnmapped, type UcumAtom, type UcumEssence, type UcumPrefix, type UcumValidation, type UnitNode, type UnknownSystem, type UnmappedMode, VERSION, type ValidateCodeResult, type ValueSet, type ValueSetCompose, type ValueSetExpansion, type ValueSetMemberDecided, type ValueSetMemberUndetermined, type ValueSetMembership, applyComplexMap, applyGem, approximateMatch, asTermType, brandsFor, buildSubsumption, codeableConcept, coding, consistsOf, defaultStatusMapper, doseFormsOf, expand, genericFor, getArray, getBoolean, getConcept, getNumber, getString, ingredientsOf, invertGem, isA, isJsonObject, isUnknownSystem, loadCodeSystem, loadComplexMap, loadConceptMap, loadGems, loadRxNormGraph, loadUcumEssence, loadValueSet, lookup, makeStatus, matchesFilter, parseCsv, parseCsvSource, parseFhirCodeSystem, parseFixedWidth, parseRrf, parseRrfLine, parseUcum, reduce, relatedByRela, resolveNdc, resolveSystem, sliceField, translate, ucumEqual, unsupportedOps, validateCode, validateCodeInValueSet, validateUcum };