ezmedicationinput 0.1.43 → 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 +4 -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 +4 -2
  13. package/dist/format.js +515 -218
  14. package/dist/i18n.d.ts +2 -2
  15. package/dist/i18n.js +641 -199
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +219 -168
  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 +13 -3
  41. package/dist/timing-summary.js +7 -7
  42. package/dist/types.d.ts +237 -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,6 +5,10 @@ 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");
9
13
  const timing_summary_1 = require("./timing-summary");
10
14
  const REGISTERED_LOCALIZATIONS = new Map();
@@ -65,8 +69,8 @@ function resolveSigTranslation(locale, config) {
65
69
  function createThaiLocalization() {
66
70
  return {
67
71
  locale: "th",
68
- formatShort: ({ internal }) => formatShortThai(internal),
69
- formatLong: ({ internal, groupMealTimingsByRelation, includeTimesPerDaySummary }) => formatLongThai(internal, {
72
+ formatShort: ({ clause }) => formatShortThai(clause),
73
+ formatLong: ({ clause, groupMealTimingsByRelation, includeTimesPerDaySummary }) => formatLongThai(clause, {
70
74
  groupMealTimingsByRelation,
71
75
  includeTimesPerDaySummary
72
76
  })
@@ -135,6 +139,8 @@ const TH_TIMES_PER_DAY = {
135
139
  3: "วันละ 3 ครั้ง",
136
140
  4: "วันละ 4 ครั้ง"
137
141
  };
142
+ const SLOWLY_QUALIFIER_CODE = "419443000";
143
+ const EMPTY_STOMACH_QUALIFIER_CODE = "717154004";
138
144
  exports.THAI_SITE_TRANSLATIONS = {
139
145
  eye: "ตา",
140
146
  eyes: "ตา",
@@ -169,6 +175,13 @@ exports.THAI_SITE_TRANSLATIONS = {
169
175
  "left upper arm": "ต้นแขนซ้าย",
170
176
  "right upper arm": "ต้นแขนขวา",
171
177
  "bilateral arms": "แขนทั้งสองข้าง",
178
+ shoulder: "ไหล่",
179
+ shoulders: "ไหล่ทั้งสองข้าง",
180
+ "left shoulder": "ไหล่ซ้าย",
181
+ "right shoulder": "ไหล่ขวา",
182
+ elbow: "ข้อศอก",
183
+ "left elbow": "ข้อศอกซ้าย",
184
+ "right elbow": "ข้อศอกขวา",
172
185
  "right leg": "ขาขวา",
173
186
  "left leg": "ขาซ้าย",
174
187
  "both legs": "ขาทั้งสองข้าง",
@@ -177,6 +190,22 @@ exports.THAI_SITE_TRANSLATIONS = {
177
190
  "left lower leg": "ขาส่วนล่างซ้าย",
178
191
  "right lower leg": "ขาส่วนล่างขวา",
179
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": "สะโพกขวา",
180
209
  "right hand": "มือขวา",
181
210
  "left hand": "มือซ้าย",
182
211
  "both hands": "มือทั้งสองข้าง",
@@ -190,19 +219,53 @@ exports.THAI_SITE_TRANSLATIONS = {
190
219
  abdomen: "ช่องท้อง",
191
220
  abdominal: "ช่องท้อง",
192
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": "ศีรษะขวา",
193
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: "ขาหนีบ",
194
243
  scalp: "หนังศีรษะ",
195
244
  face: "ใบหน้า",
196
245
  cheek: "แก้ม",
197
246
  cheeks: "แก้มทั้งสองข้าง",
198
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": "ขมับทั้งสองข้าง",
199
260
  chin: "คาง",
200
261
  neck: "คอ",
262
+ eyelid: "เปลือกตา",
263
+ eyelids: "เปลือกตา",
264
+ lip: "ริมฝีปาก",
265
+ lips: "ริมฝีปาก",
201
266
  forearm: "ปลายแขน",
202
267
  "left forearm": "ปลายแขนซ้าย",
203
268
  "right forearm": "ปลายแขนขวา",
204
- shoulder: "ไหล่",
205
- shoulders: "ไหล่ทั้งสองข้าง",
206
269
  thigh: "ต้นขา",
207
270
  thighs: "ต้นขาทั้งสองข้าง",
208
271
  "left thigh": "ต้นขาซ้าย",
@@ -234,6 +297,28 @@ exports.THAI_SITE_TRANSLATIONS = {
234
297
  skin: "ผิวหนัง",
235
298
  hair: "เส้นผม"
236
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
+ })();
237
322
  const DEFAULT_THAI_ROUTE_GRAMMAR = { verb: "ใช้" };
238
323
  const THAI_ROUTE_GRAMMAR = {
239
324
  [types_1.RouteCode["Oral route"]]: { verb: "รับประทาน", routePhrase: "ทางปาก" },
@@ -299,26 +384,168 @@ const THAI_ROUTE_GRAMMAR = {
299
384
  sitePreposition: "ที่"
300
385
  }
301
386
  };
302
- function resolveRouteGrammarThai(internal) {
303
- var _a, _b, _c;
304
- if (internal.routeCode && THAI_ROUTE_GRAMMAR[internal.routeCode]) {
305
- 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;
489
+ }
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;
306
531
  }
307
- const grammar = grammarFromRouteTextThai(internal.routeText);
532
+ const grammar = grammarFromRouteTextThai((_c = clause.route) === null || _c === void 0 ? void 0 : _c.text);
308
533
  if (grammar) {
309
534
  return grammar;
310
535
  }
311
- if (((_b = internal.unit) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase()) === "puff") {
312
- 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);
313
538
  }
314
539
  return DEFAULT_THAI_ROUTE_GRAMMAR;
315
540
  }
316
541
  function grammarFromRouteTextThai(text) {
317
- if (!text)
542
+ if (!text) {
318
543
  return undefined;
544
+ }
319
545
  const normalized = text.trim().toLowerCase();
320
- if (!normalized)
546
+ if (!normalized) {
321
547
  return undefined;
548
+ }
322
549
  if (normalized.includes("mouth") || normalized.includes("oral")) {
323
550
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Oral route"]];
324
551
  }
@@ -343,6 +570,12 @@ function grammarFromRouteTextThai(text) {
343
570
  if (normalized.includes("intravenous") || normalized === "iv") {
344
571
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Intravenous route"]];
345
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
+ }
346
579
  if (normalized.includes("nasal")) {
347
580
  return THAI_ROUTE_GRAMMAR[types_1.RouteCode["Nasal route"]];
348
581
  }
@@ -351,45 +584,75 @@ function grammarFromRouteTextThai(text) {
351
584
  }
352
585
  return undefined;
353
586
  }
354
- function formatDoseThaiShort(internal) {
355
- if (internal.doseRange) {
356
- const { low, high } = internal.doseRange;
357
- const base = `${stripTrailingZero(low)}-${stripTrailingZero(high)}`;
358
- if (internal.unit) {
359
- 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;
360
612
  }
361
- return base;
362
613
  }
363
- if (internal.dose !== undefined) {
364
- const amount = stripTrailingZero(internal.dose);
365
- if (internal.unit) {
366
- 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")}`;
367
617
  }
368
- return amount;
618
+ return stripTrailingZero(dose.value);
369
619
  }
370
620
  return undefined;
371
621
  }
372
- function formatDoseThaiLong(internal) {
373
- if (internal.doseRange) {
374
- const { low, high } = internal.doseRange;
375
- if (internal.unit) {
376
- const unit = formatUnitThai(internal.unit, high, "long");
377
- 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)}`;
378
644
  }
379
- return `ครั้งละ ${stripTrailingZero(low)} ถึง ${stripTrailingZero(high)}`;
380
645
  }
381
- if (internal.dose !== undefined) {
382
- if (internal.unit) {
383
- const unit = formatUnitThai(internal.unit, internal.dose, "long");
384
- 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")}`;
385
649
  }
386
- return `ครั้งละ ${stripTrailingZero(internal.dose)}`;
650
+ return `ครั้งละ ${stripTrailingZero(dose.value)}`;
387
651
  }
388
652
  return undefined;
389
653
  }
390
- function formatUnitThai(unit, value, style) {
654
+ function formatUnitThai(unit, _value, style) {
391
655
  const lower = unit.toLowerCase();
392
- const quantity = Math.abs(value);
393
656
  const mapping = {
394
657
  tab: { short: "เม็ด", long: "เม็ด" },
395
658
  tablet: { short: "เม็ด", long: "เม็ด" },
@@ -413,13 +676,16 @@ function formatUnitThai(unit, value, style) {
413
676
  suppositories: { short: "ยาเหน็บ", long: "ยาเหน็บ" }
414
677
  };
415
678
  const entry = mapping[lower];
416
- if (entry) {
417
- return style === "short" ? entry.short : entry.long;
418
- }
419
- return unit;
679
+ return entry ? entry[style] : unit;
420
680
  }
421
- function describeFrequencyThai(internal) {
422
- 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;
423
689
  if (frequency !== undefined &&
424
690
  frequencyMax !== undefined &&
425
691
  periodUnit === types_1.FhirPeriodUnit.Day &&
@@ -433,11 +699,13 @@ function describeFrequencyThai(internal) {
433
699
  return `วันละ ${stripTrailingZero(frequency)} ถึง ${stripTrailingZero(frequencyMax)} ครั้ง`;
434
700
  }
435
701
  if (frequency && periodUnit === types_1.FhirPeriodUnit.Day && (!period || period === 1)) {
436
- const dailyText = TH_TIMES_PER_DAY[frequency];
437
- if (dailyText) {
438
- return dailyText;
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)} นาที`;
439
707
  }
440
- return `วันละ ${stripTrailingZero(frequency)} ครั้ง`;
708
+ return `ทุก ${stripTrailingZero(period)} นาที`;
441
709
  }
442
710
  if (periodUnit === types_1.FhirPeriodUnit.Hour && period) {
443
711
  if (periodMax && periodMax !== period) {
@@ -472,6 +740,15 @@ function describeFrequencyThai(internal) {
472
740
  }
473
741
  return `ทุก ${stripTrailingZero(period)} เดือน`;
474
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
+ }
475
752
  if (timingCode) {
476
753
  const map = {
477
754
  BID: "วันละ 2 ครั้ง",
@@ -490,24 +767,44 @@ function describeFrequencyThai(internal) {
490
767
  }
491
768
  }
492
769
  if (frequency && periodUnit === undefined && period === undefined) {
493
- if (frequency === 1)
770
+ if (frequency === 1) {
494
771
  return "ครั้งเดียว";
772
+ }
495
773
  return `${stripTrailingZero(frequency)} ครั้ง`;
496
774
  }
497
775
  return undefined;
498
776
  }
499
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;
500
787
  if (!count || count <= 0) {
501
788
  return undefined;
502
789
  }
503
- const dailyText = TH_TIMES_PER_DAY[count];
504
- if (dailyText) {
505
- return dailyText;
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;
506
803
  }
507
- return `วันละ ${stripTrailingZero(count)} ครั้ง`;
804
+ return `${stripTrailingZero(count)} ครั้ง`;
508
805
  }
509
806
  function joinMealNamesThai(parts) {
510
- if (parts.length === 0) {
807
+ if (!parts.length) {
511
808
  return "";
512
809
  }
513
810
  if (parts.length === 1) {
@@ -534,13 +831,15 @@ function summarizeMealTimingGroupThai(group) {
534
831
  dinner: "เย็น"
535
832
  };
536
833
  const meals = [];
537
- for (let i = 0; i < group.meals.length; i += 1) {
538
- meals.push(mealText[group.meals[i]]);
834
+ for (const meal of group.meals) {
835
+ meals.push(mealText[meal]);
539
836
  }
540
837
  return `${relationText[group.relation]}${joinMealNamesThai(meals)}`;
541
838
  }
542
- function collectWhenPhrasesThai(internal, options) {
543
- if (!internal.when.length) {
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) {
544
843
  return [];
545
844
  }
546
845
  const unique = [];
@@ -548,7 +847,7 @@ function collectWhenPhrasesThai(internal, options) {
548
847
  let hasSpecificAfter = false;
549
848
  let hasSpecificBefore = false;
550
849
  let hasSpecificWith = false;
551
- for (const code of internal.when) {
850
+ for (const code of when) {
552
851
  if (!seen.has(code)) {
553
852
  seen.add(code);
554
853
  unique.push(code);
@@ -562,16 +861,13 @@ function collectWhenPhrasesThai(internal, options) {
562
861
  code === types_1.EventTiming["Before Dinner"]) {
563
862
  hasSpecificBefore = true;
564
863
  }
565
- if (code === types_1.EventTiming.Breakfast ||
566
- code === types_1.EventTiming.Lunch ||
567
- code === types_1.EventTiming.Dinner) {
864
+ if (code === types_1.EventTiming.Breakfast || code === types_1.EventTiming.Lunch || code === types_1.EventTiming.Dinner) {
568
865
  hasSpecificWith = true;
569
866
  }
570
867
  }
571
868
  }
572
869
  const filtered = [];
573
- for (let i = 0; i < unique.length; i += 1) {
574
- const code = unique[i];
870
+ for (const code of unique) {
575
871
  if (code === types_1.EventTiming["After Meal"] && hasSpecificAfter) {
576
872
  continue;
577
873
  }
@@ -584,19 +880,9 @@ function collectWhenPhrasesThai(internal, options) {
584
880
  filtered.push(code);
585
881
  }
586
882
  const mealGroup = (0, timing_summary_1.getMealTimingGroup)(filtered, options);
587
- if (mealGroup) {
588
- const groupedCodes = new Set(mealGroup.codes);
883
+ if (!mealGroup) {
589
884
  const phrases = [];
590
- let insertedGroup = false;
591
- for (let i = 0; i < filtered.length; i += 1) {
592
- const code = filtered[i];
593
- if (groupedCodes.has(code)) {
594
- if (!insertedGroup) {
595
- phrases.push(summarizeMealTimingGroupThai(mealGroup));
596
- insertedGroup = true;
597
- }
598
- continue;
599
- }
885
+ for (const code of filtered) {
600
886
  const text = WHEN_TEXT_THAI[code];
601
887
  if (text) {
602
888
  phrases.push(text);
@@ -604,9 +890,18 @@ function collectWhenPhrasesThai(internal, options) {
604
890
  }
605
891
  return phrases;
606
892
  }
893
+ const groupedCodes = new Set(mealGroup.codes);
607
894
  const phrases = [];
608
- for (let i = 0; i < filtered.length; i += 1) {
609
- const text = WHEN_TEXT_THAI[filtered[i]];
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];
610
905
  if (text) {
611
906
  phrases.push(text);
612
907
  }
@@ -635,39 +930,34 @@ function combineFrequencyAndEventsThai(frequency, events) {
635
930
  if (!events.length) {
636
931
  return { frequency };
637
932
  }
638
- if (events.length === 1 && events[0] === "ก่อนนอน") {
639
- if (frequency.includes("วันละ")) {
640
- return { frequency: `${frequency} และ ${events[0]}` };
641
- }
933
+ if (events.length === 1 && events[0] === "ก่อนนอน" && frequency.includes("วันละ")) {
934
+ return { frequency: `${frequency} และ ${events[0]}` };
642
935
  }
643
936
  return { frequency, event: joinWithAndThai(events) };
644
937
  }
645
- function isOralRouteThai(internal) {
646
- var _a;
647
- 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"]) {
648
941
  return true;
649
942
  }
650
- 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();
651
944
  if (!text) {
652
945
  return false;
653
946
  }
654
- return (text === "po" ||
655
- text === "oral" ||
656
- text.includes("mouth") ||
657
- text.includes("per os"));
947
+ return text === "po" || text === "oral" || text.includes("mouth") || text.includes("per os");
658
948
  }
659
- function buildRoutePhraseThai(internal, grammar, hasSite) {
660
- var _a;
661
- if (grammar.verb === "รับประทาน" && isOralRouteThai(internal)) {
949
+ function buildRoutePhraseThai(clause, grammar, hasSite) {
950
+ var _a, _b;
951
+ if (grammar.verb === "รับประทาน" && isOralRouteThai(clause)) {
662
952
  return undefined;
663
953
  }
664
954
  if (typeof grammar.routePhrase === "function") {
665
- return grammar.routePhrase({ hasSite, internal });
955
+ return grammar.routePhrase({ hasSite, clause });
666
956
  }
667
957
  if (typeof grammar.routePhrase === "string") {
668
958
  return grammar.routePhrase;
669
959
  }
670
- 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();
671
961
  if (!text) {
672
962
  return undefined;
673
963
  }
@@ -701,138 +991,261 @@ function buildRoutePhraseThai(internal, grammar, hasSite) {
701
991
  }
702
992
  return text;
703
993
  }
704
- function formatSiteThai(internal, grammar) {
705
- var _a, _b;
706
- const text = (_a = internal.siteText) === null || _a === void 0 ? void 0 : _a.trim();
707
- 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) {
708
1010
  return undefined;
709
1011
  }
710
- const translated = translateSiteThai(text);
711
- const preposition = (_b = grammar.sitePreposition) !== null && _b !== void 0 ? _b : "ที่";
1012
+ if (isVaginalRoute &&
1013
+ isVaginaSite) {
1014
+ return undefined;
1015
+ }
1016
+ const translated = translateSiteThai(text, codingCode);
1017
+ if (!translated) {
1018
+ return undefined;
1019
+ }
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 : "ที่";
712
1024
  const separator = /^[\u0E00-\u0E7F]/.test(translated) ? "" : " ";
713
1025
  return `${preposition}${separator}${translated}`.trim();
714
1026
  }
715
- function translateSiteThai(site) {
1027
+ function translateSiteThai(site, code) {
716
1028
  var _a;
717
- 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);
718
1039
  if (!normalized) {
719
1040
  return site;
720
1041
  }
721
1042
  return (_a = exports.THAI_SITE_TRANSLATIONS[normalized]) !== null && _a !== void 0 ? _a : site;
722
1043
  }
723
- function describeDayOfWeekThai(internal) {
724
- 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) {
725
1048
  return undefined;
726
1049
  }
727
- const days = internal.dayOfWeek
728
- .map((d) => { var _a; return (_a = DAY_NAMES_THAI[d]) !== null && _a !== void 0 ? _a : undefined; })
729
- .filter((d) => Boolean(d));
730
- 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) {
731
1061
  return undefined;
732
1062
  }
733
- 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}`;
734
1068
  }
735
- function formatAsNeededThai(internal) {
736
- var _a, _b;
737
- 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)) {
738
1100
  return undefined;
739
1101
  }
740
- if (internal.asNeededReason) {
741
- const translation = (_b = (_a = internal.asNeededReasonCoding) === null || _a === void 0 ? void 0 : _a.i18n) === null || _b === void 0 ? void 0 : _b.th;
742
- 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}`;
743
1127
  }
744
1128
  return "ใช้เมื่อจำเป็น";
745
1129
  }
746
- function formatShortThai(internal) {
747
- var _a;
1130
+ function formatShortThai(clause) {
1131
+ var _a, _b, _c, _d, _e, _f;
1132
+ const schedule = scheduleOf(clause);
748
1133
  const parts = [];
749
- const dose = formatDoseThaiShort(internal);
1134
+ const dose = formatDoseThaiShort(clause.dose);
750
1135
  if (dose) {
751
1136
  parts.push(dose);
752
1137
  }
753
- if (internal.routeCode) {
754
- 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];
755
1140
  if (short) {
756
1141
  parts.push(short);
757
1142
  }
758
- else if (internal.routeText) {
759
- parts.push(internal.routeText);
1143
+ else if (clause.route.text) {
1144
+ parts.push(clause.route.text);
760
1145
  }
761
1146
  }
762
- else if (internal.routeText) {
763
- parts.push(internal.routeText);
1147
+ else if ((_b = clause.route) === null || _b === void 0 ? void 0 : _b.text) {
1148
+ parts.push(clause.route.text);
764
1149
  }
765
- const timing = describeFrequencyThai(internal);
1150
+ const timing = describeFrequencyThai(schedule);
766
1151
  if (timing) {
767
1152
  parts.push(timing);
768
1153
  }
769
- else if (internal.timingCode) {
770
- parts.push(internal.timingCode);
1154
+ else if (schedule.timingCode) {
1155
+ parts.push(schedule.timingCode);
771
1156
  }
772
- else if (internal.period && internal.periodUnit) {
773
- const base = stripTrailingZero(internal.period);
774
- const qualifier = internal.periodMax && internal.periodMax !== internal.period
775
- ? `${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)}`
776
1161
  : base;
777
- 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(" "));
778
1167
  }
779
- if (internal.when.length) {
780
- const events = collectWhenPhrasesThai(internal);
781
- if (events.length) {
782
- 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));
783
1172
  }
1173
+ parts.push(times.join(","));
784
1174
  }
785
- if ((_a = internal.timeOfDay) === null || _a === void 0 ? void 0 : _a.length) {
786
- const times = internal.timeOfDay.map((t) => t.slice(0, 5)).join(",");
787
- 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(","));
788
1181
  }
789
- if (internal.dayOfWeek.length) {
790
- const days = internal.dayOfWeek
791
- .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; })
792
- .join(",");
793
- parts.push(days);
1182
+ if (schedule.count !== undefined) {
1183
+ parts.push(`x${stripTrailingZero(schedule.count)}`);
794
1184
  }
795
- if (internal.count !== undefined) {
796
- parts.push(`x${stripTrailingZero(internal.count)}`);
1185
+ const durationShort = formatDurationShortThai(schedule);
1186
+ if (durationShort) {
1187
+ parts.push(durationShort);
797
1188
  }
798
- const asNeeded = formatAsNeededThai(internal);
1189
+ const asNeeded = formatAsNeededThai(clause);
799
1190
  if (asNeeded) {
800
1191
  parts.push(asNeeded);
801
1192
  }
802
1193
  return parts.filter(Boolean).join(" ");
803
1194
  }
804
- function formatLongThai(internal, options) {
1195
+ function formatLongThai(clause, options) {
805
1196
  var _a, _b, _c;
806
- const grammar = resolveRouteGrammarThai(internal);
807
- const dosePart = (_a = formatDoseThaiLong(internal)) !== null && _a !== void 0 ? _a : "ยา";
808
- const sitePart = formatSiteThai(internal, grammar);
809
- const routePart = buildRoutePhraseThai(internal, grammar, Boolean(sitePart));
810
- const frequencyPart = (_b = describeFrequencyThai(internal)) !== null && _b !== void 0 ? _b : describeFrequencyCountThai((0, timing_summary_1.inferDailyOccurrenceCount)(internal, options));
811
- const eventParts = collectWhenPhrasesThai(internal, options);
812
- if ((_c = internal.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) {
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) {
813
1212
  const timeStrings = [];
814
- for (const time of internal.timeOfDay) {
1213
+ for (const time of schedule.timeOfDay) {
815
1214
  const parts = time.split(":");
816
- const h = parseInt(parts[0], 10);
817
- const m = parseInt(parts[1], 10);
818
- const displayM = m < 10 ? `0${m}` : `${m}`;
819
- const displayH = h < 10 ? `0${h}` : `${h}`;
820
- 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}`);
821
1223
  }
822
- if (timeStrings.length > 0) {
1224
+ if (timeStrings.length) {
823
1225
  eventParts.push(`เวลา ${timeStrings.join(", ")}`);
824
1226
  }
825
1227
  }
826
1228
  const timing = combineFrequencyAndEventsThai(frequencyPart, eventParts);
827
- const dayPart = describeDayOfWeekThai(internal);
828
- const countPart = internal.count !== undefined
829
- ? `จำนวน ${stripTrailingZero(internal.count)} ครั้ง`
1229
+ const dayPart = describeDayOfWeekThai(schedule);
1230
+ const countPart = schedule.count !== undefined && !standaloneOccurrenceCount
1231
+ ? `จำนวน ${stripTrailingZero(schedule.count)} ครั้ง`
830
1232
  : undefined;
831
- const asNeeded = formatAsNeededThai(internal);
832
- const segments = [dosePart];
1233
+ const durationPart = describeDurationThai(schedule);
1234
+ const asNeeded = formatAsNeededThai(clause);
1235
+ const segments = [];
1236
+ if (dosePart) {
1237
+ segments.push(dosePart);
1238
+ }
833
1239
  if (routePart) {
834
1240
  segments.push(routePart);
835
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
+ }
836
1249
  if (timing.frequency) {
837
1250
  segments.push(timing.frequency);
838
1251
  }
@@ -845,50 +1258,79 @@ function formatLongThai(internal, options) {
845
1258
  if (countPart) {
846
1259
  segments.push(countPart);
847
1260
  }
1261
+ if (durationPart) {
1262
+ segments.push(durationPart);
1263
+ }
848
1264
  if (asNeeded) {
849
1265
  segments.push(asNeeded);
850
1266
  }
851
- if (sitePart) {
1267
+ if (!siteFirst && sitePart) {
852
1268
  segments.push(sitePart);
853
1269
  }
854
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)}.`;
855
1282
  if (!body) {
856
- const instructionText = formatAdditionalInstructionsThai(internal);
857
- if (!instructionText) {
858
- return `${grammar.verb}.`;
859
- }
860
- return `${grammar.verb}. ${instructionText}`.trim();
1283
+ return trailingInstructionText ? `${baseSentence} ${trailingInstructionText}` : baseSentence;
861
1284
  }
862
- const instructionText = formatAdditionalInstructionsThai(internal);
863
- const baseSentence = `${grammar.verb} ${body}.`;
864
- return instructionText ? `${baseSentence} ${instructionText}` : baseSentence;
1285
+ return trailingInstructionText ? `${baseSentence} ${trailingInstructionText}` : baseSentence;
865
1286
  }
866
- function formatAdditionalInstructionsThai(internal) {
867
- var _a;
868
- 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) {
869
1291
  return undefined;
870
1292
  }
871
- const phrases = internal.additionalInstructions
872
- .map((instruction) => {
873
- var _a, _b, _c;
874
- const translation = (_b = (_a = instruction.coding) === null || _a === void 0 ? void 0 : _a.i18n) === null || _b === void 0 ? void 0 : _b.th;
875
- if (translation)
876
- return translation;
877
- const original = instruction.text || ((_c = instruction.coding) === null || _c === void 0 ? void 0 : _c.display);
878
- if (!original)
879
- return undefined;
880
- return original.trim();
881
- })
882
- .filter((text) => Boolean(text))
883
- .map((text) => text.trim())
884
- .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
+ }
885
1322
  if (!phrases.length) {
886
1323
  return undefined;
887
1324
  }
888
- return phrases
889
- .map((phrase) => (/[.!?]$/.test(phrase) ? phrase : `${phrase}.`))
890
- .join(" ")
891
- .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);
892
1334
  }
893
1335
  function stripTrailingZero(value) {
894
1336
  const text = value.toString();