ezmedicationinput 0.1.42 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +31 -1
  2. package/dist/advice-rules.json +772 -0
  3. package/dist/advice-terminology.json +104 -0
  4. package/dist/advice.d.ts +16 -0
  5. package/dist/advice.js +1375 -0
  6. package/dist/event-trigger.d.ts +14 -0
  7. package/dist/event-trigger.js +501 -0
  8. package/dist/fhir-translations.d.ts +5 -0
  9. package/dist/fhir-translations.js +117 -0
  10. package/dist/fhir.d.ts +6 -4
  11. package/dist/fhir.js +566 -134
  12. package/dist/format.d.ts +5 -2
  13. package/dist/format.js +581 -219
  14. package/dist/i18n.d.ts +4 -2
  15. package/dist/i18n.js +725 -197
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +221 -169
  18. package/dist/internal-types.d.ts +5 -5
  19. package/dist/ir.d.ts +4 -0
  20. package/dist/ir.js +178 -0
  21. package/dist/lexer/lex.d.ts +2 -0
  22. package/dist/lexer/lex.js +401 -0
  23. package/dist/lexer/meaning.d.ts +71 -0
  24. package/dist/lexer/meaning.js +619 -0
  25. package/dist/lexer/surface.d.ts +2 -0
  26. package/dist/lexer/surface.js +62 -0
  27. package/dist/lexer/token-types.d.ts +36 -0
  28. package/dist/lexer/token-types.js +19 -0
  29. package/dist/maps.d.ts +6 -12
  30. package/dist/maps.js +793 -247
  31. package/dist/parser-state.d.ts +101 -0
  32. package/dist/parser-state.js +441 -0
  33. package/dist/parser.d.ts +7 -7
  34. package/dist/parser.js +3598 -1974
  35. package/dist/prn.d.ts +4 -0
  36. package/dist/prn.js +59 -0
  37. package/dist/schedule.js +230 -32
  38. package/dist/site-phrases.d.ts +35 -0
  39. package/dist/site-phrases.js +344 -0
  40. package/dist/timing-summary.d.ts +25 -0
  41. package/dist/timing-summary.js +138 -0
  42. package/dist/types.d.ts +248 -32
  43. package/dist/types.js +49 -1
  44. package/dist/utils/text.d.ts +3 -0
  45. package/dist/utils/text.js +48 -0
  46. package/package.json +1 -1
package/dist/i18n.js CHANGED
@@ -5,7 +5,12 @@ exports.registerSigLocalization = registerSigLocalization;
5
5
  exports.getRegisteredSigLocalizations = getRegisteredSigLocalizations;
6
6
  exports.resolveSigLocalization = resolveSigLocalization;
7
7
  exports.resolveSigTranslation = resolveSigTranslation;
8
+ const advice_1 = require("./advice");
9
+ const fhir_translations_1 = require("./fhir-translations");
10
+ const maps_1 = require("./maps");
11
+ const prn_1 = require("./prn");
8
12
  const types_1 = require("./types");
13
+ const timing_summary_1 = require("./timing-summary");
9
14
  const REGISTERED_LOCALIZATIONS = new Map();
