ezmedicationinput 0.1.39 → 0.1.41

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.js CHANGED
@@ -58,6 +58,15 @@ Object.defineProperty(exports, "DEFAULT_BODY_SITE_SNOMED_SOURCE", { enumerable:
58
58
  Object.defineProperty(exports, "DEFAULT_ROUTE_SYNONYMS", { enumerable: true, get: function () { return maps_1.DEFAULT_ROUTE_SYNONYMS; } });
59
59
  Object.defineProperty(exports, "DEFAULT_UNIT_BY_ROUTE", { enumerable: true, get: function () { return maps_1.DEFAULT_UNIT_BY_ROUTE; } });
60
60
  Object.defineProperty(exports, "KNOWN_DOSAGE_FORMS_TO_DOSE", { enumerable: true, get: function () { return maps_1.KNOWN_DOSAGE_FORMS_TO_DOSE; } });
61
+ const REPEAT_NON_ANCHOR_KEYS = [
62
+ "count",
63
+ "frequency",
64
+ "frequencyMax",
65
+ "period",
66
+ "periodMax",
67
+ "periodUnit",
68
+ "offset"
69
+ ];
61
70
  function parseMealDashValues(token) {
62
71
  if (!/^[0-9]+(?:\.[0-9]+)?(?:-[0-9]+(?:\.[0-9]+)?){2,3}$/.test(token)) {
63
72
  return undefined;
@@ -155,6 +164,189 @@ function toSegmentMeta(segments) {
155
164
  range: { start: segment.start, end: segment.end }
156
165
  }));
157
166
  }
167
+ /**
168
+ * Deep equality helper for plain JSON-like parser output objects.
169
+ *
170
+ * @param left Left-side value.
171
+ * @param right Right-side value.
172
+ * @returns `true` when both values are structurally equal.
173
+ */
174
+ function deepEqual(left, right) {
175
+ if (left === right) {
176
+ return true;
177
+ }
178
+ if (left === null || right === null) {
179
+ return left === right;
180
+ }
181
+ if (Array.isArray(left) || Array.isArray(right)) {
182
+ if (!Array.isArray(left) || !Array.isArray(right)) {
183
+ return false;
184
+ }
185
+ if (left.length !== right.length) {
186
+ return false;
187
+ }
188
+ for (let i = 0; i < left.length; i += 1) {
189
+ if (!deepEqual(left[i], right[i])) {
190
+ return false;
191
+ }
192
+ }
193
+ return true;
194
+ }
195
+ if (typeof left !== "object" || typeof right !== "object") {
196
+ return false;
197
+ }
198
+ const leftRecord = left;
199
+ const rightRecord = right;
200
+ const leftKeys = Object.keys(leftRecord).filter((key) => leftRecord[key] !== undefined);
201
+ const rightKeys = Object.keys(rightRecord).filter((key) => rightRecord[key] !== undefined);
202
+ if (leftKeys.length !== rightKeys.length) {
203
+ return false;
204
+ }
205
+ for (const key of leftKeys) {
206
+ if (!Object.prototype.hasOwnProperty.call(rightRecord, key)) {
207
+ return false;
208
+ }
209
+ if (!deepEqual(leftRecord[key], rightRecord[key])) {
210
+ return false;
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+ /**
216
+ * Compares two string arrays as sets.
217
+ *
218
+ * @param left Left array.
219
+ * @param right Right array.
220
+ * @returns `true` when both arrays contain the same unique values.
221
+ */
222
+ function sameStringSet(left, right) {
223
+ const a = left !== null && left !== void 0 ? left : [];
224
+ const b = right !== null && right !== void 0 ? right : [];
225
+ if (a.length !== b.length) {
226
+ return false;
227
+ }
228
+ const set = new Set(a);
229
+ if (set.size !== b.length) {
230
+ return false;
231
+ }
232
+ for (const value of b) {
233
+ if (!set.has(value)) {
234
+ return false;
235
+ }
236
+ }
237
+ return true;
238
+ }
239
+ /**
240
+ * Determines whether a repeat block only uses merge-safe anchor fields.
241
+ *
242
+ * @param repeat FHIR timing repeat payload.
243
+ * @returns `true` when repeat contains only `when`/`timeOfDay`/`dayOfWeek`.
244
+ */
245
+ function isMergeableAnchorRepeat(repeat) {
246
+ if (!repeat) {
247
+ return true;
248
+ }
249
+ for (const key of REPEAT_NON_ANCHOR_KEYS) {
250
+ if (repeat[key] !== undefined) {
251
+ return false;
252
+ }
253
+ }
254
+ return true;
255
+ }
256
+ /**
257
+ * Checks whether two parsed items can be merged without changing semantics.
258
+ *
259
+ * @param base Existing merged item candidate.
260
+ * @param next Incoming parsed item.
261
+ * @returns `true` when both items differ only by merge-safe timing anchors.
262
+ */
263
+ function canMergeTimingOnly(base, next) {
264
+ const baseTiming = base.fhir.timing;
265
+ const nextTiming = next.fhir.timing;
266
+ const baseRepeat = baseTiming === null || baseTiming === void 0 ? void 0 : baseTiming.repeat;
267
+ const nextRepeat = nextTiming === null || nextTiming === void 0 ? void 0 : nextTiming.repeat;
268
+ if (!baseRepeat || !nextRepeat) {
269
+ return false;
270
+ }
271
+ if (!isMergeableAnchorRepeat(baseRepeat) || !isMergeableAnchorRepeat(nextRepeat)) {
272
+ return false;
273
+ }
274
+ if (!sameStringSet(baseRepeat.dayOfWeek, nextRepeat.dayOfWeek)) {
275
+ return false;
276
+ }
277
+ if (!deepEqual(baseTiming === null || baseTiming === void 0 ? void 0 : baseTiming.code, nextTiming === null || nextTiming === void 0 ? void 0 : nextTiming.code)) {
278
+ return false;
279
+ }
280
+ if (!deepEqual(baseTiming === null || baseTiming === void 0 ? void 0 : baseTiming.event, nextTiming === null || nextTiming === void 0 ? void 0 : nextTiming.event)) {
281
+ return false;
282
+ }
283
+ return (deepEqual(base.fhir.doseAndRate, next.fhir.doseAndRate) &&
284
+ deepEqual(base.fhir.route, next.fhir.route) &&
285
+ deepEqual(base.fhir.site, next.fhir.site) &&
286
+ deepEqual(base.fhir.additionalInstruction, next.fhir.additionalInstruction) &&
287
+ deepEqual(base.fhir.asNeededBoolean, next.fhir.asNeededBoolean) &&
288
+ deepEqual(base.fhir.asNeededFor, next.fhir.asNeededFor));
289
+ }
290
+ /**
291
+ * Returns a stable unique list preserving first-seen order.
292
+ *
293
+ * @param values Input values.
294
+ * @returns Deduplicated values in insertion order.
295
+ */
296
+ function uniqueStrings(values) {
297
+ const seen = new Set();
298
+ const output = [];
299
+ for (const value of values) {
300
+ if (!seen.has(value)) {
301
+ seen.add(value);
302
+ output.push(value);
303
+ }
304
+ }
305
+ return output;
306
+ }
307
+ /**
308
+ * Merges two parse results that are known to be timing-compatible.
309
+ *
310
+ * @param base Existing merged result.
311
+ * @param next Next result to fold into `base`.
312
+ * @param options Parse options used to render localized text.
313
+ * @returns New merged parse result.
314
+ */
315
+ function mergeParseResults(base, next, options) {
316
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
317
+ const baseRepeat = (_b = (_a = base.fhir.timing) === null || _a === void 0 ? void 0 : _a.repeat) !== null && _b !== void 0 ? _b : {};
318
+ const nextRepeat = (_d = (_c = next.fhir.timing) === null || _c === void 0 ? void 0 : _c.repeat) !== null && _d !== void 0 ? _d : {};
319
+ const mergedWhen = uniqueStrings([...((_e = baseRepeat.when) !== null && _e !== void 0 ? _e : []), ...((_f = nextRepeat.when) !== null && _f !== void 0 ? _f : [])]);
320
+ const mergedTimeOfDay = uniqueStrings([...((_g = baseRepeat.timeOfDay) !== null && _g !== void 0 ? _g : []), ...((_h = nextRepeat.timeOfDay) !== null && _h !== void 0 ? _h : [])]).sort();
321
+ const mergedRepeat = Object.assign(Object.assign(Object.assign(Object.assign({}, baseRepeat), (nextRepeat.dayOfWeek ? { dayOfWeek: nextRepeat.dayOfWeek } : {})), (mergedWhen.length ? { when: mergedWhen } : {})), (mergedTimeOfDay.length ? { timeOfDay: mergedTimeOfDay } : {}));
322
+ const mergedFhir = Object.assign(Object.assign({}, base.fhir), { timing: Object.assign(Object.assign({}, ((_j = base.fhir.timing) !== null && _j !== void 0 ? _j : {})), { repeat: mergedRepeat }) });
323
+ const shortText = formatSig(mergedFhir, "short", options);
324
+ const longText = formatSig(mergedFhir, "long", options);
325
+ mergedFhir.text = longText;
326
+ return {
327
+ fhir: mergedFhir,
328
+ shortText,
329
+ longText,
330
+ warnings: uniqueStrings([...((_k = base.warnings) !== null && _k !== void 0 ? _k : []), ...((_l = next.warnings) !== null && _l !== void 0 ? _l : [])]),
331
+ meta: Object.assign(Object.assign({}, base.meta), { consumedTokens: uniqueStrings([...((_m = base.meta.consumedTokens) !== null && _m !== void 0 ? _m : []), ...((_o = next.meta.consumedTokens) !== null && _o !== void 0 ? _o : [])]), leftoverText: uniqueStrings([base.meta.leftoverText, next.meta.leftoverText].filter((value) => !!value)).join(" ").trim() || undefined, siteLookups: [...((_p = base.meta.siteLookups) !== null && _p !== void 0 ? _p : []), ...((_q = next.meta.siteLookups) !== null && _q !== void 0 ? _q : [])], prnReasonLookups: [...((_r = base.meta.prnReasonLookups) !== null && _r !== void 0 ? _r : []), ...((_s = next.meta.prnReasonLookups) !== null && _s !== void 0 ? _s : [])] })
332
+ };
333
+ }
334
+ /**
335
+ * Appends a parsed segment result to the batch, reusing the current item when
336
+ * timing-only expansion can be represented as a single dosage element.
337
+ *
338
+ * @param items Accumulated batch items.
339
+ * @param next Newly parsed segment result.
340
+ * @param options Parse options used to format merged text.
341
+ */
342
+ function appendParseResult(items, next, options) {
343
+ const previous = items[items.length - 1];
344
+ if (previous && canMergeTimingOnly(previous, next)) {
345
+ items[items.length - 1] = mergeParseResults(previous, next, options);
346
+ return;
347
+ }
348
+ items.push(next);
349
+ }
158
350
  function parseSig(input, options) {
159
351
  const segments = expandMealDashSegments((0, segment_1.splitSigSegments)(input), options);
160
352
  const carry = {};
@@ -166,7 +358,7 @@ function parseSig(input, options) {
166
358
  (0, parser_1.applySiteCoding)(internal, options);
167
359
  const result = buildParseResult(internal, options);
168
360
  rebaseParseResult(result, input, segment.start);
169
- results.push(result);
361
+ appendParseResult(results, result, options);
170
362
  updateCarryForward(carry, internal);
171
363
  }
172
364
  const legacy = resolveLegacyParseResult(results, input, options);
@@ -232,7 +424,7 @@ function parseSigAsync(input, options) {
232
424
  yield (0, parser_1.applySiteCodingAsync)(internal, options);
233
425
  const result = buildParseResult(internal, options);
234
426
  rebaseParseResult(result, input, segment.start);
235
- results.push(result);
427
+ appendParseResult(results, result, options);
236
428
  updateCarryForward(carry, internal);
237
429
  }
238
430
  const legacy = resolveLegacyParseResult(results, input, options);
package/dist/maps.js CHANGED
@@ -548,6 +548,13 @@ function addMetricUnitSynonyms(map) {
548
548
  }
549
549
  exports.HOUSEHOLD_VOLUME_UNITS = ["tsp", "tbsp"];
550
550
  const STATIC_UNIT_SYNONYMS = {
551
+ u: "U",
552
+ unit: "U",
553
+ units: "U",
554
+ iu: "IU",
555
+ "i.u": "IU",
556
+ "i.u.": "IU",
557
+ ius: "IU",
551
558
  tab: "tab",
552
559
  tabs: "tab",
553
560
  tablet: "tab",
@@ -745,18 +752,43 @@ exports.DISCOURAGED_TOKENS = {
745
752
  exports.DAY_OF_WEEK_TOKENS = {
746
753
  monday: types_1.FhirDayOfWeek.Monday,
747
754
  mon: types_1.FhirDayOfWeek.Monday,
755
+ mond: types_1.FhirDayOfWeek.Monday,
748
756
  tuesday: types_1.FhirDayOfWeek.Tuesday,
749
757
  tue: types_1.FhirDayOfWeek.Tuesday,
758
+ tues: types_1.FhirDayOfWeek.Tuesday,
750
759
  wednesday: types_1.FhirDayOfWeek.Wednesday,
751
760
  wed: types_1.FhirDayOfWeek.Wednesday,
761
+ weds: types_1.FhirDayOfWeek.Wednesday,
752
762
  thursday: types_1.FhirDayOfWeek.Thursday,
753
763
  thu: types_1.FhirDayOfWeek.Thursday,
764
+ thur: types_1.FhirDayOfWeek.Thursday,
765
+ thurs: types_1.FhirDayOfWeek.Thursday,
754
766
  friday: types_1.FhirDayOfWeek.Friday,
755
767
  fri: types_1.FhirDayOfWeek.Friday,
756
768
  saturday: types_1.FhirDayOfWeek.Saturday,
757
769
  sat: types_1.FhirDayOfWeek.Saturday,
758
770
  sunday: types_1.FhirDayOfWeek.Sunday,
759
- sun: types_1.FhirDayOfWeek.Sunday
771
+ sun: types_1.FhirDayOfWeek.Sunday,
772
+ จ: types_1.FhirDayOfWeek.Monday,
773
+ จัน: types_1.FhirDayOfWeek.Monday,
774
+ จันทร์: types_1.FhirDayOfWeek.Monday,
775
+ อ: types_1.FhirDayOfWeek.Tuesday,
776
+ อัง: types_1.FhirDayOfWeek.Tuesday,
777
+ อังคาร: types_1.FhirDayOfWeek.Tuesday,
778
+ พ: types_1.FhirDayOfWeek.Wednesday,
779
+ พุธ: types_1.FhirDayOfWeek.Wednesday,
780
+ พฤ: types_1.FhirDayOfWeek.Thursday,
781
+ พฤหัส: types_1.FhirDayOfWeek.Thursday,
782
+ พฤหัสบดี: types_1.FhirDayOfWeek.Thursday,
783
+ ศ: types_1.FhirDayOfWeek.Friday,
784
+ ศุก: types_1.FhirDayOfWeek.Friday,
785
+ ศุกร์: types_1.FhirDayOfWeek.Friday,
786
+ ส: types_1.FhirDayOfWeek.Saturday,
787
+ เสา: types_1.FhirDayOfWeek.Saturday,
788
+ เสาร์: types_1.FhirDayOfWeek.Saturday,
789
+ อา: types_1.FhirDayOfWeek.Sunday,
790
+ อาท: types_1.FhirDayOfWeek.Sunday,
791
+ อาทิตย์: types_1.FhirDayOfWeek.Sunday
760
792
  };
761
793
  exports.WORD_FREQUENCIES = {
762
794
  daily: { frequency: 1, periodUnit: types_1.FhirPeriodUnit.Day },
package/dist/parser.js CHANGED
@@ -195,6 +195,11 @@ const COMBO_EVENT_TIMINGS = {
195
195
  "before sleep": types_1.EventTiming["Before Sleep"],
196
196
  "upon waking": types_1.EventTiming.Wake
197
197
  };
198
+ const DAY_RANGE_PART_PATTERN = Object.keys(maps_1.DAY_OF_WEEK_TOKENS)
199
+ .sort((a, b) => b.length - a.length)
200
+ .map((token) => token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
201
+ .join("|");
202
+ const DAY_RANGE_SPACED_HYPHEN_REGEX = new RegExp(`(^|\\s)(${DAY_RANGE_PART_PATTERN})\\s*-\\s*(${DAY_RANGE_PART_PATTERN})(?=\\s|$)`, "giu");
198
203
  const MEAL_CONTEXT_CONNECTORS = new Set(["and", "or", "&", "+", "plus"]);
199
204
  const COUNT_KEYWORDS = new Set([
200
205
  "time",
@@ -965,6 +970,7 @@ const SITE_UNIT_ROUTE_HINTS = [
965
970
  function tokenize(input) {
966
971
  const separators = /[(),;]/g;
967
972
  let normalized = input.trim().replace(separators, " ");
973
+ normalized = normalized.replace(DAY_RANGE_SPACED_HYPHEN_REGEX, (_match, prefix, start, end) => `${prefix}${start}-${end}`);
968
974
  normalized = normalized.replace(/\s-\s/g, " ; ");
969
975
  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}`);
970
976
  normalized = normalized.replace(/(\d+)\s*\/\s*(\d+)/g, (match, num, den) => {
@@ -1585,6 +1591,154 @@ function isTimingAnchorOrPrefix(tokens, index, prnReasonStart) {
1585
1591
  (isAtPrefixToken(lower) || lower === "on" || lower === "with") ||
1586
1592
  /^\d/.test(lower));
1587
1593
  }
1594
+ const DAY_SEQUENCE = [
1595
+ types_1.FhirDayOfWeek.Monday,
1596
+ types_1.FhirDayOfWeek.Tuesday,
1597
+ types_1.FhirDayOfWeek.Wednesday,
1598
+ types_1.FhirDayOfWeek.Thursday,
1599
+ types_1.FhirDayOfWeek.Friday,
1600
+ types_1.FhirDayOfWeek.Saturday,
1601
+ types_1.FhirDayOfWeek.Sunday
1602
+ ];
1603
+ const DAY_GROUP_TOKENS = {
1604
+ weekend: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1605
+ weekends: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1606
+ wknd: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1607
+ weekdays: [
1608
+ types_1.FhirDayOfWeek.Monday,
1609
+ types_1.FhirDayOfWeek.Tuesday,
1610
+ types_1.FhirDayOfWeek.Wednesday,
1611
+ types_1.FhirDayOfWeek.Thursday,
1612
+ types_1.FhirDayOfWeek.Friday
1613
+ ],
1614
+ weekday: [
1615
+ types_1.FhirDayOfWeek.Monday,
1616
+ types_1.FhirDayOfWeek.Tuesday,
1617
+ types_1.FhirDayOfWeek.Wednesday,
1618
+ types_1.FhirDayOfWeek.Thursday,
1619
+ types_1.FhirDayOfWeek.Friday
1620
+ ],
1621
+ workday: [
1622
+ types_1.FhirDayOfWeek.Monday,
1623
+ types_1.FhirDayOfWeek.Tuesday,
1624
+ types_1.FhirDayOfWeek.Wednesday,
1625
+ types_1.FhirDayOfWeek.Thursday,
1626
+ types_1.FhirDayOfWeek.Friday
1627
+ ],
1628
+ workdays: [
1629
+ types_1.FhirDayOfWeek.Monday,
1630
+ types_1.FhirDayOfWeek.Tuesday,
1631
+ types_1.FhirDayOfWeek.Wednesday,
1632
+ types_1.FhirDayOfWeek.Thursday,
1633
+ types_1.FhirDayOfWeek.Friday
1634
+ ],
1635
+ วันธรรมดา: [
1636
+ types_1.FhirDayOfWeek.Monday,
1637
+ types_1.FhirDayOfWeek.Tuesday,
1638
+ types_1.FhirDayOfWeek.Wednesday,
1639
+ types_1.FhirDayOfWeek.Thursday,
1640
+ types_1.FhirDayOfWeek.Friday
1641
+ ],
1642
+ วันทำงาน: [
1643
+ types_1.FhirDayOfWeek.Monday,
1644
+ types_1.FhirDayOfWeek.Tuesday,
1645
+ types_1.FhirDayOfWeek.Wednesday,
1646
+ types_1.FhirDayOfWeek.Thursday,
1647
+ types_1.FhirDayOfWeek.Friday
1648
+ ],
1649
+ วันหยุด: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1650
+ สุดสัปดาห์: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1651
+ เสาร์อาทิตย์: [types_1.FhirDayOfWeek.Saturday, types_1.FhirDayOfWeek.Sunday],
1652
+ จันทร์ถึงศุกร์: [
1653
+ types_1.FhirDayOfWeek.Monday,
1654
+ types_1.FhirDayOfWeek.Tuesday,
1655
+ types_1.FhirDayOfWeek.Wednesday,
1656
+ types_1.FhirDayOfWeek.Thursday,
1657
+ types_1.FhirDayOfWeek.Friday
1658
+ ]
1659
+ };
1660
+ const DAY_RANGE_CONNECTOR_TOKENS = new Set(["-", "to", "through", "thru", "ถึง", "จนถึง"]);
1661
+ function addDayOfWeek(internal, day) {
1662
+ if (!(0, array_1.arrayIncludes)(internal.dayOfWeek, day)) {
1663
+ internal.dayOfWeek.push(day);
1664
+ }
1665
+ }
1666
+ function addDayOfWeekList(internal, days) {
1667
+ for (const day of days) {
1668
+ addDayOfWeek(internal, day);
1669
+ }
1670
+ }
1671
+ function expandDayRange(start, end) {
1672
+ const startIndex = DAY_SEQUENCE.indexOf(start);
1673
+ const endIndex = DAY_SEQUENCE.indexOf(end);
1674
+ if (startIndex < 0 || endIndex < 0) {
1675
+ return [start, end];
1676
+ }
1677
+ if (startIndex <= endIndex) {
1678
+ return DAY_SEQUENCE.slice(startIndex, endIndex + 1);
1679
+ }
1680
+ return [...DAY_SEQUENCE.slice(startIndex), ...DAY_SEQUENCE.slice(0, endIndex + 1)];
1681
+ }
1682
+ function resolveDayTokenDays(tokenLower) {
1683
+ const normalized = tokenLower.replace(/[.,;:]/g, "");
1684
+ const direct = maps_1.DAY_OF_WEEK_TOKENS[normalized];
1685
+ if (direct) {
1686
+ return [direct];
1687
+ }
1688
+ const grouped = DAY_GROUP_TOKENS[normalized];
1689
+ if (grouped) {
1690
+ return grouped.slice();
1691
+ }
1692
+ const rangeMatch = normalized.match(/^([^-–—~]+)[-–—~]([^-–—~]+)$/);
1693
+ if (rangeMatch) {
1694
+ const start = maps_1.DAY_OF_WEEK_TOKENS[rangeMatch[1]];
1695
+ const end = maps_1.DAY_OF_WEEK_TOKENS[rangeMatch[2]];
1696
+ if (start && end) {
1697
+ return expandDayRange(start, end);
1698
+ }
1699
+ }
1700
+ const compactConnectorRange = normalized.match(/^(.+?)(ถึง|จนถึง|to|through|thru)(.+)$/u);
1701
+ if (compactConnectorRange) {
1702
+ const start = maps_1.DAY_OF_WEEK_TOKENS[compactConnectorRange[1]];
1703
+ const end = maps_1.DAY_OF_WEEK_TOKENS[compactConnectorRange[3]];
1704
+ if (start && end) {
1705
+ return expandDayRange(start, end);
1706
+ }
1707
+ }
1708
+ return undefined;
1709
+ }
1710
+ function tryConsumeDayRangeTokens(internal, tokens, index) {
1711
+ const startToken = tokens[index];
1712
+ if (!startToken || internal.consumed.has(startToken.index)) {
1713
+ return 0;
1714
+ }
1715
+ const startDays = resolveDayTokenDays(normalizeTokenLower(startToken));
1716
+ if (!startDays || startDays.length !== 1) {
1717
+ return 0;
1718
+ }
1719
+ const connectorToken = tokens[index + 1];
1720
+ const endToken = tokens[index + 2];
1721
+ if (!connectorToken ||
1722
+ !endToken ||
1723
+ internal.consumed.has(connectorToken.index) ||
1724
+ internal.consumed.has(endToken.index)) {
1725
+ return 0;
1726
+ }
1727
+ const connector = normalizeTokenLower(connectorToken);
1728
+ if (!DAY_RANGE_CONNECTOR_TOKENS.has(connector)) {
1729
+ return 0;
1730
+ }
1731
+ const endDays = resolveDayTokenDays(normalizeTokenLower(endToken));
1732
+ if (!endDays || endDays.length !== 1) {
1733
+ return 0;
1734
+ }
1735
+ const expanded = expandDayRange(startDays[0], endDays[0]);
1736
+ addDayOfWeekList(internal, expanded);
1737
+ mark(internal.consumed, startToken);
1738
+ mark(internal.consumed, connectorToken);
1739
+ mark(internal.consumed, endToken);
1740
+ return 3;
1741
+ }
1588
1742
  function parseAnchorSequence(internal, tokens, index, prefixCode) {
1589
1743
  var _a;
1590
1744
  const token = tokens[index];
@@ -1594,16 +1748,20 @@ function parseAnchorSequence(internal, tokens, index, prefixCode) {
1594
1748
  if (internal.consumed.has(nextToken.index)) {
1595
1749
  continue;
1596
1750
  }
1597
- const lower = nextToken.lower;
1751
+ const lower = normalizeTokenLower(nextToken);
1598
1752
  if (MEAL_CONTEXT_CONNECTORS.has(lower) || lower === ",") {
1599
1753
  mark(internal.consumed, nextToken);
1600
1754
  continue;
1601
1755
  }
1602
- const day = maps_1.DAY_OF_WEEK_TOKENS[lower];
1603
- if (day) {
1604
- if (!(0, array_1.arrayIncludes)(internal.dayOfWeek, day)) {
1605
- internal.dayOfWeek.push(day);
1606
- }
1756
+ const rangeConsumed = tryConsumeDayRangeTokens(internal, tokens, lookahead);
1757
+ if (rangeConsumed > 0) {
1758
+ converted++;
1759
+ lookahead += rangeConsumed - 1;
1760
+ continue;
1761
+ }
1762
+ const days = resolveDayTokenDays(lower);
1763
+ if (days) {
1764
+ addDayOfWeekList(internal, days);
1607
1765
  mark(internal.consumed, nextToken);
1608
1766
  converted++;
1609
1767
  continue;
@@ -1748,6 +1906,70 @@ function applyCountLimit(internal, value) {
1748
1906
  internal.count = rounded;
1749
1907
  return true;
1750
1908
  }
1909
+ const DOSE_SCALE_MULTIPLIERS = {
1910
+ k: 1000,
1911
+ thousand: 1000,
1912
+ m: 1000000,
1913
+ mn: 1000000,
1914
+ mio: 1000000,
1915
+ million: 1000000,
1916
+ b: 1000000000,
1917
+ bn: 1000000000,
1918
+ billion: 1000000000
1919
+ };
1920
+ function resolveUnitTokenAt(tokens, index, consumed, options) {
1921
+ const token = tokens[index];
1922
+ if (!token || consumed.has(token.index)) {
1923
+ return undefined;
1924
+ }
1925
+ const normalized = normalizeTokenLower(token);
1926
+ const direct = normalizeUnit(normalized, options);
1927
+ if (direct) {
1928
+ return { unit: direct, consumedIndices: [index] };
1929
+ }
1930
+ if (normalized === "international") {
1931
+ const nextToken = tokens[index + 1];
1932
+ if (!nextToken || consumed.has(nextToken.index)) {
1933
+ return undefined;
1934
+ }
1935
+ const nextNormalized = normalizeTokenLower(nextToken);
1936
+ if (nextNormalized === "unit" ||
1937
+ nextNormalized === "units" ||
1938
+ nextNormalized === "u" ||
1939
+ nextNormalized === "iu" ||
1940
+ nextNormalized === "ius") {
1941
+ return { unit: "IU", consumedIndices: [index, index + 1] };
1942
+ }
1943
+ }
1944
+ return undefined;
1945
+ }
1946
+ function resolveNumericDoseUnit(tokens, numberIndex, value, consumed, options) {
1947
+ const directUnit = resolveUnitTokenAt(tokens, numberIndex + 1, consumed, options);
1948
+ if (directUnit) {
1949
+ return {
1950
+ doseValue: value,
1951
+ unit: directUnit.unit,
1952
+ consumedIndices: directUnit.consumedIndices
1953
+ };
1954
+ }
1955
+ const scaleToken = tokens[numberIndex + 1];
1956
+ if (!scaleToken || consumed.has(scaleToken.index)) {
1957
+ return { doseValue: value, consumedIndices: [] };
1958
+ }
1959
+ const multiplier = DOSE_SCALE_MULTIPLIERS[normalizeTokenLower(scaleToken)];
1960
+ if (!multiplier) {
1961
+ return { doseValue: value, consumedIndices: [] };
1962
+ }
1963
+ const scaledUnit = resolveUnitTokenAt(tokens, numberIndex + 2, consumed, options);
1964
+ if (!scaledUnit) {
1965
+ return { doseValue: value, consumedIndices: [] };
1966
+ }
1967
+ return {
1968
+ doseValue: value * multiplier,
1969
+ unit: scaledUnit.unit,
1970
+ consumedIndices: [numberIndex + 1, ...scaledUnit.consumedIndices]
1971
+ };
1972
+ }
1751
1973
  function parseInternal(input, options) {
1752
1974
  var _a, _b, _c, _d, _e, _f, _g, _h;
1753
1975
  const tokens = tokenize(input);
@@ -2063,11 +2285,13 @@ function parseInternal(input, options) {
2063
2285
  }
2064
2286
  }
2065
2287
  // Day of week
2066
- const day = maps_1.DAY_OF_WEEK_TOKENS[token.lower];
2067
- if (day) {
2068
- if (!(0, array_1.arrayIncludes)(internal.dayOfWeek, day)) {
2069
- internal.dayOfWeek.push(day);
2070
- }
2288
+ const rangeConsumed = tryConsumeDayRangeTokens(internal, tokens, i);
2289
+ if (rangeConsumed > 0) {
2290
+ continue;
2291
+ }
2292
+ const days = resolveDayTokenDays(normalizeTokenLower(token));
2293
+ if (days) {
2294
+ addDayOfWeekList(internal, days);
2071
2295
  mark(internal.consumed, token);
2072
2296
  continue;
2073
2297
  }
@@ -2210,29 +2434,27 @@ function parseInternal(input, options) {
2210
2434
  internal.doseRange = rangeValue;
2211
2435
  }
2212
2436
  mark(internal.consumed, token);
2213
- const unitToken = tokens[i + 1];
2214
- if (unitToken && !internal.consumed.has(unitToken.index)) {
2215
- const unit = normalizeUnit(unitToken.lower, options);
2216
- if (unit) {
2217
- internal.unit = unit;
2218
- mark(internal.consumed, unitToken);
2437
+ const resolvedUnit = resolveUnitTokenAt(tokens, i + 1, internal.consumed, options);
2438
+ if (resolvedUnit) {
2439
+ internal.unit = resolvedUnit.unit;
2440
+ for (const consumedIndex of resolvedUnit.consumedIndices) {
2441
+ mark(internal.consumed, tokens[consumedIndex]);
2219
2442
  }
2220
2443
  }
2221
2444
  continue;
2222
2445
  }
2223
2446
  if (/^[0-9]+(?:\.[0-9]+)?$/.test(token.lower)) {
2224
2447
  const value = parseFloat(token.original);
2448
+ const resolvedDose = resolveNumericDoseUnit(tokens, i, value, internal.consumed, options);
2225
2449
  if (internal.dose === undefined) {
2226
- internal.dose = value;
2450
+ internal.dose = resolvedDose.doseValue;
2227
2451
  }
2228
2452
  mark(internal.consumed, token);
2229
- const unitToken = tokens[i + 1];
2230
- if (unitToken && !internal.consumed.has(unitToken.index)) {
2231
- const unit = normalizeUnit(unitToken.lower, options);
2232
- if (unit) {
2233
- internal.unit = unit;
2234
- mark(internal.consumed, unitToken);
2235
- }
2453
+ if (resolvedDose.unit) {
2454
+ internal.unit = resolvedDose.unit;
2455
+ }
2456
+ for (const consumedIndex of resolvedDose.consumedIndices) {
2457
+ mark(internal.consumed, tokens[consumedIndex]);
2236
2458
  }
2237
2459
  continue;
2238
2460
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",