ezmedicationinput 0.1.35 → 0.1.37

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/i18n.js CHANGED
@@ -551,8 +551,25 @@ function combineFrequencyAndEventsThai(frequency, events) {
551
551
  }
552
552
  return { frequency, event: joinWithAndThai(events) };
553
553
  }
554
+ function isOralRouteThai(internal) {
555
+ var _a;
556
+ if (internal.routeCode === types_1.RouteCode["Oral route"]) {
557
+ return true;
558
+ }
559
+ const text = (_a = internal.routeText) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
560
+ if (!text) {
561
+ return false;
562
+ }
563
+ return (text === "po" ||
564
+ text === "oral" ||
565
+ text.includes("mouth") ||
566
+ text.includes("per os"));
567
+ }
554
568
  function buildRoutePhraseThai(internal, grammar, hasSite) {
555
569
  var _a;
570
+ if (grammar.verb === "รับประทาน" && isOralRouteThai(internal)) {
571
+ return undefined;
572
+ }
556
573
  if (typeof grammar.routePhrase === "function") {
557
574
  return grammar.routePhrase({ hasSite, internal });
558
575
  }
package/dist/schedule.js CHANGED
@@ -419,6 +419,69 @@ function expandWhenCodes(whenCodes, config, repeat) {
419
419
  return a.time.localeCompare(b.time);
420
420
  });
421
421
  }
422
+ const DEFAULT_WHEN_FALLBACK_CLOCKS = {
423
+ [types_1.EventTiming.Wake]: ["06:00:00"],
424
+ [types_1.EventTiming["Early Morning"]]: ["06:00:00"],
425
+ [types_1.EventTiming.Morning]: ["08:00:00"],
426
+ [types_1.EventTiming["Late Morning"]]: ["10:00:00"],
427
+ [types_1.EventTiming.Breakfast]: ["08:00:00"],
428
+ [types_1.EventTiming["Before Breakfast"]]: ["07:30:00"],
429
+ [types_1.EventTiming["After Breakfast"]]: ["08:30:00"],
430
+ [types_1.EventTiming.Noon]: ["12:00:00"],
431
+ [types_1.EventTiming.Lunch]: ["12:30:00"],
432
+ [types_1.EventTiming["Before Lunch"]]: ["12:00:00"],
433
+ [types_1.EventTiming["After Lunch"]]: ["13:00:00"],
434
+ [types_1.EventTiming["Early Afternoon"]]: ["14:00:00"],
435
+ [types_1.EventTiming.Afternoon]: ["15:00:00"],
436
+ [types_1.EventTiming["Late Afternoon"]]: ["16:00:00"],
437
+ [types_1.EventTiming["Early Evening"]]: ["18:00:00"],
438
+ [types_1.EventTiming.Evening]: ["19:00:00"],
439
+ [types_1.EventTiming["Late Evening"]]: ["20:00:00"],
440
+ [types_1.EventTiming.Dinner]: ["18:30:00"],
441
+ [types_1.EventTiming["Before Dinner"]]: ["18:00:00"],
442
+ [types_1.EventTiming["After Dinner"]]: ["19:00:00"],
443
+ [types_1.EventTiming.Night]: ["21:00:00"],
444
+ [types_1.EventTiming["Before Sleep"]]: ["22:00:00"],
445
+ [types_1.EventTiming["After Sleep"]]: ["06:30:00"],
446
+ [types_1.EventTiming.Meal]: ["08:00:00", "12:30:00", "18:30:00"],
447
+ [types_1.EventTiming["Before Meal"]]: ["07:30:00", "12:00:00", "18:00:00"],
448
+ [types_1.EventTiming["After Meal"]]: ["08:30:00", "13:00:00", "19:00:00"]
449
+ };
450
+ function inferWhenFallbackEntries(whenCodes, repeat) {
451
+ const entries = [];
452
+ const seen = new Set();
453
+ const addClock = (clock) => {
454
+ var _a;
455
+ const normalized = normalizeClock(clock);
456
+ const adjusted = repeat.offset
457
+ ? applyOffset(normalized, (_a = repeat.offset) !== null && _a !== void 0 ? _a : 0)
458
+ : { time: normalized, dayShift: 0 };
459
+ const key = `${adjusted.dayShift}|${adjusted.time}`;
460
+ if (seen.has(key)) {
461
+ return;
462
+ }
463
+ seen.add(key);
464
+ entries.push(adjusted);
465
+ };
466
+ for (const code of whenCodes) {
467
+ if (code === types_1.EventTiming.Immediate) {
468
+ continue;
469
+ }
470
+ const fallbackClocks = DEFAULT_WHEN_FALLBACK_CLOCKS[code];
471
+ if (!fallbackClocks) {
472
+ continue;
473
+ }
474
+ for (const clock of fallbackClocks) {
475
+ addClock(clock);
476
+ }
477
+ }
478
+ return entries.sort((a, b) => {
479
+ if (a.dayShift !== b.dayShift) {
480
+ return a.dayShift - b.dayShift;
481
+ }
482
+ return a.time.localeCompare(b.time);
483
+ });
484
+ }
422
485
  function mergeFrequencyDefaults(base, override) {
423
486
  var _a, _b, _c, _d;
424
487
  if (!base && !override) {
@@ -433,6 +496,25 @@ function mergeFrequencyDefaults(base, override) {
433
496
  }
434
497
  return merged;
435
498
  }
499
+ function inferDailyFrequencyClocks(frequency) {
500
+ if (!Number.isFinite(frequency) || frequency <= 0) {
501
+ return [];
502
+ }
503
+ if (frequency === 1) {
504
+ return ["09:00:00"];
505
+ }
506
+ const startMinutes = 8 * 60;
507
+ const endMinutes = 20 * 60;
508
+ const spanMinutes = endMinutes - startMinutes;
509
+ const clocks = new Set();
510
+ for (let index = 0; index < frequency; index += 1) {
511
+ const minutes = startMinutes + Math.round((spanMinutes * index) / (frequency - 1));
512
+ const hour = Math.floor(minutes / SECONDS_PER_MINUTE);
513
+ const minute = minutes % SECONDS_PER_MINUTE;
514
+ clocks.add(`${pad(hour)}:${pad(minute)}:00`);
515
+ }
516
+ return Array.from(clocks).sort();
517
+ }
436
518
  /** Resolves fallback clock arrays for frequency-only schedules. */
437
519
  function resolveFrequencyClocks(timing, config) {
438
520
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
@@ -462,6 +544,11 @@ function resolveFrequencyClocks(timing, config) {
462
544
  collected.add(normalizeClock(clock));
463
545
  }
464
546
  }
547
+ if (collected.size === 0 && repeat.period === 1 && repeat.periodUnit === "d") {
548
+ for (const clock of inferDailyFrequencyClocks(repeat.frequency)) {
549
+ collected.add(clock);
550
+ }
551
+ }
465
552
  }
