ezmedicationinput 0.1.14 → 0.1.16
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/dist/fhir.js +62 -7
- package/dist/format.js +26 -2
- package/dist/index.js +78 -4
- package/dist/internal-types.d.ts +12 -1
- package/dist/maps.d.ts +17 -1
- package/dist/maps.js +314 -1
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +647 -30
- package/dist/types.d.ts +65 -1
- package/package.json +1 -1
package/dist/parser.js
CHANGED
|
@@ -11,6 +11,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.tokenize = tokenize;
|
|
13
13
|
exports.parseInternal = parseInternal;
|
|
14
|
+
exports.applyPrnReasonCoding = applyPrnReasonCoding;
|
|
15
|
+
exports.applyPrnReasonCodingAsync = applyPrnReasonCodingAsync;
|
|
14
16
|
exports.applySiteCoding = applySiteCoding;
|
|
15
17
|
exports.applySiteCodingAsync = applySiteCodingAsync;
|
|
16
18
|
const maps_1 = require("./maps");
|
|
@@ -186,6 +188,33 @@ const COUNT_CONNECTOR_WORDS = new Set([
|
|
|
186
188
|
"additional",
|
|
187
189
|
"extra"
|
|
188
190
|
]);
|
|
191
|
+
const FREQUENCY_SIMPLE_WORDS = {
|
|
192
|
+
once: 1,
|
|
193
|
+
twice: 2,
|
|
194
|
+
thrice: 3
|
|
195
|
+
};
|
|
196
|
+
const FREQUENCY_NUMBER_WORDS = {
|
|
197
|
+
one: 1,
|
|
198
|
+
two: 2,
|
|
199
|
+
three: 3,
|
|
200
|
+
four: 4,
|
|
201
|
+
five: 5,
|
|
202
|
+
six: 6,
|
|
203
|
+
seven: 7,
|
|
204
|
+
eight: 8,
|
|
205
|
+
nine: 9,
|
|
206
|
+
ten: 10,
|
|
207
|
+
eleven: 11,
|
|
208
|
+
twelve: 12
|
|
209
|
+
};
|
|
210
|
+
const FREQUENCY_TIMES_WORDS = new Set(["time", "times", "x"]);
|
|
211
|
+
const FREQUENCY_CONNECTOR_WORDS = new Set(["per", "a", "an", "each", "every"]);
|
|
212
|
+
const FREQUENCY_ADVERB_UNITS = {
|
|
213
|
+
daily: types_1.FhirPeriodUnit.Day,
|
|
214
|
+
weekly: types_1.FhirPeriodUnit.Week,
|
|
215
|
+
monthly: types_1.FhirPeriodUnit.Month,
|
|
216
|
+
hourly: types_1.FhirPeriodUnit.Hour
|
|
217
|
+
};
|
|
189
218
|
const ROUTE_DESCRIPTOR_FILLER_WORDS = new Set([
|
|
190
219
|
"per",
|
|
191
220
|
"by",
|
|
@@ -290,7 +319,7 @@ const OPHTHALMIC_CONTEXT_TOKENS = new Set([
|
|
|
290
319
|
"be"
|
|
291
320
|
]);
|
|
292
321
|
function normalizeTokenLower(token) {
|
|
293
|
-
return token.lower.replace(/[.{}]/g, "");
|
|
322
|
+
return token.lower.replace(/[.{};]/g, "");
|
|
294
323
|
}
|
|
295
324
|
function hasOphthalmicContextHint(tokens, index) {
|
|
296
325
|
for (let offset = -3; offset <= 3; offset++) {
|
|
@@ -580,6 +609,115 @@ function tryParseNumericCadence(internal, tokens, index) {
|
|
|
580
609
|
mark(internal.consumed, unitToken);
|
|
581
610
|
return true;
|
|
582
611
|
}
|
|
612
|
+
function tryParseCountBasedFrequency(internal, tokens, index, options) {
|
|
613
|
+
const token = tokens[index];
|
|
614
|
+
if (internal.consumed.has(token.index)) {
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
if (internal.frequency !== undefined ||
|
|
618
|
+
internal.frequencyMax !== undefined ||
|
|
619
|
+
internal.period !== undefined ||
|
|
620
|
+
internal.periodMax !== undefined) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
const normalized = normalizeTokenLower(token);
|
|
624
|
+
let value;
|
|
625
|
+
let requiresPeriod = true;
|
|
626
|
+
let requiresCue = true;
|
|
627
|
+
if (/^[0-9]+(?:\.[0-9]+)?$/.test(normalized)) {
|
|
628
|
+
value = parseFloat(token.original);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
const simple = FREQUENCY_SIMPLE_WORDS[normalized];
|
|
632
|
+
if (simple !== undefined) {
|
|
633
|
+
value = simple;
|
|
634
|
+
requiresPeriod = false;
|
|
635
|
+
requiresCue = false;
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
const wordValue = FREQUENCY_NUMBER_WORDS[normalized];
|
|
639
|
+
if (wordValue === undefined) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
value = wordValue;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (!Number.isFinite(value) || value === undefined || value <= 0) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
const nextToken = tokens[index + 1];
|
|
649
|
+
if (nextToken &&
|
|
650
|
+
!internal.consumed.has(nextToken.index) &&
|
|
651
|
+
normalizeUnit(normalizeTokenLower(nextToken), options)) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const partsToConsume = [];
|
|
655
|
+
let nextIndex = index + 1;
|
|
656
|
+
let periodUnit;
|
|
657
|
+
let sawCue = !requiresCue;
|
|
658
|
+
let sawTimesWord = false;
|
|
659
|
+
let sawConnectorWord = false;
|
|
660
|
+
while (true) {
|
|
661
|
+
const candidate = tokens[nextIndex];
|
|
662
|
+
if (!candidate || internal.consumed.has(candidate.index)) {
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
const lower = normalizeTokenLower(candidate);
|
|
666
|
+
if (FREQUENCY_TIMES_WORDS.has(lower)) {
|
|
667
|
+
partsToConsume.push(candidate);
|
|
668
|
+
sawCue = true;
|
|
669
|
+
sawTimesWord = true;
|
|
670
|
+
nextIndex += 1;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (FREQUENCY_CONNECTOR_WORDS.has(lower)) {
|
|
674
|
+
partsToConsume.push(candidate);
|
|
675
|
+
sawCue = true;
|
|
676
|
+
sawConnectorWord = true;
|
|
677
|
+
nextIndex += 1;
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
const adverbUnit = mapFrequencyAdverb(lower);
|
|
681
|
+
if (adverbUnit) {
|
|
682
|
+
periodUnit = adverbUnit;
|
|
683
|
+
partsToConsume.push(candidate);
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
const mappedUnit = mapIntervalUnit(lower);
|
|
687
|
+
if (mappedUnit) {
|
|
688
|
+
periodUnit = mappedUnit;
|
|
689
|
+
partsToConsume.push(candidate);
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
694
|
+
if (!periodUnit) {
|
|
695
|
+
if (requiresPeriod) {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
periodUnit = types_1.FhirPeriodUnit.Day;
|
|
699
|
+
}
|
|
700
|
+
if (requiresCue && !sawCue) {
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
internal.frequency = value;
|
|
704
|
+
internal.period = 1;
|
|
705
|
+
internal.periodUnit = periodUnit;
|
|
706
|
+
if (value === 1 && periodUnit === types_1.FhirPeriodUnit.Day && !internal.timingCode) {
|
|
707
|
+
internal.timingCode = "QD";
|
|
708
|
+
}
|
|
709
|
+
let consumeCurrentToken = true;
|
|
710
|
+
if (value === 1 && !sawConnectorWord && sawTimesWord && periodUnit !== types_1.FhirPeriodUnit.Day) {
|
|
711
|
+
consumeCurrentToken = false;
|
|
712
|
+
}
|
|
713
|
+
if (consumeCurrentToken) {
|
|
714
|
+
mark(internal.consumed, token);
|
|
715
|
+
}
|
|
716
|
+
for (const part of partsToConsume) {
|
|
717
|
+
mark(internal.consumed, part);
|
|
718
|
+
}
|
|
719
|
+
return consumeCurrentToken;
|
|
720
|
+
}
|
|
583
721
|
const SITE_UNIT_ROUTE_HINTS = [
|
|
584
722
|
{ pattern: /\beye(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
|
|
585
723
|
{ pattern: /\beyelid(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
|
|
@@ -622,8 +760,9 @@ const SITE_UNIT_ROUTE_HINTS = [
|
|
|
622
760
|
{ pattern: /\bvaginal\b/i, route: types_1.RouteCode["Per vagina"] }
|
|
623
761
|
];
|
|
624
762
|
function tokenize(input) {
|
|
625
|
-
const separators = /[()
|
|
763
|
+
const separators = /[(),;]/g;
|
|
626
764
|
let normalized = input.trim().replace(separators, " ");
|
|
765
|
+
normalized = normalized.replace(/\s-\s/g, " ; ");
|
|
627
766
|
normalized = normalized.replace(/(\d+(?:\.\d+)?)\s*\/\s*(d|day|days|wk|w|week|weeks|mo|month|months|hr|hrs|hour|hours|h|min|mins|minute|minutes)\b/gi, (_match, value, unit) => `${value} per ${unit}`);
|
|
628
767
|
normalized = normalized.replace(/(\d+)\s*\/\s*(\d+)/g, (match, num, den) => {
|
|
629
768
|
const numerator = parseFloat(num);
|
|
@@ -658,7 +797,7 @@ function tokenize(input) {
|
|
|
658
797
|
* Locates the span of the detected site tokens within the caller's original
|
|
659
798
|
* input so downstream consumers can highlight or replace the exact substring.
|
|
660
799
|
*/
|
|
661
|
-
function
|
|
800
|
+
function computeTokenRange(input, tokens, indices) {
|
|
662
801
|
if (!indices.length) {
|
|
663
802
|
return undefined;
|
|
664
803
|
}
|
|
@@ -1244,6 +1383,9 @@ function mapIntervalUnit(token) {
|
|
|
1244
1383
|
}
|
|
1245
1384
|
return undefined;
|
|
1246
1385
|
}
|
|
1386
|
+
function mapFrequencyAdverb(token) {
|
|
1387
|
+
return FREQUENCY_ADVERB_UNITS[token];
|
|
1388
|
+
}
|
|
1247
1389
|
function parseNumericRange(token) {
|
|
1248
1390
|
const rangeMatch = token.match(/^([0-9]+(?:\.[0-9]+)?)-([0-9]+(?:\.[0-9]+)?)$/);
|
|
1249
1391
|
if (!rangeMatch) {
|
|
@@ -1282,7 +1424,9 @@ function parseInternal(input, options) {
|
|
|
1282
1424
|
warnings: [],
|
|
1283
1425
|
siteTokenIndices: new Set(),
|
|
1284
1426
|
siteLookups: [],
|
|
1285
|
-
customSiteHints: buildCustomSiteHints(options === null || options === void 0 ? void 0 : options.siteCodeMap)
|
|
1427
|
+
customSiteHints: buildCustomSiteHints(options === null || options === void 0 ? void 0 : options.siteCodeMap),
|
|
1428
|
+
prnReasonLookups: [],
|
|
1429
|
+
additionalInstructions: []
|
|
1286
1430
|
};
|
|
1287
1431
|
const context = (_a = options === null || options === void 0 ? void 0 : options.context) !== null && _a !== void 0 ? _a : undefined;
|
|
1288
1432
|
const customRouteMap = (options === null || options === void 0 ? void 0 : options.routeMap)
|
|
@@ -1391,12 +1535,19 @@ function parseInternal(input, options) {
|
|
|
1391
1535
|
if (slice.some((part) => internal.consumed.has(part.index))) {
|
|
1392
1536
|
continue;
|
|
1393
1537
|
}
|
|
1394
|
-
const
|
|
1538
|
+
const normalizedParts = slice.filter((part) => !/^[;:(),]+$/.test(part.lower));
|
|
1539
|
+
const phrase = normalizedParts.map((part) => part.lower).join(" ");
|
|
1395
1540
|
const customCode = customRouteMap === null || customRouteMap === void 0 ? void 0 : customRouteMap.get(phrase);
|
|
1396
1541
|
const synonym = customCode
|
|
1397
1542
|
? { code: customCode, text: maps_1.ROUTE_TEXT[customCode] }
|
|
1398
1543
|
: maps_1.DEFAULT_ROUTE_SYNONYMS[phrase];
|
|
1399
1544
|
if (synonym) {
|
|
1545
|
+
if (phrase === "in" && slice.length === 1) {
|
|
1546
|
+
const prevToken = tokens[startIndex - 1];
|
|
1547
|
+
if (prevToken && !internal.consumed.has(prevToken.index)) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1400
1551
|
setRoute(internal, synonym.code, synonym.text);
|
|
1401
1552
|
for (const part of slice) {
|
|
1402
1553
|
mark(internal.consumed, part);
|
|
@@ -1620,6 +1771,9 @@ function parseInternal(input, options) {
|
|
|
1620
1771
|
}
|
|
1621
1772
|
}
|
|
1622
1773
|
// Numeric dose
|
|
1774
|
+
if (tryParseCountBasedFrequency(internal, tokens, i, options)) {
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1623
1777
|
const rangeValue = parseNumericRange(token.lower);
|
|
1624
1778
|
if (rangeValue) {
|
|
1625
1779
|
if (!internal.doseRange) {
|
|
@@ -1739,6 +1893,51 @@ function parseInternal(input, options) {
|
|
|
1739
1893
|
// Expand generic meal markers into specific EventTiming codes when asked to.
|
|
1740
1894
|
expandMealTimings(internal, options);
|
|
1741
1895
|
sortWhenValues(internal, options);
|
|
1896
|
+
// PRN reason text
|
|
1897
|
+
if (internal.asNeeded && prnReasonStart !== undefined) {
|
|
1898
|
+
const reasonTokens = [];
|
|
1899
|
+
const reasonIndices = [];
|
|
1900
|
+
for (let i = prnReasonStart; i < tokens.length; i++) {
|
|
1901
|
+
const token = tokens[i];
|
|
1902
|
+
if (internal.consumed.has(token.index)) {
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
reasonTokens.push(token.original);
|
|
1906
|
+
reasonIndices.push(token.index);
|
|
1907
|
+
mark(internal.consumed, token);
|
|
1908
|
+
}
|
|
1909
|
+
if (reasonTokens.length > 0) {
|
|
1910
|
+
const joined = reasonTokens.join(" ").trim();
|
|
1911
|
+
if (joined) {
|
|
1912
|
+
const sortedIndices = reasonIndices.sort((a, b) => a - b);
|
|
1913
|
+
const range = computeTokenRange(internal.input, tokens, sortedIndices);
|
|
1914
|
+
const sourceText = range ? internal.input.slice(range.start, range.end) : undefined;
|
|
1915
|
+
let sanitized = joined.replace(/\s+/g, " ").trim();
|
|
1916
|
+
let isProbe = false;
|
|
1917
|
+
const probeMatch = sanitized.match(/^\{(.+)}$/);
|
|
1918
|
+
if (probeMatch) {
|
|
1919
|
+
isProbe = true;
|
|
1920
|
+
sanitized = probeMatch[1];
|
|
1921
|
+
}
|
|
1922
|
+
sanitized = sanitized.replace(/[{}]/g, " ").replace(/\s+/g, " ").trim();
|
|
1923
|
+
const text = sanitized || joined;
|
|
1924
|
+
internal.asNeededReason = text;
|
|
1925
|
+
const normalized = text.toLowerCase();
|
|
1926
|
+
const canonical = sanitized ? (0, maps_1.normalizePrnReasonKey)(sanitized) : (0, maps_1.normalizePrnReasonKey)(text);
|
|
1927
|
+
internal.prnReasonLookupRequest = {
|
|
1928
|
+
originalText: joined,
|
|
1929
|
+
text,
|
|
1930
|
+
normalized,
|
|
1931
|
+
canonical: canonical !== null && canonical !== void 0 ? canonical : "",
|
|
1932
|
+
isProbe,
|
|
1933
|
+
inputText: internal.input,
|
|
1934
|
+
sourceText,
|
|
1935
|
+
range
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
collectAdditionalInstructions(internal, tokens);
|
|
1742
1941
|
// Determine site text from leftover tokens (excluding PRN reason tokens)
|
|
1743
1942
|
const leftoverTokens = tokens.filter((t) => !internal.consumed.has(t.index));
|
|
1744
1943
|
const siteCandidateIndices = new Set();
|
|
@@ -1746,6 +1945,13 @@ function parseInternal(input, options) {
|
|
|
1746
1945
|
const normalized = normalizeTokenLower(token);
|
|
1747
1946
|
if (isBodySiteHint(normalized, internal.customSiteHints)) {
|
|
1748
1947
|
siteCandidateIndices.add(token.index);
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
if (SITE_CONNECTORS.has(normalized)) {
|
|
1951
|
+
const next = tokens[token.index + 1];
|
|
1952
|
+
if (next && !internal.consumed.has(next.index)) {
|
|
1953
|
+
siteCandidateIndices.add(next.index);
|
|
1954
|
+
}
|
|
1749
1955
|
}
|
|
1750
1956
|
}
|
|
1751
1957
|
for (const idx of internal.siteTokenIndices) {
|
|
@@ -1807,7 +2013,7 @@ function parseInternal(input, options) {
|
|
|
1807
2013
|
.join(" ")
|
|
1808
2014
|
.trim();
|
|
1809
2015
|
if (normalizedSite) {
|
|
1810
|
-
const tokenRange =
|
|
2016
|
+
const tokenRange = computeTokenRange(internal.input, tokens, sortedIndices);
|
|
1811
2017
|
let sanitized = normalizedSite;
|
|
1812
2018
|
let isProbe = false;
|
|
1813
2019
|
const probeMatch = sanitized.match(/^\{(.+)}$/);
|
|
@@ -1858,21 +2064,6 @@ function parseInternal(input, options) {
|
|
|
1858
2064
|
}
|
|
1859
2065
|
}
|
|
1860
2066
|
}
|
|
1861
|
-
// PRN reason text
|
|
1862
|
-
if (internal.asNeeded && prnReasonStart !== undefined) {
|
|
1863
|
-
const reasonTokens = [];
|
|
1864
|
-
for (let i = prnReasonStart; i < tokens.length; i++) {
|
|
1865
|
-
const token = tokens[i];
|
|
1866
|
-
if (internal.consumed.has(token.index)) {
|
|
1867
|
-
continue;
|
|
1868
|
-
}
|
|
1869
|
-
reasonTokens.push(token.original);
|
|
1870
|
-
mark(internal.consumed, token);
|
|
1871
|
-
}
|
|
1872
|
-
if (reasonTokens.length > 0) {
|
|
1873
|
-
internal.asNeededReason = reasonTokens.join(" ");
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
2067
|
if (internal.routeCode === types_1.RouteCode["Intravitreal route (qualifier value)"] &&
|
|
1877
2068
|
(!internal.siteText || !/eye/i.test(internal.siteText))) {
|
|
1878
2069
|
internal.warnings.push("Intravitreal administrations require an eye site (e.g., OD/OS/OU).");
|
|
@@ -1883,6 +2074,14 @@ function parseInternal(input, options) {
|
|
|
1883
2074
|
* Resolves parsed site text against SNOMED dictionaries and synchronous
|
|
1884
2075
|
* callbacks, applying the best match to the in-progress parse result.
|
|
1885
2076
|
*/
|
|
2077
|
+
function applyPrnReasonCoding(internal, options) {
|
|
2078
|
+
runPrnReasonResolutionSync(internal, options);
|
|
2079
|
+
}
|
|
2080
|
+
function applyPrnReasonCodingAsync(internal, options) {
|
|
2081
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2082
|
+
yield runPrnReasonResolutionAsync(internal, options);
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
1886
2085
|
function applySiteCoding(internal, options) {
|
|
1887
2086
|
runSiteCodingResolutionSync(internal, options);
|
|
1888
2087
|
}
|
|
@@ -2098,16 +2297,21 @@ function pickSiteSelection(selections, request) {
|
|
|
2098
2297
|
* the coding system to SNOMED CT when the definition omits one.
|
|
2099
2298
|
*/
|
|
2100
2299
|
function applySiteDefinition(internal, definition) {
|
|
2101
|
-
var _a;
|
|
2300
|
+
var _a, _b;
|
|
2102
2301
|
const coding = definition.coding;
|
|
2103
|
-
internal.siteCoding =
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2302
|
+
internal.siteCoding = (coding === null || coding === void 0 ? void 0 : coding.code)
|
|
2303
|
+
? {
|
|
2304
|
+
code: coding.code,
|
|
2305
|
+
display: coding.display,
|
|
2306
|
+
system: (_a = coding.system) !== null && _a !== void 0 ? _a : SNOMED_SYSTEM
|
|
2307
|
+
}
|
|
2308
|
+
: undefined;
|
|
2108
2309
|
if (definition.text) {
|
|
2109
2310
|
internal.siteText = definition.text;
|
|
2110
2311
|
}
|
|
2312
|
+
else if (!internal.siteText && ((_b = internal.siteLookupRequest) === null || _b === void 0 ? void 0 : _b.text)) {
|
|
2313
|
+
internal.siteText = internal.siteLookupRequest.text;
|
|
2314
|
+
}
|
|
2111
2315
|
}
|
|
2112
2316
|
/**
|
|
2113
2317
|
* Converts a body-site definition into a suggestion payload so all suggestion
|
|
@@ -2115,11 +2319,15 @@ function applySiteDefinition(internal, definition) {
|
|
|
2115
2319
|
*/
|
|
2116
2320
|
function definitionToSuggestion(definition) {
|
|
2117
2321
|
var _a;
|
|
2322
|
+
const coding = definition.coding;
|
|
2323
|
+
if (!(coding === null || coding === void 0 ? void 0 : coding.code)) {
|
|
2324
|
+
return undefined;
|
|
2325
|
+
}
|
|
2118
2326
|
return {
|
|
2119
2327
|
coding: {
|
|
2120
|
-
code:
|
|
2121
|
-
display:
|
|
2122
|
-
system: (_a =
|
|
2328
|
+
code: coding.code,
|
|
2329
|
+
display: coding.display,
|
|
2330
|
+
system: (_a = coding.system) !== null && _a !== void 0 ? _a : SNOMED_SYSTEM
|
|
2123
2331
|
},
|
|
2124
2332
|
text: definition.text
|
|
2125
2333
|
};
|
|
@@ -2165,6 +2373,415 @@ function collectSuggestionResult(map, result) {
|
|
|
2165
2373
|
addSuggestionToMap(map, suggestion);
|
|
2166
2374
|
}
|
|
2167
2375
|
}
|
|
2376
|
+
function findAdditionalInstructionDefinition(text, canonical) {
|
|
2377
|
+
if (!canonical) {
|
|
2378
|
+
return undefined;
|
|
2379
|
+
}
|
|
2380
|
+
for (const entry of maps_1.DEFAULT_ADDITIONAL_INSTRUCTION_ENTRIES) {
|
|
2381
|
+
if (!entry.canonical) {
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
if (entry.canonical === canonical) {
|
|
2385
|
+
return entry.definition;
|
|
2386
|
+
}
|
|
2387
|
+
if (canonical.includes(entry.canonical) || entry.canonical.includes(canonical)) {
|
|
2388
|
+
return entry.definition;
|
|
2389
|
+
}
|
|
2390
|
+
for (const term of entry.terms) {
|
|
2391
|
+
const normalizedTerm = (0, maps_1.normalizeAdditionalInstructionKey)(term);
|
|
2392
|
+
if (!normalizedTerm) {
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
if (canonical.includes(normalizedTerm) || normalizedTerm.includes(canonical)) {
|
|
2396
|
+
return entry.definition;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return undefined;
|
|
2401
|
+
}
|
|
2402
|
+
function collectAdditionalInstructions(internal, tokens) {
|
|
2403
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2404
|
+
if (internal.additionalInstructions.length) {
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
const leftover = tokens.filter((token) => !internal.consumed.has(token.index));
|
|
2408
|
+
if (!leftover.length) {
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
const punctuationOnly = /^[;:.,-]+$/;
|
|
2412
|
+
const contentTokens = leftover.filter((token) => !punctuationOnly.test(token.original));
|
|
2413
|
+
if (!contentTokens.length) {
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
const leftoverIndices = leftover.map((token) => token.index).sort((a, b) => a - b);
|
|
2417
|
+
const contiguous = leftoverIndices.every((index, i) => i === 0 || index === leftoverIndices[i - 1] + 1);
|
|
2418
|
+
if (!contiguous) {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
const lastIndex = leftoverIndices[leftoverIndices.length - 1];
|
|
2422
|
+
for (let i = lastIndex + 1; i < tokens.length; i++) {
|
|
2423
|
+
const trailingToken = tokens[i];
|
|
2424
|
+
if (!internal.consumed.has(trailingToken.index)) {
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
const joined = contentTokens
|
|
2429
|
+
.map((token) => token.original)
|
|
2430
|
+
.join(" ")
|
|
2431
|
+
.replace(/\s+/g, " ")
|
|
2432
|
+
.trim();
|
|
2433
|
+
if (!joined) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const contentIndices = contentTokens.map((token) => token.index).sort((a, b) => a - b);
|
|
2437
|
+
const range = computeTokenRange(internal.input, tokens, contentIndices);
|
|
2438
|
+
let separatorDetected = false;
|
|
2439
|
+
if (range) {
|
|
2440
|
+
for (let cursor = range.start - 1; cursor >= 0; cursor--) {
|
|
2441
|
+
const ch = internal.input[cursor];
|
|
2442
|
+
if (ch === "\n" || ch === "\r") {
|
|
2443
|
+
separatorDetected = true;
|
|
2444
|
+
break;
|
|
2445
|
+
}
|
|
2446
|
+
if (/\s/.test(ch)) {
|
|
2447
|
+
continue;
|
|
2448
|
+
}
|
|
2449
|
+
if (/-|;|:|\.|,/.test(ch)) {
|
|
2450
|
+
separatorDetected = true;
|
|
2451
|
+
}
|
|
2452
|
+
break;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const sourceText = range
|
|
2456
|
+
? internal.input.slice(range.start, range.end)
|
|
2457
|
+
: joined;
|
|
2458
|
+
if (!separatorDetected && !/[-;:.]/.test(sourceText)) {
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
const normalized = sourceText
|
|
2462
|
+
.replace(/\s*[-:]+\s*/g, "; ")
|
|
2463
|
+
.replace(/\s*(?:\r?\n)+\s*/g, "; ")
|
|
2464
|
+
.replace(/\s+/g, " ");
|
|
2465
|
+
const segments = normalized
|
|
2466
|
+
.split(/(?:;|\.)/)
|
|
2467
|
+
.map((segment) => segment.trim())
|
|
2468
|
+
.filter((segment) => segment.length > 0);
|
|
2469
|
+
const phrases = segments.length ? segments : [joined];
|
|
2470
|
+
const seen = new Set();
|
|
2471
|
+
const instructions = [];
|
|
2472
|
+
for (const phrase of phrases) {
|
|
2473
|
+
const canonical = (0, maps_1.normalizeAdditionalInstructionKey)(phrase);
|
|
2474
|
+
const definition = (_a = maps_1.DEFAULT_ADDITIONAL_INSTRUCTION_DEFINITIONS[canonical]) !== null && _a !== void 0 ? _a : findAdditionalInstructionDefinition(phrase, canonical);
|
|
2475
|
+
const key = ((_b = definition === null || definition === void 0 ? void 0 : definition.coding) === null || _b === void 0 ? void 0 : _b.code)
|
|
2476
|
+
? `code:${(_c = definition.coding.system) !== null && _c !== void 0 ? _c : SNOMED_SYSTEM}|${definition.coding.code}`
|
|
2477
|
+
: canonical
|
|
2478
|
+
? `text:${canonical}`
|
|
2479
|
+
: phrase.toLowerCase();
|
|
2480
|
+
if (key && seen.has(key)) {
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
seen.add(key);
|
|
2484
|
+
if (definition) {
|
|
2485
|
+
instructions.push({
|
|
2486
|
+
text: (_d = definition.text) !== null && _d !== void 0 ? _d : phrase,
|
|
2487
|
+
coding: ((_e = definition.coding) === null || _e === void 0 ? void 0 : _e.code)
|
|
2488
|
+
? {
|
|
2489
|
+
code: definition.coding.code,
|
|
2490
|
+
display: definition.coding.display,
|
|
2491
|
+
system: (_f = definition.coding.system) !== null && _f !== void 0 ? _f : SNOMED_SYSTEM
|
|
2492
|
+
}
|
|
2493
|
+
: undefined
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
else {
|
|
2497
|
+
instructions.push({ text: phrase });
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
if (instructions.length) {
|
|
2501
|
+
internal.additionalInstructions = instructions;
|
|
2502
|
+
for (const token of leftover) {
|
|
2503
|
+
mark(internal.consumed, token);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function lookupPrnReasonDefinition(map, canonical) {
|
|
2508
|
+
if (!map) {
|
|
2509
|
+
return undefined;
|
|
2510
|
+
}
|
|
2511
|
+
const direct = map[canonical];
|
|
2512
|
+
if (direct) {
|
|
2513
|
+
return direct;
|
|
2514
|
+
}
|
|
2515
|
+
for (const [key, definition] of (0, object_1.objectEntries)(map)) {
|
|
2516
|
+
if ((0, maps_1.normalizePrnReasonKey)(key) === canonical) {
|
|
2517
|
+
return definition;
|
|
2518
|
+
}
|
|
2519
|
+
if (definition.aliases) {
|
|
2520
|
+
for (const alias of definition.aliases) {
|
|
2521
|
+
if ((0, maps_1.normalizePrnReasonKey)(alias) === canonical) {
|
|
2522
|
+
return definition;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return undefined;
|
|
2528
|
+
}
|
|
2529
|
+
function pickPrnReasonSelection(selections, request) {
|
|
2530
|
+
if (!selections) {
|
|
2531
|
+
return undefined;
|
|
2532
|
+
}
|
|
2533
|
+
const canonical = request.canonical;
|
|
2534
|
+
const normalizedText = (0, maps_1.normalizePrnReasonKey)(request.text);
|
|
2535
|
+
const requestRange = request.range;
|
|
2536
|
+
for (const selection of toArray(selections)) {
|
|
2537
|
+
if (!selection) {
|
|
2538
|
+
continue;
|
|
2539
|
+
}
|
|
2540
|
+
let matched = false;
|
|
2541
|
+
if (selection.range) {
|
|
2542
|
+
if (!requestRange) {
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
if (selection.range.start !== requestRange.start ||
|
|
2546
|
+
selection.range.end !== requestRange.end) {
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
matched = true;
|
|
2550
|
+
}
|
|
2551
|
+
if (selection.canonical) {
|
|
2552
|
+
if ((0, maps_1.normalizePrnReasonKey)(selection.canonical) !== canonical) {
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
matched = true;
|
|
2556
|
+
}
|
|
2557
|
+
else if (selection.text) {
|
|
2558
|
+
const normalizedSelection = (0, maps_1.normalizePrnReasonKey)(selection.text);
|
|
2559
|
+
if (normalizedSelection !== canonical && normalizedSelection !== normalizedText) {
|
|
2560
|
+
continue;
|
|
2561
|
+
}
|
|
2562
|
+
matched = true;
|
|
2563
|
+
}
|
|
2564
|
+
if (!selection.range && !selection.canonical && !selection.text) {
|
|
2565
|
+
continue;
|
|
2566
|
+
}
|
|
2567
|
+
if (matched) {
|
|
2568
|
+
return selection.resolution;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
return undefined;
|
|
2572
|
+
}
|
|
2573
|
+
function applyPrnReasonDefinition(internal, definition) {
|
|
2574
|
+
var _a;
|
|
2575
|
+
const coding = definition.coding;
|
|
2576
|
+
internal.asNeededReasonCoding = (coding === null || coding === void 0 ? void 0 : coding.code)
|
|
2577
|
+
? {
|
|
2578
|
+
code: coding.code,
|
|
2579
|
+
display: coding.display,
|
|
2580
|
+
system: (_a = coding.system) !== null && _a !== void 0 ? _a : SNOMED_SYSTEM
|
|
2581
|
+
}
|
|
2582
|
+
: undefined;
|
|
2583
|
+
if (definition.text && !internal.asNeededReason) {
|
|
2584
|
+
internal.asNeededReason = definition.text;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function definitionToPrnSuggestion(definition) {
|
|
2588
|
+
var _a, _b, _c, _d;
|
|
2589
|
+
return {
|
|
2590
|
+
coding: ((_a = definition.coding) === null || _a === void 0 ? void 0 : _a.code)
|
|
2591
|
+
? {
|
|
2592
|
+
code: definition.coding.code,
|
|
2593
|
+
display: definition.coding.display,
|
|
2594
|
+
system: (_b = definition.coding.system) !== null && _b !== void 0 ? _b : SNOMED_SYSTEM
|
|
2595
|
+
}
|
|
2596
|
+
: undefined,
|
|
2597
|
+
text: (_c = definition.text) !== null && _c !== void 0 ? _c : (_d = definition.coding) === null || _d === void 0 ? void 0 : _d.display
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
function addReasonSuggestionToMap(map, suggestion) {
|
|
2601
|
+
var _a;
|
|
2602
|
+
if (!suggestion) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const coding = suggestion.coding;
|
|
2606
|
+
const key = (coding === null || coding === void 0 ? void 0 : coding.code)
|
|
2607
|
+
? `${(_a = coding.system) !== null && _a !== void 0 ? _a : SNOMED_SYSTEM}|${coding.code}`
|
|
2608
|
+
: suggestion.text
|
|
2609
|
+
? `text:${suggestion.text.toLowerCase()}`
|
|
2610
|
+
: undefined;
|
|
2611
|
+
if (!key || map.has(key)) {
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
map.set(key, suggestion);
|
|
2615
|
+
}
|
|
2616
|
+
function collectReasonSuggestionResult(map, result) {
|
|
2617
|
+
if (!result) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
const suggestions = Array.isArray(result)
|
|
2621
|
+
? result
|
|
2622
|
+
: typeof result === "object" && "suggestions" in result
|
|
2623
|
+
? result.suggestions
|
|
2624
|
+
: [result];
|
|
2625
|
+
for (const suggestion of suggestions) {
|
|
2626
|
+
addReasonSuggestionToMap(map, suggestion);
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
function collectDefaultPrnReasonDefinitions(request) {
|
|
2630
|
+
const canonical = request.canonical;
|
|
2631
|
+
const normalized = request.normalized;
|
|
2632
|
+
const seen = new Set();
|
|
2633
|
+
for (const entry of maps_1.DEFAULT_PRN_REASON_ENTRIES) {
|
|
2634
|
+
if (!entry.canonical) {
|
|
2635
|
+
continue;
|
|
2636
|
+
}
|
|
2637
|
+
if (entry.canonical === canonical) {
|
|
2638
|
+
seen.add(entry.definition);
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
if (canonical && (entry.canonical.includes(canonical) || canonical.includes(entry.canonical))) {
|
|
2642
|
+
seen.add(entry.definition);
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
for (const term of entry.terms) {
|
|
2646
|
+
const normalizedTerm = (0, maps_1.normalizePrnReasonKey)(term);
|
|
2647
|
+
if (!normalizedTerm) {
|
|
2648
|
+
continue;
|
|
2649
|
+
}
|
|
2650
|
+
if (canonical && canonical.includes(normalizedTerm)) {
|
|
2651
|
+
seen.add(entry.definition);
|
|
2652
|
+
break;
|
|
2653
|
+
}
|
|
2654
|
+
if (normalized.includes(normalizedTerm)) {
|
|
2655
|
+
seen.add(entry.definition);
|
|
2656
|
+
break;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
if (!seen.size) {
|
|
2661
|
+
for (const entry of maps_1.DEFAULT_PRN_REASON_ENTRIES) {
|
|
2662
|
+
seen.add(entry.definition);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
return Array.from(seen);
|
|
2666
|
+
}
|
|
2667
|
+
function runPrnReasonResolutionSync(internal, options) {
|
|
2668
|
+
internal.prnReasonLookups = [];
|
|
2669
|
+
const request = internal.prnReasonLookupRequest;
|
|
2670
|
+
if (!request) {
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const canonical = request.canonical;
|
|
2674
|
+
const selection = pickPrnReasonSelection(options === null || options === void 0 ? void 0 : options.prnReasonSelections, request);
|
|
2675
|
+
const customDefinition = lookupPrnReasonDefinition(options === null || options === void 0 ? void 0 : options.prnReasonMap, canonical);
|
|
2676
|
+
let resolution = selection !== null && selection !== void 0 ? selection : customDefinition;
|
|
2677
|
+
if (!resolution) {
|
|
2678
|
+
for (const resolver of toArray(options === null || options === void 0 ? void 0 : options.prnReasonResolvers)) {
|
|
2679
|
+
const result = resolver(request);
|
|
2680
|
+
if (isPromise(result)) {
|
|
2681
|
+
throw new Error("PRN reason resolver returned a Promise; use parseSigAsync for asynchronous PRN reason resolution.");
|
|
2682
|
+
}
|
|
2683
|
+
if (result) {
|
|
2684
|
+
resolution = result;
|
|
2685
|
+
break;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
const defaultDefinition = canonical ? maps_1.DEFAULT_PRN_REASON_DEFINITIONS[canonical] : undefined;
|
|
2690
|
+
if (!resolution && defaultDefinition) {
|
|
2691
|
+
resolution = defaultDefinition;
|
|
2692
|
+
}
|
|
2693
|
+
if (resolution) {
|
|
2694
|
+
applyPrnReasonDefinition(internal, resolution);
|
|
2695
|
+
}
|
|
2696
|
+
else {
|
|
2697
|
+
internal.asNeededReasonCoding = undefined;
|
|
2698
|
+
}
|
|
2699
|
+
const needsSuggestions = request.isProbe || !resolution;
|
|
2700
|
+
if (!needsSuggestions) {
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const suggestionMap = new Map();
|
|
2704
|
+
if (selection) {
|
|
2705
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(selection));
|
|
2706
|
+
}
|
|
2707
|
+
if (customDefinition) {
|
|
2708
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(customDefinition));
|
|
2709
|
+
}
|
|
2710
|
+
if (defaultDefinition) {
|
|
2711
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(defaultDefinition));
|
|
2712
|
+
}
|
|
2713
|
+
for (const definition of collectDefaultPrnReasonDefinitions(request)) {
|
|
2714
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(definition));
|
|
2715
|
+
}
|
|
2716
|
+
for (const resolver of toArray(options === null || options === void 0 ? void 0 : options.prnReasonSuggestionResolvers)) {
|
|
2717
|
+
const result = resolver(request);
|
|
2718
|
+
if (isPromise(result)) {
|
|
2719
|
+
throw new Error("PRN reason suggestion resolver returned a Promise; use parseSigAsync for asynchronous PRN reason suggestions.");
|
|
2720
|
+
}
|
|
2721
|
+
collectReasonSuggestionResult(suggestionMap, result);
|
|
2722
|
+
}
|
|
2723
|
+
const suggestions = Array.from(suggestionMap.values());
|
|
2724
|
+
if (suggestions.length || request.isProbe) {
|
|
2725
|
+
internal.prnReasonLookups.push({ request, suggestions });
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
function runPrnReasonResolutionAsync(internal, options) {
|
|
2729
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
2730
|
+
internal.prnReasonLookups = [];
|
|
2731
|
+
const request = internal.prnReasonLookupRequest;
|
|
2732
|
+
if (!request) {
|
|
2733
|
+
return;
|
|
2734
|
+
}
|
|
2735
|
+
const canonical = request.canonical;
|
|
2736
|
+
const selection = pickPrnReasonSelection(options === null || options === void 0 ? void 0 : options.prnReasonSelections, request);
|
|
2737
|
+
const customDefinition = lookupPrnReasonDefinition(options === null || options === void 0 ? void 0 : options.prnReasonMap, canonical);
|
|
2738
|
+
let resolution = selection !== null && selection !== void 0 ? selection : customDefinition;
|
|
2739
|
+
if (!resolution) {
|
|
2740
|
+
for (const resolver of toArray(options === null || options === void 0 ? void 0 : options.prnReasonResolvers)) {
|
|
2741
|
+
const result = yield resolver(request);
|
|
2742
|
+
if (result) {
|
|
2743
|
+
resolution = result;
|
|
2744
|
+
break;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
const defaultDefinition = canonical ? maps_1.DEFAULT_PRN_REASON_DEFINITIONS[canonical] : undefined;
|
|
2749
|
+
if (!resolution && defaultDefinition) {
|
|
2750
|
+
resolution = defaultDefinition;
|
|
2751
|
+
}
|
|
2752
|
+
if (resolution) {
|
|
2753
|
+
applyPrnReasonDefinition(internal, resolution);
|
|
2754
|
+
}
|
|
2755
|
+
else {
|
|
2756
|
+
internal.asNeededReasonCoding = undefined;
|
|
2757
|
+
}
|
|
2758
|
+
const needsSuggestions = request.isProbe || !resolution;
|
|
2759
|
+
if (!needsSuggestions) {
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
const suggestionMap = new Map();
|
|
2763
|
+
if (selection) {
|
|
2764
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(selection));
|
|
2765
|
+
}
|
|
2766
|
+
if (customDefinition) {
|
|
2767
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(customDefinition));
|
|
2768
|
+
}
|
|
2769
|
+
if (defaultDefinition) {
|
|
2770
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(defaultDefinition));
|
|
2771
|
+
}
|
|
2772
|
+
for (const definition of collectDefaultPrnReasonDefinitions(request)) {
|
|
2773
|
+
addReasonSuggestionToMap(suggestionMap, definitionToPrnSuggestion(definition));
|
|
2774
|
+
}
|
|
2775
|
+
for (const resolver of toArray(options === null || options === void 0 ? void 0 : options.prnReasonSuggestionResolvers)) {
|
|
2776
|
+
const result = yield resolver(request);
|
|
2777
|
+
collectReasonSuggestionResult(suggestionMap, result);
|
|
2778
|
+
}
|
|
2779
|
+
const suggestions = Array.from(suggestionMap.values());
|
|
2780
|
+
if (suggestions.length || request.isProbe) {
|
|
2781
|
+
internal.prnReasonLookups.push({ request, suggestions });
|
|
2782
|
+
}
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2168
2785
|
/**
|
|
2169
2786
|
* Wraps scalar or array configuration into an array to simplify iteration.
|
|
2170
2787
|
*/
|