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 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 (const token of tokens) {
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 match = token.lower.match(/^([0-9]+(?:\.[0-9]+)?)[x*]([0-9]+(?:\.[0-9]+)?)$/);
1520
- if (match) {
1521
- const dose = parseFloat(match[1]);
1522
- const freq = parseFloat(match[2]);
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".
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",