@cosyte/terminology 0.0.5 → 0.0.6
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.
- package/CHANGELOG.md +229 -0
- package/README.md +8 -2
- package/dist/index.cjs +92 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -10
- package/dist/index.d.ts +100 -10
- package/dist/index.mjs +92 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -100,10 +100,63 @@ declare function codeableConcept(init: CodeableConcept): CodeableConcept;
|
|
|
100
100
|
* them, so renaming or removing one is a **breaking change**, and the full set is snapshotted via
|
|
101
101
|
* `sortedCodeSet` (`@cosyte/test-utils`) as a reviewable tripwire.
|
|
102
102
|
*
|
|
103
|
-
* **Value-free by construction
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
103
|
+
* **Value-free by construction**, and the property is stated as what it actually is: every string a
|
|
104
|
+
* diagnostic or fatal message is assembled from is **owned by the engine**. Three kinds, and no
|
|
105
|
+
* fourth: a string literal; an entry in a frozen table keyed on the engine's own vocabulary; and a
|
|
106
|
+
* **locus this package built** — a 1-based `line`, a column count, or an index path over the
|
|
107
|
+
* caller's own resource made of integers and FHIR field names (`ConceptMap group[0].element[0].target[0]`,
|
|
108
|
+
* `compose.include[2]`). Nothing a caller configured and nothing their release or resource contained
|
|
109
|
+
* is ever one of them, so no such string reaches a `message`, a `stack`, or a `detail`, at any
|
|
110
|
+
* length.
|
|
111
|
+
*
|
|
112
|
+
* Read that as the rule, and note what it does **not** say. It does not say no factory takes a
|
|
113
|
+
* `string` parameter. Several do: `malformed(path, fault)` in `conceptmap/load.ts` and
|
|
114
|
+
* `valueset/load.ts`, and `cannotExpand(detail, path)`, `truncated(detail, path)` and
|
|
115
|
+
* `underPath(d, prefix)` in `valueset/` — where `expand.ts` and `validate.ts` each carry their own
|
|
116
|
+
* copy. It says every **argument** reaching them is engine-owned: each `fault` and `detail` is a
|
|
117
|
+
* literal at the call site, each `path` and `prefix` is built from indices by the loader that
|
|
118
|
+
* raises it. That distinction is the thing to preserve — an absolute "no factory takes a value
|
|
119
|
+
* parameter" reads as a stronger guarantee than the one the code makes, and those factories are the
|
|
120
|
+
* counter-example to it. Do not write the count down; derive it.
|
|
121
|
+
*
|
|
122
|
+
* Positional context is value-free the same way: a `line` integer, an `ExpansionDiagnostic.path`
|
|
123
|
+
* index path into the caller's own resource (`compose.include[2]`), or a closed-set token
|
|
124
|
+
* (`RXNCONSO` / `RXNREL` / `RXNSAT`) — never a URI, a column name, or a field the file supplied.
|
|
125
|
+
* This matters because a code *in patient context* can be PHI, and a thrown error is the one surface
|
|
126
|
+
* that reaches a log or an error reporter without the consumer choosing to put it there.
|
|
127
|
+
*
|
|
128
|
+
* **▶ THE CLAIM IS ABOUT THE MESSAGE FIELDS, NOT ABOUT THE OBJECTS. DO NOT SHORTEN IT TO "A
|
|
129
|
+
* DIAGNOSTIC IS SAFE TO LOG".** The safe strings are the *message* fields:
|
|
130
|
+
* `TerminologyError.message` / `.stack`, `LoadWarning.detail`, `GemLoadWarning.detail`,
|
|
131
|
+
* `ComplexMapLoadWarning.detail`, `RxNormLoadWarning.detail`, `ExpansionDiagnostic.detail`,
|
|
132
|
+
* `ParseFailure.reason` and `UcumValidation.reason`, plus the loci beside them.
|
|
133
|
+
*
|
|
134
|
+
* The **objects** these codes appear on are a different matter, and they carry verbatim text from
|
|
135
|
+
* two *different* provenances, which decide differently:
|
|
136
|
+
*
|
|
137
|
+
* - **Your query, echoed back** — `LookupUnknown.input`, `LookupResult.code`, `UnknownSystem.input`,
|
|
138
|
+
* `TranslateUnmapped.source`, `CrosswalkUnmapped.source`, `CrosswalkNoMap.source`,
|
|
139
|
+
* `RxNormUnknown.rxcui`, `RxNormRelated.predicates`, `NdcUnmapped.ndc`,
|
|
140
|
+
* `ValueSetMembership.coding`, and **`MapProvenance.sourceSystem`** — which is the `system` off
|
|
141
|
+
* the `Coding` you passed to `translate`, verbatim and unbounded, whenever that coding
|
|
142
|
+
* carries one; the map's own `group.source` is only the fallback for a coding that does not. The
|
|
143
|
+
* query is the one genuinely **patient-derived** input this engine takes (a code off a parsed
|
|
144
|
+
* message, a unit off an observation, an NDC off a prescription), so this is the class that
|
|
145
|
+
* decides a PHI question.
|
|
146
|
+
* - **Your loaded artifact, carried through** — `ComplexMapContextRequired.rules` (the map
|
|
147
|
+
* document's own `rule` / `advice` text) and `MapProvenance.targetSystem` / `.conceptMapUrl` /
|
|
148
|
+
* `.conceptMapVersion`, which are read off the loaded map (`group.target`, `map.url`,
|
|
149
|
+
* `map.version`). These are *reference data*, bounded by the map or release you loaded, not by
|
|
150
|
+
* anything a patient supplied. **`MapProvenance` straddles the two lists** — do not read the whole
|
|
151
|
+
* record as reference data on the strength of the fields that are.
|
|
152
|
+
*
|
|
153
|
+
* Read both lists as illustrative, not closed: **assume any result field may hold what you passed
|
|
154
|
+
* in.** So `JSON.stringify(lookupResult)` into a log is a PHI decision, and `String(err)` is not.
|
|
155
|
+
* Log the `code` and the locus; log a value only if you would log the code you passed in.
|
|
156
|
+
*
|
|
157
|
+
* Those echoes are deliberate and are not going away: they are how a never-fabricate outcome says
|
|
158
|
+
* *which* thing it refused to guess, they are the caller's own data returned to the caller, and
|
|
159
|
+
* bounding them would mean fabricating a miss.
|
|
107
160
|
*
|
|
108
161
|
* @packageDocumentation
|
|
109
162
|
*/
|
|
@@ -318,8 +371,10 @@ type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
|
|
|
318
371
|
* The engine's typed error. Carries a stable {@link FatalCode} so callers branch on `err.code`
|
|
319
372
|
* without string-matching the message. Thrown only for {@link FATAL_CODES} conditions.
|
|
320
373
|
*
|
|
321
|
-
* The `message` is **value-free**: it names the structural fault (a resource path, a missing
|
|
322
|
-
*
|
|
374
|
+
* The `message` is **value-free**: it names the structural fault (a resource path, a missing field,
|
|
375
|
+
* the *role* of a configured column) and never echoes an input value — so it is safe to log, and
|
|
376
|
+
* safe in an `err.stack` that reaches an error reporter. The rule the strings are built by is in
|
|
377
|
+
* this module's docblock above.
|
|
323
378
|
*
|
|
324
379
|
* @example
|
|
325
380
|
* ```ts
|
|
@@ -338,6 +393,9 @@ declare class TerminologyError extends Error {
|
|
|
338
393
|
/**
|
|
339
394
|
* @param code - The stable {@link FatalCode} describing the fault.
|
|
340
395
|
* @param message - A **value-free** structural description (path + fault; never an input value).
|
|
396
|
+
* Every piece of it must be engine-owned — a literal, a frozen-table entry, or a locus this
|
|
397
|
+
* package built. This string reaches `err.stack`, so interpolating a caller-supplied or
|
|
398
|
+
* document-derived value into it is how a diagnostic becomes a leak.
|
|
341
399
|
*/
|
|
342
400
|
constructor(code: FatalCode, message: string);
|
|
343
401
|
}
|
|
@@ -611,7 +669,13 @@ interface MapProvenance {
|
|
|
611
669
|
readonly conceptMapUrl?: string;
|
|
612
670
|
/** The ConceptMap's business `version`, when declared (mappings are release-scoped). */
|
|
613
671
|
readonly conceptMapVersion?: string;
|
|
614
|
-
/**
|
|
672
|
+
/**
|
|
673
|
+
* The source code system for this translation. **This is an echo of your query, not reference
|
|
674
|
+
* data off the map**: it is the `system` on the `Coding` you passed to `translate`, verbatim and
|
|
675
|
+
* unbounded, whenever that coding carries one; the matched (or consulted) group's own `source` is
|
|
676
|
+
* only the fallback for a coding that does not. Log it only if you would log the code you passed
|
|
677
|
+
* in.
|
|
678
|
+
*/
|
|
615
679
|
readonly sourceSystem?: string;
|
|
616
680
|
/** The target code system of the group that matched, when known. */
|
|
617
681
|
readonly targetSystem?: string;
|
|
@@ -1272,6 +1336,9 @@ declare function parseRrf(source: RrfSource): {
|
|
|
1272
1336
|
* surfaced** as a `TERM_CSV_MALFORMED` warning; an unterminated final quote is surfaced too — never a
|
|
1273
1337
|
* crash. A configured code/display header that is **absent from the header row** is a *fatal*
|
|
1274
1338
|
* (`TERM_CODESYSTEM_MALFORMED`): the source config does not match the file, so nothing loads cleanly.
|
|
1339
|
+
* That fatal names the missing column's **role** (`code`, `display`, `status`, a property column)
|
|
1340
|
+
* from a frozen table, never the column name you configured — the message reaches `err.stack`, and a
|
|
1341
|
+
* column name is consumer-supplied text of unbounded length.
|
|
1275
1342
|
*
|
|
1276
1343
|
* @packageDocumentation
|
|
1277
1344
|
*/
|
|
@@ -1296,7 +1363,8 @@ declare function parseCsv(content: string): string[][];
|
|
|
1296
1363
|
* @param source - The CSV source.
|
|
1297
1364
|
* @returns The parsed concepts (by code) and the load warnings.
|
|
1298
1365
|
* @throws {TerminologyError} `TERM_CODESYSTEM_MALFORMED` when the file has no header row, or a
|
|
1299
|
-
* configured `code`/`display`/`status`/property column is absent from the header.
|
|
1366
|
+
* configured `code`/`display`/`status`/property column is absent from the header. The message
|
|
1367
|
+
* names the missing column's **role**, never the name you configured.
|
|
1300
1368
|
* @example
|
|
1301
1369
|
* ```ts
|
|
1302
1370
|
* import { parseCsvSource } from "@cosyte/terminology";
|
|
@@ -1607,8 +1675,18 @@ interface ExpansionDiagnostic {
|
|
|
1607
1675
|
readonly code: DiagnosticCode;
|
|
1608
1676
|
/** A **value-free** structural description of the concern (never echoes a patient value). */
|
|
1609
1677
|
readonly detail: string;
|
|
1610
|
-
/**
|
|
1611
|
-
|
|
1678
|
+
/**
|
|
1679
|
+
* A **value-free structural locus** into the caller's own resource — `"expansion"`, or a
|
|
1680
|
+
* `compose`-relative index path such as `"compose.include[2]"` or
|
|
1681
|
+
* `"compose.include[0].valueSet[1]"`. A diagnostic raised while expanding a *referenced* value set
|
|
1682
|
+
* is prefixed with the reference that reached it (`"compose.include[0].valueSet[1]/compose.include[3]"`).
|
|
1683
|
+
*
|
|
1684
|
+
* Built entirely from integers and the engine's own field names, so nothing the caller or their
|
|
1685
|
+
* resource supplied can reach it, at any length. It replaces the URI this field used to carry:
|
|
1686
|
+
* a canonical URI is unbounded consumer-supplied text on a diagnostic surface, and an index path
|
|
1687
|
+
* is strictly more precise anyway (two `include`s may name the same system).
|
|
1688
|
+
*/
|
|
1689
|
+
readonly path?: string;
|
|
1612
1690
|
}
|
|
1613
1691
|
/**
|
|
1614
1692
|
* The result of {@link expand}: the flattened membership plus an honest **completeness** signal.
|
|
@@ -2160,8 +2238,20 @@ declare function parseUcum(unit: string): ParseResult;
|
|
|
2160
2238
|
* Reduce a parsed UCUM unit to its canonical {@link Reduction} — a linear scalar×dimension form, or
|
|
2161
2239
|
* an opaque `special` form for expressions involving a non-linear special unit.
|
|
2162
2240
|
*
|
|
2241
|
+
* The reduction of one atom is cached against **that atom object**, so a `UnitNode` you assembled
|
|
2242
|
+
* yourself is answered from its own atoms and cannot change how a unit off {@link parseUcum} reduces,
|
|
2243
|
+
* in this call or any later one.
|
|
2244
|
+
*
|
|
2163
2245
|
* @param node - The AST from {@link parseUcum}.
|
|
2164
2246
|
* @returns The reduced form.
|
|
2247
|
+
* @throws {Error} If reducing `node` reaches an atom with no linear definition, or one whose
|
|
2248
|
+
* definition does not parse. **No expression {@link parseUcum} accepts reaches either**; a node
|
|
2249
|
+
* you assemble yourself can, including by defining an atom in terms of a special unit such as
|
|
2250
|
+
* `Cel`, which lands on the table's own `Cel`. A third guard, against a definition that leads
|
|
2251
|
+
* back to itself, is **not** reachable from a node you assemble — a definition is resolved
|
|
2252
|
+
* against the bundled table, so your atom can name a table atom but never be one — and is
|
|
2253
|
+
* reached only by mutating an atom of the loaded table in place. The message names the fault,
|
|
2254
|
+
* never the atom.
|
|
2165
2255
|
* @example
|
|
2166
2256
|
* ```ts
|
|
2167
2257
|
* import { parseUcum, reduce } from "@cosyte/terminology";
|
package/dist/index.d.ts
CHANGED
|
@@ -100,10 +100,63 @@ declare function codeableConcept(init: CodeableConcept): CodeableConcept;
|
|
|
100
100
|
* them, so renaming or removing one is a **breaking change**, and the full set is snapshotted via
|
|
101
101
|
* `sortedCodeSet` (`@cosyte/test-utils`) as a reviewable tripwire.
|
|
102
102
|
*
|
|
103
|
-
* **Value-free by construction
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
103
|
+
* **Value-free by construction**, and the property is stated as what it actually is: every string a
|
|
104
|
+
* diagnostic or fatal message is assembled from is **owned by the engine**. Three kinds, and no
|
|
105
|
+
* fourth: a string literal; an entry in a frozen table keyed on the engine's own vocabulary; and a
|
|
106
|
+
* **locus this package built** — a 1-based `line`, a column count, or an index path over the
|
|
107
|
+
* caller's own resource made of integers and FHIR field names (`ConceptMap group[0].element[0].target[0]`,
|
|
108
|
+
* `compose.include[2]`). Nothing a caller configured and nothing their release or resource contained
|
|
109
|
+
* is ever one of them, so no such string reaches a `message`, a `stack`, or a `detail`, at any
|
|
110
|
+
* length.
|
|
111
|
+
*
|
|
112
|
+
* Read that as the rule, and note what it does **not** say. It does not say no factory takes a
|
|
113
|
+
* `string` parameter. Several do: `malformed(path, fault)` in `conceptmap/load.ts` and
|
|
114
|
+
* `valueset/load.ts`, and `cannotExpand(detail, path)`, `truncated(detail, path)` and
|
|
115
|
+
* `underPath(d, prefix)` in `valueset/` — where `expand.ts` and `validate.ts` each carry their own
|
|
116
|
+
* copy. It says every **argument** reaching them is engine-owned: each `fault` and `detail` is a
|
|
117
|
+
* literal at the call site, each `path` and `prefix` is built from indices by the loader that
|
|
118
|
+
* raises it. That distinction is the thing to preserve — an absolute "no factory takes a value
|
|
119
|
+
* parameter" reads as a stronger guarantee than the one the code makes, and those factories are the
|
|
120
|
+
* counter-example to it. Do not write the count down; derive it.
|
|
121
|
+
*
|
|
122
|
+
* Positional context is value-free the same way: a `line` integer, an `ExpansionDiagnostic.path`
|
|
123
|
+
* index path into the caller's own resource (`compose.include[2]`), or a closed-set token
|
|
124
|
+
* (`RXNCONSO` / `RXNREL` / `RXNSAT`) — never a URI, a column name, or a field the file supplied.
|
|
125
|
+
* This matters because a code *in patient context* can be PHI, and a thrown error is the one surface
|
|
126
|
+
* that reaches a log or an error reporter without the consumer choosing to put it there.
|
|
127
|
+
*
|
|
128
|
+
* **▶ THE CLAIM IS ABOUT THE MESSAGE FIELDS, NOT ABOUT THE OBJECTS. DO NOT SHORTEN IT TO "A
|
|
129
|
+
* DIAGNOSTIC IS SAFE TO LOG".** The safe strings are the *message* fields:
|
|
130
|
+
* `TerminologyError.message` / `.stack`, `LoadWarning.detail`, `GemLoadWarning.detail`,
|
|
131
|
+
* `ComplexMapLoadWarning.detail`, `RxNormLoadWarning.detail`, `ExpansionDiagnostic.detail`,
|
|
132
|
+
* `ParseFailure.reason` and `UcumValidation.reason`, plus the loci beside them.
|
|
133
|
+
*
|
|
134
|
+
* The **objects** these codes appear on are a different matter, and they carry verbatim text from
|
|
135
|
+
* two *different* provenances, which decide differently:
|
|
136
|
+
*
|
|
137
|
+
* - **Your query, echoed back** — `LookupUnknown.input`, `LookupResult.code`, `UnknownSystem.input`,
|
|
138
|
+
* `TranslateUnmapped.source`, `CrosswalkUnmapped.source`, `CrosswalkNoMap.source`,
|
|
139
|
+
* `RxNormUnknown.rxcui`, `RxNormRelated.predicates`, `NdcUnmapped.ndc`,
|
|
140
|
+
* `ValueSetMembership.coding`, and **`MapProvenance.sourceSystem`** — which is the `system` off
|
|
141
|
+
* the `Coding` you passed to `translate`, verbatim and unbounded, whenever that coding
|
|
142
|
+
* carries one; the map's own `group.source` is only the fallback for a coding that does not. The
|
|
143
|
+
* query is the one genuinely **patient-derived** input this engine takes (a code off a parsed
|
|
144
|
+
* message, a unit off an observation, an NDC off a prescription), so this is the class that
|
|
145
|
+
* decides a PHI question.
|
|
146
|
+
* - **Your loaded artifact, carried through** — `ComplexMapContextRequired.rules` (the map
|
|
147
|
+
* document's own `rule` / `advice` text) and `MapProvenance.targetSystem` / `.conceptMapUrl` /
|
|
148
|
+
* `.conceptMapVersion`, which are read off the loaded map (`group.target`, `map.url`,
|
|
149
|
+
* `map.version`). These are *reference data*, bounded by the map or release you loaded, not by
|
|
150
|
+
* anything a patient supplied. **`MapProvenance` straddles the two lists** — do not read the whole
|
|
151
|
+
* record as reference data on the strength of the fields that are.
|
|
152
|
+
*
|
|
153
|
+
* Read both lists as illustrative, not closed: **assume any result field may hold what you passed
|
|
154
|
+
* in.** So `JSON.stringify(lookupResult)` into a log is a PHI decision, and `String(err)` is not.
|
|
155
|
+
* Log the `code` and the locus; log a value only if you would log the code you passed in.
|
|
156
|
+
*
|
|
157
|
+
* Those echoes are deliberate and are not going away: they are how a never-fabricate outcome says
|
|
158
|
+
* *which* thing it refused to guess, they are the caller's own data returned to the caller, and
|
|
159
|
+
* bounding them would mean fabricating a miss.
|
|
107
160
|
*
|
|
108
161
|
* @packageDocumentation
|
|
109
162
|
*/
|
|
@@ -318,8 +371,10 @@ type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
|
|
|
318
371
|
* The engine's typed error. Carries a stable {@link FatalCode} so callers branch on `err.code`
|
|
319
372
|
* without string-matching the message. Thrown only for {@link FATAL_CODES} conditions.
|
|
320
373
|
*
|
|
321
|
-
* The `message` is **value-free**: it names the structural fault (a resource path, a missing
|
|
322
|
-
*
|
|
374
|
+
* The `message` is **value-free**: it names the structural fault (a resource path, a missing field,
|
|
375
|
+
* the *role* of a configured column) and never echoes an input value — so it is safe to log, and
|
|
376
|
+
* safe in an `err.stack` that reaches an error reporter. The rule the strings are built by is in
|
|
377
|
+
* this module's docblock above.
|
|
323
378
|
*
|
|
324
379
|
* @example
|
|
325
380
|
* ```ts
|
|
@@ -338,6 +393,9 @@ declare class TerminologyError extends Error {
|
|
|
338
393
|
/**
|
|
339
394
|
* @param code - The stable {@link FatalCode} describing the fault.
|
|
340
395
|
* @param message - A **value-free** structural description (path + fault; never an input value).
|
|
396
|
+
* Every piece of it must be engine-owned — a literal, a frozen-table entry, or a locus this
|
|
397
|
+
* package built. This string reaches `err.stack`, so interpolating a caller-supplied or
|
|
398
|
+
* document-derived value into it is how a diagnostic becomes a leak.
|
|
341
399
|
*/
|
|
342
400
|
constructor(code: FatalCode, message: string);
|
|
343
401
|
}
|
|
@@ -611,7 +669,13 @@ interface MapProvenance {
|
|
|
611
669
|
readonly conceptMapUrl?: string;
|
|
612
670
|
/** The ConceptMap's business `version`, when declared (mappings are release-scoped). */
|
|
613
671
|
readonly conceptMapVersion?: string;
|
|
614
|
-
/**
|
|
672
|
+
/**
|
|
673
|
+
* The source code system for this translation. **This is an echo of your query, not reference
|
|
674
|
+
* data off the map**: it is the `system` on the `Coding` you passed to `translate`, verbatim and
|
|
675
|
+
* unbounded, whenever that coding carries one; the matched (or consulted) group's own `source` is
|
|
676
|
+
* only the fallback for a coding that does not. Log it only if you would log the code you passed
|
|
677
|
+
* in.
|
|
678
|
+
*/
|
|
615
679
|
readonly sourceSystem?: string;
|
|
616
680
|
/** The target code system of the group that matched, when known. */
|
|
617
681
|
readonly targetSystem?: string;
|
|
@@ -1272,6 +1336,9 @@ declare function parseRrf(source: RrfSource): {
|
|
|
1272
1336
|
* surfaced** as a `TERM_CSV_MALFORMED` warning; an unterminated final quote is surfaced too — never a
|
|
1273
1337
|
* crash. A configured code/display header that is **absent from the header row** is a *fatal*
|
|
1274
1338
|
* (`TERM_CODESYSTEM_MALFORMED`): the source config does not match the file, so nothing loads cleanly.
|
|
1339
|
+
* That fatal names the missing column's **role** (`code`, `display`, `status`, a property column)
|
|
1340
|
+
* from a frozen table, never the column name you configured — the message reaches `err.stack`, and a
|
|
1341
|
+
* column name is consumer-supplied text of unbounded length.
|
|
1275
1342
|
*
|
|
1276
1343
|
* @packageDocumentation
|
|
1277
1344
|
*/
|
|
@@ -1296,7 +1363,8 @@ declare function parseCsv(content: string): string[][];
|
|
|
1296
1363
|
* @param source - The CSV source.
|
|
1297
1364
|
* @returns The parsed concepts (by code) and the load warnings.
|
|
1298
1365
|
* @throws {TerminologyError} `TERM_CODESYSTEM_MALFORMED` when the file has no header row, or a
|
|
1299
|
-
* configured `code`/`display`/`status`/property column is absent from the header.
|
|
1366
|
+
* configured `code`/`display`/`status`/property column is absent from the header. The message
|
|
1367
|
+
* names the missing column's **role**, never the name you configured.
|
|
1300
1368
|
* @example
|
|
1301
1369
|
* ```ts
|
|
1302
1370
|
* import { parseCsvSource } from "@cosyte/terminology";
|
|
@@ -1607,8 +1675,18 @@ interface ExpansionDiagnostic {
|
|
|
1607
1675
|
readonly code: DiagnosticCode;
|
|
1608
1676
|
/** A **value-free** structural description of the concern (never echoes a patient value). */
|
|
1609
1677
|
readonly detail: string;
|
|
1610
|
-
/**
|
|
1611
|
-
|
|
1678
|
+
/**
|
|
1679
|
+
* A **value-free structural locus** into the caller's own resource — `"expansion"`, or a
|
|
1680
|
+
* `compose`-relative index path such as `"compose.include[2]"` or
|
|
1681
|
+
* `"compose.include[0].valueSet[1]"`. A diagnostic raised while expanding a *referenced* value set
|
|
1682
|
+
* is prefixed with the reference that reached it (`"compose.include[0].valueSet[1]/compose.include[3]"`).
|
|
1683
|
+
*
|
|
1684
|
+
* Built entirely from integers and the engine's own field names, so nothing the caller or their
|
|
1685
|
+
* resource supplied can reach it, at any length. It replaces the URI this field used to carry:
|
|
1686
|
+
* a canonical URI is unbounded consumer-supplied text on a diagnostic surface, and an index path
|
|
1687
|
+
* is strictly more precise anyway (two `include`s may name the same system).
|
|
1688
|
+
*/
|
|
1689
|
+
readonly path?: string;
|
|
1612
1690
|
}
|
|
1613
1691
|
/**
|
|
1614
1692
|
* The result of {@link expand}: the flattened membership plus an honest **completeness** signal.
|
|
@@ -2160,8 +2238,20 @@ declare function parseUcum(unit: string): ParseResult;
|
|
|
2160
2238
|
* Reduce a parsed UCUM unit to its canonical {@link Reduction} — a linear scalar×dimension form, or
|
|
2161
2239
|
* an opaque `special` form for expressions involving a non-linear special unit.
|
|
2162
2240
|
*
|
|
2241
|
+
* The reduction of one atom is cached against **that atom object**, so a `UnitNode` you assembled
|
|
2242
|
+
* yourself is answered from its own atoms and cannot change how a unit off {@link parseUcum} reduces,
|
|
2243
|
+
* in this call or any later one.
|
|
2244
|
+
*
|
|
2163
2245
|
* @param node - The AST from {@link parseUcum}.
|
|
2164
2246
|
* @returns The reduced form.
|
|
2247
|
+
* @throws {Error} If reducing `node` reaches an atom with no linear definition, or one whose
|
|
2248
|
+
* definition does not parse. **No expression {@link parseUcum} accepts reaches either**; a node
|
|
2249
|
+
* you assemble yourself can, including by defining an atom in terms of a special unit such as
|
|
2250
|
+
* `Cel`, which lands on the table's own `Cel`. A third guard, against a definition that leads
|
|
2251
|
+
* back to itself, is **not** reachable from a node you assemble — a definition is resolved
|
|
2252
|
+
* against the bundled table, so your atom can name a table atom but never be one — and is
|
|
2253
|
+
* reached only by mutating an atom of the loaded table in place. The message names the fault,
|
|
2254
|
+
* never the atom.
|
|
2165
2255
|
* @example
|
|
2166
2256
|
* ```ts
|
|
2167
2257
|
* import { parseUcum, reduce } from "@cosyte/terminology";
|