10
15
  function registerSigLocalization(localization) {
11
16
  REGISTERED_LOCALIZATIONS.set(localization.locale.toLowerCase(), localization);
@@ -64,8 +69,11 @@ function resolveSigTranslation(locale, config) {
64
69
  function createThaiLocalization() {
65
70
  return {
66
71
  locale: "th",
67
- formatShort: ({ internal }) => formatShortThai(internal),
68
- formatLong: ({ internal }) => formatLongThai(internal)
72
+ formatShort: ({ clause }) => formatShortThai(clause),
73
+ formatLong: ({ clause, groupMealTimingsByRelation, includeTimesPerDaySummary }) => formatLongThai(clause, {
74
+ groupMealTimingsByRelation,
75
+ includeTimesPerDaySummary
76
+ })
69
77
  };
70
78
  }
71
79
  registerSigLocalization(createThaiLocalization());
@@ -125,6 +133,14 @@ const DAY_NAMES_THAI = {
125
133
  sat: "วันเสาร์",
126
134
  sun: "วันอาทิตย์"
127
135
  };
136
+ const TH_TIMES_PER_DAY = {
137
+ 1: "วันละครั้ง",
138
+ 2: "วันละ 2 ครั้ง",
139
+ 3: "วันละ 3 ครั้ง",
140
+ 4: "วันละ 4 ครั้ง"
141
+ };
142
+ const SLOWLY_QUALIFIER_CODE = "419443000";
143
+ const EMPTY_STOMACH_QUALIFIER_CODE = "717154004";
128
144
  exports.THAI_SITE_TRANSLATIONS = {
129
145
  eye: "ตา",
130
146
  eyes: "ตา",
@@ -159,6 +175,13 @@ exports.THAI_SITE_TRANSLATIONS = {
159
175
  "left upper arm": "ต้นแขนซ้าย",
160
176
  "right upper arm": "ต้นแขนขวา",
161
177
  "bilateral arms": "แขนทั้งสองข้าง",
178
+ shoulder: "ไหล่",
179
+ shoulders: "ไหล่ทั้งสองข้าง",
180
+ "left shoulder": "ไหล่ซ้าย",
181
+ "right shoulder": "ไหล่ขวา",
182
+ elbow: "ข้อศอก",
183
+ "left elbow": "ข้อศอกซ้าย",
184
+ "right elbow": "ข้อศอกขวา",
162
185
  "right leg": "ขาขวา",
163
186
  "left leg": "ขาซ้าย",
164
187
  "both legs": "ขาทั้งสองข้าง",
@@ -167,6 +190,22 @@ exports.THAI_SITE_TRANSLATIONS = {
167
190
  "left lower leg": "ขาส่วนล่างซ้าย",
168
191
  "right lower leg": "ขาส่วนล่างขวา",
169
192
  "bilateral legs": "ขาทั้งสองข้าง",
193
+ knee: "เข่า",
194
+ "left knee": "เข่าซ้าย",
195
+ "right knee": "เข่าขวา",
196
+ "both knees": "เข่าทั้งสองข้าง",
197
+ "bilateral knees": "เข่าทั้งสองข้าง",
198
+ wrist: "ข้อมือ",
199
+ "left wrist": "ข้อมือซ้าย",
200
+ "right wrist": "ข้อมือขวา",
201
+ ankle: "ข้อเท้า",
202
+ "left ankle": "ข้อเท้าซ้าย",
203
+ "right ankle": "ข้อเท้าขวา",
204
+ "both ankles": "ข้อเท้าทั้งสองข้าง",
205
+ "bilateral ankles": "ข้อเท้าทั้งสองข้าง",
206
+ hip: "สะโพก",
207
+ "left hip": "สะโพกซ้าย",
208
+ "right hip": "สะโพกขวา",
170
209
  "right hand": "มือขวา",
171
210
  "left hand": "มือซ้าย",
172
211
  "both hands": "มือทั้งสองข้าง",
@@ -180,19 +219,53 @@ exports.THAI_SITE_TRANSLATIONS = {
180
219
  abdomen: "ช่องท้อง",
181
220
  abdominal: "ช่องท้อง",
182
221
  belly: "ท้อง",
222
+ "affected area": "บริเวณที่เป็น",
223
+ "affected site": "บริเวณที่เป็น",
224
+ "บริเวณที่เป็น": "บริเวณที่เป็น",
225
+ head: "ศีรษะ",
226
+ "left head": "ศีรษะซ้าย",
227
+ "left side of head": "ศีรษะซ้าย",
228
+ "right head": "ศีรษะขวา",
229
+ "right side of head": "ศีรษะขวา",
183
230
  back: "แผ่นหลัง",
231
+ chest: "ทรวงอก",
232
+ "chest wall": "ผนังทรวงอก",
233
+ breast: "เต้านม",
234
+ "left breast": "เต้านมซ้าย",
235
+ "right breast": "เต้านมขวา",
236
+ "both breasts": "เต้านมทั้งสองข้าง",
237
+ "bilateral breasts": "เต้านมทั้งสองข้าง",
238
+ axilla: "รักแร้",
239
+ axillae: "รักแร้ทั้งสองข้าง",
240
+ armpit: "รักแร้",
241
+ armpits: "รักแร้ทั้งสองข้าง",
242
+ groin: "ขาหนีบ",
184
243
  scalp: "หนังศีรษะ",
185
244
  face: "ใบหน้า",
186
245
  cheek: "แก้ม",
187
246
  cheeks: "แก้มทั้งสองข้าง",
188
247
  forehead: "หน้าผาก",
248
+ temple: "ขมับ",
249
+ "temple region": "ขมับ",
250
+ "temporal region": "ขมับ",
251
+ temples: "ขมับทั้งสองข้าง",
252
+ "left temple": "ขมับซ้าย",
253
+ "left temple region": "ขมับซ้าย",
254
+ "left temporal region": "ขมับซ้าย",
255
+ "right temple": "ขมับขวา",
256
+ "right temple region": "ขมับขวา",
257
+ "right temporal region": "ขมับขวา",
258
+ "both temples": "ขมับทั้งสองข้าง",
259
+ "bilateral temples": "ขมับทั้งสองข้าง",
189
260
  chin: "คาง",
190
261
  neck: "คอ",
262
+ eyelid: "เปลือกตา",
263
+ eyelids: "เปลือกตา",
264
+ lip: "ริมฝีปาก",
265
+ lips: "ริมฝีปาก",
191
266
  forearm: "ปลายแขน",
192
267
  "left forearm": "ปลายแขนซ้าย",
193
268
  "right forearm": "ปลายแขนขวา",
194
- shoulder: "ไหล่",
195
- shoulders: "ไหล่ทั้งสองข้าง",
196
269
  thigh: "ต้นขา",
197
270
  thighs: "ต้นขาทั้งสองข้าง",
198
271
  "left thigh": "ต้นขาซ้าย",
@@ -224,6 +297,28 @@ exports.THAI_SITE_TRANSLATIONS = {
224
297
  skin: "ผิวหนัง",
225
298
  hair: "เส้นผม"
226
299
  };
300
+ const THAI_SITE_CODE_TRANSLATIONS = (() => {
301
+ var _a;
302
+ const translations = {};
303
+ for (const { names, definition } of maps_1.DEFAULT_BODY_SITE_SNOMED_SOURCE) {
304
+ const code = (_a = definition.coding) === null || _a === void 0 ? void 0 : _a.code;
305
+ if (!code || translations[code]) {
306
+ continue;
307
+ }
308
+ let translated;
309
+ for (const name of names) {
310
+ const candidate = exports.THAI_SITE_TRANSLATIONS[(0, maps_1.normalizeBodySiteKey)(name)];
311
+ if (candidate) {
312
+ translated = candidate;
313
+ break;
314
+ }
315
+ }
316
+ if (translated) {
317
+ translations[code] = translated;
318
+ }
319
+ }
320
+ return translations;
321
+ })();
227
322
  const DEFAULT_THAI_ROUTE_GRAMMAR = { verb: "ใช้" };
228
323
  const THAI_ROUTE_GRAMMAR = {
229
324
  [types_1.RouteCode["Oral route"]]: { verb: "รับประทาน", routePhrase: "ทางปาก" },
@@ -289,26 +384,168 @@ const THAI_ROUTE_GRAMMAR = {
289
384
  sitePreposition: "ที่"
290
385
  }
291
386
  };
292
- function resolveRouteGrammarThai(internal) {
293
- var _a, _b, _c;
294
- if (internal.routeCode && THAI_ROUTE_GRAMMAR[internal.routeCode]) {
295
- return (_a = THAI_ROUTE_GRAMMAR[internal.routeCode]) !== null && _a !== void 0 ? _a : DEFAULT_THAI_ROUTE_GRAMMAR;
387
+ const THAI_METHOD_TEXT_VERBS = {
388
+ Apply: "ทา",
389
+ "Apply sunscreen": "ทากันแดด",
390
+ Dab: "แต้ม",
391
+ Drink: "รับประทาน",
392
+ Insert: "สอด",
393
+ Instill: "หยอด",
394
+ Massage: "นวด",
395
+ Reapply: "ทาซ้ำ",
396
+ "Reapply sunscreen": "ทากันแดดซ้ำ",
397
+ Rub: "ถู",
398
+ Spray: "พ่น",
399
+ Shampoo: "สระ",
400
+ Swallow: "รับประทาน",
401
+ Take: "รับประทาน",
402
+ "Use shampoo": "สระ",
403
+ Wash: "ล้าง"
404
+ };
405
+ const THAI_IMPLIED_OBJECT_VERBS = new Set([
406
+ "ทา",
407
+ "ทาซ้ำ",
408
+ "แต้ม",
409
+ "ถู",
410
+ "นวด",
411
+ "พ่น",
412
+ "หยอด",
413
+ "สอด",
414
+ "ล้าง",
415
+ "สระ"
416
+ ]);
417
+ const THAI_SUPPRESSIBLE_ROUTE_VERBS = new Set([
418
+ "ทา",
419
+ "ทากันแดด",
420
+ "ทาซ้ำ",
421
+ "ทากันแดดซ้ำ",
422
+ "แต้ม",
423
+ "ถู",
424
+ "นวด",
425
+ "ล้าง",
426
+ "สระ"
427
+ ]);
428
+ const THAI_SITE_FIRST_VERBS = new Set([
429
+ "ทา",
430
+ "ทากันแดด",
431
+ "ทาซ้ำ",
432
+ "ทากันแดดซ้ำ",
433
+ "แต้ม",
434
+ "ถู",
435
+ "นวด",
436
+ "ล้าง",
437
+ "สระ",
438
+ "พ่น"
439
+ ]);
440
+ function resolveThaiMethodVerb(clause, grammar) {
441
+ var _a, _b, _c, _d, _e;
442
+ const translatedText = (0, fhir_translations_1.getPrimitiveTranslation)((_a = clause.method) === null || _a === void 0 ? void 0 : _a._text, "th");
443
+ if (translatedText) {
444
+ return translatedText;
445
+ }
446
+ const methodText = (_c = (_b = clause.method) === null || _b === void 0 ? void 0 : _b.text) === null || _c === void 0 ? void 0 : _c.trim();
447
+ if (methodText) {
448
+ const overridden = THAI_METHOD_TEXT_VERBS[methodText];
449
+ if (overridden) {
450
+ return overridden;
451
+ }
452
+ }
453
+ const translatedDisplay = (0, fhir_translations_1.getPrimitiveTranslation)((_e = (_d = clause.method) === null || _d === void 0 ? void 0 : _d.coding) === null || _e === void 0 ? void 0 : _e._display, "th");
454
+ if (translatedDisplay) {
455
+ return translatedDisplay;
456
+ }
457
+ return grammar.verb;
458
+ }
459
+ function joinThaiVerbAndBody(verb, body) {
460
+ const trimmedBody = body.trim();
461
+ if (!trimmedBody) {
462
+ return verb;
463
+ }
464
+ switch (verb) {
465
+ case "พ่น":
466
+ if (trimmedBody.startsWith("เข้า")) {
467
+ return `${verb}${trimmedBody}`;
468
+ }
469
+ break;
470
+ case "ทาซ้ำ":
471
+ case "ทากันแดดซ้ำ":
472
+ case "สระ":
473
+ if (trimmedBody.startsWith("ทุก") ||
474
+ trimmedBody.startsWith("วัน") ||
475
+ trimmedBody.startsWith("ก่อน") ||
476
+ trimmedBody.startsWith("หลัง")) {
477
+ return `${verb}${trimmedBody}`;
478
+ }
479
+ break;
480
+ default:
481
+ break;
482
+ }
483
+ return `${verb} ${trimmedBody}`;
484
+ }
485
+ function shouldUseGenericMedicationObjectThai(clause, verb, explicitDosePart) {
486
+ var _a;
487
+ if (explicitDosePart) {
488
+ return false;
296
489
  }
297
- const grammar = grammarFromRouteTextThai(internal.routeText);
490
+ if (THAI_IMPLIED_OBJECT_VERBS.has(verb)) {
491
+ return false;
492
+ }
493
+ switch ((_a = clause.route) === null || _a === void 0 ? void 0 : _a.code) {
494
+ case types_1.RouteCode["Topical route"]:
495
+ case types_1.RouteCode["Transdermal route"]:
496
+ case types_1.RouteCode["Nasal route"]:
497
+ case types_1.RouteCode["Ophthalmic route"]:
498
+ case types_1.RouteCode["Otic route"]:
499
+ case types_1.RouteCode["Per rectum"]:
500
+ case types_1.RouteCode["Per vagina"]:
501
+ return false;
502
+ default:
503
+ return true;
504
+ }
505
+ }
506
+ function shouldSuppressRoutePhraseThai(clause, verb, hasSite, explicitDosePart) {
507
+ var _a;
508
+ if (hasSite || explicitDosePart) {
509
+ return false;
510
+ }
511
+ if (!THAI_SUPPRESSIBLE_ROUTE_VERBS.has(verb)) {
512
+ return false;
513
+ }
514
+ switch ((_a = clause.route) === null || _a === void 0 ? void 0 : _a.code) {
515
+ case types_1.RouteCode["Topical route"]:
516
+ case types_1.RouteCode["Transdermal route"]:
517
+ return true;
518
+ default:
519
+ return false;
520
+ }
521
+ }
522
+ function scheduleOf(clause) {
523
+ var _a;
524
+ return (_a = clause.schedule) !== null && _a !== void 0 ? _a : {};
525
+ }
526
+ function resolveRouteGrammarThai(clause) {
527
+ var _a, _b, _c, _d, _e, _f;
528
+ const routeCode = (_a = clause.route) === null || _a === void 0 ? void 0 : _a.code;
529
+ if (routeCode && THAI_ROUTE_GRAMMAR[routeCode]) {
530
+ return (_b = THAI_ROUTE_GRAMMAR[routeCode]) !== null && _b !== void 0 ? _b : DEFAULT_THAI_ROUTE_GRAMMAR;
531
+ }
532
+ const grammar = grammarFromRouteTextThai((_c = clause.route) === null || _c === void 0 ? void 0 : _c.text);
298
533
  if (grammar) {
299
534
  return grammar;
300
535
  }
301
- if (((_b = internal.unit) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase()) === "puff") {
302
- return (_c = THAI_ROUTE_GRAMMAR[types_1.RouteCode["Respiratory tract route (qualifier value)"]]) !== null && _c !== void 0 ? _c : DEFAULT_THAI_ROUTE_GRAMMAR;
536
+ if (((_e = (_d = clause.dose) === null || _d === void 0 ? void 0 : _d.unit) === null || _e === void 0 ? void 0 : _e.trim().toLowerCase()) === "puff") {
537
+ return ((_f = THAI_ROUTE_GRAMMAR[types_1.RouteCode["Respiratory tract route (qualifier value)"]]) !== null && _f !== void 0 ? _f : DEFAULT_THAI_ROUTE_GRAMMAR);
303
538
  }
304
539
  return DEFAULT_THAI_ROUTE_GRAMMAR;
305
540
  }
306
541
  function grammarFromRouteTextThai(text) {
307
- if (!text)
542
+ if (!text) {
308
543
  return undefined;
544
+ }
309
545
  const normalized = text.trim().toLowerCase();
310
- if (!normalized)
546
+ if (!normalized) {
311
547
  return undefined;
548
+ }
312
549
  if (normalized.includes("mouth") || normalized.includes("oral")) {
313
550
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Oral route"]];
314
551
  }
@@ -333,6 +570,12 @@ function grammarFromRouteTextThai(text) {
333
570
  if (normalized.includes("intravenous") || normalized === "iv") {
334
571
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Intravenous route"]];
335
572
  }
573
+ if (normalized.includes("rectal") || normalized.includes("rectum")) {
574
+ return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Per rectum"]];
575
+ }
576
+ if (normalized.includes("vagin")) {
577
+ return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Per vagina"]];
578
+ }
336
579
  if (normalized.includes("nasal")) {
337
580
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Nasal route"]];
338
581
  }
@@ -341,45 +584,75 @@ function grammarFromRouteTextThai(text) {
341
584
  }
342
585
  return undefined;
343
586
  }
344
- function formatDoseThaiShort(internal) {
345
- if (internal.doseRange) {
346
- const { low, high } = internal.doseRange;
347
- const base = `${stripTrailingZero(low)}-${stripTrailingZero(high)}`;
348
- if (internal.unit) {
349
- return `${base} ${formatUnitThai(internal.unit, high, "short")}`;
587
+ function formatDoseThaiShort(dose) {
588
+ if (!dose) {
589
+ return undefined;
590
+ }
591
+ if (dose.range) {
592
+ if (dose.range.low !== undefined && dose.range.high !== undefined) {
593
+ const base = `${stripTrailingZero(dose.range.low)}-${stripTrailingZero(dose.range.high)}`;
594
+ if (dose.unit) {
595
+ return `${base} ${formatUnitThai(dose.unit, dose.range.high, "short")}`;
596
+ }
597
+ return base;
598
+ }
599
+ if (dose.range.low !== undefined) {
600
+ const base = `>=${stripTrailingZero(dose.range.low)}`;
601
+ if (dose.unit) {
602
+ return `${base} ${formatUnitThai(dose.unit, dose.range.low, "short")}`;
603
+ }
604
+ return base;
605
+ }
606
+ if (dose.range.high !== undefined) {
607
+ const base = `<=${stripTrailingZero(dose.range.high)}`;
608
+ if (dose.unit) {
609
+ return `${base} ${formatUnitThai(dose.unit, dose.range.high, "short")}`;
610
+ }
611
+ return base;
350
612
  }
351
- return base;
352
613
  }
353
- if (internal.dose !== undefined) {
354
- const amount = stripTrailingZero(internal.dose);
355
- if (internal.unit) {
356
- return `${amount} ${formatUnitThai(internal.unit, internal.dose, "short")}`;
614
+ if (dose.value !== undefined) {
615
+ if (dose.unit) {
616
+ return `${stripTrailingZero(dose.value)} ${formatUnitThai(dose.unit, dose.value, "short")}`;
357
617
  }
358
- return amount;
618
+ return stripTrailingZero(dose.value);
359
619
  }
360
620
  return undefined;
361
621
  }
362
- function formatDoseThaiLong(internal) {
363
- if (internal.doseRange) {
364
- const { low, high } = internal.doseRange;
365
- if (internal.unit) {
366
- const unit = formatUnitThai(internal.unit, high, "long");
367
- return `ครั้งละ ${stripTrailingZero(low)} ถึง ${stripTrailingZero(high)} ${unit}`;
622
+ function formatDoseThaiLong(dose) {
623
+ if (!dose) {
624
+ return undefined;
625
+ }
626
+ if (dose.range) {
627
+ if (dose.range.low !== undefined && dose.range.high !== undefined) {
628
+ if (dose.unit) {
629
+ return `ครั้งละ ${stripTrailingZero(dose.range.low)} ถึง ${stripTrailingZero(dose.range.high)} ${formatUnitThai(dose.unit, dose.range.high, "long")}`;
630
+ }
631
+ return `ครั้งละ ${stripTrailingZero(dose.range.low)} ถึง ${stripTrailingZero(dose.range.high)}`;
632
+ }
633
+ if (dose.range.low !== undefined) {
634
+ if (dose.unit) {
635
+ return `ครั้งละ อย่างน้อย ${stripTrailingZero(dose.range.low)} ${formatUnitThai(dose.unit, dose.range.low, "long")}`;
636
+ }
637
+ return `ครั้งละ อย่างน้อย ${stripTrailingZero(dose.range.low)}`;
638
+ }
639
+ if (dose.range.high !== undefined) {
640
+ if (dose.unit) {
641
+ return `ครั้งละ ไม่เกิน ${stripTrailingZero(dose.range.high)} ${formatUnitThai(dose.unit, dose.range.high, "long")}`;
642
+ }
643
+ return `ครั้งละ ไม่เกิน ${stripTrailingZero(dose.range.high)}`;
368
644
  }
369
- return `ครั้งละ ${stripTrailingZero(low)} ถึง ${stripTrailingZero(high)}`;
370
645
  }
371
- if (internal.dose !== undefined) {
372
- if (internal.unit) {
373
- const unit = formatUnitThai(internal.unit, internal.dose, "long");
374
- return `ครั้งละ ${stripTrailingZero(internal.dose)} ${unit}`;
646
+ if (dose.value !== undefined) {
647
+ if (dose.unit) {
648
+ return `ครั้งละ ${stripTrailingZero(dose.value)} ${formatUnitThai(dose.unit, dose.value, "long")}`;
375
649
  }
376
- return `ครั้งละ ${stripTrailingZero(internal.dose)}`;
650
+ return `ครั้งละ ${stripTrailingZero(dose.value)}`;
377
651
  }
378
652
  return undefined;
379
653
  }
380
- function formatUnitThai(unit, value, style) {
654
+ function formatUnitThai(unit, _value, style) {
381
655
  const lower = unit.toLowerCase();
382
- const quantity = Math.abs(value);
383
656
  const mapping = {
384
657
  tab: { short: "เม็ด", long: "เม็ด" },
385
658
  tablet: { short: "เม็ด", long: "เม็ด" },
@@ -403,13 +676,16 @@ function formatUnitThai(unit, value, style) {
403
676
  suppositories: { short: "ยาเหน็บ", long: "ยาเหน็บ" }
404
677
  };
405
678
  const entry = mapping[lower];
406
- if (entry) {
407
- return style === "short" ? entry.short : entry.long;
408
- }
409
- return unit;
679
+ return entry ? entry[style] : unit;
410
680
  }
411
- function describeFrequencyThai(internal) {
412
- const { frequency, frequencyMax, period, periodMax, periodUnit, timingCode } = internal;
681
+ function describeFrequencyThai(schedule) {
682
+ var _a;
683
+ const frequency = schedule === null || schedule === void 0 ? void 0 : schedule.frequency;
684
+ const frequencyMax = schedule === null || schedule === void 0 ? void 0 : schedule.frequencyMax;
685
+ const period = schedule === null || schedule === void 0 ? void 0 : schedule.period;
686
+ const periodMax = schedule === null || schedule === void 0 ? void 0 : schedule.periodMax;
687
+ const periodUnit = schedule === null || schedule === void 0 ? void 0 : schedule.periodUnit;
688
+ const timingCode = schedule === null || schedule === void 0 ? void 0 : schedule.timingCode;
413
689
  if (frequency !== undefined &&
414
690
  frequencyMax !== undefined &&
415
691
  periodUnit === types_1.FhirPeriodUnit.Day &&
@@ -423,15 +699,13 @@ function describeFrequencyThai(internal) {
423
699
  return `วันละ ${stripTrailingZero(frequency)} ถึง ${stripTrailingZero(frequencyMax)} ครั้ง`;
424
700
  }
425
701
  if (frequency && periodUnit === types_1.FhirPeriodUnit.Day && (!period || period === 1)) {
426
- if (frequency === 1)
427
- return "วันละครั้ง";
428
- if (frequency === 2)
429
- return "วันละ 2 ครั้ง";
430
- if (frequency === 3)
431
- return "วันละ 3 ครั้ง";
432
- if (frequency === 4)
433
- return "วันละ 4 ครั้ง";
434
- return `วันละ ${stripTrailingZero(frequency)} ครั้ง`;
702
+ return (_a = TH_TIMES_PER_DAY[frequency]) !== null && _a !== void 0 ? _a : `วันละ ${stripTrailingZero(frequency)} ครั้ง`;
703
+ }
704
+ if (periodUnit === types_1.FhirPeriodUnit.Minute && period) {
705
+ if (periodMax && periodMax !== period) {
706
+ return `ทุก ${stripTrailingZero(period)} ถึง ${stripTrailingZero(periodMax)} นาที`;
707
+ }
708
+ return `ทุก ${stripTrailingZero(period)} นาที`;
435
709
  }
436
710
  if (periodUnit === types_1.FhirPeriodUnit.Hour && period) {
437
711
  if (periodMax && periodMax !== period) {
@@ -466,6 +740,15 @@ function describeFrequencyThai(internal) {
466
740
  }
467
741
  return `ทุก ${stripTrailingZero(period)} เดือน`;
468
742
  }
743
+ if (periodUnit === types_1.FhirPeriodUnit.Year && period) {
744
+ if (period === 1 && (!periodMax || periodMax === 1)) {
745
+ return "ปีละครั้ง";
746
+ }
747
+ if (periodMax && periodMax !== period) {
748
+ return `ทุก ${stripTrailingZero(period)} ถึง ${stripTrailingZero(periodMax)} ปี`;
749
+ }
750
+ return `ทุก ${stripTrailingZero(period)} ปี`;
751
+ }
469
752
  if (timingCode) {
470
753
  const map = {
471
754
  BID: "วันละ 2 ครั้ง",
@@ -484,48 +767,146 @@ function describeFrequencyThai(internal) {
484
767
  }
485
768
  }
486
769
  if (frequency && periodUnit === undefined && period === undefined) {
487
- if (frequency === 1)
770
+ if (frequency === 1) {
488
771
  return "ครั้งเดียว";
772
+ }
489
773
  return `${stripTrailingZero(frequency)} ครั้ง`;
490
774
  }
491
775
  return undefined;
492
776
  }
493
- function collectWhenPhrasesThai(internal) {
494
- if (!internal.when.length) {
777
+ function describeFrequencyCountThai(count) {
778
+ var _a;
779
+ if (!count || count <= 0) {
780
+ return undefined;
781
+ }
782
+ return (_a = TH_TIMES_PER_DAY[count]) !== null && _a !== void 0 ? _a : `วันละ ${stripTrailingZero(count)} ครั้ง`;
783
+ }
784
+ function describeStandaloneOccurrenceCountThai(schedule) {
785
+ var _a, _b, _c;
786
+ const count = schedule === null || schedule === void 0 ? void 0 : schedule.count;
787
+ if (!count || count <= 0) {
788
+ return undefined;
789
+ }
790
+ if ((schedule === null || schedule === void 0 ? void 0 : schedule.frequency) !== undefined ||
791
+ (schedule === null || schedule === void 0 ? void 0 : schedule.frequencyMax) !== undefined ||
792
+ (schedule === null || schedule === void 0 ? void 0 : schedule.period) !== undefined ||
793
+ (schedule === null || schedule === void 0 ? void 0 : schedule.periodMax) !== undefined ||
794
+ (schedule === null || schedule === void 0 ? void 0 : schedule.periodUnit) !== undefined ||
795
+ ((_a = schedule === null || schedule === void 0 ? void 0 : schedule.dayOfWeek) === null || _a === void 0 ? void 0 : _a.length) ||
796
+ ((_b = schedule === null || schedule === void 0 ? void 0 : schedule.when) === null || _b === void 0 ? void 0 : _b.length) ||
797
+ ((_c = schedule === null || schedule === void 0 ? void 0 : schedule.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) ||
798
+ (schedule === null || schedule === void 0 ? void 0 : schedule.duration) !== undefined ||
799
+ (schedule === null || schedule === void 0 ? void 0 : schedule.durationMax) !== undefined ||
800
+ (schedule === null || schedule === void 0 ? void 0 : schedule.durationUnit) !== undefined ||
801
+ (schedule === null || schedule === void 0 ? void 0 : schedule.timingCode)) {
802
+ return undefined;
803
+ }
804
+ return `${stripTrailingZero(count)} ครั้ง`;
805
+ }
806
+ function joinMealNamesThai(parts) {
807
+ if (!parts.length) {
808
+ return "";
809
+ }
810
+ if (parts.length === 1) {
811
+ return parts[0];
812
+ }
813
+ if (parts.length === 2) {
814
+ return `${parts[0]} และ${parts[1]}`;
815
+ }
816
+ let text = parts[0];
817
+ for (let i = 1; i < parts.length - 1; i += 1) {
818
+ text += ` ${parts[i]}`;
819
+ }
820
+ return `${text} และ${parts[parts.length - 1]}`;
821
+ }
822
+ function summarizeMealTimingGroupThai(group) {
823
+ const relationText = {
824
+ before: "ก่อนอาหาร",
825
+ after: "หลังอาหาร",
826
+ with: "พร้อมอาหาร"
827
+ };
828
+ const mealText = {
829
+ breakfast: "เช้า",
830
+ lunch: "กลางวัน",
831
+ dinner: "เย็น"
832
+ };
833
+ const meals = [];
834
+ for (const meal of group.meals) {
835
+ meals.push(mealText[meal]);
836
+ }
837
+ return `${relationText[group.relation]}${joinMealNamesThai(meals)}`;
838
+ }
839
+ function collectWhenPhrasesThai(schedule, options) {
840
+ var _a;
841
+ const when = (_a = schedule === null || schedule === void 0 ? void 0 : schedule.when) !== null && _a !== void 0 ? _a : [];
842
+ if (!when.length) {
495
843
  return [];
496
844
  }
497
845
  const unique = [];
498
846
  const seen = new Set();
499
- for (const code of internal.when) {
847
+ let hasSpecificAfter = false;
848
+ let hasSpecificBefore = false;
849
+ let hasSpecificWith = false;
850
+ for (const code of when) {
500
851
  if (!seen.has(code)) {
501
852
  seen.add(code);
502
853
  unique.push(code);
854
+ if (code === types_1.EventTiming["After Breakfast"] ||
855
+ code === types_1.EventTiming["After Lunch"] ||
856
+ code === types_1.EventTiming["After Dinner"]) {
857
+ hasSpecificAfter = true;
858
+ }
859
+ if (code === types_1.EventTiming["Before Breakfast"] ||
860
+ code === types_1.EventTiming["Before Lunch"] ||
861
+ code === types_1.EventTiming["Before Dinner"]) {
862
+ hasSpecificBefore = true;
863
+ }
864
+ if (code === types_1.EventTiming.Breakfast || code === types_1.EventTiming.Lunch || code === types_1.EventTiming.Dinner) {
865
+ hasSpecificWith = true;
866
+ }
503
867
  }
504
868
  }
505
- const hasSpecificAfter = unique.some((code) => code === types_1.EventTiming["After Breakfast"] ||
506
- code === types_1.EventTiming["After Lunch"] ||
507
- code === types_1.EventTiming["After Dinner"]);
508
- const hasSpecificBefore = unique.some((code) => code === types_1.EventTiming["Before Breakfast"] ||
509
- code === types_1.EventTiming["Before Lunch"] ||
510
- code === types_1.EventTiming["Before Dinner"]);
511
- const hasSpecificWith = unique.some((code) => code === types_1.EventTiming.Breakfast ||
512
- code === types_1.EventTiming.Lunch ||
513
- code === types_1.EventTiming.Dinner);
514
- return unique
515
- .filter((code) => {
869
+ const filtered = [];
870
+ for (const code of unique) {
516
871
  if (code === types_1.EventTiming["After Meal"] && hasSpecificAfter) {
517
- return false;
872
+ continue;
518
873
  }
519
874
  if (code === types_1.EventTiming["Before Meal"] && hasSpecificBefore) {
520
- return false;
875
+ continue;
521
876
  }
522
877
  if (code === types_1.EventTiming.Meal && hasSpecificWith) {
523
- return false;
878
+ continue;
524
879
  }
525
- return true;
526
- })
527
- .map((code) => { var _a; return (_a = WHEN_TEXT_THAI[code]) !== null && _a !== void 0 ? _a : undefined; })
528
- .filter((text) => Boolean(text));
880
+ filtered.push(code);
881
+ }
882
+ const mealGroup = (0, timing_summary_1.getMealTimingGroup)(filtered, options);
883
+ if (!mealGroup) {
884
+ const phrases = [];
885
+ for (const code of filtered) {
886
+ const text = WHEN_TEXT_THAI[code];
887
+ if (text) {
888
+ phrases.push(text);
889
+ }
890
+ }
891
+ return phrases;
892
+ }
893
+ const groupedCodes = new Set(mealGroup.codes);
894
+ const phrases = [];
895
+ let insertedGroup = false;
896
+ for (const code of filtered) {
897
+ if (groupedCodes.has(code)) {
898
+ if (!insertedGroup) {
899
+ phrases.push(summarizeMealTimingGroupThai(mealGroup));
900
+ insertedGroup = true;
901
+ }
902
+ continue;
903
+ }
904
+ const text = WHEN_TEXT_THAI[code];
905
+ if (text) {
906
+ phrases.push(text);
907
+ }
908
+ }
909
+ return phrases;
529
910
  }
530
911
  function joinWithAndThai(parts) {
531
912
  if (!parts.length) {
@@ -549,39 +930,34 @@ function combineFrequencyAndEventsThai(frequency, events) {
549
930
  if (!events.length) {
550
931
  return { frequency };
551
932
  }
552
- if (events.length === 1 && events[0] === "ก่อนนอน") {
553
- if (frequency.includes("วันละ")) {
554
- return { frequency: `${frequency} และ ${events[0]}` };
555
- }
933
+ if (events.length === 1 && events[0] === "ก่อนนอน" && frequency.includes("วันละ")) {
934
+ return { frequency: `${frequency} และ ${events[0]}` };
556
935
  }
557
936
  return { frequency, event: joinWithAndThai(events) };
558
937
  }
559
- function isOralRouteThai(internal) {
560
- var _a;
561
- if (internal.routeCode === types_1.RouteCode["Oral route"]) {
938
+ function isOralRouteThai(clause) {
939
+ var _a, _b, _c;
940
+ if (((_a = clause.route) === null || _a === void 0 ? void 0 : _a.code) === types_1.RouteCode["Oral route"]) {
562
941
  return true;
563
942
  }
564
- const text = (_a = internal.routeText) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
943
+ const text = (_c = (_b = clause.route) === null || _b === void 0 ? void 0 : _b.text) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase();
565
944
  if (!text) {
566
945
  return false;
567
946
  }
568
- return (text === "po" ||
569
- text === "oral" ||
570
- text.includes("mouth") ||
571
- text.includes("per os"));
947
+ return text === "po" || text === "oral" || text.includes("mouth") || text.includes("per os");
572
948
  }
573
- function buildRoutePhraseThai(internal, grammar, hasSite) {
574
- var _a;
575
- if (grammar.verb === "รับประทาน" && isOralRouteThai(internal)) {
949
+ function buildRoutePhraseThai(clause, grammar, hasSite) {
950
+ var _a, _b;
951
+ if (grammar.verb === "รับประทาน" && isOralRouteThai(clause)) {
576
952
  return undefined;
577
953
  }
578
954
  if (typeof grammar.routePhrase === "function") {
579
- return grammar.routePhrase({ hasSite, internal });
955
+ return grammar.routePhrase({ hasSite, clause });
580
956
  }
581
957
  if (typeof grammar.routePhrase === "string") {
582
958
  return grammar.routePhrase;
583
959
  }
584
- const text = (_a = internal.routeText) === null || _a === void 0 ? void 0 : _a.trim();
960
+ const text = (_b = (_a = clause.route) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.trim();
585
961
  if (!text) {
586
962
  return undefined;
587
963
  }
@@ -615,138 +991,261 @@ function buildRoutePhraseThai(internal, grammar, hasSite) {
615
991
  }
616
992
  return text;
617
993
  }
618
- function formatSiteThai(internal, grammar) {
619
- var _a, _b;
620
- const text = (_a = internal.siteText) === null || _a === void 0 ? void 0 : _a.trim();
621
- if (!text) {
994
+ function formatSiteThai(clause, grammar) {
995
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
996
+ const text = ((_b = (_a = clause.site) === null || _a === void 0 ? void 0 : _a.text) === null || _b === void 0 ? void 0 : _b.trim()) || ((_e = (_d = (_c = clause.site) === null || _c === void 0 ? void 0 : _c.coding) === null || _d === void 0 ? void 0 : _d.display) === null || _e === void 0 ? void 0 : _e.trim());
997
+ const lower = text === null || text === void 0 ? void 0 : text.toLowerCase();
998
+ const codingCode = (_g = (_f = clause.site) === null || _f === void 0 ? void 0 : _f.coding) === null || _g === void 0 ? void 0 : _g.code;
999
+ const routeText = (_j = (_h = clause.route) === null || _h === void 0 ? void 0 : _h.text) === null || _j === void 0 ? void 0 : _j.trim().toLowerCase();
1000
+ const isRectalRoute = ((_k = clause.route) === null || _k === void 0 ? void 0 : _k.code) === types_1.RouteCode["Per rectum"] ||
1001
+ routeText === "rectum" ||
1002
+ routeText === "rectal";
1003
+ const isVaginalRoute = ((_l = clause.route) === null || _l === void 0 ? void 0 : _l.code) === types_1.RouteCode["Per vagina"] ||
1004
+ routeText === "vagina" ||
1005
+ routeText === "vaginal";
1006
+ const isRectumSite = codingCode === "34402009" || lower === "rectum" || lower === "rectal";
1007
+ const isVaginaSite = codingCode === "76784001" || lower === "vagina" || lower === "vaginal";
1008
+ if (isRectalRoute &&
1009
+ isRectumSite) {
1010
+ return undefined;
1011
+ }
1012
+ if (isVaginalRoute &&
1013
+ isVaginaSite) {
1014
+ return undefined;
1015
+ }
1016
+ const translated = translateSiteThai(text, codingCode);
1017
+ if (!translated) {
622
1018
  return undefined;
623
1019
  }
624
- const translated = translateSiteThai(text);
625
- const preposition = (_b = grammar.sitePreposition) !== null && _b !== void 0 ? _b : "ที่";
1020
+ if (((_m = clause.route) === null || _m === void 0 ? void 0 : _m.code) === types_1.RouteCode["Nasal route"]) {
1021
+ return `เข้า${translated}`;
1022
+ }
1023
+ const preposition = (_o = grammar.sitePreposition) !== null && _o !== void 0 ? _o : "ที่";
626
1024
  const separator = /^[\u0E00-\u0E7F]/.test(translated) ? "" : " ";
627
1025
  return `${preposition}${separator}${translated}`.trim();
628
1026
  }
629
- function translateSiteThai(site) {
1027
+ function translateSiteThai(site, code) {
630
1028
  var _a;
631
- const normalized = site.trim().toLowerCase().replace(/\s+/g, " ");
1029
+ if (code) {
1030
+ const translatedByCode = THAI_SITE_CODE_TRANSLATIONS[code];
1031
+ if (translatedByCode) {
1032
+ return translatedByCode;
1033
+ }
1034
+ }
1035
+ if (!site) {
1036
+ return undefined;
1037
+ }
1038
+ const normalized = (0, maps_1.normalizeBodySiteKey)(site);
632
1039
  if (!normalized) {
633
1040
  return site;
634
1041
  }
635
1042
  return (_a = exports.THAI_SITE_TRANSLATIONS[normalized]) !== null && _a !== void 0 ? _a : site;
636
1043
  }
637
- function describeDayOfWeekThai(internal) {
638
- if (!internal.dayOfWeek.length) {
1044
+ function describeDayOfWeekThai(schedule) {
1045
+ var _a;
1046
+ const dayOfWeek = (_a = schedule === null || schedule === void 0 ? void 0 : schedule.dayOfWeek) !== null && _a !== void 0 ? _a : [];
1047
+ if (!dayOfWeek.length) {
639
1048
  return undefined;
640
1049
  }
641
- const days = internal.dayOfWeek
642
- .map((d) => { var _a; return (_a = DAY_NAMES_THAI[d]) !== null && _a !== void 0 ? _a : undefined; })
643
- .filter((d) => Boolean(d));
644
- if (!days.length) {
1050
+ const days = [];
1051
+ for (const day of dayOfWeek) {
1052
+ const text = DAY_NAMES_THAI[day];
1053
+ if (text) {
1054
+ days.push(text);
1055
+ }
1056
+ }
1057
+ return days.length ? `ใน${joinWithAndThai(days)}` : undefined;
1058
+ }
1059
+ function formatDurationShortThai(schedule) {
1060
+ if (schedule.duration === undefined || !schedule.durationUnit) {
645
1061
  return undefined;
646
1062
  }
647
- return `ใน${joinWithAndThai(days)}`;
1063
+ const base = stripTrailingZero(schedule.duration);
1064
+ const qualifier = schedule.durationMax !== undefined && schedule.durationMax !== schedule.duration
1065
+ ? `${base}-${stripTrailingZero(schedule.durationMax)}`
1066
+ : base;
1067
+ return `x${qualifier}${schedule.durationUnit}`;
648
1068
  }
649
- function formatAsNeededThai(internal) {
650
- var _a, _b;
651
- if (!internal.asNeeded) {
1069
+ function describeDurationThai(schedule) {
1070
+ if (!schedule || schedule.duration === undefined || !schedule.durationUnit) {
1071
+ return undefined;
1072
+ }
1073
+ const unit = schedule.durationUnit;
1074
+ const label = () => {
1075
+ switch (unit) {
1076
+ case types_1.FhirPeriodUnit.Minute:
1077
+ return "นาที";
1078
+ case types_1.FhirPeriodUnit.Hour:
1079
+ return "ชั่วโมง";
1080
+ case types_1.FhirPeriodUnit.Day:
1081
+ return "วัน";
1082
+ case types_1.FhirPeriodUnit.Week:
1083
+ return "สัปดาห์";
1084
+ case types_1.FhirPeriodUnit.Month:
1085
+ return "เดือน";
1086
+ case types_1.FhirPeriodUnit.Year:
1087
+ return "ปี";
1088
+ default:
1089
+ return unit;
1090
+ }
1091
+ };
1092
+ if (schedule.durationMax !== undefined && schedule.durationMax !== schedule.duration) {
1093
+ return `เป็นเวลา ${stripTrailingZero(schedule.duration)} ถึง ${stripTrailingZero(schedule.durationMax)} ${label()}`;
1094
+ }
1095
+ return `เป็นเวลา ${stripTrailingZero(schedule.duration)} ${label()}`;
1096
+ }
1097
+ function formatAsNeededThai(clause) {
1098
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
1099
+ if (!((_a = clause.prn) === null || _a === void 0 ? void 0 : _a.enabled)) {
652
1100
  return undefined;
653
1101
  }
654
- if (internal.asNeededReason) {
655
- const translation = (_b = (_a = internal.asNeededReasonCoding) === null || _a === void 0 ? void 0 : _a.i18n) === null || _b === void 0 ? void 0 : _b.th;
656
- return `ใช้เมื่อจำเป็นสำหรับ ${translation || internal.asNeededReason}`;
1102
+ if ((_b = clause.prn.reasons) === null || _b === void 0 ? void 0 : _b.length) {
1103
+ const translatedReasons = [];
1104
+ for (const reason of clause.prn.reasons) {
1105
+ let text = (_c = reason.text) !== null && _c !== void 0 ? _c : (_d = reason.coding) === null || _d === void 0 ? void 0 : _d.display;
1106
+ const coding = reason.coding;
1107
+ if (coding === null || coding === void 0 ? void 0 : coding.code) {
1108
+ const definition = (0, maps_1.findPrnReasonDefinitionByCoding)((_e = coding.system) !== null && _e !== void 0 ? _e : "http://snomed.info/sct", coding.code);
1109
+ text = (_g = (_f = definition === null || definition === void 0 ? void 0 : definition.i18n) === null || _f === void 0 ? void 0 : _f.th) !== null && _g !== void 0 ? _g : text;
1110
+ }
1111
+ translatedReasons.push({ text, coding: reason.coding });
1112
+ }
1113
+ const joined = (0, prn_1.getPreferredCanonicalPrnReasonText)(undefined, translatedReasons, "หรือ");
1114
+ if (joined) {
1115
+ return `ใช้เมื่อจำเป็นสำหรับ ${joined}`;
1116
+ }
1117
+ }
1118
+ let translation;
1119
+ const coding = (_h = clause.prn.reason) === null || _h === void 0 ? void 0 : _h.coding;
1120
+ if (coding === null || coding === void 0 ? void 0 : coding.code) {
1121
+ const definition = (0, maps_1.findPrnReasonDefinitionByCoding)((_j = coding.system) !== null && _j !== void 0 ? _j : "http://snomed.info/sct", coding.code);
1122
+ translation = (_k = definition === null || definition === void 0 ? void 0 : definition.i18n) === null || _k === void 0 ? void 0 : _k.th;
1123
+ }
1124
+ const reason = (_l = translation !== null && translation !== void 0 ? translation : (0, prn_1.getPreferredCanonicalPrnReasonText)(clause.prn.reason, clause.prn.reasons, "หรือ")) !== null && _l !== void 0 ? _l : coding === null || coding === void 0 ? void 0 : coding.display;
1125
+ if (reason) {
1126
+ return `ใช้เมื่อจำเป็นสำหรับ ${reason}`;
657
1127
  }
658
1128
  return "ใช้เมื่อจำเป็น";
659
1129
  }
660
- function formatShortThai(internal) {
661
- var _a;
1130
+ function formatShortThai(clause) {
1131
+ var _a, _b, _c, _d, _e, _f;
1132
+ const schedule = scheduleOf(clause);
662
1133
  const parts = [];
663
- const dose = formatDoseThaiShort(internal);
1134
+ const dose = formatDoseThaiShort(clause.dose);
664
1135
  if (dose) {
665
1136
  parts.push(dose);
666
1137
  }
667
- if (internal.routeCode) {
668
- const short = ROUTE_SHORT[internal.routeCode];
1138
+ if ((_a = clause.route) === null || _a === void 0 ? void 0 : _a.code) {
1139
+ const short = ROUTE_SHORT[clause.route.code];
669
1140
  if (short) {
670
1141
  parts.push(short);
671
1142
  }
672
- else if (internal.routeText) {
673
- parts.push(internal.routeText);
1143
+ else if (clause.route.text) {
1144
+ parts.push(clause.route.text);
674
1145
  }
675
1146
  }
676
- else if (internal.routeText) {
677
- parts.push(internal.routeText);
1147
+ else if ((_b = clause.route) === null || _b === void 0 ? void 0 : _b.text) {
1148
+ parts.push(clause.route.text);
678
1149
  }
679
- const timing = describeFrequencyThai(internal);
1150
+ const timing = describeFrequencyThai(schedule);
680
1151
  if (timing) {
681
1152
  parts.push(timing);
682
1153
  }
683
- else if (internal.timingCode) {
684
- parts.push(internal.timingCode);
1154
+ else if (schedule.timingCode) {
1155
+ parts.push(schedule.timingCode);
685
1156
  }
686
- else if (internal.period && internal.periodUnit) {
687
- const base = stripTrailingZero(internal.period);
688
- const qualifier = internal.periodMax && internal.periodMax !== internal.period
689
- ? `${base}-${stripTrailingZero(internal.periodMax)}`
1157
+ else if (schedule.period && schedule.periodUnit) {
1158
+ const base = stripTrailingZero(schedule.period);
1159
+ const qualifier = schedule.periodMax && schedule.periodMax !== schedule.period
1160
+ ? `${base}-${stripTrailingZero(schedule.periodMax)}`
690
1161
  : base;
691
- parts.push(`Q${qualifier}${internal.periodUnit.toUpperCase()}`);
1162
+ parts.push(`Q${qualifier}${schedule.periodUnit.toUpperCase()}`);
1163
+ }
1164
+ const events = collectWhenPhrasesThai(schedule);
1165
+ if (events.length) {
1166
+ parts.push(events.join(" "));
692
1167
  }
693
- if (internal.when.length) {
694
- const events = collectWhenPhrasesThai(internal);
695
- if (events.length) {
696
- parts.push(events.join(" "));
1168
+ if ((_c = schedule.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) {
1169
+ const times = [];
1170
+ for (const time of schedule.timeOfDay) {
1171
+ times.push(time.slice(0, 5));
697
1172
  }
1173
+ parts.push(times.join(","));
698
1174
  }
699
- if ((_a = internal.timeOfDay) === null || _a === void 0 ? void 0 : _a.length) {
700
- const times = internal.timeOfDay.map((t) => t.slice(0, 5)).join(",");
701
- parts.push(times);
1175
+ if ((_d = schedule.dayOfWeek) === null || _d === void 0 ? void 0 : _d.length) {
1176
+ const days = [];
1177
+ for (const day of schedule.dayOfWeek) {
1178
+ days.push((_f = (_e = DAY_NAMES_THAI[day]) === null || _e === void 0 ? void 0 : _e.replace(/^วัน/, "")) !== null && _f !== void 0 ? _f : day);
1179
+ }
1180
+ parts.push(days.join(","));
702
1181
  }
703
- if (internal.dayOfWeek.length) {
704
- const days = internal.dayOfWeek
705
- .map((d) => { var _a, _b; return (_b = (_a = DAY_NAMES_THAI[d]) === null || _a === void 0 ? void 0 : _a.replace(/^วัน/, "")) !== null && _b !== void 0 ? _b : d; })
706
- .join(",");
707
- parts.push(days);
1182
+ if (schedule.count !== undefined) {
1183
+ parts.push(`x${stripTrailingZero(schedule.count)}`);
708
1184
  }
709
- if (internal.count !== undefined) {
710
- parts.push(`x${stripTrailingZero(internal.count)}`);
1185
+ const durationShort = formatDurationShortThai(schedule);
1186
+ if (durationShort) {
1187
+ parts.push(durationShort);
711
1188
  }
712
- const asNeeded = formatAsNeededThai(internal);
1189
+ const asNeeded = formatAsNeededThai(clause);
713
1190
  if (asNeeded) {
714
1191
  parts.push(asNeeded);
715
1192
  }
716
1193
  return parts.filter(Boolean).join(" ");
717
1194
  }
718
- function formatLongThai(internal) {
719
- var _a, _b;
720
- const grammar = resolveRouteGrammarThai(internal);
721
- const dosePart = (_a = formatDoseThaiLong(internal)) !== null && _a !== void 0 ? _a : "ยา";
722
- const sitePart = formatSiteThai(internal, grammar);
723
- const routePart = buildRoutePhraseThai(internal, grammar, Boolean(sitePart));
724
- const frequencyPart = describeFrequencyThai(internal);
725
- const eventParts = collectWhenPhrasesThai(internal);
726
- if ((_b = internal.timeOfDay) === null || _b === void 0 ? void 0 : _b.length) {
1195
+ function formatLongThai(clause, options) {
1196
+ var _a, _b, _c;
1197
+ const schedule = scheduleOf(clause);
1198
+ const grammar = resolveRouteGrammarThai(clause);
1199
+ const verb = resolveThaiMethodVerb(clause, grammar);
1200
+ const explicitDosePart = formatDoseThaiLong(clause.dose);
1201
+ const sitePart = formatSiteThai(clause, grammar);
1202
+ const dosePart = shouldUseGenericMedicationObjectThai(clause, verb, explicitDosePart)
1203
+ ? explicitDosePart !== null && explicitDosePart !== void 0 ? explicitDosePart : "ยา"
1204
+ : explicitDosePart;
1205
+ const routePart = shouldSuppressRoutePhraseThai(clause, verb, Boolean(sitePart), explicitDosePart)
1206
+ ? undefined
1207
+ : buildRoutePhraseThai(clause, grammar, Boolean(sitePart));
1208
+ const standaloneOccurrenceCount = describeStandaloneOccurrenceCountThai(schedule);
1209
+ const frequencyPart = (_b = (_a = describeFrequencyThai(schedule)) !== null && _a !== void 0 ? _a : standaloneOccurrenceCount) !== null && _b !== void 0 ? _b : describeFrequencyCountThai((0, timing_summary_1.inferDailyOccurrenceCount)(schedule, options));
1210
+ const eventParts = collectWhenPhrasesThai(schedule, options);
1211
+ if ((_c = schedule.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) {
727
1212
  const timeStrings = [];
728
- for (const time of internal.timeOfDay) {
1213
+ for (const time of schedule.timeOfDay) {
729
1214
  const parts = time.split(":");
730
- const h = parseInt(parts[0], 10);
731
- const m = parseInt(parts[1], 10);
732
- const displayM = m < 10 ? `0${m}` : `${m}`;
733
- const displayH = h < 10 ? `0${h}` : `${h}`;
734
- timeStrings.push(`${displayH}:${displayM}`);
1215
+ const hours = Number(parts[0]);
1216
+ const minutes = Number(parts[1]);
1217
+ if (!Number.isFinite(hours) || !Number.isFinite(minutes)) {
1218
+ continue;
1219
+ }
1220
+ const displayMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
1221
+ const displayHours = hours < 10 ? `0${hours}` : `${hours}`;
1222
+ timeStrings.push(`${displayHours}:${displayMinutes}`);
735
1223
  }
736
- if (timeStrings.length > 0) {
1224
+ if (timeStrings.length) {
737
1225
  eventParts.push(`เวลา ${timeStrings.join(", ")}`);
738
1226
  }
739
1227
  }
740
1228
  const timing = combineFrequencyAndEventsThai(frequencyPart, eventParts);
741
- const dayPart = describeDayOfWeekThai(internal);
742
- const countPart = internal.count !== undefined
743
- ? `จำนวน ${stripTrailingZero(internal.count)} ครั้ง`
1229
+ const dayPart = describeDayOfWeekThai(schedule);
1230
+ const countPart = schedule.count !== undefined && !standaloneOccurrenceCount
1231
+ ? `จำนวน ${stripTrailingZero(schedule.count)} ครั้ง`
744
1232
  : undefined;
745
- const asNeeded = formatAsNeededThai(internal);
746
- const segments = [dosePart];
1233
+ const durationPart = describeDurationThai(schedule);
1234
+ const asNeeded = formatAsNeededThai(clause);
1235
+ const segments = [];
1236
+ if (dosePart) {
1237
+ segments.push(dosePart);
1238
+ }
747
1239
  if (routePart) {
748
1240
  segments.push(routePart);
749
1241
  }
1242
+ const siteFirst = Boolean(sitePart) &&
1243
+ THAI_SITE_FIRST_VERBS.has(verb) &&
1244
+ explicitDosePart === undefined &&
1245
+ routePart === undefined;
1246
+ if (siteFirst && sitePart) {
1247
+ segments.push(sitePart);
1248
+ }
750
1249
  if (timing.frequency) {
751
1250
  segments.push(timing.frequency);
752
1251
  }
@@ -759,50 +1258,79 @@ function formatLongThai(internal) {
759
1258
  if (countPart) {
760
1259
  segments.push(countPart);
761
1260
  }
1261
+ if (durationPart) {
1262
+ segments.push(durationPart);
1263
+ }
762
1264
  if (asNeeded) {
763
1265
  segments.push(asNeeded);
764
1266
  }
765
- if (sitePart) {
1267
+ if (!siteFirst && sitePart) {
766
1268
  segments.push(sitePart);
767
1269
  }
768
1270
  const body = segments.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
1271
+ const instructionPhrases = [];
1272
+ const instructionText = formatAdditionalInstructionsThai(clause);
1273
+ if (instructionText) {
1274
+ instructionPhrases.push(instructionText);
1275
+ }
1276
+ const patientInstruction = formatPatientInstructionSentence(clause.patientInstruction);
1277
+ if (patientInstruction) {
1278
+ instructionPhrases.push(patientInstruction);
1279
+ }
1280
+ const trailingInstructionText = instructionPhrases.join(" ").trim() || undefined;
1281
+ const baseSentence = `${joinThaiVerbAndBody(verb, body)}.`;
769
1282
  if (!body) {
770
- const instructionText = formatAdditionalInstructionsThai(internal);
771
- if (!instructionText) {
772
- return `${grammar.verb}.`;
773
- }
774
- return `${grammar.verb}. ${instructionText}`.trim();
1283
+ return trailingInstructionText ? `${baseSentence} ${trailingInstructionText}` : baseSentence;
775
1284
  }
776
- const instructionText = formatAdditionalInstructionsThai(internal);
777
- const baseSentence = `${grammar.verb} ${body}.`;
778
- return instructionText ? `${baseSentence} ${instructionText}` : baseSentence;
1285
+ return trailingInstructionText ? `${baseSentence} ${trailingInstructionText}` : baseSentence;
779
1286
  }
780
- function formatAdditionalInstructionsThai(internal) {
781
- var _a;
782
- if (!((_a = internal.additionalInstructions) === null || _a === void 0 ? void 0 : _a.length)) {
1287
+ function formatAdditionalInstructionsThai(clause) {
1288
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1289
+ const instructions = (_a = clause.additionalInstructions) !== null && _a !== void 0 ? _a : [];
1290
+ if (!instructions.length) {
783
1291
  return undefined;
784
1292
  }
785
- const phrases = internal.additionalInstructions
786
- .map((instruction) => {
787
- var _a, _b, _c;
788
- const translation = (_b = (_a = instruction.coding) === null || _a === void 0 ? void 0 : _a.i18n) === null || _b === void 0 ? void 0 : _b.th;
789
- if (translation)
790
- return translation;
791
- const original = instruction.text || ((_c = instruction.coding) === null || _c === void 0 ? void 0 : _c.display);
792
- if (!original)
793
- return undefined;
794
- return original.trim();
795
- })
796
- .filter((text) => Boolean(text))
797
- .map((text) => text.trim())
798
- .filter((text) => text.length > 0);
1293
+ const phrases = [];
1294
+ const grammar = resolveRouteGrammarThai(clause);
1295
+ const verb = resolveThaiMethodVerb(clause, grammar);
1296
+ for (const instruction of instructions) {
1297
+ if (((_b = instruction.coding) === null || _b === void 0 ? void 0 : _b.code) === SLOWLY_QUALIFIER_CODE) {
1298
+ const contextual = verb ? `${verb}ช้าๆ` : "ช้าๆ";
1299
+ phrases.push(contextual);
1300
+ continue;
1301
+ }
1302
+ if (((_c = instruction.coding) === null || _c === void 0 ? void 0 : _c.code) === EMPTY_STOMACH_QUALIFIER_CODE ||
1303
+ ((_d = instruction.frames) === null || _d === void 0 ? void 0 : _d.some((frame) => frame.relation === types_1.AdviceRelation.On &&
1304
+ frame.args.some((arg) => arg.role === types_1.AdviceArgumentRole.MealState &&
1305
+ arg.conceptId === "empty_stomach")))) {
1306
+ phrases.push("ขณะท้องว่าง");
1307
+ continue;
1308
+ }
1309
+ let text = (_e = instruction.text) !== null && _e !== void 0 ? _e : (_f = instruction.coding) === null || _f === void 0 ? void 0 : _f.display;
1310
+ if ((_g = instruction.coding) === null || _g === void 0 ? void 0 : _g.code) {
1311
+ const definition = (0, advice_1.findAdditionalInstructionDefinitionByCoding)((_h = instruction.coding.system) !== null && _h !== void 0 ? _h : "http://snomed.info/sct", instruction.coding.code);
1312
+ text = (_k = (_j = definition === null || definition === void 0 ? void 0 : definition.i18n) === null || _j === void 0 ? void 0 : _j.th) !== null && _k !== void 0 ? _k : text;
1313
+ }
1314
+ if (!text) {
1315
+ continue;
1316
+ }
1317
+ const trimmed = text.trim();
1318
+ if (trimmed) {
1319
+ phrases.push(trimmed);
1320
+ }
1321
+ }
799
1322
  if (!phrases.length) {
800
1323
  return undefined;
801
1324
  }
802
- return phrases
803
- .map((phrase) => (/[.!?]$/.test(phrase) ? phrase : `${phrase}.`))
804
- .join(" ")
805
- .trim();
1325
+ return phrases.map((phrase) => (/[.!?]$/.test(phrase) ? phrase : `${phrase}.`)).join(" ").trim();
1326
+ }
1327
+ function formatPatientInstructionSentence(text) {
1328
+ const trimmed = text === null || text === void 0 ? void 0 : text.trim();
1329
+ if (!trimmed) {
1330
+ return undefined;
1331
+ }
1332
+ const sentence = /^[.!?]$/.test(trimmed.slice(-1)) ? trimmed : `${trimmed}.`;
1333
+ return sentence.charAt(0).toUpperCase() + sentence.slice(1);
806
1334
  }
807
1335
  function stripTrailingZero(value) {
808
1336
  const text = value.toString();