466
553
  return Array.from(collected).sort();
467
554
  }
@@ -547,6 +634,11 @@ function nextDueDoses(dosage, options) {
547
634
  return a.time.localeCompare(b.time);
548
635
  });
549
636
  }
637
+ if (expanded.length === 0 &&
638
+ timeOfDayEntries.length === 0 &&
639
+ (!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
640
+ expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
641
+ }
550
642
  const includesImmediate = (0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate);
551
643
  if (includesImmediate) {
552
644
  const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
@@ -561,46 +653,53 @@ function nextDueDoses(dosage, options) {
561
653
  if (results.length >= effectiveLimit) {
562
654
  return results.slice(0, effectiveLimit);
563
655
  }
564
- if (expanded.length === 0) {
565
- return results.slice(0, effectiveLimit);
566
- }
567
- let currentDay = startOfLocalDay(from, timeZone);
568
- let iterations = 0;
569
- const maxIterations = effectiveLimit * 31;
570
- while (results.length < effectiveLimit && iterations < maxIterations) {
571
- const weekday = getLocalWeekday(currentDay, timeZone);
572
- if (!enforceDayFilter || dayFilter.has(weekday)) {
573
- for (const entry of expanded) {
574
- const targetDay = entry.dayShift === 0
575
- ? currentDay
576
- : addLocalDays(currentDay, entry.dayShift, timeZone);
577
- const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
578
- if (!zoned) {
579
- continue;
580
- }
581
- if (zoned < from) {
582
- continue;
583
- }
584
- if (orderedAt && zoned < orderedAt) {
585
- continue;
586
- }
587
- const iso = formatZonedIso(zoned, timeZone);
588
- if (!seen.has(iso)) {
589
- seen.add(iso);
590
- results.push(iso);
591
- if (results.length === effectiveLimit) {
592
- break;
656
+ const canFallbackToFrequency = expanded.length === 0 &&
657
+ timeOfDayEntries.length === 0 &&
658
+ !!repeat.frequency &&
659
+ !!repeat.period &&
660
+ !!repeat.periodUnit;
661
+ if (!canFallbackToFrequency) {
662
+ if (expanded.length === 0) {
663
+ return results.slice(0, effectiveLimit);
664
+ }
665
+ let currentDay = startOfLocalDay(from, timeZone);
666
+ let iterations = 0;
667
+ const maxIterations = effectiveLimit * 31;
668
+ while (results.length < effectiveLimit && iterations < maxIterations) {
669
+ const weekday = getLocalWeekday(currentDay, timeZone);
670
+ if (!enforceDayFilter || dayFilter.has(weekday)) {
671
+ for (const entry of expanded) {
672
+ const targetDay = entry.dayShift === 0
673
+ ? currentDay
674
+ : addLocalDays(currentDay, entry.dayShift, timeZone);
675
+ const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
676
+ if (!zoned) {
677
+ continue;
678
+ }
679
+ if (zoned < from) {
680
+ continue;
681
+ }
682
+ if (orderedAt && zoned < orderedAt) {
683
+ continue;
684
+ }
685
+ const iso = formatZonedIso(zoned, timeZone);
686
+ if (!seen.has(iso)) {
687
+ seen.add(iso);
688
+ results.push(iso);
689
+ if (results.length === effectiveLimit) {
690
+ break;
691
+ }
593
692
  }
594
693
  }
595
694
  }
695
+ if (results.length >= effectiveLimit) {
696
+ break;
697
+ }
698
+ currentDay = addLocalDays(currentDay, 1, timeZone);
699
+ iterations += 1;
596
700
  }
597
- if (results.length >= effectiveLimit) {
598
- break;
599
- }
600
- currentDay = addLocalDays(currentDay, 1, timeZone);
601
- iterations += 1;
701
+ return results.slice(0, effectiveLimit);
602
702
  }
603
- return results.slice(0, effectiveLimit);
604
703
  }
605
704
  const treatAsInterval = !!repeat.period &&
606
705
  !!repeat.periodUnit &&
@@ -700,40 +799,52 @@ function derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, ti
700
799
  return a.time.localeCompare(b.time);
701
800
  });
702
801
  }
802
+ if (expanded.length === 0 &&
803
+ timeOfDayEntries.length === 0 &&
804
+ (!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
805
+ expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
806
+ }
703
807
  if ((0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate)) {
704
808
  if (recordCandidate(orderedAt) && normalizedCount !== undefined && seen.size >= normalizedCount) {
705
809
  return count;
706
810
  }
707
811
  }
708
- if (expanded.length === 0) {
709
- return count;
710
- }
711
- let currentDay = startOfLocalDay(orderedAt, timeZone);
712
- let iterations = 0;
713
- const maxIterations = normalizedCount !== undefined ? normalizedCount * 31 : 31 * 365;
714
- while (currentDay < from && iterations < maxIterations) {
715
- const weekday = getLocalWeekday(currentDay, timeZone);
716
- if (!enforceDayFilter || dayFilter.has(weekday)) {
717
- for (const entry of expanded) {
718
- const targetDay = entry.dayShift === 0
719
- ? currentDay
720
- : addLocalDays(currentDay, entry.dayShift, timeZone);
721
- const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
722
- if (!zoned) {
723
- continue;
724
- }
725
- if (zoned < orderedAt || zoned >= from) {
726
- continue;
727
- }
728
- if (recordCandidate(zoned) && normalizedCount !== undefined && seen.size >= normalizedCount) {
729
- return count;
812
+ const canFallbackToFrequency = expanded.length === 0 &&
813
+ timeOfDayEntries.length === 0 &&
814
+ !!repeat.frequency &&
815
+ !!repeat.period &&
816
+ !!repeat.periodUnit;
817
+ if (!canFallbackToFrequency) {
818
+ if (expanded.length === 0) {
819
+ return count;
820
+ }
821
+ let currentDay = startOfLocalDay(orderedAt, timeZone);
822
+ let iterations = 0;
823
+ const maxIterations = normalizedCount !== undefined ? normalizedCount * 31 : 31 * 365;
824
+ while (currentDay < from && iterations < maxIterations) {
825
+ const weekday = getLocalWeekday(currentDay, timeZone);
826
+ if (!enforceDayFilter || dayFilter.has(weekday)) {
827
+ for (const entry of expanded) {
828
+ const targetDay = entry.dayShift === 0
829
+ ? currentDay
830
+ : addLocalDays(currentDay, entry.dayShift, timeZone);
831
+ const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
832
+ if (!zoned) {
833
+ continue;
834
+ }
835
+ if (zoned < orderedAt || zoned >= from) {
836
+ continue;
837
+ }
838
+ if (recordCandidate(zoned) && normalizedCount !== undefined && seen.size >= normalizedCount) {
839
+ return count;
840
+ }
730
841
  }
731
842
  }
843
+ currentDay = addLocalDays(currentDay, 1, timeZone);
844
+ iterations += 1;
732
845
  }
733
- currentDay = addLocalDays(currentDay, 1, timeZone);
734
- iterations += 1;
846
+ return count;
735
847
  }
736
- return count;
737
848
  }
738
849
  const treatAsInterval = !!repeat.period &&
739
850
  !!repeat.periodUnit &&
@@ -943,33 +1054,45 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
943
1054
  return a.time.localeCompare(b.time);
944
1055
  });
945
1056
  }
1057
+ if (expanded.length === 0 &&
1058
+ timeOfDayEntries.length === 0 &&
1059
+ (!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
1060
+ expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
1061
+ }
946
1062
  if ((0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate)) {
947
1063
  const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
948
1064
  if (!orderedAt || orderedAt >= from) {
949
1065
  recordCandidate(immediateSource);
950
1066
  }
951
1067
  }
952
- if (expanded.length === 0)
953
- return count;
954
- let currentDay = startOfLocalDay(from, timeZone);
955
- let iterations = 0;
956
- const maxIterations = limit !== undefined ? limit * 31 : 365 * 31;
957
- while (count < (limit !== null && limit !== void 0 ? limit : Infinity) && currentDay < to && iterations < maxIterations) {
958
- const weekday = getLocalWeekday(currentDay, timeZone);
959
- if (!enforceDayFilter || dayFilter.has(weekday)) {
960
- for (const entry of expanded) {
961
- const targetDay = entry.dayShift === 0
962
- ? currentDay
963
- : addLocalDays(currentDay, entry.dayShift, timeZone);
964
- const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
965
- if (zoned)
966
- recordCandidate(zoned);
1068
+ const canFallbackToFrequency = expanded.length === 0 &&
1069
+ timeOfDayEntries.length === 0 &&
1070
+ !!repeat.frequency &&
1071
+ !!repeat.period &&
1072
+ !!repeat.periodUnit;
1073
+ if (!canFallbackToFrequency) {
1074
+ if (expanded.length === 0)
1075
+ return count;
1076
+ let currentDay = startOfLocalDay(from, timeZone);
1077
+ let iterations = 0;
1078
+ const maxIterations = limit !== undefined ? limit * 31 : 365 * 31;
1079
+ while (count < (limit !== null && limit !== void 0 ? limit : Infinity) && currentDay < to && iterations < maxIterations) {
1080
+ const weekday = getLocalWeekday(currentDay, timeZone);
1081
+ if (!enforceDayFilter || dayFilter.has(weekday)) {
1082
+ for (const entry of expanded) {
1083
+ const targetDay = entry.dayShift === 0
1084
+ ? currentDay
1085
+ : addLocalDays(currentDay, entry.dayShift, timeZone);
1086
+ const zoned = makeZonedDateFromDay(targetDay, timeZone, entry.time);
1087
+ if (zoned)
1088
+ recordCandidate(zoned);
1089
+ }
967
1090
  }
1091
+ currentDay = addLocalDays(currentDay, 1, timeZone);
1092
+ iterations += 1;
968
1093
  }
969
- currentDay = addLocalDays(currentDay, 1, timeZone);
970
- iterations += 1;
1094
+ return count;
971
1095
  }
972
- return count;
973
1096
  }
974
1097
  const treatAsInterval = !!repeat.period &&
975
1098
  !!repeat.periodUnit &&
@@ -1047,7 +1170,7 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
1047
1170
  }
1048
1171
  return count;
1049
1172
  }
1050
- function calculateTotalUnits(options) {
1173
+ function calculateTotalUnitsSingle(options) {
1051
1174
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
1052
1175
  const { dosage, durationValue, durationUnit, roundToMultiple, context } = options;
1053
1176
  const from = coerceDate(options.from, "from");
@@ -1102,3 +1225,24 @@ function calculateTotalUnits(options) {
1102
1225
  }
1103
1226
  return result;
1104
1227
  }
1228
+ function calculateTotalUnits(options) {
1229
+ if (Array.isArray(options.dosage)) {
1230
+ const hasAnyDosage = options.dosage.length > 0;
1231
+ if (!hasAnyDosage) {
1232
+ return { totalUnits: 0 };
1233
+ }
1234
+ let totalUnits = 0;
1235
+ let totalContainers = 0;
1236
+ let sawContainers = false;
1237
+ for (const dosage of options.dosage) {
1238
+ const result = calculateTotalUnitsSingle(Object.assign(Object.assign({}, options), { dosage }));
1239
+ totalUnits += result.totalUnits;
1240
+ if (result.totalContainers !== undefined) {
1241
+ totalContainers += result.totalContainers;
1242
+ sawContainers = true;
1243
+ }
1244
+ }
1245
+ return sawContainers ? { totalUnits, totalContainers } : { totalUnits };
1246
+ }
1247
+ return calculateTotalUnitsSingle(options);
1248
+ }
package/dist/types.d.ts CHANGED
@@ -673,7 +673,7 @@ export interface TotalUnitsResult {
673
673
  totalContainers?: number;
674
674
  }
675
675
  export interface TotalUnitsOptions extends NextDueDoseOptions {
676
- dosage: FhirDosage;
676
+ dosage: FhirDosage | FhirDosage[];
677
677
  durationValue: number;
678
678
  durationUnit: FhirPeriodUnit;
679
679
  roundToMultiple?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",