ezmedicationinput 0.1.49 → 0.1.52

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.cjs CHANGED
@@ -62,6 +62,7 @@ __export(index_exports, {
62
62
  AdviceModality: () => AdviceModality,
63
63
  AdvicePolarity: () => AdvicePolarity,
64
64
  AdviceRelation: () => AdviceRelation,
65
+ BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL: () => BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL,
65
66
  BODY_SITE_SPATIAL_RELATION_EXTENSION_URL: () => BODY_SITE_SPATIAL_RELATION_EXTENSION_URL,
66
67
  DEFAULT_BODY_SITE_SNOMED: () => DEFAULT_BODY_SITE_SNOMED,
67
68
  DEFAULT_BODY_SITE_SNOMED_SOURCE: () => DEFAULT_BODY_SITE_SNOMED_SOURCE,
@@ -86,6 +87,7 @@ __export(index_exports, {
86
87
  SNOMED_CT_TOPOGRAPHICAL_MODIFIER_CODE: () => SNOMED_CT_TOPOGRAPHICAL_MODIFIER_CODE,
87
88
  SNOMED_CT_TOPOGRAPHICAL_MODIFIER_DISPLAY: () => SNOMED_CT_TOPOGRAPHICAL_MODIFIER_DISPLAY,
88
89
  SNOMED_SYSTEM: () => SNOMED_SYSTEM,
90
+ buildBodySiteAdministrationTargetCountExtension: () => buildBodySiteAdministrationTargetCountExtension,
89
91
  buildBodySiteSpatialRelationExtension: () => buildBodySiteSpatialRelationExtension,
90
92
  buildBodySiteSpatialRelationExtensions: () => buildBodySiteSpatialRelationExtensions,
91
93
  buildBodySiteTopographicalModifierCoding: () => buildBodySiteTopographicalModifierCoding,
@@ -99,6 +101,7 @@ __export(index_exports, {
99
101
  formatSig: () => formatSig,
100
102
  formatSigBatch: () => formatSigBatch,
101
103
  fromFhirDosage: () => fromFhirDosage,
104
+ getBodySiteAdministrationTargetCount: () => getBodySiteAdministrationTargetCount,
102
105
  getBodySiteCode: () => getBodySiteCode,
103
106
  getBodySiteCodeAsync: () => getBodySiteCodeAsync,
104
107
  getBodySiteText: () => getBodySiteText,
@@ -117,6 +120,7 @@ __export(index_exports, {
117
120
  lookupBodySite: () => lookupBodySite,
118
121
  lookupBodySiteAsync: () => lookupBodySiteAsync,
119
122
  nextDueDoses: () => nextDueDoses,
123
+ parseBodySiteAdministrationTargetCountExtension: () => parseBodySiteAdministrationTargetCountExtension,
120
124
  parseBodySiteSpatialRelationExtension: () => parseBodySiteSpatialRelationExtension,
121
125
  parseSig: () => parseSig,
122
126
  parseSigAsync: () => parseSigAsync,
@@ -134,60 +138,6 @@ __export(index_exports, {
134
138
  });
135
139
  module.exports = __toCommonJS(index_exports);
136
140
 
137
- // src/prn.ts
138
- function getCanonicalPrnReasonText(reason) {
139
- var _a2, _b;
140
- return (_b = reason == null ? void 0 : reason.text) != null ? _b : (_a2 = reason == null ? void 0 : reason.coding) == null ? void 0 : _a2.display;
141
- }
142
- function joinCanonicalPrnReasonTexts(reasons, conjunction = "or") {
143
- var _a2;
144
- if (!(reasons == null ? void 0 : reasons.length)) {
145
- return void 0;
146
- }
147
- const texts = [];
148
- for (const reason of reasons) {
149
- const text = (_a2 = getCanonicalPrnReasonText(reason)) == null ? void 0 : _a2.trim();
150
- if (!text) {
151
- continue;
152
- }
153
- texts.push(text);
154
- }
155
- switch (texts.length) {
156
- case 0:
157
- return void 0;
158
- case 1:
159
- return texts[0];
160
- case 2:
161
- return `${texts[0]} ${conjunction} ${texts[1]}`;
162
- default: {
163
- let combined = "";
164
- for (let index = 0; index < texts.length; index += 1) {
165
- if (index === 0) {
166
- combined = texts[index];
167
- continue;
168
- }
169
- if (index === texts.length - 1) {
170
- combined += ` ${conjunction} ${texts[index]}`;
171
- continue;
172
- }
173
- combined += `, ${texts[index]}`;
174
- }
175
- return combined;
176
- }
177
- }
178
- }
179
- function getPreferredCanonicalPrnReasonText(reason, reasons, conjunction = "or") {
180
- var _a2;
181
- const direct = (_a2 = getCanonicalPrnReasonText(reason)) == null ? void 0 : _a2.trim();
182
- if (!(reasons == null ? void 0 : reasons.length)) {
183
- return direct;
184
- }
185
- if (!direct) {
186
- return joinCanonicalPrnReasonTexts(reasons, conjunction);
187
- }
188
- return /[,/;]/.test(direct) || /\b(?:or|and|and\/or)\b/i.test(direct) || /\s(?:หรือ|และ)\s/.test(direct) ? joinCanonicalPrnReasonTexts(reasons, conjunction) : direct;
189
- }
190
-
191
141
  // src/types.ts
192
142
  var EventTiming = /* @__PURE__ */ ((EventTiming4) => {
193
143
  EventTiming4["Before Sleep"] = "HS";
@@ -455,6 +405,328 @@ var AdviceArgumentRole = /* @__PURE__ */ ((AdviceArgumentRole2) => {
455
405
  return AdviceArgumentRole2;
456
406
  })(AdviceArgumentRole || {});
457
407
 
408
+ // src/timing-summary.ts
409
+ var MEAL_TIMING_DETAILS = {
410
+ ["ACM" /* Before Breakfast */]: { relation: "before", meal: "breakfast" },
411
+ ["ACD" /* Before Lunch */]: { relation: "before", meal: "lunch" },
412
+ ["ACV" /* Before Dinner */]: { relation: "before", meal: "dinner" },
413
+ ["PCM" /* After Breakfast */]: { relation: "after", meal: "breakfast" },
414
+ ["PCD" /* After Lunch */]: { relation: "after", meal: "lunch" },
415
+ ["PCV" /* After Dinner */]: { relation: "after", meal: "dinner" },
416
+ ["CM" /* Breakfast */]: { relation: "with", meal: "breakfast" },
417
+ ["CD" /* Lunch */]: { relation: "with", meal: "lunch" },
418
+ ["CV" /* Dinner */]: { relation: "with", meal: "dinner" }
419
+ };
420
+ var MEAL_ORDER = {
421
+ breakfast: 0,
422
+ lunch: 1,
423
+ dinner: 2
424
+ };
425
+ var INFERABLE_DAILY_EVENT_TIMINGS = /* @__PURE__ */ new Set([
426
+ "HS" /* Before Sleep */,
427
+ "ACM" /* Before Breakfast */,
428
+ "ACD" /* Before Lunch */,
429
+ "ACV" /* Before Dinner */,
430
+ "PCM" /* After Breakfast */,
431
+ "PCD" /* After Lunch */,
432
+ "PCV" /* After Dinner */,
433
+ "CM" /* Breakfast */,
434
+ "CD" /* Lunch */,
435
+ "CV" /* Dinner */,
436
+ "MORN" /* Morning */,
437
+ "MORN.early" /* Early Morning */,
438
+ "MORN.late" /* Late Morning */,
439
+ "NOON" /* Noon */,
440
+ "AFT" /* Afternoon */,
441
+ "AFT.early" /* Early Afternoon */,
442
+ "AFT.late" /* Late Afternoon */,
443
+ "EVE" /* Evening */,
444
+ "EVE.early" /* Early Evening */,
445
+ "EVE.late" /* Late Evening */,
446
+ "NIGHT" /* Night */,
447
+ "WAKE" /* Wake */,
448
+ "PHS" /* After Sleep */
449
+ ]);
450
+ function uniqueValues(values) {
451
+ const seen = /* @__PURE__ */ new Set();
452
+ const result = [];
453
+ for (const value of values) {
454
+ if (!seen.has(value)) {
455
+ seen.add(value);
456
+ result.push(value);
457
+ }
458
+ }
459
+ return result;
460
+ }
461
+ function getMealTimingGroup(when, options) {
462
+ if (!(options == null ? void 0 : options.groupMealTimingsByRelation)) {
463
+ return void 0;
464
+ }
465
+ const uniqueWhen2 = uniqueValues(when);
466
+ if (uniqueWhen2.length < 2) {
467
+ return void 0;
468
+ }
469
+ let relation;
470
+ const meals = [];
471
+ const groupedCodes = [];
472
+ let sawFirstMeal = false;
473
+ for (let i = 0; i < uniqueWhen2.length; i += 1) {
474
+ const code = uniqueWhen2[i];
475
+ const detail = MEAL_TIMING_DETAILS[code];
476
+ if (!detail) {
477
+ if (sawFirstMeal) {
478
+ break;
479
+ }
480
+ continue;
481
+ }
482
+ if (!sawFirstMeal) {
483
+ sawFirstMeal = true;
484
+ }
485
+ if (!relation) {
486
+ relation = detail.relation;
487
+ } else if (relation !== detail.relation && detail.relation !== "with") {
488
+ break;
489
+ }
490
+ meals.push(detail.meal);
491
+ groupedCodes.push(code);
492
+ }
493
+ if (groupedCodes.length < 2) {
494
+ return void 0;
495
+ }
496
+ for (let i = 1; i < meals.length; i += 1) {
497
+ const current = meals[i];
498
+ let j = i - 1;
499
+ while (j >= 0 && MEAL_ORDER[meals[j]] > MEAL_ORDER[current]) {
500
+ meals[j + 1] = meals[j];
501
+ j -= 1;
502
+ }
503
+ meals[j + 1] = current;
504
+ }
505
+ if (!relation) {
506
+ return void 0;
507
+ }
508
+ return {
509
+ relation,
510
+ meals,
511
+ codes: groupedCodes
512
+ };
513
+ }
514
+ function inferDailyOccurrenceCount(input, options) {
515
+ var _a2, _b, _c, _d;
516
+ if (!(options == null ? void 0 : options.includeTimesPerDaySummary)) {
517
+ return void 0;
518
+ }
519
+ if (input.frequency !== void 0 || input.frequencyMax !== void 0 || input.timingCode) {
520
+ return void 0;
521
+ }
522
+ if (input.period !== void 0 || input.periodMax !== void 0 || input.periodUnit !== void 0) {
523
+ return void 0;
524
+ }
525
+ if (((_b = (_a2 = input.dayOfWeek) == null ? void 0 : _a2.length) != null ? _b : 0) > 0) {
526
+ return void 0;
527
+ }
528
+ const uniqueWhen2 = uniqueValues((_c = input.when) != null ? _c : []);
529
+ for (let i = 0; i < uniqueWhen2.length; i += 1) {
530
+ if (!INFERABLE_DAILY_EVENT_TIMINGS.has(uniqueWhen2[i])) {
531
+ return void 0;
532
+ }
533
+ }
534
+ const uniqueTimes = uniqueValues((_d = input.timeOfDay) != null ? _d : []);
535
+ const occurrences = uniqueWhen2.length + uniqueTimes.length;
536
+ if (occurrences === 0) {
537
+ return void 0;
538
+ }
539
+ return occurrences;
540
+ }
541
+
542
+ // src/localized-timing.ts
543
+ function uniqueWhenEvents(schedule) {
544
+ var _a2;
545
+ const when = (_a2 = schedule == null ? void 0 : schedule.when) != null ? _a2 : [];
546
+ if (!when.length) {
547
+ return [];
548
+ }
549
+ const unique = [];
550
+ const seen = /* @__PURE__ */ new Set();
551
+ for (const code of when) {
552
+ if (!seen.has(code)) {
553
+ seen.add(code);
554
+ unique.push(code);
555
+ }
556
+ }
557
+ return unique;
558
+ }
559
+ function filterLocalizedWhenEvents(schedule) {
560
+ const unique = uniqueWhenEvents(schedule);
561
+ const specificAfter = /* @__PURE__ */ new Set();
562
+ const specificBefore = /* @__PURE__ */ new Set();
563
+ const specificWith = /* @__PURE__ */ new Set();
564
+ for (const code of unique) {
565
+ if (code === "PCM" /* After Breakfast */ || code === "PCD" /* After Lunch */ || code === "PCV" /* After Dinner */) {
566
+ specificAfter.add(code);
567
+ }
568
+ if (code === "ACM" /* Before Breakfast */ || code === "ACD" /* Before Lunch */ || code === "ACV" /* Before Dinner */) {
569
+ specificBefore.add(code);
570
+ }
571
+ if (code === "CM" /* Breakfast */ || code === "CD" /* Lunch */ || code === "CV" /* Dinner */) {
572
+ specificWith.add(code);
573
+ }
574
+ }
575
+ const hasAllAfter = specificAfter.has("PCM" /* After Breakfast */) && specificAfter.has("PCD" /* After Lunch */) && specificAfter.has("PCV" /* After Dinner */);
576
+ const hasAllBefore = specificBefore.has("ACM" /* Before Breakfast */) && specificBefore.has("ACD" /* Before Lunch */) && specificBefore.has("ACV" /* Before Dinner */);
577
+ const hasAllWith = specificWith.has("CM" /* Breakfast */) && specificWith.has("CD" /* Lunch */) && specificWith.has("CV" /* Dinner */);
578
+ const filtered = [];
579
+ for (const code of unique) {
580
+ if (code === "PC" /* After Meal */ && hasAllAfter) {
581
+ continue;
582
+ }
583
+ if (code === "AC" /* Before Meal */ && hasAllBefore) {
584
+ continue;
585
+ }
586
+ if (code === "C" /* Meal */ && hasAllWith) {
587
+ continue;
588
+ }
589
+ filtered.push(code);
590
+ }
591
+ return filtered;
592
+ }
593
+ function collectLocalizedWhenPhrases(schedule, grammar, options) {
594
+ const filtered = filterLocalizedWhenEvents(schedule);
595
+ if (!filtered.length) {
596
+ return [];
597
+ }
598
+ const mealGroup = getMealTimingGroup(filtered, options);
599
+ if (!mealGroup) {
600
+ const phrases2 = [];
601
+ for (const code of filtered) {
602
+ const text = grammar.whenText[code];
603
+ if (text) {
604
+ phrases2.push(text);
605
+ }
606
+ }
607
+ return phrases2;
608
+ }
609
+ const groupedCodes = new Set(mealGroup.codes);
610
+ const phrases = [];
611
+ let insertedGroup = false;
612
+ for (const code of filtered) {
613
+ if (groupedCodes.has(code)) {
614
+ if (!insertedGroup) {
615
+ phrases.push(grammar.summarizeMealTimingGroup(mealGroup));
616
+ insertedGroup = true;
617
+ }
618
+ continue;
619
+ }
620
+ const text = grammar.whenText[code];
621
+ if (text) {
622
+ phrases.push(text);
623
+ }
624
+ }
625
+ return phrases;
626
+ }
627
+ function inferExplicitDailyFrequency(schedule) {
628
+ var _a2;
629
+ if (!schedule) {
630
+ return void 0;
631
+ }
632
+ if (schedule.frequency !== void 0 && schedule.frequencyMax === void 0 && schedule.periodUnit === "d" /* Day */ && (schedule.period === void 0 || schedule.period === 1) && schedule.periodMax === void 0) {
633
+ return schedule.frequency;
634
+ }
635
+ switch ((_a2 = schedule.timingCode) == null ? void 0 : _a2.toUpperCase()) {
636
+ case "QD":
637
+ return 1;
638
+ case "BID":
639
+ return 2;
640
+ case "TID":
641
+ return 3;
642
+ case "QID":
643
+ return 4;
644
+ default:
645
+ return void 0;
646
+ }
647
+ }
648
+ function isBedtimeOnlyWhen(schedule) {
649
+ const filtered = filterLocalizedWhenEvents(schedule);
650
+ return filtered.length === 1 && filtered[0] === "HS" /* Before Sleep */;
651
+ }
652
+ function combineLocalizedFrequencyAndEvents(schedule, frequency, events, grammar, options) {
653
+ var _a2, _b, _c;
654
+ if (!frequency) {
655
+ if (!events.length) {
656
+ return {};
657
+ }
658
+ return { event: grammar.joinList(events) };
659
+ }
660
+ if (!events.length) {
661
+ return { frequency };
662
+ }
663
+ if (events.length === 1 && isBedtimeOnlyWhen(schedule)) {
664
+ const dailyCount = (_a2 = inferExplicitDailyFrequency(schedule)) != null ? _a2 : inferDailyOccurrenceCount(schedule != null ? schedule : {}, options);
665
+ const style = (_c = (_b = grammar.bedtimeJoinStyle) == null ? void 0 : _b.call(grammar, dailyCount)) != null ? _c : "separate";
666
+ if (style === "adjacent") {
667
+ return { frequency: `${frequency} ${events[0]}` };
668
+ }
669
+ if (style === "conjunction") {
670
+ return { frequency: grammar.joinList([frequency, events[0]]) };
671
+ }
672
+ }
673
+ return { frequency, event: grammar.joinList(events) };
674
+ }
675
+
676
+ // src/prn.ts
677
+ function getCanonicalPrnReasonText(reason) {
678
+ var _a2, _b;
679
+ return (_b = reason == null ? void 0 : reason.text) != null ? _b : (_a2 = reason == null ? void 0 : reason.coding) == null ? void 0 : _a2.display;
680
+ }
681
+ function joinCanonicalPrnReasonTexts(reasons, conjunction = "or") {
682
+ var _a2;
683
+ if (!(reasons == null ? void 0 : reasons.length)) {
684
+ return void 0;
685
+ }
686
+ const texts = [];
687
+ for (const reason of reasons) {
688
+ const text = (_a2 = getCanonicalPrnReasonText(reason)) == null ? void 0 : _a2.trim();
689
+ if (!text) {
690
+ continue;
691
+ }
692
+ texts.push(text);
693
+ }
694
+ switch (texts.length) {
695
+ case 0:
696
+ return void 0;
697
+ case 1:
698
+ return texts[0];
699
+ case 2:
700
+ return `${texts[0]} ${conjunction} ${texts[1]}`;
701
+ default: {
702
+ let combined = "";
703
+ for (let index = 0; index < texts.length; index += 1) {
704
+ if (index === 0) {
705
+ combined = texts[index];
706
+ continue;
707
+ }
708
+ if (index === texts.length - 1) {
709
+ combined += ` ${conjunction} ${texts[index]}`;
710
+ continue;
711
+ }
712
+ combined += `, ${texts[index]}`;
713
+ }
714
+ return combined;
715
+ }
716
+ }
717
+ }
718
+ function getPreferredCanonicalPrnReasonText(reason, reasons, conjunction = "or") {
719
+ var _a2;
720
+ const direct = (_a2 = getCanonicalPrnReasonText(reason)) == null ? void 0 : _a2.trim();
721
+ if (!(reasons == null ? void 0 : reasons.length)) {
722
+ return direct;
723
+ }
724
+ if (!direct) {
725
+ return joinCanonicalPrnReasonTexts(reasons, conjunction);
726
+ }
727
+ return /[,/;]/.test(direct) || /\b(?:or|and|and\/or)\b/i.test(direct) || /\s(?:หรือ|และ)\s/.test(direct) ? joinCanonicalPrnReasonTexts(reasons, conjunction) : direct;
728
+ }
729
+
458
730
  // src/snomed.ts
459
731
  var SNOMED_SYSTEM = "http://snomed.info/sct";
460
732
  var SNOMED_CT_FINDING_SITE_ATTRIBUTE_CODE = "363698007";
@@ -481,6 +753,24 @@ function normalizeLocaleKey(locale) {
481
753
  function cloneI18nRecord(i18n) {
482
754
  return i18n ? __spreadValues({}, i18n) : void 0;
483
755
  }
756
+ function mergeI18nRecords(...records) {
757
+ var _a2;
758
+ const merged = {};
759
+ for (const record of records) {
760
+ if (!record) {
761
+ continue;
762
+ }
763
+ for (const locale in record) {
764
+ const normalizedLocale = normalizeLocaleKey(locale);
765
+ const content = (_a2 = record[locale]) == null ? void 0 : _a2.trim();
766
+ if (!normalizedLocale || !content) {
767
+ continue;
768
+ }
769
+ merged[normalizedLocale] = content;
770
+ }
771
+ }
772
+ return Object.keys(merged).length ? merged : void 0;
773
+ }
484
774
  function cloneExtension(extension) {
485
775
  var _a2, _b, _c, _d;
486
776
  return {
@@ -567,6 +857,32 @@ function buildTranslationPrimitiveElement(translations, base) {
567
857
  extension: nextExtensions
568
858
  };
569
859
  }
860
+ function mergeTranslationPrimitiveElement(base, translations) {
861
+ var _a2, _b, _c, _d;
862
+ const nextExtensions = (_b = (_a2 = base == null ? void 0 : base.extension) == null ? void 0 : _a2.map(cloneExtension)) != null ? _b : [];
863
+ const existingTranslationLocales = /* @__PURE__ */ new Set();
864
+ for (const extension of nextExtensions) {
865
+ if (extension.url !== FHIR_TRANSLATION_EXTENSION_URL) {
866
+ continue;
867
+ }
868
+ const { locale, content } = getTranslationParts(extension);
869
+ if (locale && content) {
870
+ existingTranslationLocales.add(locale);
871
+ }
872
+ }
873
+ const additions = (_d = (_c = buildTranslationPrimitiveElement(translations)) == null ? void 0 : _c.extension) != null ? _d : [];
874
+ for (const extension of additions) {
875
+ const { locale } = getTranslationParts(extension);
876
+ if (locale && existingTranslationLocales.has(locale)) {
877
+ continue;
878
+ }
879
+ if (locale) {
880
+ existingTranslationLocales.add(locale);
881
+ }
882
+ nextExtensions.push(extension);
883
+ }
884
+ return nextExtensions.length ? { extension: nextExtensions } : void 0;
885
+ }
570
886
  function getTranslationParts(extension) {
571
887
  var _a2, _b;
572
888
  const parts = (_a2 = extension.extension) != null ? _a2 : [];
@@ -610,6 +926,24 @@ function getPrimitiveTranslation(element, locale) {
610
926
  }
611
927
  return languagePrefixMatch;
612
928
  }
929
+ function getPrimitiveTranslations(element) {
930
+ var _a2;
931
+ if (!((_a2 = element == null ? void 0 : element.extension) == null ? void 0 : _a2.length)) {
932
+ return void 0;
933
+ }
934
+ const translations = {};
935
+ for (const extension of element.extension) {
936
+ if (extension.url !== FHIR_TRANSLATION_EXTENSION_URL) {
937
+ continue;
938
+ }
939
+ const { locale, content } = getTranslationParts(extension);
940
+ if (!locale || !content) {
941
+ continue;
942
+ }
943
+ translations[locale] = content;
944
+ }
945
+ return Object.keys(translations).length ? translations : void 0;
946
+ }
613
947
 
614
948
  // src/body-site-spatial.ts
615
949
  var SNOMED_SYSTEM2 = "http://snomed.info/sct";
@@ -1201,10 +1535,11 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1201
1535
  }
1202
1536
  },
1203
1537
  {
1204
- names: ["both eyes", "bilateral eyes", "\u0E15\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07", "\u0E15\u0E32\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07"],
1538
+ names: ["both eyes", "bilateral eyes", "each eye", "\u0E15\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07", "\u0E15\u0E32\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07"],
1205
1539
  definition: {
1206
1540
  coding: { code: "40638003", display: "Structure of both eyes" },
1207
1541
  text: "both eyes",
1542
+ administrationTargetCount: 2,
1208
1543
  i18n: { th: "\u0E15\u0E32\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07" },
1209
1544
  routeHint: RouteCode["Ophthalmic route"]
1210
1545
  }
@@ -1218,10 +1553,19 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1218
1553
  }
1219
1554
  },
1220
1555
  {
1221
- names: ["ears", "both ears", "bilateral ears", "inside ears", "\u0E2B\u0E39\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07", "\u0E2B\u0E39\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07"],
1556
+ names: [
1557
+ "ears",
1558
+ "both ears",
1559
+ "bilateral ears",
1560
+ "each ear",
1561
+ "inside ears",
1562
+ "\u0E2B\u0E39\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07",
1563
+ "\u0E2B\u0E39\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07"
1564
+ ],
1222
1565
  definition: {
1223
1566
  coding: { code: "34338003", display: "Both ears" },
1224
1567
  text: "both ears",
1568
+ administrationTargetCount: 2,
1225
1569
  routeHint: RouteCode["Otic route"]
1226
1570
  }
1227
1571
  },
@@ -1239,10 +1583,18 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1239
1583
  }
1240
1584
  },
1241
1585
  {
1242
- names: ["ear canals", "both ear canals", "inside ear canals", "both external auditory canals"],
1586
+ names: [
1587
+ "ear canals",
1588
+ "both ear canals",
1589
+ "each ear canal",
1590
+ "inside ear canals",
1591
+ "both external auditory canals",
1592
+ "each external auditory canal"
1593
+ ],
1243
1594
  definition: {
1244
1595
  coding: { code: "181178004", display: "Entire external auditory canal" },
1245
1596
  text: "both ear canals",
1597
+ administrationTargetCount: 2,
1246
1598
  routeHint: RouteCode["Otic route"]
1247
1599
  }
1248
1600
  },
@@ -1279,8 +1631,12 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1279
1631
  }
1280
1632
  },
1281
1633
  {
1282
- names: ["nostril", "nostrils"],
1283
- definition: { coding: { code: "1797002", display: "Naris" }, routeHint: RouteCode["Nasal route"] }
1634
+ names: ["nostril"],
1635
+ definition: {
1636
+ coding: { code: "1797002", display: "Naris" },
1637
+ text: "nostril",
1638
+ routeHint: RouteCode["Nasal route"]
1639
+ }
1284
1640
  },
1285
1641
  {
1286
1642
  names: ["left nostril", "left naris"],
@@ -1297,8 +1653,22 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1297
1653
  }
1298
1654
  },
1299
1655
  {
1300
- names: ["nares", "anterior nares"],
1301
- definition: { coding: { code: "244506005", display: "Anterior nares" }, routeHint: RouteCode["Nasal route"] }
1656
+ names: [
1657
+ "nostrils",
1658
+ "both nostrils",
1659
+ "bilateral nostrils",
1660
+ "each nostril",
1661
+ "nares",
1662
+ "anterior nares",
1663
+ "\u0E23\u0E39\u0E08\u0E21\u0E39\u0E01\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07"
1664
+ ],
1665
+ definition: {
1666
+ coding: { code: "244506005", display: "Anterior nares" },
1667
+ text: "both nostrils",
1668
+ administrationTargetCount: 2,
1669
+ i18n: { th: "\u0E23\u0E39\u0E08\u0E21\u0E39\u0E01\u0E17\u0E31\u0E49\u0E07\u0E2A\u0E2D\u0E07\u0E02\u0E49\u0E32\u0E07" },
1670
+ routeHint: RouteCode["Nasal route"]
1671
+ }
1302
1672
  },
1303
1673
  {
1304
1674
  names: ["nose"],
@@ -1824,6 +2194,24 @@ var DEFAULT_BODY_SITE_SNOMED_SOURCE = [
1824
2194
  names: ["affected area", "affected areas", "affected site", "\u0E1A\u0E23\u0E34\u0E40\u0E27\u0E13\u0E17\u0E35\u0E48\u0E40\u0E1B\u0E47\u0E19"],
1825
2195
  definition: { text: "affected area", routeHint: RouteCode["Topical route"] }
1826
2196
  },
2197
+ {
2198
+ names: ["lesion", "skin lesion"],
2199
+ definition: {
2200
+ coding: { code: "95324001", display: "Skin lesion" },
2201
+ text: "lesion",
2202
+ routeHint: RouteCode["Topical route"],
2203
+ i18n: { th: "\u0E23\u0E2D\u0E22\u0E42\u0E23\u0E04" }
2204
+ }
2205
+ },
2206
+ {
2207
+ names: ["lesions", "skin lesions"],
2208
+ definition: {
2209
+ coding: { code: "95324001", display: "Skin lesion" },
2210
+ text: "lesions",
2211
+ routeHint: RouteCode["Topical route"],
2212
+ i18n: { th: "\u0E23\u0E2D\u0E22\u0E42\u0E23\u0E04" }
2213
+ }
2214
+ },
1827
2215
  {
1828
2216
  names: ["left head", "left side of head"],
1829
2217
  definition: { coding: { code: "64237003", display: "Structure of left half of head" }, routeHint: RouteCode["Topical route"] }
@@ -4403,6 +4791,7 @@ var BODY_SITE_FEATURE_SCORE_BONUS = bodySiteFeatureScoreBonus(lexical_classes_de
4403
4791
  var CONNECTORS = setOf(lexical_classes_default.connectors);
4404
4792
  var ROUTE_SITE_PREPOSITIONS = setOf(lexical_classes_default.routeSitePrepositions);
4405
4793
  var SITE_DISPLAY_FILLERS = SITE_FILLERS;
4794
+ var SITE_MULTIPLICITY_WORDS = setOf(["both", "each", "bilateral"]);
4406
4795
  var NON_SITE_ANCHORED_PHRASES = setOf(lexical_classes_default.nonSiteAnchoredPhrases);
4407
4796
  var EXTERNAL_SITE_LOCATIVE_PREFIXES = setOf(lexical_classes_default.externalSiteLocativePrefixes);
4408
4797
  var ROUTE_BLOCKED_BY_FOLLOWING_PARTITIVE_HEADS = setOf(
@@ -4596,7 +4985,7 @@ function buildBodySiteCoding(definition) {
4596
4985
  code: coding.code,
4597
4986
  display: coding.display,
4598
4987
  system: (_a2 = coding.system) != null ? _a2 : SNOMED_SYSTEM3,
4599
- i18n: definition == null ? void 0 : definition.i18n
4988
+ i18n: mergeI18nRecords(definition == null ? void 0 : definition.i18n, coding.i18n)
4600
4989
  };
4601
4990
  }
4602
4991
  function cloneBodySiteCode(coding) {
@@ -4608,7 +4997,7 @@ function cloneBodySiteCode(coding) {
4608
4997
  code: coding.code,
4609
4998
  display: coding.display,
4610
4999
  system: (_a2 = coding.system) != null ? _a2 : SNOMED_SYSTEM3,
4611
- i18n: coding.i18n
5000
+ i18n: mergeI18nRecords(coding.i18n)
4612
5001
  };
4613
5002
  }
4614
5003
  function lookupDefinitionForCanonical(canonical, customSiteMap) {
@@ -4900,186 +5289,52 @@ function inferPreferredPreposition(canonical, features, definition) {
4900
5289
  if (routeHint === RouteCode["Topical route"] || routeHint === RouteCode["Transdermal route"]) {
4901
5290
  return "to";
4902
5291
  }
4903
- if (routeHint === RouteCode["Per rectum"] || routeHint === RouteCode["Per vagina"] || routeHint === RouteCode["Subcutaneous route"] || routeHint === RouteCode["Intramuscular route"] || routeHint === RouteCode["Intravenous route"] || routeHint === RouteCode["Nasal route"]) {
4904
- return routeHint === RouteCode["Nasal route"] ? "into" : "to";
4905
- }
4906
- const words = canonical.split(/\s+/).filter((word) => word.length > 0);
4907
- for (const word of words) {
4908
- if (OTIC_SITE_WORDS.has(word) || OPHTHALMIC_SITE_WORDS.has(word)) {
4909
- return "in";
4910
- }
4911
- if (NASAL_SITE_WORDS.has(word)) {
4912
- return "into";
4913
- }
4914
- }
4915
- return void 0;
4916
- }
4917
- function resolveBodySitePhrase(text, customSiteMap, context) {
4918
- var _a2, _b, _c, _d, _e, _f, _g;
4919
- const trimmed = text.trim().replace(/\s+/g, " ");
4920
- if (!trimmed) {
4921
- return void 0;
4922
- }
4923
- const lookupCanonical = normalizeBodySiteKey(trimmed);
4924
- const contextualCanonical = resolveContextualBodySiteAlias(lookupCanonical, context);
4925
- const displaySourceText = contextualCanonical != null ? contextualCanonical : trimmed;
4926
- const displayText = normalizeSiteDisplayText(displaySourceText, customSiteMap);
4927
- const canonical = normalizeBodySiteKey(displayText);
4928
- const definition = (_e = (_d = (_c = (_b = (_a2 = lookupBodySiteDefinition(customSiteMap, lookupCanonical)) != null ? _a2 : contextualCanonical ? lookupBodySiteDefinition(customSiteMap, contextualCanonical) : void 0) != null ? _b : contextualCanonical ? DEFAULT_BODY_SITE_SNOMED[contextualCanonical] : void 0) != null ? _c : DEFAULT_BODY_SITE_SNOMED[lookupCanonical]) != null ? _d : lookupBodySiteDefinition(customSiteMap, canonical)) != null ? _e : DEFAULT_BODY_SITE_SNOMED[canonical];
4929
- const coding = buildBodySiteCoding(definition);
4930
- const finalDisplayText = (_f = definition == null ? void 0 : definition.text) != null ? _f : displayText;
4931
- const features = parseBodySiteFeatures(finalDisplayText, coding, customSiteMap);
4932
- const spatialRelation = (_g = definition == null ? void 0 : definition.spatialRelation) != null ? _g : buildSpatialRelation(features, finalDisplayText, customSiteMap);
4933
- return {
4934
- lookupCanonical,
4935
- resolutionCanonical: contextualCanonical != null ? contextualCanonical : lookupCanonical,
4936
- canonical: normalizeBodySiteKey(finalDisplayText) || canonical,
4937
- displayText: finalDisplayText,
4938
- coding,
4939
- spatialRelation,
4940
- definition,
4941
- features,
4942
- englishObjectText: renderBodySiteObject(features),
4943
- preferredPreposition: inferPreferredPreposition(
4944
- normalizeBodySiteKey(finalDisplayText) || canonical,
4945
- features,
4946
- definition
4947
- )
4948
- };
4949
- }
4950
-
4951
- // src/timing-summary.ts
4952
- var MEAL_TIMING_DETAILS = {
4953
- ["ACM" /* Before Breakfast */]: { relation: "before", meal: "breakfast" },
4954
- ["ACD" /* Before Lunch */]: { relation: "before", meal: "lunch" },
4955
- ["ACV" /* Before Dinner */]: { relation: "before", meal: "dinner" },
4956
- ["PCM" /* After Breakfast */]: { relation: "after", meal: "breakfast" },
4957
- ["PCD" /* After Lunch */]: { relation: "after", meal: "lunch" },
4958
- ["PCV" /* After Dinner */]: { relation: "after", meal: "dinner" },
4959
- ["CM" /* Breakfast */]: { relation: "with", meal: "breakfast" },
4960
- ["CD" /* Lunch */]: { relation: "with", meal: "lunch" },
4961
- ["CV" /* Dinner */]: { relation: "with", meal: "dinner" }
4962
- };
4963
- var MEAL_ORDER = {
4964
- breakfast: 0,
4965
- lunch: 1,
4966
- dinner: 2
4967
- };
4968
- var INFERABLE_DAILY_EVENT_TIMINGS = /* @__PURE__ */ new Set([
4969
- "HS" /* Before Sleep */,
4970
- "ACM" /* Before Breakfast */,
4971
- "ACD" /* Before Lunch */,
4972
- "ACV" /* Before Dinner */,
4973
- "PCM" /* After Breakfast */,
4974
- "PCD" /* After Lunch */,
4975
- "PCV" /* After Dinner */,
4976
- "CM" /* Breakfast */,
4977
- "CD" /* Lunch */,
4978
- "CV" /* Dinner */,
4979
- "MORN" /* Morning */,
4980
- "MORN.early" /* Early Morning */,
4981
- "MORN.late" /* Late Morning */,
4982
- "NOON" /* Noon */,
4983
- "AFT" /* Afternoon */,
4984
- "AFT.early" /* Early Afternoon */,
4985
- "AFT.late" /* Late Afternoon */,
4986
- "EVE" /* Evening */,
4987
- "EVE.early" /* Early Evening */,
4988
- "EVE.late" /* Late Evening */,
4989
- "NIGHT" /* Night */,
4990
- "WAKE" /* Wake */,
4991
- "PHS" /* After Sleep */
4992
- ]);
4993
- function uniqueValues(values) {
4994
- const seen = /* @__PURE__ */ new Set();
4995
- const result = [];
4996
- for (const value of values) {
4997
- if (!seen.has(value)) {
4998
- seen.add(value);
4999
- result.push(value);
5000
- }
5001
- }
5002
- return result;
5003
- }
5004
- function getMealTimingGroup(when, options) {
5005
- if (!(options == null ? void 0 : options.groupMealTimingsByRelation)) {
5006
- return void 0;
5007
- }
5008
- const uniqueWhen2 = uniqueValues(when);
5009
- if (uniqueWhen2.length < 2) {
5010
- return void 0;
5011
- }
5012
- let relation;
5013
- const meals = [];
5014
- const groupedCodes = [];
5015
- let sawFirstMeal = false;
5016
- for (let i = 0; i < uniqueWhen2.length; i += 1) {
5017
- const code = uniqueWhen2[i];
5018
- const detail = MEAL_TIMING_DETAILS[code];
5019
- if (!detail) {
5020
- if (sawFirstMeal) {
5021
- break;
5022
- }
5023
- continue;
5024
- }
5025
- if (!sawFirstMeal) {
5026
- sawFirstMeal = true;
5027
- }
5028
- if (!relation) {
5029
- relation = detail.relation;
5030
- } else if (relation !== detail.relation && detail.relation !== "with") {
5031
- break;
5032
- }
5033
- meals.push(detail.meal);
5034
- groupedCodes.push(code);
5035
- }
5036
- if (groupedCodes.length < 2) {
5037
- return void 0;
5038
- }
5039
- for (let i = 1; i < meals.length; i += 1) {
5040
- const current = meals[i];
5041
- let j = i - 1;
5042
- while (j >= 0 && MEAL_ORDER[meals[j]] > MEAL_ORDER[current]) {
5043
- meals[j + 1] = meals[j];
5044
- j -= 1;
5045
- }
5046
- meals[j + 1] = current;
5047
- }
5048
- if (!relation) {
5049
- return void 0;
5050
- }
5051
- return {
5052
- relation,
5053
- meals,
5054
- codes: groupedCodes
5055
- };
5056
- }
5057
- function inferDailyOccurrenceCount(input, options) {
5058
- var _a2, _b, _c, _d;
5059
- if (!(options == null ? void 0 : options.includeTimesPerDaySummary)) {
5060
- return void 0;
5061
- }
5062
- if (input.frequency !== void 0 || input.frequencyMax !== void 0 || input.timingCode) {
5063
- return void 0;
5064
- }
5065
- if (input.period !== void 0 || input.periodMax !== void 0 || input.periodUnit !== void 0) {
5066
- return void 0;
5067
- }
5068
- if (((_b = (_a2 = input.dayOfWeek) == null ? void 0 : _a2.length) != null ? _b : 0) > 0) {
5069
- return void 0;
5292
+ if (routeHint === RouteCode["Per rectum"] || routeHint === RouteCode["Per vagina"] || routeHint === RouteCode["Subcutaneous route"] || routeHint === RouteCode["Intramuscular route"] || routeHint === RouteCode["Intravenous route"] || routeHint === RouteCode["Nasal route"]) {
5293
+ return routeHint === RouteCode["Nasal route"] ? "into" : "to";
5070
5294
  }
5071
- const uniqueWhen2 = uniqueValues((_c = input.when) != null ? _c : []);
5072
- for (let i = 0; i < uniqueWhen2.length; i += 1) {
5073
- if (!INFERABLE_DAILY_EVENT_TIMINGS.has(uniqueWhen2[i])) {
5074
- return void 0;
5295
+ const words = canonical.split(/\s+/).filter((word) => word.length > 0);
5296
+ for (const word of words) {
5297
+ if (OTIC_SITE_WORDS.has(word) || OPHTHALMIC_SITE_WORDS.has(word)) {
5298
+ return "in";
5299
+ }
5300
+ if (NASAL_SITE_WORDS.has(word)) {
5301
+ return "into";
5075
5302
  }
5076
5303
  }
5077
- const uniqueTimes = uniqueValues((_d = input.timeOfDay) != null ? _d : []);
5078
- const occurrences = uniqueWhen2.length + uniqueTimes.length;
5079
- if (occurrences === 0) {
5304
+ return void 0;
5305
+ }
5306
+ function resolveBodySitePhrase(text, customSiteMap, context) {
5307
+ var _a2, _b, _c, _d, _e, _f, _g;
5308
+ const trimmed = text.trim().replace(/\s+/g, " ");
5309
+ if (!trimmed) {
5080
5310
  return void 0;
5081
5311
  }
5082
- return occurrences;
5312
+ const lookupCanonical = normalizeBodySiteKey(trimmed);
5313
+ const contextualCanonical = resolveContextualBodySiteAlias(lookupCanonical, context);
5314
+ const displaySourceText = contextualCanonical != null ? contextualCanonical : trimmed;
5315
+ const displayText = normalizeSiteDisplayText(displaySourceText, customSiteMap);
5316
+ const canonical = normalizeBodySiteKey(displayText);
5317
+ const definition = (_e = (_d = (_c = (_b = (_a2 = lookupBodySiteDefinition(customSiteMap, lookupCanonical)) != null ? _a2 : contextualCanonical ? lookupBodySiteDefinition(customSiteMap, contextualCanonical) : void 0) != null ? _b : contextualCanonical ? DEFAULT_BODY_SITE_SNOMED[contextualCanonical] : void 0) != null ? _c : DEFAULT_BODY_SITE_SNOMED[lookupCanonical]) != null ? _d : lookupBodySiteDefinition(customSiteMap, canonical)) != null ? _e : DEFAULT_BODY_SITE_SNOMED[canonical];
5318
+ const coding = buildBodySiteCoding(definition);
5319
+ const finalDisplayText = (_f = definition == null ? void 0 : definition.text) != null ? _f : displayText;
5320
+ const features = parseBodySiteFeatures(finalDisplayText, coding, customSiteMap);
5321
+ const spatialRelation = (_g = definition == null ? void 0 : definition.spatialRelation) != null ? _g : buildSpatialRelation(features, finalDisplayText, customSiteMap);
5322
+ return {
5323
+ lookupCanonical,
5324
+ resolutionCanonical: contextualCanonical != null ? contextualCanonical : lookupCanonical,
5325
+ canonical: normalizeBodySiteKey(finalDisplayText) || canonical,
5326
+ displayText: finalDisplayText,
5327
+ coding,
5328
+ spatialRelation,
5329
+ definition,
5330
+ features,
5331
+ englishObjectText: renderBodySiteObject(features),
5332
+ preferredPreposition: inferPreferredPreposition(
5333
+ normalizeBodySiteKey(finalDisplayText) || canonical,
5334
+ features,
5335
+ definition
5336
+ )
5337
+ };
5083
5338
  }
5084
5339
 
5085
5340
  // src/format.ts
@@ -5573,73 +5828,22 @@ function summarizeMealTimingGroup(group) {
5573
5828
  }
5574
5829
  return `${relationText} ${joinWithAnd(group.meals)}`;
5575
5830
  }
5576
- function collectWhenPhrases(schedule, options) {
5577
- var _a2, _b, _c;
5578
- const when = (_a2 = schedule == null ? void 0 : schedule.when) != null ? _a2 : [];
5579
- if (!when.length) {
5580
- return [];
5581
- }
5582
- const unique = [];
5583
- const seen = /* @__PURE__ */ new Set();
5584
- let hasSpecificAfter = false;
5585
- let hasSpecificBefore = false;
5586
- let hasSpecificWith = false;
5587
- for (const code of when) {
5588
- if (!seen.has(code)) {
5589
- seen.add(code);
5590
- unique.push(code);
5591
- if (code === "PCM" /* After Breakfast */ || code === "PCD" /* After Lunch */ || code === "PCV" /* After Dinner */) {
5592
- hasSpecificAfter = true;
5593
- }
5594
- if (code === "ACM" /* Before Breakfast */ || code === "ACD" /* Before Lunch */ || code === "ACV" /* Before Dinner */) {
5595
- hasSpecificBefore = true;
5596
- }
5597
- if (code === "CM" /* Breakfast */ || code === "CD" /* Lunch */ || code === "CV" /* Dinner */) {
5598
- hasSpecificWith = true;
5599
- }
5600
- }
5601
- }
5602
- const filtered = [];
5603
- for (const code of unique) {
5604
- if (code === "PC" /* After Meal */ && hasSpecificAfter) {
5605
- continue;
5606
- }
5607
- if (code === "AC" /* Before Meal */ && hasSpecificBefore) {
5608
- continue;
5609
- }
5610
- if (code === "C" /* Meal */ && hasSpecificWith) {
5611
- continue;
5612
- }
5613
- filtered.push(code);
5614
- }
5615
- const mealGroup = getMealTimingGroup(filtered, options);
5616
- if (!mealGroup) {
5617
- const phrases2 = [];
5618
- for (const code of filtered) {
5619
- const text = (_b = WHEN_TEXT[code]) != null ? _b : code;
5620
- if (text) {
5621
- phrases2.push(text);
5622
- }
5623
- }
5624
- return phrases2;
5625
- }
5626
- const groupedCodes = new Set(mealGroup.codes);
5627
- const phrases = [];
5628
- let insertedGroup = false;
5629
- for (const code of filtered) {
5630
- if (groupedCodes.has(code)) {
5631
- if (!insertedGroup) {
5632
- phrases.push(summarizeMealTimingGroup(mealGroup));
5633
- insertedGroup = true;
5634
- }
5635
- continue;
5831
+ var EN_TIMING_GRAMMAR = {
5832
+ whenText: WHEN_TEXT,
5833
+ joinList: joinWithAnd,
5834
+ summarizeMealTimingGroup,
5835
+ bedtimeJoinStyle: (dailyCount) => {
5836
+ if (dailyCount === 1) {
5837
+ return "adjacent";
5636
5838
  }
5637
- const text = (_c = WHEN_TEXT[code]) != null ? _c : code;
5638
- if (text) {
5639
- phrases.push(text);
5839
+ if (dailyCount === 2 || dailyCount === 3 || dailyCount === 4) {
5840
+ return "conjunction";
5640
5841
  }
5842
+ return "separate";
5641
5843
  }
5642
- return phrases;
5844
+ };
5845
+ function collectWhenPhrases(schedule, options) {
5846
+ return collectLocalizedWhenPhrases(schedule, EN_TIMING_GRAMMAR, options);
5643
5847
  }
5644
5848
  function joinWithAnd(parts) {
5645
5849
  if (!parts.length) {
@@ -5653,23 +5857,14 @@ function joinWithAnd(parts) {
5653
5857
  }
5654
5858
  return `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
5655
5859
  }
5656
- function combineFrequencyAndEvents(frequency, events) {
5657
- if (!frequency) {
5658
- if (!events.length) {
5659
- return {};
5660
- }
5661
- return { event: joinWithAnd(events) };
5662
- }
5663
- if (!events.length) {
5664
- return { frequency };
5665
- }
5666
- if (events.length === 1 && events[0] === "at bedtime") {
5667
- const lowerFrequency = frequency.toLowerCase();
5668
- if (lowerFrequency === "twice daily" || lowerFrequency === "three times daily" || lowerFrequency === "four times daily") {
5669
- return { frequency: `${frequency} and ${events[0]}` };
5670
- }
5671
- }
5672
- return { frequency, event: joinWithAnd(events) };
5860
+ function combineFrequencyAndEvents(schedule, frequency, events, options) {
5861
+ return combineLocalizedFrequencyAndEvents(
5862
+ schedule,
5863
+ frequency,
5864
+ events,
5865
+ EN_TIMING_GRAMMAR,
5866
+ options
5867
+ );
5673
5868
  }
5674
5869
  function buildRoutePhrase(clause, grammar, hasSite) {
5675
5870
  var _a2, _b;
@@ -5974,7 +6169,7 @@ function formatLong(clause, options) {
5974
6169
  eventParts.push(`at ${timeStrings.join(", ")}`);
5975
6170
  }
5976
6171
  }
5977
- const timing = combineFrequencyAndEvents(frequencyPart, eventParts);
6172
+ const timing = combineFrequencyAndEvents(schedule, frequencyPart, eventParts, options);
5978
6173
  const dayPart = describeDayOfWeek(schedule);
5979
6174
  const countPart = schedule.count !== void 0 && !standaloneOccurrenceCount ? `for ${stripTrailingZero(schedule.count)} ${schedule.count === 1 ? "dose" : "doses"}` : void 0;
5980
6175
  const durationPart = describeDuration(schedule);
@@ -9769,6 +9964,81 @@ function listSupportedBodySiteGrammar() {
9769
9964
  };
9770
9965
  }
9771
9966
 
9967
+ // src/body-site-target.ts
9968
+ var BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL = "urn:ezmedicationinput:body-site-administration-target-count";
9969
+ function normalizeAdministrationTargetCount(value) {
9970
+ if (!Number.isFinite(value) || value === void 0) {
9971
+ return void 0;
9972
+ }
9973
+ const rounded = Math.trunc(value);
9974
+ return rounded >= 2 ? rounded : void 0;
9975
+ }
9976
+ function siteTextFromLike(site, options) {
9977
+ if (!site) {
9978
+ return void 0;
9979
+ }
9980
+ if (typeof site === "string") {
9981
+ return site;
9982
+ }
9983
+ if ("text" in site && site.text) {
9984
+ return site.text;
9985
+ }
9986
+ const coding = "coding" in site ? Array.isArray(site.coding) ? site.coding.find((candidate) => candidate == null ? void 0 : candidate.code) : site.coding : void 0;
9987
+ return (coding == null ? void 0 : coding.code) ? getBodySiteText(
9988
+ {
9989
+ system: coding.system,
9990
+ code: coding.code,
9991
+ display: coding.display
9992
+ },
9993
+ { siteCodeMap: options == null ? void 0 : options.siteCodeMap }
9994
+ ) : void 0;
9995
+ }
9996
+ function buildBodySiteAdministrationTargetCountExtension(targetCount) {
9997
+ const normalized = normalizeAdministrationTargetCount(targetCount);
9998
+ if (normalized === void 0) {
9999
+ return void 0;
10000
+ }
10001
+ return {
10002
+ url: BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL,
10003
+ valueInteger: normalized
10004
+ };
10005
+ }
10006
+ function parseBodySiteAdministrationTargetCountExtension(concept) {
10007
+ var _a2;
10008
+ const extension = (_a2 = concept == null ? void 0 : concept.extension) == null ? void 0 : _a2.find(
10009
+ (candidate) => candidate.url === BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL
10010
+ );
10011
+ return normalizeAdministrationTargetCount(extension == null ? void 0 : extension.valueInteger);
10012
+ }
10013
+ function getBodySiteAdministrationTargetCount(site, options) {
10014
+ var _a2;
10015
+ if (!site) {
10016
+ return void 0;
10017
+ }
10018
+ if (typeof site !== "string" && "administrationTargetCount" in site) {
10019
+ const direct = normalizeAdministrationTargetCount(site.administrationTargetCount);
10020
+ if (direct !== void 0) {
10021
+ return direct;
10022
+ }
10023
+ }
10024
+ if (typeof site !== "string") {
10025
+ if ("extension" in site) {
10026
+ const fromExtension = parseBodySiteAdministrationTargetCountExtension(site);
10027
+ if (fromExtension !== void 0) {
10028
+ return fromExtension;
10029
+ }
10030
+ }
10031
+ }
10032
+ const text = siteTextFromLike(site, options);
10033
+ if (!text) {
10034
+ return void 0;
10035
+ }
10036
+ const resolved = resolveBodySitePhrase(text, options == null ? void 0 : options.siteCodeMap, {
10037
+ bodySiteContext: options == null ? void 0 : options.bodySiteContext
10038
+ });
10039
+ return normalizeAdministrationTargetCount((_a2 = resolved == null ? void 0 : resolved.definition) == null ? void 0 : _a2.administrationTargetCount);
10040
+ }
10041
+
9772
10042
  // src/parser-state.ts
9773
10043
  var ParserState = class {
9774
10044
  constructor(input, tokens, customSiteHints) {
@@ -10031,6 +10301,7 @@ var ParserState = class {
10031
10301
  for (const reason of value) {
10032
10302
  reasons.push({
10033
10303
  text: reason.text,
10304
+ i18n: cloneI18nRecord(reason.i18n),
10034
10305
  spatialRelation: cloneBodySiteSpatialRelation(reason.spatialRelation),
10035
10306
  coding: reason.coding ? {
10036
10307
  code: reason.coding.code,
@@ -10038,7 +10309,7 @@ var ParserState = class {
10038
10309
  system: reason.coding.system,
10039
10310
  extension: cloneExtensions(reason.coding.extension),
10040
10311
  _display: clonePrimitiveElement(reason.coding._display),
10041
- i18n: reason.coding.i18n
10312
+ i18n: cloneI18nRecord(reason.coding.i18n)
10042
10313
  } : void 0
10043
10314
  });
10044
10315
  }
@@ -10070,7 +10341,7 @@ var ParserState = class {
10070
10341
  system: value.system,
10071
10342
  extension: cloneExtensions(value.extension),
10072
10343
  _display: clonePrimitiveElement(value._display),
10073
- i18n: value.i18n
10344
+ i18n: cloneI18nRecord(value.i18n)
10074
10345
  } : void 0;
10075
10346
  }
10076
10347
  get siteText() {
@@ -10103,7 +10374,7 @@ var ParserState = class {
10103
10374
  code: value.code,
10104
10375
  display: value.display,
10105
10376
  system: value.system,
10106
- i18n: value.i18n
10377
+ i18n: cloneI18nRecord(value.i18n)
10107
10378
  } : void 0;
10108
10379
  }
10109
10380
  get siteSpatialRelation() {
@@ -10138,6 +10409,20 @@ var ParserState = class {
10138
10409
  sourceText: value.sourceText
10139
10410
  };
10140
10411
  }
10412
+ get siteAdministrationTargetCount() {
10413
+ var _a2;
10414
+ return (_a2 = this.clause.site) == null ? void 0 : _a2.administrationTargetCount;
10415
+ }
10416
+ set siteAdministrationTargetCount(value) {
10417
+ if (value === void 0) {
10418
+ if (this.clause.site) {
10419
+ delete this.clause.site.administrationTargetCount;
10420
+ this.cleanupSite();
10421
+ }
10422
+ return;
10423
+ }
10424
+ this.ensureSite().administrationTargetCount = value;
10425
+ }
10141
10426
  get additionalInstructions() {
10142
10427
  if (!this.clause.additionalInstructions) {
10143
10428
  this.clause.additionalInstructions = [];
@@ -10210,7 +10495,7 @@ var ParserState = class {
10210
10495
  if (!site) {
10211
10496
  return;
10212
10497
  }
10213
- if (site.text === void 0 && site.coding === void 0 && site.spatialRelation === void 0 && site.source === void 0 && site.inferred === void 0 && site.evidence === void 0) {
10498
+ if (site.text === void 0 && site.coding === void 0 && site.spatialRelation === void 0 && site.administrationTargetCount === void 0 && site.source === void 0 && site.inferred === void 0 && site.evidence === void 0) {
10214
10499
  delete this.clause.site;
10215
10500
  }
10216
10501
  }
@@ -10288,12 +10573,12 @@ function selectCanonicalSiteCoding(site, options) {
10288
10573
  );
10289
10574
  return (options == null ? void 0 : options.bodySitePostcoordination) === true ? postcoordinated != null ? postcoordinated : site == null ? void 0 : site.coding : (_a2 = site == null ? void 0 : site.coding) != null ? _a2 : postcoordinated;
10290
10575
  }
10291
- function buildSiteCodingArray(siteCoding) {
10576
+ function buildSiteCodingArray(siteCoding, options) {
10292
10577
  var _a2;
10293
10578
  if (!(siteCoding == null ? void 0 : siteCoding.code)) {
10294
10579
  return void 0;
10295
10580
  }
10296
- const displayElement = buildTranslationPrimitiveElement(siteCoding.i18n);
10581
+ const displayElement = (options == null ? void 0 : options.includeTranslationExtensions) ? buildTranslationPrimitiveElement(siteCoding.i18n) : void 0;
10297
10582
  return [
10298
10583
  __spreadValues({
10299
10584
  system: (_a2 = siteCoding.system) != null ? _a2 : SNOMED_SYSTEM5,
@@ -10313,6 +10598,18 @@ function getFallbackSiteText(site) {
10313
10598
  display: siteCoding.display
10314
10599
  }) : void 0;
10315
10600
  }
10601
+ function codeableConceptTranslationI18n(concept, coding, fallback) {
10602
+ return {
10603
+ text: mergeI18nRecords(
10604
+ fallback,
10605
+ getPrimitiveTranslations(concept == null ? void 0 : concept._text)
10606
+ ),
10607
+ display: mergeI18nRecords(
10608
+ coding == null ? void 0 : coding.i18n,
10609
+ getPrimitiveTranslations(coding == null ? void 0 : coding._display)
10610
+ )
10611
+ };
10612
+ }
10316
10613
  function lowerFirst(value) {
10317
10614
  const trimmed = value == null ? void 0 : value.trim();
10318
10615
  return trimmed ? trimmed.charAt(0).toLowerCase() + trimmed.slice(1) : void 0;
@@ -10559,7 +10856,7 @@ function appendWarning(warnings, warning) {
10559
10856
  return warnings;
10560
10857
  }
10561
10858
  function canonicalToFhir(clause, textOverride, options) {
10562
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I;
10859
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N;
10563
10860
  const dosage = {};
10564
10861
  const repeat = {};
10565
10862
  let hasRepeat = false;
@@ -10659,21 +10956,36 @@ function canonicalToFhir(clause, textOverride, options) {
10659
10956
  }
10660
10957
  if (((_l = clause.site) == null ? void 0 : _l.text) || ((_n = (_m = clause.site) == null ? void 0 : _m.coding) == null ? void 0 : _n.code) || ((_o = clause.site) == null ? void 0 : _o.spatialRelation)) {
10661
10958
  const siteCoding = selectCanonicalSiteCoding(clause.site, options);
10662
- const siteTextElement = buildTranslationPrimitiveElement(siteCoding == null ? void 0 : siteCoding.i18n);
10959
+ const siteTextElement = (options == null ? void 0 : options.includeTranslationExtensions) ? buildTranslationPrimitiveElement((_p = clause.site.i18n) != null ? _p : siteCoding == null ? void 0 : siteCoding.i18n) : void 0;
10960
+ const siteTargetCount = (_r = (_q = clause.site) == null ? void 0 : _q.administrationTargetCount) != null ? _r : getBodySiteAdministrationTargetCount(clause.site);
10961
+ const inferredSiteTargetCount = getBodySiteAdministrationTargetCount({
10962
+ text: (_s = clause.site) == null ? void 0 : _s.text,
10963
+ coding: siteCoding ? {
10964
+ code: siteCoding.code,
10965
+ display: siteCoding.display,
10966
+ system: siteCoding.system,
10967
+ i18n: siteCoding.i18n
10968
+ } : void 0
10969
+ });
10970
+ const siteTargetCountExtension = siteTargetCount !== void 0 && siteTargetCount !== inferredSiteTargetCount ? buildBodySiteAdministrationTargetCountExtension(siteTargetCount) : void 0;
10971
+ const siteExtensions = [
10972
+ ...(_u = buildBodySiteSpatialRelationExtensions((_t = clause.site) == null ? void 0 : _t.spatialRelation)) != null ? _u : [],
10973
+ ...siteTargetCountExtension ? [siteTargetCountExtension] : []
10974
+ ];
10663
10975
  dosage.site = __spreadProps(__spreadValues({
10664
- text: (_p = clause.site) == null ? void 0 : _p.text
10976
+ text: (_v = clause.site) == null ? void 0 : _v.text
10665
10977
  }, siteTextElement ? { _text: siteTextElement } : {}), {
10666
- coding: buildSiteCodingArray(siteCoding),
10667
- extension: buildBodySiteSpatialRelationExtensions((_q = clause.site) == null ? void 0 : _q.spatialRelation)
10978
+ coding: buildSiteCodingArray(siteCoding, options),
10979
+ extension: siteExtensions.length ? siteExtensions : void 0
10668
10980
  });
10669
10981
  }
10670
- if (((_r = clause.method) == null ? void 0 : _r.text) || ((_s = clause.method) == null ? void 0 : _s._text) || ((_u = (_t = clause.method) == null ? void 0 : _t.coding) == null ? void 0 : _u.code)) {
10982
+ if (((_w = clause.method) == null ? void 0 : _w.text) || ((_x = clause.method) == null ? void 0 : _x._text) || ((_z = (_y = clause.method) == null ? void 0 : _y.coding) == null ? void 0 : _z.code)) {
10671
10983
  dosage.method = {
10672
- text: (_v = clause.method) == null ? void 0 : _v.text,
10673
- _text: clonePrimitiveElement((_w = clause.method) == null ? void 0 : _w._text),
10674
- coding: ((_y = (_x = clause.method) == null ? void 0 : _x.coding) == null ? void 0 : _y.code) ? [
10984
+ text: (_A = clause.method) == null ? void 0 : _A.text,
10985
+ _text: clonePrimitiveElement((_B = clause.method) == null ? void 0 : _B._text),
10986
+ coding: ((_D = (_C = clause.method) == null ? void 0 : _C.coding) == null ? void 0 : _D.code) ? [
10675
10987
  {
10676
- system: (_z = clause.method.coding.system) != null ? _z : SNOMED_SYSTEM5,
10988
+ system: (_E = clause.method.coding.system) != null ? _E : SNOMED_SYSTEM5,
10677
10989
  code: clause.method.coding.code,
10678
10990
  display: clause.method.coding.display,
10679
10991
  _display: clonePrimitiveElement(clause.method.coding._display)
@@ -10681,14 +10993,14 @@ function canonicalToFhir(clause, textOverride, options) {
10681
10993
  ] : void 0
10682
10994
  };
10683
10995
  }
10684
- if ((_A = clause.additionalInstructions) == null ? void 0 : _A.length) {
10996
+ if ((_F = clause.additionalInstructions) == null ? void 0 : _F.length) {
10685
10997
  dosage.additionalInstruction = [];
10686
10998
  for (const instruction of clause.additionalInstructions) {
10687
10999
  dosage.additionalInstruction.push({
10688
11000
  text: instruction.text,
10689
- coding: ((_B = instruction.coding) == null ? void 0 : _B.code) ? [
11001
+ coding: ((_G = instruction.coding) == null ? void 0 : _G.code) ? [
10690
11002
  {
10691
- system: (_C = instruction.coding.system) != null ? _C : SNOMED_SYSTEM5,
11003
+ system: (_H = instruction.coding.system) != null ? _H : SNOMED_SYSTEM5,
10692
11004
  code: instruction.coding.code,
10693
11005
  display: instruction.coding.display
10694
11006
  }
@@ -10696,9 +11008,9 @@ function canonicalToFhir(clause, textOverride, options) {
10696
11008
  });
10697
11009
  }
10698
11010
  }
10699
- if ((_D = clause.prn) == null ? void 0 : _D.enabled) {
11011
+ if ((_I = clause.prn) == null ? void 0 : _I.enabled) {
10700
11012
  dosage.asNeededBoolean = true;
10701
- const reasons = ((_E = clause.prn.reasons) == null ? void 0 : _E.length) ? clause.prn.reasons : clause.prn.reason ? [clause.prn.reason] : [];
11013
+ const reasons = ((_J = clause.prn.reasons) == null ? void 0 : _J.length) ? clause.prn.reasons : clause.prn.reason ? [clause.prn.reason] : [];
10702
11014
  if (reasons.length) {
10703
11015
  dosage.asNeededFor = [];
10704
11016
  for (const reason of reasons) {
@@ -10706,15 +11018,18 @@ function canonicalToFhir(clause, textOverride, options) {
10706
11018
  if (reason.text) {
10707
11019
  concept.text = reason.text;
10708
11020
  }
10709
- const reasonTextElement = buildTranslationPrimitiveElement((_F = reason.coding) == null ? void 0 : _F.i18n);
11021
+ const reasonTextElement = (options == null ? void 0 : options.includeTranslationExtensions) ? buildTranslationPrimitiveElement((_L = reason.i18n) != null ? _L : (_K = reason.coding) == null ? void 0 : _K.i18n) : void 0;
10710
11022
  if (reasonTextElement) {
10711
11023
  concept._text = reasonTextElement;
10712
11024
  }
10713
- if ((_G = reason.coding) == null ? void 0 : _G.code) {
10714
- const displayElement = (_H = clonePrimitiveElement(reason.coding._display)) != null ? _H : buildTranslationPrimitiveElement(reason.coding.i18n);
11025
+ if ((_M = reason.coding) == null ? void 0 : _M.code) {
11026
+ const displayElement = (options == null ? void 0 : options.includeTranslationExtensions) ? mergeTranslationPrimitiveElement(
11027
+ reason.coding._display,
11028
+ reason.coding.i18n
11029
+ ) : clonePrimitiveElement(reason.coding._display);
10715
11030
  concept.coding = [
10716
11031
  __spreadProps(__spreadValues({
10717
- system: (_I = reason.coding.system) != null ? _I : SNOMED_SYSTEM5,
11032
+ system: (_N = reason.coding.system) != null ? _N : SNOMED_SYSTEM5,
10718
11033
  code: reason.coding.code,
10719
11034
  display: reason.coding.display
10720
11035
  }, displayElement ? { _display: displayElement } : {}), {
@@ -10754,14 +11069,19 @@ function canonicalFromFhir(dosage) {
10754
11069
  const siteCoding = selectPreferredSiteCoding(dosage.site);
10755
11070
  const siteSpatialRelation = parseBodySiteSpatialRelationExtension(dosage.site);
10756
11071
  const siteText = getFallbackSiteText(dosage.site);
10757
- if (siteText || (siteCoding == null ? void 0 : siteCoding.code) || siteSpatialRelation) {
11072
+ const siteI18n = codeableConceptTranslationI18n(dosage.site, siteCoding);
11073
+ const siteAdministrationTargetCount = getBodySiteAdministrationTargetCount(dosage.site);
11074
+ if (siteText || (siteCoding == null ? void 0 : siteCoding.code) || siteSpatialRelation || siteAdministrationTargetCount !== void 0) {
10758
11075
  clause.site = {
10759
11076
  text: siteText,
11077
+ i18n: siteI18n.text,
10760
11078
  spatialRelation: siteSpatialRelation,
11079
+ administrationTargetCount: siteAdministrationTargetCount,
10761
11080
  coding: (siteCoding == null ? void 0 : siteCoding.code) ? {
10762
11081
  code: siteCoding.code,
10763
11082
  display: siteCoding.display,
10764
- system: siteCoding.system
11083
+ system: siteCoding.system,
11084
+ i18n: siteI18n.display
10765
11085
  } : void 0,
10766
11086
  source: "text"
10767
11087
  };
@@ -10816,16 +11136,21 @@ function canonicalFromFhir(dosage) {
10816
11136
  };
10817
11137
  }
10818
11138
  const prnReasons = ((_y = dosage.asNeededFor) == null ? void 0 : _y.length) ? dosage.asNeededFor.map((concept) => {
10819
- var _a3;
11139
+ var _a3, _b2;
10820
11140
  const coding = (_a3 = concept.coding) == null ? void 0 : _a3.find((code) => Boolean(code.code));
11141
+ const defaultDef = (coding == null ? void 0 : coding.code) ? findPrnReasonDefinitionByCoding((_b2 = coding.system) != null ? _b2 : SNOMED_SYSTEM5, coding.code) : void 0;
11142
+ const i18n = codeableConceptTranslationI18n(concept, coding, defaultDef == null ? void 0 : defaultDef.i18n);
10821
11143
  return {
10822
11144
  text: getFallbackPrnReasonText(concept),
11145
+ i18n: i18n.text,
10823
11146
  spatialRelation: parseBodySiteSpatialRelationExtension(concept),
10824
11147
  coding: (coding == null ? void 0 : coding.code) ? {
10825
11148
  code: coding.code,
10826
11149
  display: coding.display,
10827
11150
  system: coding.system,
10828
- extension: cloneExtensions(coding.extension)
11151
+ extension: cloneExtensions(coding.extension),
11152
+ _display: clonePrimitiveElement(coding._display),
11153
+ i18n: i18n.display
10829
11154
  } : void 0
10830
11155
  };
10831
11156
  }) : void 0;
@@ -11832,73 +12157,22 @@ function summarizeMealTimingGroupThai(group) {
11832
12157
  }
11833
12158
  return `${relationText[group.relation]}${joinMealNamesThai(meals)}`;
11834
12159
  }
11835
- function collectWhenPhrasesThai(schedule, options) {
11836
- var _a2;
11837
- const when = (_a2 = schedule == null ? void 0 : schedule.when) != null ? _a2 : [];
11838
- if (!when.length) {
11839
- return [];
11840
- }
11841
- const unique = [];
11842
- const seen = /* @__PURE__ */ new Set();
11843
- let hasSpecificAfter = false;
11844
- let hasSpecificBefore = false;
11845
- let hasSpecificWith = false;
11846
- for (const code of when) {
11847
- if (!seen.has(code)) {
11848
- seen.add(code);
11849
- unique.push(code);
11850
- if (code === "PCM" /* After Breakfast */ || code === "PCD" /* After Lunch */ || code === "PCV" /* After Dinner */) {
11851
- hasSpecificAfter = true;
11852
- }
11853
- if (code === "ACM" /* Before Breakfast */ || code === "ACD" /* Before Lunch */ || code === "ACV" /* Before Dinner */) {
11854
- hasSpecificBefore = true;
11855
- }
11856
- if (code === "CM" /* Breakfast */ || code === "CD" /* Lunch */ || code === "CV" /* Dinner */) {
11857
- hasSpecificWith = true;
11858
- }
11859
- }
11860
- }
11861
- const filtered = [];
11862
- for (const code of unique) {
11863
- if (code === "PC" /* After Meal */ && hasSpecificAfter) {
11864
- continue;
11865
- }
11866
- if (code === "AC" /* Before Meal */ && hasSpecificBefore) {
11867
- continue;
11868
- }
11869
- if (code === "C" /* Meal */ && hasSpecificWith) {
11870
- continue;
11871
- }
11872
- filtered.push(code);
11873
- }
11874
- const mealGroup = getMealTimingGroup(filtered, options);
11875
- if (!mealGroup) {
11876
- const phrases2 = [];
11877
- for (const code of filtered) {
11878
- const text = WHEN_TEXT_THAI[code];
11879
- if (text) {
11880
- phrases2.push(text);
11881
- }
11882
- }
11883
- return phrases2;
11884
- }
11885
- const groupedCodes = new Set(mealGroup.codes);
11886
- const phrases = [];
11887
- let insertedGroup = false;
11888
- for (const code of filtered) {
11889
- if (groupedCodes.has(code)) {
11890
- if (!insertedGroup) {
11891
- phrases.push(summarizeMealTimingGroupThai(mealGroup));
11892
- insertedGroup = true;
11893
- }
11894
- continue;
12160
+ var TH_TIMING_GRAMMAR = {
12161
+ whenText: WHEN_TEXT_THAI,
12162
+ joinList: joinWithAndThai,
12163
+ summarizeMealTimingGroup: summarizeMealTimingGroupThai,
12164
+ bedtimeJoinStyle: (dailyCount) => {
12165
+ if (dailyCount === 1) {
12166
+ return "adjacent";
11895
12167
  }
11896
- const text = WHEN_TEXT_THAI[code];
11897
- if (text) {
11898
- phrases.push(text);
12168
+ if (dailyCount === 2 || dailyCount === 3 || dailyCount === 4) {
12169
+ return "conjunction";
11899
12170
  }
12171
+ return "separate";
11900
12172
  }
11901
- return phrases;
12173
+ };
12174
+ function collectWhenPhrasesThai(schedule, options) {
12175
+ return collectLocalizedWhenPhrases(schedule, TH_TIMING_GRAMMAR, options);
11902
12176
  }
11903
12177
  function joinWithAndThai(parts) {
11904
12178
  if (!parts.length) {
@@ -11912,20 +12186,14 @@ function joinWithAndThai(parts) {
11912
12186
  }
11913
12187
  return `${parts.slice(0, -1).join(", ")} \u0E41\u0E25\u0E30 ${parts[parts.length - 1]}`;
11914
12188
  }
11915
- function combineFrequencyAndEventsThai(frequency, events) {
11916
- if (!frequency) {
11917
- if (!events.length) {
11918
- return {};
11919
- }
11920
- return { event: joinWithAndThai(events) };
11921
- }
11922
- if (!events.length) {
11923
- return { frequency };
11924
- }
11925
- if (events.length === 1 && events[0] === "\u0E01\u0E48\u0E2D\u0E19\u0E19\u0E2D\u0E19" && frequency.includes("\u0E27\u0E31\u0E19\u0E25\u0E30")) {
11926
- return { frequency: `${frequency} \u0E41\u0E25\u0E30 ${events[0]}` };
11927
- }
11928
- return { frequency, event: joinWithAndThai(events) };
12189
+ function combineFrequencyAndEventsThai(schedule, frequency, events, options) {
12190
+ return combineLocalizedFrequencyAndEvents(
12191
+ schedule,
12192
+ frequency,
12193
+ events,
12194
+ TH_TIMING_GRAMMAR,
12195
+ options
12196
+ );
11929
12197
  }
11930
12198
  function isOralRouteThai(clause) {
11931
12199
  var _a2, _b, _c;
@@ -11984,7 +12252,7 @@ function buildRoutePhraseThai(clause, grammar, hasSite) {
11984
12252
  return text;
11985
12253
  }
11986
12254
  function formatSiteThai(clause, grammar) {
11987
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
12255
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
11988
12256
  const text = ((_b = (_a2 = clause.site) == null ? void 0 : _a2.text) == null ? void 0 : _b.trim()) || ((_e = (_d = (_c = clause.site) == null ? void 0 : _c.coding) == null ? void 0 : _d.display) == null ? void 0 : _e.trim());
11989
12257
  const lower = text == null ? void 0 : text.toLowerCase();
11990
12258
  const codingCode = (_g = (_f = clause.site) == null ? void 0 : _f.coding) == null ? void 0 : _g.code;
@@ -11999,14 +12267,14 @@ function formatSiteThai(clause, grammar) {
11999
12267
  if (isVaginalRoute && isVaginaSite) {
12000
12268
  return void 0;
12001
12269
  }
12002
- const translated = text ? translateSiteThai(text, codingCode, (_l = clause.site) == null ? void 0 : _l.spatialRelation) : translateSpatialSiteThai(void 0, (_m = clause.site) == null ? void 0 : _m.spatialRelation);
12270
+ const translated = (_t = (_q = (_m = (_l = clause.site) == null ? void 0 : _l.i18n) == null ? void 0 : _m.th) != null ? _q : (_p = (_o = (_n = clause.site) == null ? void 0 : _n.coding) == null ? void 0 : _o.i18n) == null ? void 0 : _p.th) != null ? _t : text ? translateSiteThai(text, codingCode, (_r = clause.site) == null ? void 0 : _r.spatialRelation) : translateSpatialSiteThai(void 0, (_s = clause.site) == null ? void 0 : _s.spatialRelation);
12003
12271
  if (!translated) {
12004
12272
  return void 0;
12005
12273
  }
12006
- if (((_n = clause.route) == null ? void 0 : _n.code) === RouteCode["Nasal route"]) {
12274
+ if (((_u = clause.route) == null ? void 0 : _u.code) === RouteCode["Nasal route"]) {
12007
12275
  return `\u0E40\u0E02\u0E49\u0E32${translated}`;
12008
12276
  }
12009
- const preposition = (_o = grammar.sitePreposition) != null ? _o : "\u0E17\u0E35\u0E48";
12277
+ const preposition = (_v = grammar.sitePreposition) != null ? _v : "\u0E17\u0E35\u0E48";
12010
12278
  const separator = /^[\u0E00-\u0E7F]/.test(translated) ? "" : " ";
12011
12279
  return `${preposition}${separator}${translated}`.trim();
12012
12280
  }
@@ -12304,7 +12572,7 @@ function formatLongThai(clause, options) {
12304
12572
  eventParts.push(`\u0E40\u0E27\u0E25\u0E32 ${timeStrings.join(", ")}`);
12305
12573
  }
12306
12574
  }
12307
- const timing = combineFrequencyAndEventsThai(frequencyPart, eventParts);
12575
+ const timing = combineFrequencyAndEventsThai(schedule, frequencyPart, eventParts, options);
12308
12576
  const dayPart = describeDayOfWeekThai(schedule);
12309
12577
  const countPart = schedule.count !== void 0 && !standaloneOccurrenceCount ? `\u0E08\u0E33\u0E19\u0E27\u0E19 ${stripTrailingZero2(schedule.count)} \u0E04\u0E23\u0E31\u0E49\u0E07` : void 0;
12310
12578
  const durationPart = describeDurationThai(schedule);
@@ -12736,6 +13004,12 @@ function sameOptionalScalar(left, right) {
12736
13004
  function mergeOptionalScalar(left, right) {
12737
13005
  return left !== void 0 ? left : right;
12738
13006
  }
13007
+ function mergeI18nRecords2(left, right) {
13008
+ if (!left && !right) {
13009
+ return void 0;
13010
+ }
13011
+ return __spreadValues(__spreadValues({}, left != null ? left : {}), right != null ? right : {});
13012
+ }
12739
13013
  function sameCoding(left, right) {
12740
13014
  var _a2, _b;
12741
13015
  if (!(left == null ? void 0 : left.code) || !(right == null ? void 0 : right.code)) {
@@ -12798,6 +13072,7 @@ function mergeSite(left, right, context) {
12798
13072
  }
12799
13073
  return {
12800
13074
  text: mergeOptionalScalar(left.text, right.text),
13075
+ i18n: mergeI18nRecords2(left.i18n, right.i18n),
12801
13076
  source: mergeOptionalScalar(left.source, right.source),
12802
13077
  coding: mergeOptionalScalar(left.coding, right.coding),
12803
13078
  spatialRelation: mergeOptionalScalar(left.spatialRelation, right.spatialRelation),
@@ -13145,6 +13420,7 @@ var TOKEN_SITE_CANDIDATES = {
13145
13420
  le: [{ text: "left eye", route: RouteCode["Ophthalmic route"], source: "abbreviation" }],
13146
13421
  ou: [{ text: "both eyes", route: RouteCode["Ophthalmic route"], source: "abbreviation" }],
13147
13422
  be: [{ text: "both eyes", route: RouteCode["Ophthalmic route"], source: "abbreviation" }],
13423
+ au: [{ text: "both ears", route: RouteCode["Otic route"], source: "abbreviation" }],
13148
13424
  vod: [
13149
13425
  {
13150
13426
  text: "right eye",
@@ -15694,6 +15970,9 @@ function inferRouteFromContext(ctx) {
15694
15970
  // src/hpsg/rules/site-rules.ts
15695
15971
  function siteBoundary(lower, context) {
15696
15972
  var _a2, _b, _c;
15973
+ if (SITE_MULTIPLICITY_WORDS.has(lower)) {
15974
+ return false;
15975
+ }
15697
15976
  const siteLike = Boolean(resolveBodySitePhrase(lower, (_a2 = context.options) == null ? void 0 : _a2.siteCodeMap, {
15698
15977
  bodySiteContext: (_c = (_b = context.options) == null ? void 0 : _b.context) == null ? void 0 : _c.bodySiteContext
15699
15978
  }));
@@ -15755,7 +16034,7 @@ function shouldSuppressEyeSiteAbbreviation(context, start, lower) {
15755
16034
  }
15756
16035
  function siteLexicalRule() {
15757
16036
  return lexicalRule("hpsg.lex.site", (context, start) => {
15758
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
16037
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r;
15759
16038
  const token = (_a2 = tokensAvailable(context, start, 1)) == null ? void 0 : _a2[0];
15760
16039
  if (!token) {
15761
16040
  return [];
@@ -15821,7 +16100,7 @@ function siteLexicalRule() {
15821
16100
  continue;
15822
16101
  }
15823
16102
  phraseTokens.push(candidate);
15824
- if (!SITE_DISPLAY_FILLERS.has(candidateLower)) {
16103
+ if (!SITE_DISPLAY_FILLERS.has(candidateLower) || SITE_MULTIPLICITY_WORDS.has(candidateLower)) {
15825
16104
  displayTokens.push(candidate);
15826
16105
  }
15827
16106
  }
@@ -15867,6 +16146,7 @@ function siteLexicalRule() {
15867
16146
  valence: {
15868
16147
  site: {
15869
16148
  text: displayText,
16149
+ i18n: (_r = resolved == null ? void 0 : resolved.definition) == null ? void 0 : _r.i18n,
15870
16150
  source: "text",
15871
16151
  coding: resolved == null ? void 0 : resolved.coding,
15872
16152
  spatialRelation: resolved == null ? void 0 : resolved.spatialRelation,
@@ -15894,7 +16174,7 @@ function siteLexicalRule() {
15894
16174
  }
15895
16175
  function bareSiteLexicalRule() {
15896
16176
  return lexicalRule("hpsg.lex.site.bare", (context, start) => {
15897
- var _a2, _b, _c, _d, _e, _f, _g, _h;
16177
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i;
15898
16178
  const signs = [];
15899
16179
  const first = (_a2 = tokensAvailable(context, start, 1)) == null ? void 0 : _a2[0];
15900
16180
  if (!first) {
@@ -15940,6 +16220,7 @@ function bareSiteLexicalRule() {
15940
16220
  valence: {
15941
16221
  site: {
15942
16222
  text: displayText,
16223
+ i18n: (_h = resolved.definition) == null ? void 0 : _h.i18n,
15943
16224
  source: "text",
15944
16225
  coding: resolved.coding,
15945
16226
  spatialRelation: resolved.spatialRelation,
@@ -15947,7 +16228,7 @@ function bareSiteLexicalRule() {
15947
16228
  originalText,
15948
16229
  text: sourceText,
15949
16230
  normalized: sourceText.toLowerCase(),
15950
- canonical: (_h = resolved.resolutionCanonical) != null ? _h : normalizeBodySiteKey(displayText),
16231
+ canonical: (_i = resolved.resolutionCanonical) != null ? _i : normalizeBodySiteKey(displayText),
15951
16232
  isProbe,
15952
16233
  inputText: context.state.input,
15953
16234
  sourceText,
@@ -16654,7 +16935,7 @@ function applyRouteSiteDefault(state) {
16654
16935
  system: (_i = definition.coding.system) != null ? _i : "http://snomed.info/sct",
16655
16936
  code: definition.coding.code,
16656
16937
  display: definition.coding.display,
16657
- i18n: definition.i18n
16938
+ i18n: mergeI18nRecords(definition.i18n, definition.coding.i18n)
16658
16939
  };
16659
16940
  }
16660
16941
  }
@@ -17967,13 +18248,16 @@ function applySiteDefinition(internal, definition) {
17967
18248
  code: coding.code,
17968
18249
  display: coding.display,
17969
18250
  system: (_a2 = coding.system) != null ? _a2 : SNOMED_SYSTEM9,
17970
- i18n: definition.i18n
18251
+ i18n: mergeI18nRecords(definition.i18n, coding.i18n)
17971
18252
  } : void 0;
17972
18253
  if (definition.text) {
17973
18254
  internal.siteText = definition.text;
17974
18255
  } else if ((_b = internal.siteLookupRequest) == null ? void 0 : _b.text) {
17975
18256
  internal.siteText = internal.siteLookupRequest.text;
17976
18257
  }
18258
+ if (definition.administrationTargetCount !== void 0) {
18259
+ internal.siteAdministrationTargetCount = definition.administrationTargetCount;
18260
+ }
17977
18261
  if (definition.spatialRelation) {
17978
18262
  internal.siteSpatialRelation = definition.spatialRelation;
17979
18263
  } else if ((_c = internal.siteLookupRequest) == null ? void 0 : _c.spatialRelation) {
@@ -18324,8 +18608,11 @@ function seedKnownSiteCoding(state) {
18324
18608
  system: (_b = definition.coding.system) != null ? _b : SNOMED_SYSTEM10,
18325
18609
  code: definition.coding.code,
18326
18610
  display: definition.coding.display,
18327
- i18n: definition.i18n
18611
+ i18n: mergeI18nRecords(definition.i18n, definition.coding.i18n)
18328
18612
  };
18613
+ if (state.siteAdministrationTargetCount === void 0) {
18614
+ state.siteAdministrationTargetCount = definition.administrationTargetCount;
18615
+ }
18329
18616
  }
18330
18617
  function parseClauseState(input, options) {
18331
18618
  const tokens = tokenize(input);
@@ -19656,6 +19943,21 @@ function normalizeClock(clock) {
19656
19943
  }
19657
19944
  return `${pad(hour)}:${pad(minute)}:${pad(second)}`;
19658
19945
  }
19946
+ function doseUnitSupportsPerTargetMultiplication(doseUnit, context) {
19947
+ const semantics = getDoseUnitSemantics(doseUnit, context);
19948
+ if (!semantics) {
19949
+ return false;
19950
+ }
19951
+ return semantics.kind !== "metric" && semantics.kind !== "biologic_unit";
19952
+ }
19953
+ function getAdministrationTargetMultiplier(dosage, context) {
19954
+ var _a2, _b, _c, _d;
19955
+ const doseUnit = (_c = (_b = (_a2 = dosage.doseAndRate) == null ? void 0 : _a2[0]) == null ? void 0 : _b.doseQuantity) == null ? void 0 : _c.unit;
19956
+ if (!doseUnitSupportsPerTargetMultiplication(doseUnit, context)) {
19957
+ return 1;
19958
+ }
19959
+ return (_d = getBodySiteAdministrationTargetCount(dosage.site)) != null ? _d : 1;
19960
+ }
19659
19961
  function getDateTimeFormat(timeZone) {
19660
19962
  let formatter = dateTimeFormatCache.get(timeZone);
19661
19963
  if (!formatter) {
@@ -21075,7 +21377,8 @@ function calculateTotalUnitsSingle(options) {
21075
21377
  2e3
21076
21378
  );
21077
21379
  const doseQuantity = (_j = (_i = (_h = (_g = dosage.doseAndRate) == null ? void 0 : _g[0]) == null ? void 0 : _h.doseQuantity) == null ? void 0 : _i.value) != null ? _j : 0;
21078
- let totalUnits = roundCalculatedUnits(count * doseQuantity);
21380
+ const targetMultiplier = getAdministrationTargetMultiplier(dosage, context);
21381
+ let totalUnits = roundCalculatedUnits(count * doseQuantity * targetMultiplier);
21079
21382
  if (roundToMultiple && roundToMultiple > 0) {
21080
21383
  totalUnits = roundCalculatedUnits(Math.ceil(totalUnits / roundToMultiple) * roundToMultiple);
21081
21384
  }
@@ -21626,11 +21929,12 @@ function cloneBodySiteCoding2(coding) {
21626
21929
  return {
21627
21930
  code: coding.code,
21628
21931
  display: coding.display,
21629
- system: coding.system
21932
+ system: coding.system,
21933
+ i18n: cloneI18nRecord(coding.i18n)
21630
21934
  };
21631
21935
  }
21632
21936
  function buildNormalizedMetaFromClause(clause, fhir, options) {
21633
- var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F;
21937
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H;
21634
21938
  const additionalInstructions = ((_a2 = clause.additionalInstructions) == null ? void 0 : _a2.length) ? clause.additionalInstructions.map((instruction) => ({
21635
21939
  text: instruction.text,
21636
21940
  coding: cloneCoding2(instruction.coding)
@@ -21643,22 +21947,23 @@ function buildNormalizedMetaFromClause(clause, fhir, options) {
21643
21947
  unit: (_h = clause.dose) == null ? void 0 : _h.unit,
21644
21948
  unitKind: unitSemantics == null ? void 0 : unitSemantics.kind,
21645
21949
  unitSemantics,
21646
- site: ((_i = clause.site) == null ? void 0 : _i.text) || ((_k = (_j = clause.site) == null ? void 0 : _j.coding) == null ? void 0 : _k.code) || ((_l = clause.site) == null ? void 0 : _l.spatialRelation) ? {
21647
- text: (_m = clause.site) == null ? void 0 : _m.text,
21950
+ site: ((_i = clause.site) == null ? void 0 : _i.text) || ((_k = (_j = clause.site) == null ? void 0 : _j.coding) == null ? void 0 : _k.code) || ((_l = clause.site) == null ? void 0 : _l.spatialRelation) || ((_m = clause.site) == null ? void 0 : _m.administrationTargetCount) !== void 0 ? {
21951
+ text: (_n = clause.site) == null ? void 0 : _n.text,
21648
21952
  coding: siteCoding,
21649
- spatialRelation: cloneBodySiteSpatialRelation((_n = clause.site) == null ? void 0 : _n.spatialRelation)
21953
+ spatialRelation: cloneBodySiteSpatialRelation((_o = clause.site) == null ? void 0 : _o.spatialRelation),
21954
+ administrationTargetCount: (_p = clause.site) == null ? void 0 : _p.administrationTargetCount
21650
21955
  } : void 0,
21651
- method: ((_o = clause.method) == null ? void 0 : _o.text) || ((_q = (_p = clause.method) == null ? void 0 : _p.coding) == null ? void 0 : _q.code) ? {
21652
- text: (_r = clause.method) == null ? void 0 : _r.text,
21653
- coding: cloneCoding2((_s = clause.method) == null ? void 0 : _s.coding)
21956
+ method: ((_q = clause.method) == null ? void 0 : _q.text) || ((_s = (_r = clause.method) == null ? void 0 : _r.coding) == null ? void 0 : _s.code) ? {
21957
+ text: (_t = clause.method) == null ? void 0 : _t.text,
21958
+ coding: cloneCoding2((_u = clause.method) == null ? void 0 : _u.coding)
21654
21959
  } : void 0,
21655
21960
  patientInstruction: clause.patientInstruction,
21656
- prnReason: ((_u = (_t = clause.prn) == null ? void 0 : _t.reason) == null ? void 0 : _u.text) || ((_x = (_w = (_v = clause.prn) == null ? void 0 : _v.reason) == null ? void 0 : _w.coding) == null ? void 0 : _x.code) ? {
21657
- text: (_z = (_y = clause.prn) == null ? void 0 : _y.reason) == null ? void 0 : _z.text,
21658
- coding: cloneCoding2((_B = (_A = clause.prn) == null ? void 0 : _A.reason) == null ? void 0 : _B.coding),
21659
- spatialRelation: cloneBodySiteSpatialRelation((_D = (_C = clause.prn) == null ? void 0 : _C.reason) == null ? void 0 : _D.spatialRelation)
21961
+ prnReason: ((_w = (_v = clause.prn) == null ? void 0 : _v.reason) == null ? void 0 : _w.text) || ((_z = (_y = (_x = clause.prn) == null ? void 0 : _x.reason) == null ? void 0 : _y.coding) == null ? void 0 : _z.code) ? {
21962
+ text: (_B = (_A = clause.prn) == null ? void 0 : _A.reason) == null ? void 0 : _B.text,
21963
+ coding: cloneCoding2((_D = (_C = clause.prn) == null ? void 0 : _C.reason) == null ? void 0 : _D.coding),
21964
+ spatialRelation: cloneBodySiteSpatialRelation((_F = (_E = clause.prn) == null ? void 0 : _E.reason) == null ? void 0 : _F.spatialRelation)
21660
21965
  } : void 0,
21661
- prnReasons: ((_F = (_E = clause.prn) == null ? void 0 : _E.reasons) == null ? void 0 : _F.length) ? clause.prn.reasons.map((reason) => ({
21966
+ prnReasons: ((_H = (_G = clause.prn) == null ? void 0 : _G.reasons) == null ? void 0 : _H.length) ? clause.prn.reasons.map((reason) => ({
21662
21967
  text: reason.text,
21663
21968
  coding: cloneCoding2(reason.coding),
21664
21969
  spatialRelation: cloneBodySiteSpatialRelation(reason.spatialRelation)
@@ -21673,7 +21978,8 @@ function buildParseResult(state, options) {
21673
21978
  const shortText = formatCanonicalClause(clause, "short", localization, options);
21674
21979
  const longText = formatCanonicalClause(clause, "long", localization, options);
21675
21980
  const fhir = canonicalToFhir(clause, longText, {
21676
- bodySitePostcoordination: options == null ? void 0 : options.bodySitePostcoordination
21981
+ bodySitePostcoordination: options == null ? void 0 : options.bodySitePostcoordination,
21982
+ includeTranslationExtensions: Boolean((options == null ? void 0 : options.locale) || (options == null ? void 0 : options.i18n))
21677
21983
  });
21678
21984
  const consumedTokens = [];
21679
21985
  const leftoverParts = [];
@@ -21856,6 +22162,7 @@ function resolvePrimaryLintResult(results, input, options) {
21856
22162
  AdviceModality,
21857
22163
  AdvicePolarity,
21858
22164
  AdviceRelation,
22165
+ BODY_SITE_ADMINISTRATION_TARGET_COUNT_EXTENSION_URL,
21859
22166
  BODY_SITE_SPATIAL_RELATION_EXTENSION_URL,
21860
22167
  DEFAULT_BODY_SITE_SNOMED,
21861
22168
  DEFAULT_BODY_SITE_SNOMED_SOURCE,
@@ -21880,6 +22187,7 @@ function resolvePrimaryLintResult(results, input, options) {
21880
22187
  SNOMED_CT_TOPOGRAPHICAL_MODIFIER_CODE,
21881
22188
  SNOMED_CT_TOPOGRAPHICAL_MODIFIER_DISPLAY,
21882
22189
  SNOMED_SYSTEM,
22190
+ buildBodySiteAdministrationTargetCountExtension,
21883
22191
  buildBodySiteSpatialRelationExtension,
21884
22192
  buildBodySiteSpatialRelationExtensions,
21885
22193
  buildBodySiteTopographicalModifierCoding,
@@ -21893,6 +22201,7 @@ function resolvePrimaryLintResult(results, input, options) {
21893
22201
  formatSig,
21894
22202
  formatSigBatch,
21895
22203
  fromFhirDosage,
22204
+ getBodySiteAdministrationTargetCount,
21896
22205
  getBodySiteCode,
21897
22206
  getBodySiteCodeAsync,
21898
22207
  getBodySiteText,
@@ -21911,6 +22220,7 @@ function resolvePrimaryLintResult(results, input, options) {
21911
22220
  lookupBodySite,
21912
22221
  lookupBodySiteAsync,
21913
22222
  nextDueDoses,
22223
+ parseBodySiteAdministrationTargetCountExtension,
21914
22224
  parseBodySiteSpatialRelationExtension,
21915
22225
  parseSig,
21916
22226
  parseSigAsync,