ezmedicationinput 0.1.22 → 0.1.24
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/index.d.ts +2 -1
- package/dist/index.js +20 -0
- package/dist/parser.d.ts +5 -1
- package/dist/parser.js +118 -5
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FhirDosage, FormatOptions, ParseOptions, ParseResult } from "./types";
|
|
1
|
+
import { FhirDosage, FormatOptions, LintResult, ParseOptions, ParseResult } from "./types";
|
|
2
2
|
export { parseInternal } from "./parser";
|
|
3
3
|
export { suggestSig } from "./suggest";
|
|
4
4
|
export * from "./types";
|
|
@@ -6,6 +6,7 @@ export { nextDueDoses } from "./schedule";
|
|
|
6
6
|
export { getRegisteredSigLocalizations, registerSigLocalization, resolveSigLocalization, resolveSigTranslation } from "./i18n";
|
|
7
7
|
export type { SigLocalization, SigLocalizationConfig, SigTranslation, SigTranslationConfig } from "./i18n";
|
|
8
8
|
export declare function parseSig(input: string, options?: ParseOptions): ParseResult;
|
|
9
|
+
export declare function lintSig(input: string, options?: ParseOptions): LintResult;
|
|
9
10
|
export declare function parseSigAsync(input: string, options?: ParseOptions): Promise<ParseResult>;
|
|
10
11
|
export declare function formatSig(dosage: FhirDosage, style?: "short" | "long", options?: FormatOptions): string;
|
|
11
12
|
export declare function fromFhirDosage(dosage: FhirDosage, options?: FormatOptions): ParseResult;
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.resolveSigTranslation = exports.resolveSigLocalization = exports.registerSigLocalization = exports.getRegisteredSigLocalizations = exports.nextDueDoses = exports.suggestSig = exports.parseInternal = void 0;
|
|
27
27
|
exports.parseSig = parseSig;
|
|
28
|
+
exports.lintSig = lintSig;
|
|
28
29
|
exports.parseSigAsync = parseSigAsync;
|
|
29
30
|
exports.formatSig = formatSig;
|
|
30
31
|
exports.fromFhirDosage = fromFhirDosage;
|
|
@@ -50,6 +51,25 @@ function parseSig(input, options) {
|
|
|
50
51
|
(0, parser_1.applySiteCoding)(internal, options);
|
|
51
52
|
return buildParseResult(internal, options);
|
|
52
53
|
}
|
|
54
|
+
function lintSig(input, options) {
|
|
55
|
+
const internal = (0, parser_1.parseInternal)(input, options);
|
|
56
|
+
(0, parser_1.applyPrnReasonCoding)(internal, options);
|
|
57
|
+
(0, parser_1.applySiteCoding)(internal, options);
|
|
58
|
+
const result = buildParseResult(internal, options);
|
|
59
|
+
const groups = (0, parser_1.findUnparsedTokenGroups)(internal);
|
|
60
|
+
const issues = groups.map((group) => {
|
|
61
|
+
const text = group.range
|
|
62
|
+
? internal.input.slice(group.range.start, group.range.end)
|
|
63
|
+
: group.tokens.map((token) => token.original).join(" ");
|
|
64
|
+
return {
|
|
65
|
+
message: "Unrecognized text",
|
|
66
|
+
text: text.trim() || text,
|
|
67
|
+
tokens: group.tokens.map((token) => token.original),
|
|
68
|
+
range: group.range
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
return { result, issues };
|
|
72
|
+
}
|
|
53
73
|
function parseSigAsync(input, options) {
|
|
54
74
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
75
|
const internal = (0, parser_1.parseInternal)(input, options);
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ParsedSigInternal, Token } from "./internal-types";
|
|
2
|
-
import { ParseOptions } from "./types";
|
|
2
|
+
import { ParseOptions, TextRange } from "./types";
|
|
3
3
|
export declare function tokenize(input: string): Token[];
|
|
4
|
+
export declare function findUnparsedTokenGroups(internal: ParsedSigInternal): Array<{
|
|
5
|
+
tokens: Token[];
|
|
6
|
+
range?: TextRange;
|
|
7
|
+
}>;
|
|
4
8
|
export declare function parseInternal(input: string, options?: ParseOptions): ParsedSigInternal;
|
|
5
9
|
/**
|
|
6
10
|
* Resolves parsed site text against SNOMED dictionaries and synchronous
|
package/dist/parser.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.tokenize = tokenize;
|
|
13
|
+
exports.findUnparsedTokenGroups = findUnparsedTokenGroups;
|
|
13
14
|
exports.parseInternal = parseInternal;
|
|
14
15
|
exports.applyPrnReasonCoding = applyPrnReasonCoding;
|
|
15
16
|
exports.applyPrnReasonCodingAsync = applyPrnReasonCodingAsync;
|
|
@@ -885,6 +886,68 @@ function refineSiteRange(input, sanitized, tokenRange) {
|
|
|
885
886
|
}
|
|
886
887
|
return { start: startIndex, end: startIndex + lowerSanitized.length };
|
|
887
888
|
}
|
|
889
|
+
function findUnparsedTokenGroups(internal) {
|
|
890
|
+
const leftoverTokens = internal.tokens
|
|
891
|
+
.filter((token) => !internal.consumed.has(token.index))
|
|
892
|
+
.sort((a, b) => a.index - b.index);
|
|
893
|
+
if (leftoverTokens.length === 0) {
|
|
894
|
+
return [];
|
|
895
|
+
}
|
|
896
|
+
const groups = [];
|
|
897
|
+
let currentGroup = [];
|
|
898
|
+
let previousIndex;
|
|
899
|
+
let minimumStart = 0;
|
|
900
|
+
const locateRange = (tokensToLocate, initial) => {
|
|
901
|
+
const lowerInput = internal.input.toLowerCase();
|
|
902
|
+
let searchStart = minimumStart;
|
|
903
|
+
let rangeStart;
|
|
904
|
+
let rangeEnd;
|
|
905
|
+
for (const token of tokensToLocate) {
|
|
906
|
+
const segment = token.original.trim();
|
|
907
|
+
if (!segment) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const lowerSegment = segment.toLowerCase();
|
|
911
|
+
const foundIndex = lowerInput.indexOf(lowerSegment, searchStart);
|
|
912
|
+
if (foundIndex === -1) {
|
|
913
|
+
return initial;
|
|
914
|
+
}
|
|
915
|
+
if (rangeStart === undefined) {
|
|
916
|
+
rangeStart = foundIndex;
|
|
917
|
+
}
|
|
918
|
+
const segmentEnd = foundIndex + lowerSegment.length;
|
|
919
|
+
rangeEnd = rangeEnd === undefined ? segmentEnd : Math.max(rangeEnd, segmentEnd);
|
|
920
|
+
searchStart = segmentEnd;
|
|
921
|
+
}
|
|
922
|
+
if (rangeStart === undefined || rangeEnd === undefined) {
|
|
923
|
+
return initial;
|
|
924
|
+
}
|
|
925
|
+
return { start: rangeStart, end: rangeEnd };
|
|
926
|
+
};
|
|
927
|
+
const flush = () => {
|
|
928
|
+
if (!currentGroup.length) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const indices = currentGroup.map((token) => token.index);
|
|
932
|
+
const initialRange = computeTokenRange(internal.input, internal.tokens, indices);
|
|
933
|
+
const range = locateRange(currentGroup, initialRange);
|
|
934
|
+
groups.push({ tokens: currentGroup, range });
|
|
935
|
+
if (range) {
|
|
936
|
+
minimumStart = Math.max(minimumStart, range.end);
|
|
937
|
+
}
|
|
938
|
+
currentGroup = [];
|
|
939
|
+
previousIndex = undefined;
|
|
940
|
+
};
|
|
941
|
+
for (const token of leftoverTokens) {
|
|
942
|
+
if (previousIndex !== undefined && token.index !== previousIndex + 1) {
|
|
943
|
+
flush();
|
|
944
|
+
}
|
|
945
|
+
currentGroup.push(token);
|
|
946
|
+
previousIndex = token.index;
|
|
947
|
+
}
|
|
948
|
+
flush();
|
|
949
|
+
return groups;
|
|
950
|
+
}
|
|
888
951
|
function splitToken(token) {
|
|
889
952
|
if (/^[0-9]+(?:\.[0-9]+)?$/.test(token)) {
|
|
890
953
|
return [token];
|
|
@@ -1513,13 +1576,14 @@ function parseInternal(input, options) {
|
|
|
1513
1576
|
}
|
|
1514
1577
|
}
|
|
1515
1578
|
// Multiplicative tokens like 1x3
|
|
1516
|
-
for (
|
|
1579
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1580
|
+
const token = tokens[i];
|
|
1517
1581
|
if (internal.consumed.has(token.index))
|
|
1518
1582
|
continue;
|
|
1519
|
-
const
|
|
1520
|
-
if (
|
|
1521
|
-
const dose = parseFloat(
|
|
1522
|
-
const freq = parseFloat(
|
|
1583
|
+
const combined = token.lower.match(/^([0-9]+(?:\.[0-9]+)?)[x*]([0-9]+(?:\.[0-9]+)?)$/);
|
|
1584
|
+
if (combined) {
|
|
1585
|
+
const dose = parseFloat(combined[1]);
|
|
1586
|
+
const freq = parseFloat(combined[2]);
|
|
1523
1587
|
if (internal.dose === undefined) {
|
|
1524
1588
|
internal.dose = dose;
|
|
1525
1589
|
}
|
|
@@ -1527,6 +1591,55 @@ function parseInternal(input, options) {
|
|
|
1527
1591
|
internal.period = 1;
|
|
1528
1592
|
internal.periodUnit = types_1.FhirPeriodUnit.Day;
|
|
1529
1593
|
mark(internal.consumed, token);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const hasNumericDoseBefore = () => {
|
|
1597
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
1598
|
+
const prev = tokens[j];
|
|
1599
|
+
if (!prev) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
if (internal.consumed.has(prev.index)) {
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if (/^[0-9]+(?:\.[0-9]+)?$/.test(prev.lower)) {
|
|
1606
|
+
return true;
|
|
1607
|
+
}
|
|
1608
|
+
if (normalizeUnit(prev.lower, options)) {
|
|
1609
|
+
continue;
|
|
1610
|
+
}
|
|
1611
|
+
break;
|
|
1612
|
+
}
|
|
1613
|
+
return false;
|
|
1614
|
+
};
|
|
1615
|
+
if (internal.frequency === undefined && hasNumericDoseBefore()) {
|
|
1616
|
+
const prefix = token.lower.match(/^[x*]([0-9]+(?:\.[0-9]+)?)$/);
|
|
1617
|
+
if (prefix) {
|
|
1618
|
+
const freq = parseFloat(prefix[1]);
|
|
1619
|
+
if (Number.isFinite(freq)) {
|
|
1620
|
+
internal.frequency = freq;
|
|
1621
|
+
internal.period = 1;
|
|
1622
|
+
internal.periodUnit = types_1.FhirPeriodUnit.Day;
|
|
1623
|
+
mark(internal.consumed, token);
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if (token.lower === "x" || token.lower === "*") {
|
|
1628
|
+
const next = tokens[i + 1];
|
|
1629
|
+
if (next &&
|
|
1630
|
+
!internal.consumed.has(next.index) &&
|
|
1631
|
+
/^[0-9]+(?:\.[0-9]+)?$/.test(next.lower)) {
|
|
1632
|
+
const freq = parseFloat(next.original);
|
|
1633
|
+
if (Number.isFinite(freq)) {
|
|
1634
|
+
internal.frequency = freq;
|
|
1635
|
+
internal.period = 1;
|
|
1636
|
+
internal.periodUnit = types_1.FhirPeriodUnit.Day;
|
|
1637
|
+
mark(internal.consumed, token);
|
|
1638
|
+
mark(internal.consumed, next);
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1530
1643
|
}
|
|
1531
1644
|
}
|
|
1532
1645
|
const applyRouteDescriptor = (code, text) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -527,6 +527,22 @@ export interface ParseResult {
|
|
|
527
527
|
}>;
|
|
528
528
|
};
|
|
529
529
|
}
|
|
530
|
+
export interface LintIssue {
|
|
531
|
+
/** Human-readable description of why the segment could not be parsed. */
|
|
532
|
+
message: string;
|
|
533
|
+
/** Original substring that triggered the issue. */
|
|
534
|
+
text: string;
|
|
535
|
+
/** Tokens contributing to the unparsed segment. */
|
|
536
|
+
tokens: string[];
|
|
537
|
+
/** Location of {@link text} relative to the caller's original input. */
|
|
538
|
+
range?: TextRange;
|
|
539
|
+
}
|
|
540
|
+
export interface LintResult {
|
|
541
|
+
/** Standard parse output including FHIR representation and metadata. */
|
|
542
|
+
result: ParseResult;
|
|
543
|
+
/** Segments of the input that could not be interpreted. */
|
|
544
|
+
issues: LintIssue[];
|
|
545
|
+
}
|
|
530
546
|
/**
|
|
531
547
|
* Maps EventTiming codes (or other institution-specific timing strings) to
|
|
532
548
|
* 24-hour clock representations such as "08:00".
|