ezmedicationinput 0.1.41 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/context.d.ts +2 -1
- package/dist/context.js +16 -0
- package/dist/format.d.ts +2 -1
- package/dist/format.js +102 -37
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +128 -37
- package/dist/index.js +13 -12
- package/dist/parser.js +8 -1
- package/dist/timing-summary.d.ts +15 -0
- package/dist/timing-summary.js +138 -0
- package/dist/types.d.ts +11 -0
- package/dist/utils/enum.d.ts +2 -0
- package/dist/utils/enum.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,6 +139,8 @@ Use either helper depending on your source:
|
|
|
139
139
|
|
|
140
140
|
- `formatParseBatch(batch, style?, separator?)` when you already have `parseSig` output.
|
|
141
141
|
- `formatSigBatch(dosages, style?, { separator })` when you have an array of FHIR `Dosage` entries.
|
|
142
|
+
- `formatSig(dosage, style?, options?)` / `fromFhirDosage(dosage, options?)` when you want
|
|
143
|
+
locale-aware long-text rendering controls for a single dosage.
|
|
142
144
|
|
|
143
145
|
```ts
|
|
144
146
|
import { formatParseBatch, formatSigBatch, parseSig } from "ezmedicationinput";
|
|
@@ -152,6 +154,31 @@ const shortFromFhir = formatSigBatch(batch.items.map((item) => item.fhir), "shor
|
|
|
152
154
|
// => same combined short sig text
|
|
153
155
|
```
|
|
154
156
|
|
|
157
|
+
Formatting options:
|
|
158
|
+
|
|
159
|
+
- `locale`: selects the registered localization, such as `"en"` or `"th"`.
|
|
160
|
+
- `i18n`: overrides or augments the registered localization callbacks.
|
|
161
|
+
- `groupMealTimingsByRelation`: compacts repeated meal relation phrases when all
|
|
162
|
+
meal anchors share the same relation.
|
|
163
|
+
Example EN: `after breakfast, lunch and dinner`
|
|
164
|
+
Example TH: `หลังอาหารเช้า กลางวัน และเย็น`
|
|
165
|
+
- `includeTimesPerDaySummary`: prepends a daily count when the formatter can
|
|
166
|
+
safely infer one from explicit daily anchors and no cadence already exists.
|
|
167
|
+
Example EN: `three times daily after breakfast, lunch and dinner`
|
|
168
|
+
Example TH: `วันละ 3 ครั้ง หลังอาหารเช้า กลางวัน และเย็น`
|
|
169
|
+
|
|
170
|
+
Notes:
|
|
171
|
+
|
|
172
|
+
- `groupMealTimingsByRelation` only applies to homogeneous specific meal anchors
|
|
173
|
+
(`before breakfast/lunch/dinner`, `after breakfast/lunch/dinner`, or `with breakfast/lunch/dinner`).
|
|
174
|
+
When additional non-meal daily anchors exist, the formatter groups the meal
|
|
175
|
+
subset and leaves the extra anchors explicit.
|
|
176
|
+
Example EN: `before breakfast, lunch and dinner and at bedtime`
|
|
177
|
+
Example TH: `ก่อนอาหารเช้า กลางวัน และเย็น และก่อนนอน`
|
|
178
|
+
- `includeTimesPerDaySummary` is independent from meal grouping. It counts
|
|
179
|
+
explicit daily anchors only when no `frequency`, `timingCode`, interval, or
|
|
180
|
+
day-of-week cadence is already present.
|
|
181
|
+
|
|
155
182
|
### Sig (directions) suggestions
|
|
156
183
|
|
|
157
184
|
Use `suggestSig` to drive autocomplete experiences while the clinician is
|
package/dist/context.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { MedicationContext } from "./types";
|
|
1
|
+
import { MedicationContext, RouteCode } from "./types";
|
|
2
2
|
export declare function normalizeDosageForm(form: string | undefined): string | undefined;
|
|
3
3
|
export declare function inferUnitFromContext(ctx: MedicationContext | undefined): string | undefined;
|
|
4
|
+
export declare function inferRouteFromContext(ctx: MedicationContext | undefined): RouteCode | undefined;
|
package/dist/context.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.normalizeDosageForm = normalizeDosageForm;
|
|
4
4
|
exports.inferUnitFromContext = inferUnitFromContext;
|
|
5
|
+
exports.inferRouteFromContext = inferRouteFromContext;
|
|
5
6
|
const maps_1 = require("./maps");
|
|
6
7
|
function normalizeDosageForm(form) {
|
|
7
8
|
var _a;
|
|
@@ -32,3 +33,18 @@ function inferUnitFromContext(ctx) {
|
|
|
32
33
|
}
|
|
33
34
|
return undefined;
|
|
34
35
|
}
|
|
36
|
+
function inferRouteFromContext(ctx) {
|
|
37
|
+
var _a;
|
|
38
|
+
if (!(ctx === null || ctx === void 0 ? void 0 : ctx.dosageForm)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const normalized = normalizeDosageForm(ctx.dosageForm);
|
|
42
|
+
if (!normalized) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const snomed = maps_1.KNOWN_TMT_DOSAGE_FORM_TO_SNOMED_ROUTE[normalized];
|
|
46
|
+
if (!snomed) {
|
|
47
|
+
return (_a = maps_1.DEFAULT_ROUTE_SYNONYMS[normalized]) === null || _a === void 0 ? void 0 : _a.code;
|
|
48
|
+
}
|
|
49
|
+
return maps_1.ROUTE_BY_SNOMED[snomed];
|
|
50
|
+
}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ParsedSigInternal } from "./internal-types";
|
|
2
2
|
import type { SigLocalization } from "./i18n";
|
|
3
|
-
|
|
3
|
+
import { type TimingSummaryOptions } from "./timing-summary";
|
|
4
|
+
export declare function formatInternal(internal: ParsedSigInternal, style: "short" | "long", localization?: SigLocalization, options?: TimingSummaryOptions): string;
|
package/dist/format.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatInternal = formatInternal;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
|
+
const timing_summary_1 = require("./timing-summary");
|
|
5
6
|
const ROUTE_SHORT = {
|
|
6
7
|
[types_1.RouteCode["Oral route"]]: "PO",
|
|
7
8
|
[types_1.RouteCode["Sublingual route"]]: "SL",
|
|
@@ -56,6 +57,12 @@ const DAY_NAMES = {
|
|
|
56
57
|
sat: "Saturday",
|
|
57
58
|
sun: "Sunday"
|
|
58
59
|
};
|
|
60
|
+
const EN_TIMES_PER_DAY = {
|
|
61
|
+
1: "once daily",
|
|
62
|
+
2: "twice daily",
|
|
63
|
+
3: "three times daily",
|
|
64
|
+
4: "four times daily"
|
|
65
|
+
};
|
|
59
66
|
const DEFAULT_ROUTE_GRAMMAR = { verb: "Use" };
|
|
60
67
|
const ROUTE_GRAMMAR = {
|
|
61
68
|
[types_1.RouteCode["Oral route"]]: { verb: "Take", routePhrase: "by mouth" },
|
|
@@ -201,14 +208,10 @@ function describeFrequency(internal) {
|
|
|
201
208
|
return `${stripTrailingZero(frequency)} to ${stripTrailingZero(frequencyMax)} times daily`;
|
|
202
209
|
}
|
|
203
210
|
if (frequency && periodUnit === types_1.FhirPeriodUnit.Day && (!period || period === 1)) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (frequency === 3)
|
|
209
|
-
return "three times daily";
|
|
210
|
-
if (frequency === 4)
|
|
211
|
-
return "four times daily";
|
|
211
|
+
const dailyText = EN_TIMES_PER_DAY[frequency];
|
|
212
|
+
if (dailyText) {
|
|
213
|
+
return dailyText;
|
|
214
|
+
}
|
|
212
215
|
return `${stripTrailingZero(frequency)} times daily`;
|
|
213
216
|
}
|
|
214
217
|
if (periodUnit === types_1.FhirPeriodUnit.Hour && period) {
|
|
@@ -271,6 +274,16 @@ function describeFrequency(internal) {
|
|
|
271
274
|
}
|
|
272
275
|
return undefined;
|
|
273
276
|
}
|
|
277
|
+
function describeFrequencyCount(count) {
|
|
278
|
+
if (!count || count <= 0) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
const dailyText = EN_TIMES_PER_DAY[count];
|
|
282
|
+
if (dailyText) {
|
|
283
|
+
return dailyText;
|
|
284
|
+
}
|
|
285
|
+
return `${stripTrailingZero(count)} times daily`;
|
|
286
|
+
}
|
|
274
287
|
function formatDoseShort(internal) {
|
|
275
288
|
if (internal.doseRange) {
|
|
276
289
|
const { low, high } = internal.doseRange;
|
|
@@ -304,42 +317,90 @@ function formatDoseLong(internal) {
|
|
|
304
317
|
}
|
|
305
318
|
return undefined;
|
|
306
319
|
}
|
|
307
|
-
function
|
|
320
|
+
function summarizeMealTimingGroup(group) {
|
|
321
|
+
let relationText = "with";
|
|
322
|
+
if (group.relation === "before") {
|
|
323
|
+
relationText = "before";
|
|
324
|
+
}
|
|
325
|
+
else if (group.relation === "after") {
|
|
326
|
+
relationText = "after";
|
|
327
|
+
}
|
|
328
|
+
return `${relationText} ${joinWithAnd(group.meals)}`;
|
|
329
|
+
}
|
|
330
|
+
function collectWhenPhrases(internal, options) {
|
|
331
|
+
var _a, _b;
|
|
308
332
|
if (!internal.when.length) {
|
|
309
333
|
return [];
|
|
310
334
|
}
|
|
311
335
|
const unique = [];
|
|
312
336
|
const seen = new Set();
|
|
337
|
+
let hasSpecificAfter = false;
|
|
338
|
+
let hasSpecificBefore = false;
|
|
339
|
+
let hasSpecificWith = false;
|
|
313
340
|
for (const code of internal.when) {
|
|
314
341
|
if (!seen.has(code)) {
|
|
315
342
|
seen.add(code);
|
|
316
343
|
unique.push(code);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
344
|
+
if (code === types_1.EventTiming["After Breakfast"] ||
|
|
345
|
+
code === types_1.EventTiming["After Lunch"] ||
|
|
346
|
+
code === types_1.EventTiming["After Dinner"]) {
|
|
347
|
+
hasSpecificAfter = true;
|
|
348
|
+
}
|
|
349
|
+
if (code === types_1.EventTiming["Before Breakfast"] ||
|
|
350
|
+
code === types_1.EventTiming["Before Lunch"] ||
|
|
351
|
+
code === types_1.EventTiming["Before Dinner"]) {
|
|
352
|
+
hasSpecificBefore = true;
|
|
353
|
+
}
|
|
354
|
+
if (code === types_1.EventTiming.Breakfast ||
|
|
355
|
+
code === types_1.EventTiming.Lunch ||
|
|
356
|
+
code === types_1.EventTiming.Dinner) {
|
|
357
|
+
hasSpecificWith = true;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const filtered = [];
|
|
362
|
+
for (let i = 0; i < unique.length; i += 1) {
|
|
363
|
+
const code = unique[i];
|
|
330
364
|
if (code === types_1.EventTiming["After Meal"] && hasSpecificAfter) {
|
|
331
|
-
|
|
365
|
+
continue;
|
|
332
366
|
}
|
|
333
367
|
if (code === types_1.EventTiming["Before Meal"] && hasSpecificBefore) {
|
|
334
|
-
|
|
368
|
+
continue;
|
|
335
369
|
}
|
|
336
370
|
if (code === types_1.EventTiming.Meal && hasSpecificWith) {
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
filtered.push(code);
|
|
374
|
+
}
|
|
375
|
+
const mealGroup = (0, timing_summary_1.getMealTimingGroup)(filtered, options);
|
|
376
|
+
if (mealGroup) {
|
|
377
|
+
const groupedCodes = new Set(mealGroup.codes);
|
|
378
|
+
const phrases = [];
|
|
379
|
+
let insertedGroup = false;
|
|
380
|
+
for (let i = 0; i < filtered.length; i += 1) {
|
|
381
|
+
const code = filtered[i];
|
|
382
|
+
if (groupedCodes.has(code)) {
|
|
383
|
+
if (!insertedGroup) {
|
|
384
|
+
phrases.push(summarizeMealTimingGroup(mealGroup));
|
|
385
|
+
insertedGroup = true;
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const text = (_a = WHEN_TEXT[code]) !== null && _a !== void 0 ? _a : code;
|
|
390
|
+
if (text) {
|
|
391
|
+
phrases.push(text);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return phrases;
|
|
395
|
+
}
|
|
396
|
+
const phrases = [];
|
|
397
|
+
for (let i = 0; i < filtered.length; i += 1) {
|
|
398
|
+
const text = (_b = WHEN_TEXT[filtered[i]]) !== null && _b !== void 0 ? _b : filtered[i];
|
|
399
|
+
if (text) {
|
|
400
|
+
phrases.push(text);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return phrases;
|
|
343
404
|
}
|
|
344
405
|
function joinWithAnd(parts) {
|
|
345
406
|
if (!parts.length) {
|
|
@@ -485,10 +546,10 @@ function describeDayOfWeek(internal) {
|
|
|
485
546
|
}
|
|
486
547
|
return `on ${joinWithAnd(days)}`;
|
|
487
548
|
}
|
|
488
|
-
function formatInternal(internal, style, localization) {
|
|
549
|
+
function formatInternal(internal, style, localization, options) {
|
|
489
550
|
const defaults = {
|
|
490
551
|
short: formatShort(internal),
|
|
491
|
-
long: formatLong(internal)
|
|
552
|
+
long: formatLong(internal, options)
|
|
492
553
|
};
|
|
493
554
|
if (!localization) {
|
|
494
555
|
return defaults[style];
|
|
@@ -499,6 +560,8 @@ function formatInternal(internal, style, localization) {
|
|
|
499
560
|
style: "short",
|
|
500
561
|
internal,
|
|
501
562
|
defaultText: defaults.short,
|
|
563
|
+
groupMealTimingsByRelation: Boolean(options === null || options === void 0 ? void 0 : options.groupMealTimingsByRelation),
|
|
564
|
+
includeTimesPerDaySummary: Boolean(options === null || options === void 0 ? void 0 : options.includeTimesPerDaySummary),
|
|
502
565
|
formatDefault
|
|
503
566
|
};
|
|
504
567
|
return localization.formatShort(context);
|
|
@@ -508,6 +571,8 @@ function formatInternal(internal, style, localization) {
|
|
|
508
571
|
style: "long",
|
|
509
572
|
internal,
|
|
510
573
|
defaultText: defaults.long,
|
|
574
|
+
groupMealTimingsByRelation: Boolean(options === null || options === void 0 ? void 0 : options.groupMealTimingsByRelation),
|
|
575
|
+
includeTimesPerDaySummary: Boolean(options === null || options === void 0 ? void 0 : options.includeTimesPerDaySummary),
|
|
511
576
|
formatDefault
|
|
512
577
|
};
|
|
513
578
|
return localization.formatLong(context);
|
|
@@ -578,15 +643,15 @@ function formatShort(internal) {
|
|
|
578
643
|
}
|
|
579
644
|
return parts.filter(Boolean).join(" ");
|
|
580
645
|
}
|
|
581
|
-
function formatLong(internal) {
|
|
582
|
-
var _a, _b;
|
|
646
|
+
function formatLong(internal, options) {
|
|
647
|
+
var _a, _b, _c;
|
|
583
648
|
const grammar = resolveRouteGrammar(internal);
|
|
584
649
|
const dosePart = (_a = formatDoseLong(internal)) !== null && _a !== void 0 ? _a : "the medication";
|
|
585
650
|
const sitePart = formatSite(internal, grammar);
|
|
586
651
|
const routePart = buildRoutePhrase(internal, grammar, Boolean(sitePart));
|
|
587
|
-
const frequencyPart = describeFrequency(internal);
|
|
588
|
-
const eventParts = collectWhenPhrases(internal);
|
|
589
|
-
if ((
|
|
652
|
+
const frequencyPart = (_b = describeFrequency(internal)) !== null && _b !== void 0 ? _b : describeFrequencyCount((0, timing_summary_1.inferDailyOccurrenceCount)(internal, options));
|
|
653
|
+
const eventParts = collectWhenPhrases(internal, options);
|
|
654
|
+
if ((_c = internal.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) {
|
|
590
655
|
const timeStrings = [];
|
|
591
656
|
for (const time of internal.timeOfDay) {
|
|
592
657
|
const parts = time.split(":");
|
package/dist/i18n.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export interface SigFormatContext {
|
|
|
3
3
|
readonly style: "short" | "long";
|
|
4
4
|
readonly internal: ParsedSigInternal;
|
|
5
5
|
readonly defaultText: string;
|
|
6
|
+
readonly groupMealTimingsByRelation: boolean;
|
|
7
|
+
readonly includeTimesPerDaySummary: boolean;
|
|
6
8
|
formatDefault(style: "short" | "long"): string;
|
|
7
9
|
}
|
|
8
10
|
export interface SigShortContext extends SigFormatContext {
|
package/dist/i18n.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.getRegisteredSigLocalizations = getRegisteredSigLocalizations;
|
|
|
6
6
|
exports.resolveSigLocalization = resolveSigLocalization;
|
|
7
7
|
exports.resolveSigTranslation = resolveSigTranslation;
|
|
8
8
|
const types_1 = require("./types");
|
|
9
|
+
const timing_summary_1 = require("./timing-summary");
|
|
9
10
|
const REGISTERED_LOCALIZATIONS = new Map();
|
|
10
11
|
function registerSigLocalization(localization) {
|
|
11
12
|
REGISTERED_LOCALIZATIONS.set(localization.locale.toLowerCase(), localization);
|
|
@@ -65,7 +66,10 @@ function createThaiLocalization() {
|
|
|
65
66
|
return {
|
|
66
67
|
locale: "th",
|
|
67
68
|
formatShort: ({ internal }) => formatShortThai(internal),
|
|
68
|
-
formatLong: ({ internal }) => formatLongThai(internal
|
|
69
|
+
formatLong: ({ internal, groupMealTimingsByRelation, includeTimesPerDaySummary }) => formatLongThai(internal, {
|
|
70
|
+
groupMealTimingsByRelation,
|
|
71
|
+
includeTimesPerDaySummary
|
|
72
|
+
})
|
|
69
73
|
};
|
|
70
74
|
}
|
|
71
75
|
registerSigLocalization(createThaiLocalization());
|
|
@@ -125,6 +129,12 @@ const DAY_NAMES_THAI = {
|
|
|
125
129
|
sat: "วันเสาร์",
|
|
126
130
|
sun: "วันอาทิตย์"
|
|
127
131
|
};
|
|
132
|
+
const TH_TIMES_PER_DAY = {
|
|
133
|
+
1: "วันละครั้ง",
|
|
134
|
+
2: "วันละ 2 ครั้ง",
|
|
135
|
+
3: "วันละ 3 ครั้ง",
|
|
136
|
+
4: "วันละ 4 ครั้ง"
|
|
137
|
+
};
|
|
128
138
|
exports.THAI_SITE_TRANSLATIONS = {
|
|
129
139
|
eye: "ตา",
|
|
130
140
|
eyes: "ตา",
|
|
@@ -231,7 +241,6 @@ const THAI_ROUTE_GRAMMAR = {
|
|
|
231
241
|
[types_1.RouteCode["Buccal route"]]: { verb: "อมกระพุ้งแก้ม", routePhrase: "ที่กระพุ้งแก้ม" },
|
|
232
242
|
[types_1.RouteCode["Respiratory tract route (qualifier value)"]]: {
|
|
233
243
|
verb: "สูด",
|
|
234
|
-
routePhrase: ({ hasSite }) => (hasSite ? undefined : "โดยการสูดดม"),
|
|
235
244
|
sitePreposition: "ที่"
|
|
236
245
|
},
|
|
237
246
|
[types_1.RouteCode["Nasal route"]]: {
|
|
@@ -291,12 +300,18 @@ const THAI_ROUTE_GRAMMAR = {
|
|
|
291
300
|
}
|
|
292
301
|
};
|
|
293
302
|
function resolveRouteGrammarThai(internal) {
|
|
294
|
-
var _a;
|
|
303
|
+
var _a, _b, _c;
|
|
295
304
|
if (internal.routeCode && THAI_ROUTE_GRAMMAR[internal.routeCode]) {
|
|
296
305
|
return (_a = THAI_ROUTE_GRAMMAR[internal.routeCode]) !== null && _a !== void 0 ? _a : DEFAULT_THAI_ROUTE_GRAMMAR;
|
|
297
306
|
}
|
|
298
307
|
const grammar = grammarFromRouteTextThai(internal.routeText);
|
|
299
|
-
|
|
308
|
+
if (grammar) {
|
|
309
|
+
return grammar;
|
|
310
|
+
}
|
|
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;
|
|
313
|
+
}
|
|
314
|
+
return DEFAULT_THAI_ROUTE_GRAMMAR;
|
|
300
315
|
}
|
|
301
316
|
function grammarFromRouteTextThai(text) {
|
|
302
317
|
if (!text)
|
|
@@ -418,14 +433,10 @@ function describeFrequencyThai(internal) {
|
|
|
418
433
|
return `วันละ ${stripTrailingZero(frequency)} ถึง ${stripTrailingZero(frequencyMax)} ครั้ง`;
|
|
419
434
|
}
|
|
420
435
|
if (frequency && periodUnit === types_1.FhirPeriodUnit.Day && (!period || period === 1)) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (frequency === 3)
|
|
426
|
-
return "วันละ 3 ครั้ง";
|
|
427
|
-
if (frequency === 4)
|
|
428
|
-
return "วันละ 4 ครั้ง";
|
|
436
|
+
const dailyText = TH_TIMES_PER_DAY[frequency];
|
|
437
|
+
if (dailyText) {
|
|
438
|
+
return dailyText;
|
|
439
|
+
}
|
|
429
440
|
return `วันละ ${stripTrailingZero(frequency)} ครั้ง`;
|
|
430
441
|
}
|
|
431
442
|
if (periodUnit === types_1.FhirPeriodUnit.Hour && period) {
|
|
@@ -485,42 +496,122 @@ function describeFrequencyThai(internal) {
|
|
|
485
496
|
}
|
|
486
497
|
return undefined;
|
|
487
498
|
}
|
|
488
|
-
function
|
|
499
|
+
function describeFrequencyCountThai(count) {
|
|
500
|
+
if (!count || count <= 0) {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
const dailyText = TH_TIMES_PER_DAY[count];
|
|
504
|
+
if (dailyText) {
|
|
505
|
+
return dailyText;
|
|
506
|
+
}
|
|
507
|
+
return `วันละ ${stripTrailingZero(count)} ครั้ง`;
|
|
508
|
+
}
|
|
509
|
+
function joinMealNamesThai(parts) {
|
|
510
|
+
if (parts.length === 0) {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
if (parts.length === 1) {
|
|
514
|
+
return parts[0];
|
|
515
|
+
}
|
|
516
|
+
if (parts.length === 2) {
|
|
517
|
+
return `${parts[0]} และ${parts[1]}`;
|
|
518
|
+
}
|
|
519
|
+
let text = parts[0];
|
|
520
|
+
for (let i = 1; i < parts.length - 1; i += 1) {
|
|
521
|
+
text += ` ${parts[i]}`;
|
|
522
|
+
}
|
|
523
|
+
return `${text} และ${parts[parts.length - 1]}`;
|
|
524
|
+
}
|
|
525
|
+
function summarizeMealTimingGroupThai(group) {
|
|
526
|
+
const relationText = {
|
|
527
|
+
before: "ก่อนอาหาร",
|
|
528
|
+
after: "หลังอาหาร",
|
|
529
|
+
with: "พร้อมอาหาร"
|
|
530
|
+
};
|
|
531
|
+
const mealText = {
|
|
532
|
+
breakfast: "เช้า",
|
|
533
|
+
lunch: "กลางวัน",
|
|
534
|
+
dinner: "เย็น"
|
|
535
|
+
};
|
|
536
|
+
const meals = [];
|
|
537
|
+
for (let i = 0; i < group.meals.length; i += 1) {
|
|
538
|
+
meals.push(mealText[group.meals[i]]);
|
|
539
|
+
}
|
|
540
|
+
return `${relationText[group.relation]}${joinMealNamesThai(meals)}`;
|
|
541
|
+
}
|
|
542
|
+
function collectWhenPhrasesThai(internal, options) {
|
|
489
543
|
if (!internal.when.length) {
|
|
490
544
|
return [];
|
|
491
545
|
}
|
|
492
546
|
const unique = [];
|
|
493
547
|
const seen = new Set();
|
|
548
|
+
let hasSpecificAfter = false;
|
|
549
|
+
let hasSpecificBefore = false;
|
|
550
|
+
let hasSpecificWith = false;
|
|
494
551
|
for (const code of internal.when) {
|
|
495
552
|
if (!seen.has(code)) {
|
|
496
553
|
seen.add(code);
|
|
497
554
|
unique.push(code);
|
|
555
|
+
if (code === types_1.EventTiming["After Breakfast"] ||
|
|
556
|
+
code === types_1.EventTiming["After Lunch"] ||
|
|
557
|
+
code === types_1.EventTiming["After Dinner"]) {
|
|
558
|
+
hasSpecificAfter = true;
|
|
559
|
+
}
|
|
560
|
+
if (code === types_1.EventTiming["Before Breakfast"] ||
|
|
561
|
+
code === types_1.EventTiming["Before Lunch"] ||
|
|
562
|
+
code === types_1.EventTiming["Before Dinner"]) {
|
|
563
|
+
hasSpecificBefore = true;
|
|
564
|
+
}
|
|
565
|
+
if (code === types_1.EventTiming.Breakfast ||
|
|
566
|
+
code === types_1.EventTiming.Lunch ||
|
|
567
|
+
code === types_1.EventTiming.Dinner) {
|
|
568
|
+
hasSpecificWith = true;
|
|
569
|
+
}
|
|
498
570
|
}
|
|
499
571
|
}
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
code
|
|
503
|
-
const hasSpecificBefore = unique.some((code) => code === types_1.EventTiming["Before Breakfast"] ||
|
|
504
|
-
code === types_1.EventTiming["Before Lunch"] ||
|
|
505
|
-
code === types_1.EventTiming["Before Dinner"]);
|
|
506
|
-
const hasSpecificWith = unique.some((code) => code === types_1.EventTiming.Breakfast ||
|
|
507
|
-
code === types_1.EventTiming.Lunch ||
|
|
508
|
-
code === types_1.EventTiming.Dinner);
|
|
509
|
-
return unique
|
|
510
|
-
.filter((code) => {
|
|
572
|
+
const filtered = [];
|
|
573
|
+
for (let i = 0; i < unique.length; i += 1) {
|
|
574
|
+
const code = unique[i];
|
|
511
575
|
if (code === types_1.EventTiming["After Meal"] && hasSpecificAfter) {
|
|
512
|
-
|
|
576
|
+
continue;
|
|
513
577
|
}
|
|
514
578
|
if (code === types_1.EventTiming["Before Meal"] && hasSpecificBefore) {
|
|
515
|
-
|
|
579
|
+
continue;
|
|
516
580
|
}
|
|
517
581
|
if (code === types_1.EventTiming.Meal && hasSpecificWith) {
|
|
518
|
-
|
|
582
|
+
continue;
|
|
519
583
|
}
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
|
|
584
|
+
filtered.push(code);
|
|
585
|
+
}
|
|
586
|
+
const mealGroup = (0, timing_summary_1.getMealTimingGroup)(filtered, options);
|
|
587
|
+
if (mealGroup) {
|
|
588
|
+
const groupedCodes = new Set(mealGroup.codes);
|
|
589
|
+
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
|
+
}
|
|
600
|
+
const text = WHEN_TEXT_THAI[code];
|
|
601
|
+
if (text) {
|
|
602
|
+
phrases.push(text);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return phrases;
|
|
606
|
+
}
|
|
607
|
+
const phrases = [];
|
|
608
|
+
for (let i = 0; i < filtered.length; i += 1) {
|
|
609
|
+
const text = WHEN_TEXT_THAI[filtered[i]];
|
|
610
|
+
if (text) {
|
|
611
|
+
phrases.push(text);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return phrases;
|
|
524
615
|
}
|
|
525
616
|
function joinWithAndThai(parts) {
|
|
526
617
|
if (!parts.length) {
|
|
@@ -606,7 +697,7 @@ function buildRoutePhraseThai(internal, grammar, hasSite) {
|
|
|
606
697
|
return "ทางจมูก";
|
|
607
698
|
}
|
|
608
699
|
if (normalized.includes("inhal")) {
|
|
609
|
-
return
|
|
700
|
+
return undefined;
|
|
610
701
|
}
|
|
611
702
|
return text;
|
|
612
703
|
}
|
|
@@ -710,15 +801,15 @@ function formatShortThai(internal) {
|
|
|
710
801
|
}
|
|
711
802
|
return parts.filter(Boolean).join(" ");
|
|
712
803
|
}
|
|
713
|
-
function formatLongThai(internal) {
|
|
714
|
-
var _a, _b;
|
|
804
|
+
function formatLongThai(internal, options) {
|
|
805
|
+
var _a, _b, _c;
|
|
715
806
|
const grammar = resolveRouteGrammarThai(internal);
|
|
716
807
|
const dosePart = (_a = formatDoseThaiLong(internal)) !== null && _a !== void 0 ? _a : "ยา";
|
|
717
808
|
const sitePart = formatSiteThai(internal, grammar);
|
|
718
809
|
const routePart = buildRoutePhraseThai(internal, grammar, Boolean(sitePart));
|
|
719
|
-
const frequencyPart = describeFrequencyThai(internal);
|
|
720
|
-
const eventParts = collectWhenPhrasesThai(internal);
|
|
721
|
-
if ((
|
|
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) {
|
|
722
813
|
const timeStrings = [];
|
|
723
814
|
for (const time of internal.timeOfDay) {
|
|
724
815
|
const parts = time.split(":");
|
package/dist/index.js
CHANGED
|
@@ -443,7 +443,7 @@ function parseSigAsync(input, options) {
|
|
|
443
443
|
function formatSig(dosage, style = "short", options) {
|
|
444
444
|
const internal = (0, fhir_1.internalFromFhir)(dosage);
|
|
445
445
|
const localization = (0, i18n_1.resolveSigLocalization)(options === null || options === void 0 ? void 0 : options.locale, options === null || options === void 0 ? void 0 : options.i18n);
|
|
446
|
-
return (0, format_1.formatInternal)(internal, style, localization);
|
|
446
|
+
return (0, format_1.formatInternal)(internal, style, localization, options);
|
|
447
447
|
}
|
|
448
448
|
function formatSigBatch(dosages, style = "short", options) {
|
|
449
449
|
var _a;
|
|
@@ -464,12 +464,13 @@ function formatParseBatch(batch, style = "short", separator = ", ") {
|
|
|
464
464
|
return texts.join(separator);
|
|
465
465
|
}
|
|
466
466
|
function fromFhirDosage(dosage, options) {
|
|
467
|
-
var _a, _b, _c, _d, _e
|
|
467
|
+
var _a, _b, _c, _d, _e;
|
|
468
468
|
const internal = (0, fhir_1.internalFromFhir)(dosage);
|
|
469
469
|
const localization = (0, i18n_1.resolveSigLocalization)(options === null || options === void 0 ? void 0 : options.locale, options === null || options === void 0 ? void 0 : options.i18n);
|
|
470
|
-
const shortText = (0, format_1.formatInternal)(internal, "short", localization);
|
|
471
|
-
const computedLong = (0, format_1.formatInternal)(internal, "long", localization);
|
|
472
|
-
const longText =
|
|
470
|
+
const shortText = (0, format_1.formatInternal)(internal, "short", localization, options);
|
|
471
|
+
const computedLong = (0, format_1.formatInternal)(internal, "long", localization, options);
|
|
472
|
+
const longText = computedLong || dosage.text || "";
|
|
473
|
+
dosage.text = longText;
|
|
473
474
|
return {
|
|
474
475
|
fhir: dosage,
|
|
475
476
|
shortText,
|
|
@@ -480,10 +481,10 @@ function fromFhirDosage(dosage, options) {
|
|
|
480
481
|
normalized: {
|
|
481
482
|
route: internal.routeCode,
|
|
482
483
|
unit: internal.unit,
|
|
483
|
-
site: internal.siteText || ((
|
|
484
|
+
site: internal.siteText || ((_a = internal.siteCoding) === null || _a === void 0 ? void 0 : _a.code)
|
|
484
485
|
? {
|
|
485
486
|
text: internal.siteText,
|
|
486
|
-
coding: ((
|
|
487
|
+
coding: ((_b = internal.siteCoding) === null || _b === void 0 ? void 0 : _b.code)
|
|
487
488
|
? {
|
|
488
489
|
code: internal.siteCoding.code,
|
|
489
490
|
display: internal.siteCoding.display,
|
|
@@ -492,10 +493,10 @@ function fromFhirDosage(dosage, options) {
|
|
|
492
493
|
: undefined
|
|
493
494
|
}
|
|
494
495
|
: undefined,
|
|
495
|
-
prnReason: internal.asNeededReason || ((
|
|
496
|
+
prnReason: internal.asNeededReason || ((_c = internal.asNeededReasonCoding) === null || _c === void 0 ? void 0 : _c.code)
|
|
496
497
|
? {
|
|
497
498
|
text: internal.asNeededReason,
|
|
498
|
-
coding: ((
|
|
499
|
+
coding: ((_d = internal.asNeededReasonCoding) === null || _d === void 0 ? void 0 : _d.code)
|
|
499
500
|
? {
|
|
500
501
|
code: internal.asNeededReasonCoding.code,
|
|
501
502
|
display: internal.asNeededReasonCoding.display,
|
|
@@ -504,7 +505,7 @@ function fromFhirDosage(dosage, options) {
|
|
|
504
505
|
: undefined
|
|
505
506
|
}
|
|
506
507
|
: undefined,
|
|
507
|
-
additionalInstructions: ((
|
|
508
|
+
additionalInstructions: ((_e = internal.additionalInstructions) === null || _e === void 0 ? void 0 : _e.length)
|
|
508
509
|
? internal.additionalInstructions.map((instruction) => {
|
|
509
510
|
var _a;
|
|
510
511
|
return ({
|
|
@@ -526,8 +527,8 @@ function fromFhirDosage(dosage, options) {
|
|
|
526
527
|
function buildParseResult(internal, options) {
|
|
527
528
|
var _a, _b, _c;
|
|
528
529
|
const localization = (0, i18n_1.resolveSigLocalization)(options === null || options === void 0 ? void 0 : options.locale, options === null || options === void 0 ? void 0 : options.i18n);
|
|
529
|
-
const shortText = (0, format_1.formatInternal)(internal, "short", localization);
|
|
530
|
-
const longText = (0, format_1.formatInternal)(internal, "long", localization);
|
|
530
|
+
const shortText = (0, format_1.formatInternal)(internal, "short", localization, options);
|
|
531
|
+
const longText = (0, format_1.formatInternal)(internal, "long", localization, options);
|
|
531
532
|
const fhir = (0, fhir_1.toFhir)(internal);
|
|
532
533
|
if (longText) {
|
|
533
534
|
fhir.text = longText;
|
package/dist/parser.js
CHANGED
|
@@ -336,6 +336,7 @@ const EYE_SITE_TOKENS = {
|
|
|
336
336
|
};
|
|
337
337
|
const OPHTHALMIC_ROUTE_CODES = new Set([
|
|
338
338
|
types_1.RouteCode["Ophthalmic route"],
|
|
339
|
+
types_1.RouteCode["Ocular route (qualifier value)"],
|
|
339
340
|
types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
340
341
|
]);
|
|
341
342
|
const OPHTHALMIC_CONTEXT_TOKENS = new Set([
|
|
@@ -538,9 +539,13 @@ function shouldTreatEyeTokenAsSite(internal, tokens, index, context) {
|
|
|
538
539
|
const currentToken = tokens[index];
|
|
539
540
|
const normalizedSelf = normalizeTokenLower(currentToken);
|
|
540
541
|
const eyeMeta = EYE_SITE_TOKENS[normalizedSelf];
|
|
542
|
+
const contextRoute = (0, context_1.inferRouteFromContext)(context !== null && context !== void 0 ? context : undefined);
|
|
541
543
|
if (internal.routeCode && !OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) {
|
|
542
544
|
return false;
|
|
543
545
|
}
|
|
546
|
+
if (contextRoute && !OPHTHALMIC_ROUTE_CODES.has(contextRoute)) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
544
549
|
if (internal.siteText) {
|
|
545
550
|
return false;
|
|
546
551
|
}
|
|
@@ -548,7 +553,9 @@ function shouldTreatEyeTokenAsSite(internal, tokens, index, context) {
|
|
|
548
553
|
return false;
|
|
549
554
|
}
|
|
550
555
|
const dosageForm = (_a = context === null || context === void 0 ? void 0 : context.dosageForm) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
551
|
-
const contextImpliesOphthalmic =
|
|
556
|
+
const contextImpliesOphthalmic = contextRoute
|
|
557
|
+
? OPHTHALMIC_ROUTE_CODES.has(contextRoute)
|
|
558
|
+
: Boolean(dosageForm && /(eye|ophth|ocular|intravit)/i.test(dosageForm));
|
|
552
559
|
const eyeRouteImpliesOphthalmic = (eyeMeta === null || eyeMeta === void 0 ? void 0 : eyeMeta.route) === types_1.RouteCode["Intravitreal route (qualifier value)"];
|
|
553
560
|
const ophthalmicContext = hasOphthalmicContextHint(tokens, index) ||
|
|
554
561
|
(internal.routeCode !== undefined && OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) ||
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ParsedSigInternal } from "./internal-types";
|
|
2
|
+
import { EventTiming } from "./types";
|
|
3
|
+
export interface TimingSummaryOptions {
|
|
4
|
+
groupMealTimingsByRelation?: boolean;
|
|
5
|
+
includeTimesPerDaySummary?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type MealRelation = "before" | "after" | "with";
|
|
8
|
+
export type MealName = "breakfast" | "lunch" | "dinner";
|
|
9
|
+
export interface MealTimingGroup {
|
|
10
|
+
relation: MealRelation;
|
|
11
|
+
meals: MealName[];
|
|
12
|
+
codes: EventTiming[];
|
|
13
|
+
}
|
|
14
|
+
export declare function getMealTimingGroup(when: EventTiming[], options?: TimingSummaryOptions): MealTimingGroup | undefined;
|
|
15
|
+
export declare function inferDailyOccurrenceCount(internal: ParsedSigInternal, options?: TimingSummaryOptions): number | undefined;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getMealTimingGroup = getMealTimingGroup;
|
|
4
|
+
exports.inferDailyOccurrenceCount = inferDailyOccurrenceCount;
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const MEAL_TIMING_DETAILS = {
|
|
7
|
+
[types_1.EventTiming["Before Breakfast"]]: { relation: "before", meal: "breakfast" },
|
|
8
|
+
[types_1.EventTiming["Before Lunch"]]: { relation: "before", meal: "lunch" },
|
|
9
|
+
[types_1.EventTiming["Before Dinner"]]: { relation: "before", meal: "dinner" },
|
|
10
|
+
[types_1.EventTiming["After Breakfast"]]: { relation: "after", meal: "breakfast" },
|
|
11
|
+
[types_1.EventTiming["After Lunch"]]: { relation: "after", meal: "lunch" },
|
|
12
|
+
[types_1.EventTiming["After Dinner"]]: { relation: "after", meal: "dinner" },
|
|
13
|
+
[types_1.EventTiming.Breakfast]: { relation: "with", meal: "breakfast" },
|
|
14
|
+
[types_1.EventTiming.Lunch]: { relation: "with", meal: "lunch" },
|
|
15
|
+
[types_1.EventTiming.Dinner]: { relation: "with", meal: "dinner" }
|
|
16
|
+
};
|
|
17
|
+
const MEAL_ORDER = {
|
|
18
|
+
breakfast: 0,
|
|
19
|
+
lunch: 1,
|
|
20
|
+
dinner: 2
|
|
21
|
+
};
|
|
22
|
+
const INFERABLE_DAILY_EVENT_TIMINGS = new Set([
|
|
23
|
+
types_1.EventTiming["Before Sleep"],
|
|
24
|
+
types_1.EventTiming["Before Breakfast"],
|
|
25
|
+
types_1.EventTiming["Before Lunch"],
|
|
26
|
+
types_1.EventTiming["Before Dinner"],
|
|
27
|
+
types_1.EventTiming["After Breakfast"],
|
|
28
|
+
types_1.EventTiming["After Lunch"],
|
|
29
|
+
types_1.EventTiming["After Dinner"],
|
|
30
|
+
types_1.EventTiming.Breakfast,
|
|
31
|
+
types_1.EventTiming.Lunch,
|
|
32
|
+
types_1.EventTiming.Dinner,
|
|
33
|
+
types_1.EventTiming.Morning,
|
|
34
|
+
types_1.EventTiming["Early Morning"],
|
|
35
|
+
types_1.EventTiming["Late Morning"],
|
|
36
|
+
types_1.EventTiming.Noon,
|
|
37
|
+
types_1.EventTiming.Afternoon,
|
|
38
|
+
types_1.EventTiming["Early Afternoon"],
|
|
39
|
+
types_1.EventTiming["Late Afternoon"],
|
|
40
|
+
types_1.EventTiming.Evening,
|
|
41
|
+
types_1.EventTiming["Early Evening"],
|
|
42
|
+
types_1.EventTiming["Late Evening"],
|
|
43
|
+
types_1.EventTiming.Night,
|
|
44
|
+
types_1.EventTiming.Wake,
|
|
45
|
+
types_1.EventTiming["After Sleep"]
|
|
46
|
+
]);
|
|
47
|
+
function uniqueValues(values) {
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
const result = [];
|
|
50
|
+
for (const value of values) {
|
|
51
|
+
if (!seen.has(value)) {
|
|
52
|
+
seen.add(value);
|
|
53
|
+
result.push(value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function getMealTimingGroup(when, options) {
|
|
59
|
+
if (!(options === null || options === void 0 ? void 0 : options.groupMealTimingsByRelation)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
const uniqueWhen = uniqueValues(when);
|
|
63
|
+
if (uniqueWhen.length < 2) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
let relation;
|
|
67
|
+
const meals = [];
|
|
68
|
+
const groupedCodes = [];
|
|
69
|
+
let sawFirstMeal = false;
|
|
70
|
+
for (let i = 0; i < uniqueWhen.length; i += 1) {
|
|
71
|
+
const code = uniqueWhen[i];
|
|
72
|
+
const detail = MEAL_TIMING_DETAILS[code];
|
|
73
|
+
if (!detail) {
|
|
74
|
+
if (sawFirstMeal) {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!sawFirstMeal) {
|
|
80
|
+
sawFirstMeal = true;
|
|
81
|
+
}
|
|
82
|
+
if (!relation) {
|
|
83
|
+
relation = detail.relation;
|
|
84
|
+
}
|
|
85
|
+
else if (relation !== detail.relation) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
meals.push(detail.meal);
|
|
89
|
+
groupedCodes.push(code);
|
|
90
|
+
}
|
|
91
|
+
if (groupedCodes.length < 2) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
for (let i = 1; i < meals.length; i += 1) {
|
|
95
|
+
const current = meals[i];
|
|
96
|
+
let j = i - 1;
|
|
97
|
+
while (j >= 0 && MEAL_ORDER[meals[j]] > MEAL_ORDER[current]) {
|
|
98
|
+
meals[j + 1] = meals[j];
|
|
99
|
+
j -= 1;
|
|
100
|
+
}
|
|
101
|
+
meals[j + 1] = current;
|
|
102
|
+
}
|
|
103
|
+
if (!relation) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
relation,
|
|
108
|
+
meals,
|
|
109
|
+
codes: groupedCodes
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function inferDailyOccurrenceCount(internal, options) {
|
|
113
|
+
var _a;
|
|
114
|
+
if (!(options === null || options === void 0 ? void 0 : options.includeTimesPerDaySummary)) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (internal.frequency !== undefined || internal.frequencyMax !== undefined || internal.timingCode) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (internal.period !== undefined || internal.periodMax !== undefined || internal.periodUnit !== undefined) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
if (internal.dayOfWeek.length > 0) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const uniqueWhen = uniqueValues(internal.when);
|
|
127
|
+
for (let i = 0; i < uniqueWhen.length; i += 1) {
|
|
128
|
+
if (!INFERABLE_DAILY_EVENT_TIMINGS.has(uniqueWhen[i])) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const uniqueTimes = uniqueValues((_a = internal.timeOfDay) !== null && _a !== void 0 ? _a : []);
|
|
133
|
+
const occurrences = uniqueWhen.length + uniqueTimes.length;
|
|
134
|
+
if (occurrences === 0) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return occurrences;
|
|
138
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -296,6 +296,17 @@ export interface MedicationContext {
|
|
|
296
296
|
export interface FormatOptions {
|
|
297
297
|
locale?: "en" | "th" | string;
|
|
298
298
|
i18n?: SigTranslationConfig;
|
|
299
|
+
/**
|
|
300
|
+
* Collapses repeated meal relation phrases into a grouped phrase when all
|
|
301
|
+
* meal anchors share the same relation (for example, "after breakfast,
|
|
302
|
+
* lunch and dinner" instead of repeating "after" for each meal).
|
|
303
|
+
*/
|
|
304
|
+
groupMealTimingsByRelation?: boolean;
|
|
305
|
+
/**
|
|
306
|
+
* Adds a per-day frequency summary when it can be derived safely from the
|
|
307
|
+
* schedule (for example, "three times daily" or "วันละ 3 ครั้ง").
|
|
308
|
+
*/
|
|
309
|
+
includeTimesPerDaySummary?: boolean;
|
|
299
310
|
}
|
|
300
311
|
export interface FormatBatchOptions extends FormatOptions {
|
|
301
312
|
/**
|