ezmedicationinput 0.1.17 → 0.1.18

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.
Files changed (2) hide show
  1. package/dist/parser.js +136 -16
  2. package/package.json +1 -1
package/dist/parser.js CHANGED
@@ -1901,7 +1901,7 @@ function parseInternal(input, options) {
1901
1901
  for (let i = prnReasonStart; i < tokens.length; i++) {
1902
1902
  const token = tokens[i];
1903
1903
  if (internal.consumed.has(token.index)) {
1904
- continue;
1904
+ internal.consumed.delete(token.index);
1905
1905
  }
1906
1906
  reasonTokens.push(token.original);
1907
1907
  reasonIndices.push(token.index);
@@ -1948,6 +1948,26 @@ function parseInternal(input, options) {
1948
1948
  }
1949
1949
  }
1950
1950
  }
1951
+ if (reasonTokens.length > 0) {
1952
+ const siteStart = findTrailingPrnSiteSuffix(reasonObjects, internal, options);
1953
+ if (siteStart !== undefined) {
1954
+ for (let i = siteStart; i < reasonObjects.length; i++) {
1955
+ internal.consumed.delete(reasonObjects[i].index);
1956
+ }
1957
+ reasonObjects.splice(siteStart);
1958
+ reasonTokens.splice(siteStart);
1959
+ reasonIndices.splice(siteStart);
1960
+ if (reasonTokens.length > 0) {
1961
+ sortedIndices = reasonIndices.slice().sort((a, b) => a - b);
1962
+ range = computeTokenRange(internal.input, tokens, sortedIndices);
1963
+ sourceText = range ? internal.input.slice(range.start, range.end) : undefined;
1964
+ }
1965
+ else {
1966
+ range = undefined;
1967
+ sourceText = undefined;
1968
+ }
1969
+ }
1970
+ }
1951
1971
  if (reasonTokens.length > 0) {
1952
1972
  const joined = reasonTokens.join(" ").trim();
1953
1973
  if (joined) {
@@ -2492,18 +2512,52 @@ function normalizeSiteDisplayText(text, customSiteMap) {
2492
2512
  return trimmed;
2493
2513
  }
2494
2514
  const canonicalInput = (0, maps_1.normalizeBodySiteKey)(trimmed);
2495
- if (!canonicalInput || !isAdjectivalSitePhrase(canonicalInput)) {
2496
- return trimmed;
2497
- }
2498
- const definition = (_a = lookupBodySiteDefinition(customSiteMap, canonicalInput)) !== null && _a !== void 0 ? _a : maps_1.DEFAULT_BODY_SITE_SNOMED[canonicalInput];
2499
- if (!definition) {
2515
+ if (!canonicalInput) {
2500
2516
  return trimmed;
2501
2517
  }
2502
- const preferred = pickPreferredBodySitePhrase(canonicalInput, definition, customSiteMap);
2503
- if (!preferred) {
2504
- return trimmed;
2518
+ const resolvePreferred = (canonical) => {
2519
+ var _a;
2520
+ const definition = (_a = lookupBodySiteDefinition(customSiteMap, canonical)) !== null && _a !== void 0 ? _a : maps_1.DEFAULT_BODY_SITE_SNOMED[canonical];
2521
+ if (!definition) {
2522
+ return undefined;
2523
+ }
2524
+ const preferred = pickPreferredBodySitePhrase(canonical, definition, customSiteMap);
2525
+ const textValue = preferred !== null && preferred !== void 0 ? preferred : canonical;
2526
+ const normalized = (0, maps_1.normalizeBodySiteKey)(textValue);
2527
+ if (!normalized) {
2528
+ return undefined;
2529
+ }
2530
+ return { text: textValue, canonical: normalized };
2531
+ };
2532
+ if (isAdjectivalSitePhrase(canonicalInput)) {
2533
+ const direct = resolvePreferred(canonicalInput);
2534
+ return (_a = direct === null || direct === void 0 ? void 0 : direct.text) !== null && _a !== void 0 ? _a : trimmed;
2535
+ }
2536
+ const words = canonicalInput.split(/\s+/).filter((word) => word.length > 0);
2537
+ for (let i = 1; i < words.length; i++) {
2538
+ const prefix = words.slice(0, i);
2539
+ if (!prefix.every((word) => isAdjectivalSitePhrase(word))) {
2540
+ continue;
2541
+ }
2542
+ const candidateCanonical = words.slice(i).join(" ");
2543
+ if (!candidateCanonical) {
2544
+ continue;
2545
+ }
2546
+ const candidatePreferred = resolvePreferred(candidateCanonical);
2547
+ if (!candidatePreferred) {
2548
+ continue;
2549
+ }
2550
+ const prefixMatches = prefix.every((word) => {
2551
+ const normalizedPrefix = resolvePreferred(word);
2552
+ return (normalizedPrefix !== undefined &&
2553
+ normalizedPrefix.canonical === candidatePreferred.canonical);
2554
+ });
2555
+ if (!prefixMatches) {
2556
+ continue;
2557
+ }
2558
+ return candidatePreferred.text;
2505
2559
  }
2506
- return preferred;
2560
+ return trimmed;
2507
2561
  }
2508
2562
  function pickPreferredBodySitePhrase(canonical, definition, customSiteMap) {
2509
2563
  const synonyms = new Set();
@@ -2532,12 +2586,12 @@ function pickPreferredBodySitePhrase(canonical, definition, customSiteMap) {
2532
2586
  if (normalizedKey) {
2533
2587
  synonyms.add(normalizedKey);
2534
2588
  }
2535
- }
2536
- if (candidate.aliases) {
2537
- for (const alias of candidate.aliases) {
2538
- const normalizedAlias = (0, maps_1.normalizeBodySiteKey)(alias);
2539
- if (normalizedAlias) {
2540
- synonyms.add(normalizedAlias);
2589
+ if (candidate.aliases) {
2590
+ for (const alias of candidate.aliases) {
2591
+ const normalizedAlias = (0, maps_1.normalizeBodySiteKey)(alias);
2592
+ if (normalizedAlias) {
2593
+ synonyms.add(normalizedAlias);
2594
+ }
2541
2595
  }
2542
2596
  }
2543
2597
  }
@@ -2810,6 +2864,72 @@ function findPrnReasonSeparator(sourceText) {
2810
2864
  }
2811
2865
  return undefined;
2812
2866
  }
2867
+ function findTrailingPrnSiteSuffix(tokens, internal, options) {
2868
+ var _a;
2869
+ let suffixStart;
2870
+ let hasSiteHint = false;
2871
+ let hasConnector = false;
2872
+ for (let i = tokens.length - 1; i >= 0; i--) {
2873
+ const token = tokens[i];
2874
+ const lower = normalizeTokenLower(token);
2875
+ if (!lower) {
2876
+ if (suffixStart !== undefined && token.original.trim()) {
2877
+ break;
2878
+ }
2879
+ continue;
2880
+ }
2881
+ if (isBodySiteHint(lower, internal.customSiteHints)) {
2882
+ hasSiteHint = true;
2883
+ suffixStart = i;
2884
+ continue;
2885
+ }
2886
+ if (suffixStart !== undefined) {
2887
+ if (SITE_CONNECTORS.has(lower)) {
2888
+ hasConnector = true;
2889
+ suffixStart = i;
2890
+ continue;
2891
+ }
2892
+ if (SITE_FILLER_WORDS.has(lower) || ROUTE_DESCRIPTOR_FILLER_WORDS.has(lower)) {
2893
+ suffixStart = i;
2894
+ continue;
2895
+ }
2896
+ }
2897
+ if (suffixStart !== undefined) {
2898
+ break;
2899
+ }
2900
+ }
2901
+ if (!hasSiteHint || !hasConnector || suffixStart === undefined || suffixStart === 0) {
2902
+ return undefined;
2903
+ }
2904
+ const suffixTokens = tokens.slice(suffixStart);
2905
+ const siteWords = [];
2906
+ for (const token of suffixTokens) {
2907
+ const trimmed = token.original.trim();
2908
+ if (!trimmed) {
2909
+ continue;
2910
+ }
2911
+ const lower = normalizeTokenLower(token);
2912
+ if (SITE_CONNECTORS.has(lower) ||
2913
+ SITE_FILLER_WORDS.has(lower) ||
2914
+ ROUTE_DESCRIPTOR_FILLER_WORDS.has(lower)) {
2915
+ continue;
2916
+ }
2917
+ siteWords.push(trimmed);
2918
+ }
2919
+ if (!siteWords.length) {
2920
+ return undefined;
2921
+ }
2922
+ const sitePhrase = siteWords.join(" ");
2923
+ const canonical = (0, maps_1.normalizeBodySiteKey)(sitePhrase);
2924
+ if (!canonical) {
2925
+ return undefined;
2926
+ }
2927
+ const definition = (_a = lookupBodySiteDefinition(options === null || options === void 0 ? void 0 : options.siteCodeMap, canonical)) !== null && _a !== void 0 ? _a : maps_1.DEFAULT_BODY_SITE_SNOMED[canonical];
2928
+ if (!definition) {
2929
+ return undefined;
2930
+ }
2931
+ return suffixStart;
2932
+ }
2813
2933
  function lookupPrnReasonDefinition(map, canonical) {
2814
2934
  if (!map) {
2815
2935
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",