ezmedicationinput 0.1.14 → 0.1.15

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 +142 -0
  2. package/package.json +1 -1
package/dist/parser.js CHANGED
@@ -186,6 +186,33 @@ const COUNT_CONNECTOR_WORDS = new Set([
186
186
  "additional",
187
187
  "extra"
188
188
  ]);
189
+ const FREQUENCY_SIMPLE_WORDS = {
190
+ once: 1,
191
+ twice: 2,
192
+ thrice: 3
193
+ };
194
+ const FREQUENCY_NUMBER_WORDS = {
195
+ one: 1,
196
+ two: 2,
197
+ three: 3,
198
+ four: 4,
199
+ five: 5,
200
+ six: 6,
201
+ seven: 7,
202
+ eight: 8,
203
+ nine: 9,
204
+ ten: 10,
205
+ eleven: 11,
206
+ twelve: 12
207
+ };
208
+ const FREQUENCY_TIMES_WORDS = new Set(["time", "times", "x"]);
209
+ const FREQUENCY_CONNECTOR_WORDS = new Set(["per", "a", "an", "each", "every"]);
210
+ const FREQUENCY_ADVERB_UNITS = {
211
+ daily: types_1.FhirPeriodUnit.Day,
212
+ weekly: types_1.FhirPeriodUnit.Week,
213
+ monthly: types_1.FhirPeriodUnit.Month,
214
+ hourly: types_1.FhirPeriodUnit.Hour
215
+ };
189
216
  const ROUTE_DESCRIPTOR_FILLER_WORDS = new Set([
190
217
  "per",
191
218
  "by",
@@ -580,6 +607,115 @@ function tryParseNumericCadence(internal, tokens, index) {
580
607
  mark(internal.consumed, unitToken);
581
608
  return true;
582
609
  }
610
+ function tryParseCountBasedFrequency(internal, tokens, index, options) {
611
+ const token = tokens[index];
612
+ if (internal.consumed.has(token.index)) {
613
+ return false;
614
+ }
615
+ if (internal.frequency !== undefined ||
616
+ internal.frequencyMax !== undefined ||
617
+ internal.period !== undefined ||
618
+ internal.periodMax !== undefined) {
619
+ return false;
620
+ }
621
+ const normalized = normalizeTokenLower(token);
622
+ let value;
623
+ let requiresPeriod = true;
624
+ let requiresCue = true;
625
+ if (/^[0-9]+(?:\.[0-9]+)?$/.test(normalized)) {
626
+ value = parseFloat(token.original);
627
+ }
628
+ else {
629
+ const simple = FREQUENCY_SIMPLE_WORDS[normalized];
630
+ if (simple !== undefined) {
631
+ value = simple;
632
+ requiresPeriod = false;
633
+ requiresCue = false;
634
+ }
635
+ else {
636
+ const wordValue = FREQUENCY_NUMBER_WORDS[normalized];
637
+ if (wordValue === undefined) {
638
+ return false;
639
+ }
640
+ value = wordValue;
641
+ }
642
+ }
643
+ if (!Number.isFinite(value) || value === undefined || value <= 0) {
644
+ return false;
645
+ }
646
+ const nextToken = tokens[index + 1];
647
+ if (nextToken &&
648
+ !internal.consumed.has(nextToken.index) &&
649
+ normalizeUnit(normalizeTokenLower(nextToken), options)) {
650
+ return false;
651
+ }
652
+ const partsToConsume = [];
653
+ let nextIndex = index + 1;
654
+ let periodUnit;
655
+ let sawCue = !requiresCue;
656
+ let sawTimesWord = false;
657
+ let sawConnectorWord = false;
658
+ while (true) {
659
+ const candidate = tokens[nextIndex];
660
+ if (!candidate || internal.consumed.has(candidate.index)) {
661
+ break;
662
+ }
663
+ const lower = normalizeTokenLower(candidate);
664
+ if (FREQUENCY_TIMES_WORDS.has(lower)) {
665
+ partsToConsume.push(candidate);
666
+ sawCue = true;
667
+ sawTimesWord = true;
668
+ nextIndex += 1;
669
+ continue;
670
+ }
671
+ if (FREQUENCY_CONNECTOR_WORDS.has(lower)) {
672
+ partsToConsume.push(candidate);
673
+ sawCue = true;
674
+ sawConnectorWord = true;
675
+ nextIndex += 1;
676
+ continue;
677
+ }
678
+ const adverbUnit = mapFrequencyAdverb(lower);
679
+ if (adverbUnit) {
680
+ periodUnit = adverbUnit;
681
+ partsToConsume.push(candidate);
682
+ break;
683
+ }
684
+ const mappedUnit = mapIntervalUnit(lower);
685
+ if (mappedUnit) {
686
+ periodUnit = mappedUnit;
687
+ partsToConsume.push(candidate);
688
+ break;
689
+ }
690
+ break;
691
+ }
692
+ if (!periodUnit) {
693
+ if (requiresPeriod) {
694
+ return false;
695
+ }
696
+ periodUnit = types_1.FhirPeriodUnit.Day;
697
+ }
698
+ if (requiresCue && !sawCue) {
699
+ return false;
700
+ }
701
+ internal.frequency = value;
702
+ internal.period = 1;
703
+ internal.periodUnit = periodUnit;
704
+ if (value === 1 && periodUnit === types_1.FhirPeriodUnit.Day && !internal.timingCode) {
705
+ internal.timingCode = "QD";
706
+ }
707
+ let consumeCurrentToken = true;
708
+ if (value === 1 && !sawConnectorWord && sawTimesWord && periodUnit !== types_1.FhirPeriodUnit.Day) {
709
+ consumeCurrentToken = false;
710
+ }
711
+ if (consumeCurrentToken) {
712
+ mark(internal.consumed, token);
713
+ }
714
+ for (const part of partsToConsume) {
715
+ mark(internal.consumed, part);
716
+ }
717
+ return consumeCurrentToken;
718
+ }
583
719
  const SITE_UNIT_ROUTE_HINTS = [
584
720
  { pattern: /\beye(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
585
721
  { pattern: /\beyelid(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
@@ -1244,6 +1380,9 @@ function mapIntervalUnit(token) {
1244
1380
  }
1245
1381
  return undefined;
1246
1382
  }
1383
+ function mapFrequencyAdverb(token) {
1384
+ return FREQUENCY_ADVERB_UNITS[token];
1385
+ }
1247
1386
  function parseNumericRange(token) {
1248
1387
  const rangeMatch = token.match(/^([0-9]+(?:\.[0-9]+)?)-([0-9]+(?:\.[0-9]+)?)$/);
1249
1388
  if (!rangeMatch) {
@@ -1620,6 +1759,9 @@ function parseInternal(input, options) {
1620
1759
  }
1621
1760
  }
1622
1761
  // Numeric dose
1762
+ if (tryParseCountBasedFrequency(internal, tokens, i, options)) {
1763
+ continue;
1764
+ }
1623
1765
  const rangeValue = parseNumericRange(token.lower);
1624
1766
  if (rangeValue) {
1625
1767
  if (!internal.doseRange) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",