ezmedicationinput 0.1.36 → 0.1.38
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 +4 -0
- package/dist/i18n.js +17 -0
- package/dist/index.js +93 -3
- package/dist/schedule.js +78 -0
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -346,6 +346,10 @@ You can specify the number of times (total count) the medication is supposed to
|
|
|
346
346
|
combinations (e.g. `1x3` → breakfast/lunch/dinner). This also respects
|
|
347
347
|
`context.mealRelation` when provided and only applies to schedules with four
|
|
348
348
|
or fewer daily doses.
|
|
349
|
+
- `enableMealDashSyntax`: when `true`, enables shorthand meal-dose patterns
|
|
350
|
+
such as `1-0-1`, `1-0-1 pc`, `10-12-0 ac`, and `1-0-0-1 ac`. The parser
|
|
351
|
+
expands them into multiple dosage clauses aligned to breakfast/lunch/dinner
|
|
352
|
+
(plus bedtime for a 4th slot).
|
|
349
353
|
- `twoPerDayPair`: controls whether 2× AC/PC/C doses expand to breakfast+dinner (default) or breakfast+lunch.
|
|
350
354
|
- `assumeSingleDiscreteDose`: when `true`, missing discrete doses (such as
|
|
351
355
|
tablets or capsules) default to a single unit when the parser can infer a
|
package/dist/i18n.js
CHANGED
|
@@ -551,8 +551,25 @@ function combineFrequencyAndEventsThai(frequency, events) {
|
|
|
551
551
|
}
|
|
552
552
|
return { frequency, event: joinWithAndThai(events) };
|
|
553
553
|
}
|
|
554
|
+
function isOralRouteThai(internal) {
|
|
555
|
+
var _a;
|
|
556
|
+
if (internal.routeCode === types_1.RouteCode["Oral route"]) {
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
const text = (_a = internal.routeText) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
|
|
560
|
+
if (!text) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
return (text === "po" ||
|
|
564
|
+
text === "oral" ||
|
|
565
|
+
text.includes("mouth") ||
|
|
566
|
+
text.includes("per os"));
|
|
567
|
+
}
|
|
554
568
|
function buildRoutePhraseThai(internal, grammar, hasSite) {
|
|
555
569
|
var _a;
|
|
570
|
+
if (grammar.verb === "รับประทาน" && isOralRouteThai(internal)) {
|
|
571
|
+
return undefined;
|
|
572
|
+
}
|
|
556
573
|
if (typeof grammar.routePhrase === "function") {
|
|
557
574
|
return grammar.routePhrase({ hasSite, internal });
|
|
558
575
|
}
|
package/dist/index.js
CHANGED
|
@@ -58,6 +58,96 @@ Object.defineProperty(exports, "DEFAULT_BODY_SITE_SNOMED_SOURCE", { enumerable:
|
|
|
58
58
|
Object.defineProperty(exports, "DEFAULT_ROUTE_SYNONYMS", { enumerable: true, get: function () { return maps_1.DEFAULT_ROUTE_SYNONYMS; } });
|
|
59
59
|
Object.defineProperty(exports, "DEFAULT_UNIT_BY_ROUTE", { enumerable: true, get: function () { return maps_1.DEFAULT_UNIT_BY_ROUTE; } });
|
|
60
60
|
Object.defineProperty(exports, "KNOWN_DOSAGE_FORMS_TO_DOSE", { enumerable: true, get: function () { return maps_1.KNOWN_DOSAGE_FORMS_TO_DOSE; } });
|
|
61
|
+
function parseMealDashValues(token) {
|
|
62
|
+
if (!/^[0-9]+(?:\.[0-9]+)?(?:-[0-9]+(?:\.[0-9]+)?){2,3}$/.test(token)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const values = token.split("-").map((part) => Number(part));
|
|
66
|
+
if (values.length !== 3 && values.length !== 4) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (!values.every((value) => Number.isFinite(value) && value >= 0)) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
return values;
|
|
73
|
+
}
|
|
74
|
+
function mealDashEvents(length, relation) {
|
|
75
|
+
const base = relation === "ac"
|
|
76
|
+
? ["ACM", "ACD", "ACV"]
|
|
77
|
+
: relation === "pc"
|
|
78
|
+
? ["PCM", "PCD", "PCV"]
|
|
79
|
+
: ["CM", "CD", "CV"];
|
|
80
|
+
if (length === 4) {
|
|
81
|
+
return [...base, "HS"];
|
|
82
|
+
}
|
|
83
|
+
return base;
|
|
84
|
+
}
|
|
85
|
+
function formatMealDashAmount(value) {
|
|
86
|
+
if (Number.isInteger(value)) {
|
|
87
|
+
return String(value);
|
|
88
|
+
}
|
|
89
|
+
return String(value).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
90
|
+
}
|
|
91
|
+
function expandMealDashSegment(segment) {
|
|
92
|
+
const tokens = (0, parser_1.tokenize)(segment.text);
|
|
93
|
+
if (tokens.length === 0) {
|
|
94
|
+
return [segment];
|
|
95
|
+
}
|
|
96
|
+
const firstToken = tokens[0];
|
|
97
|
+
const values = parseMealDashValues(firstToken.lower);
|
|
98
|
+
if (!values) {
|
|
99
|
+
return [segment];
|
|
100
|
+
}
|
|
101
|
+
let relation = "meal";
|
|
102
|
+
let relationIndex = -1;
|
|
103
|
+
for (let i = 1; i < tokens.length; i += 1) {
|
|
104
|
+
const lower = tokens[i].lower.replace(/[.,;:]/g, "");
|
|
105
|
+
if (lower === "ac") {
|
|
106
|
+
relation = "ac";
|
|
107
|
+
relationIndex = i;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (lower === "pc") {
|
|
111
|
+
relation = "pc";
|
|
112
|
+
relationIndex = i;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const suffixTokens = tokens
|
|
117
|
+
.filter((token, index) => index !== 0 && index !== relationIndex)
|
|
118
|
+
.map((token) => token.original);
|
|
119
|
+
const events = mealDashEvents(values.length, relation);
|
|
120
|
+
const expanded = values
|
|
121
|
+
.map((value, index) => ({ value, event: events[index] }))
|
|
122
|
+
.filter(({ value }) => value > 0)
|
|
123
|
+
.map(({ value, event }) => {
|
|
124
|
+
const text = [formatMealDashAmount(value), ...suffixTokens, event]
|
|
125
|
+
.filter((part) => part && part.trim().length > 0)
|
|
126
|
+
.join(" ")
|
|
127
|
+
.replace(/\s+/g, " ")
|
|
128
|
+
.trim();
|
|
129
|
+
return {
|
|
130
|
+
text,
|
|
131
|
+
start: segment.start,
|
|
132
|
+
end: segment.end
|
|
133
|
+
};
|
|
134
|
+
})
|
|
135
|
+
.filter((item) => item.text.length > 0);
|
|
136
|
+
if (expanded.length === 0) {
|
|
137
|
+
return [segment];
|
|
138
|
+
}
|
|
139
|
+
return expanded;
|
|
140
|
+
}
|
|
141
|
+
function expandMealDashSegments(segments, options) {
|
|
142
|
+
if (!(options === null || options === void 0 ? void 0 : options.enableMealDashSyntax)) {
|
|
143
|
+
return segments;
|
|
144
|
+
}
|
|
145
|
+
const expanded = [];
|
|
146
|
+
for (const segment of segments) {
|
|
147
|
+
expanded.push(...expandMealDashSegment(segment));
|
|
148
|
+
}
|
|
149
|
+
return expanded;
|
|
150
|
+
}
|
|
61
151
|
function toSegmentMeta(segments) {
|
|
62
152
|
return segments.map((segment, index) => ({
|
|
63
153
|
index,
|
|
@@ -66,7 +156,7 @@ function toSegmentMeta(segments) {
|
|
|
66
156
|
}));
|
|
67
157
|
}
|
|
68
158
|
function parseSig(input, options) {
|
|
69
|
-
const segments = (0, segment_1.splitSigSegments)(input);
|
|
159
|
+
const segments = expandMealDashSegments((0, segment_1.splitSigSegments)(input), options);
|
|
70
160
|
const carry = {};
|
|
71
161
|
const results = [];
|
|
72
162
|
for (const segment of segments) {
|
|
@@ -92,7 +182,7 @@ function parseSig(input, options) {
|
|
|
92
182
|
};
|
|
93
183
|
}
|
|
94
184
|
function lintSig(input, options) {
|
|
95
|
-
const segments = (0, segment_1.splitSigSegments)(input);
|
|
185
|
+
const segments = expandMealDashSegments((0, segment_1.splitSigSegments)(input), options);
|
|
96
186
|
const carry = {};
|
|
97
187
|
const results = [];
|
|
98
188
|
for (const segment of segments) {
|
|
@@ -132,7 +222,7 @@ function lintSig(input, options) {
|
|
|
132
222
|
}
|
|
133
223
|
function parseSigAsync(input, options) {
|
|
134
224
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
const segments = (0, segment_1.splitSigSegments)(input);
|
|
225
|
+
const segments = expandMealDashSegments((0, segment_1.splitSigSegments)(input), options);
|
|
136
226
|
const carry = {};
|
|
137
227
|
const results = [];
|
|
138
228
|
for (const segment of segments) {
|
package/dist/schedule.js
CHANGED
|
@@ -419,6 +419,69 @@ function expandWhenCodes(whenCodes, config, repeat) {
|
|
|
419
419
|
return a.time.localeCompare(b.time);
|
|
420
420
|
});
|
|
421
421
|
}
|
|
422
|
+
const DEFAULT_WHEN_FALLBACK_CLOCKS = {
|
|
423
|
+
[types_1.EventTiming.Wake]: ["06:00:00"],
|
|
424
|
+
[types_1.EventTiming["Early Morning"]]: ["06:00:00"],
|
|
425
|
+
[types_1.EventTiming.Morning]: ["08:00:00"],
|
|
426
|
+
[types_1.EventTiming["Late Morning"]]: ["10:00:00"],
|
|
427
|
+
[types_1.EventTiming.Breakfast]: ["08:00:00"],
|
|
428
|
+
[types_1.EventTiming["Before Breakfast"]]: ["07:30:00"],
|
|
429
|
+
[types_1.EventTiming["After Breakfast"]]: ["08:30:00"],
|
|
430
|
+
[types_1.EventTiming.Noon]: ["12:00:00"],
|
|
431
|
+
[types_1.EventTiming.Lunch]: ["12:30:00"],
|
|
432
|
+
[types_1.EventTiming["Before Lunch"]]: ["12:00:00"],
|
|
433
|
+
[types_1.EventTiming["After Lunch"]]: ["13:00:00"],
|
|
434
|
+
[types_1.EventTiming["Early Afternoon"]]: ["14:00:00"],
|
|
435
|
+
[types_1.EventTiming.Afternoon]: ["15:00:00"],
|
|
436
|
+
[types_1.EventTiming["Late Afternoon"]]: ["16:00:00"],
|
|
437
|
+
[types_1.EventTiming["Early Evening"]]: ["18:00:00"],
|
|
438
|
+
[types_1.EventTiming.Evening]: ["19:00:00"],
|
|
439
|
+
[types_1.EventTiming["Late Evening"]]: ["20:00:00"],
|
|
440
|
+
[types_1.EventTiming.Dinner]: ["18:30:00"],
|
|
441
|
+
[types_1.EventTiming["Before Dinner"]]: ["18:00:00"],
|
|
442
|
+
[types_1.EventTiming["After Dinner"]]: ["19:00:00"],
|
|
443
|
+
[types_1.EventTiming.Night]: ["21:00:00"],
|
|
444
|
+
[types_1.EventTiming["Before Sleep"]]: ["22:00:00"],
|
|
445
|
+
[types_1.EventTiming["After Sleep"]]: ["06:30:00"],
|
|
446
|
+
[types_1.EventTiming.Meal]: ["08:00:00", "12:30:00", "18:30:00"],
|
|
447
|
+
[types_1.EventTiming["Before Meal"]]: ["07:30:00", "12:00:00", "18:00:00"],
|
|
448
|
+
[types_1.EventTiming["After Meal"]]: ["08:30:00", "13:00:00", "19:00:00"]
|
|
449
|
+
};
|
|
450
|
+
function inferWhenFallbackEntries(whenCodes, repeat) {
|
|
451
|
+
const entries = [];
|
|
452
|
+
const seen = new Set();
|
|
453
|
+
const addClock = (clock) => {
|
|
454
|
+
var _a;
|
|
455
|
+
const normalized = normalizeClock(clock);
|
|
456
|
+
const adjusted = repeat.offset
|
|
457
|
+
? applyOffset(normalized, (_a = repeat.offset) !== null && _a !== void 0 ? _a : 0)
|
|
458
|
+
: { time: normalized, dayShift: 0 };
|
|
459
|
+
const key = `${adjusted.dayShift}|${adjusted.time}`;
|
|
460
|
+
if (seen.has(key)) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
seen.add(key);
|
|
464
|
+
entries.push(adjusted);
|
|
465
|
+
};
|
|
466
|
+
for (const code of whenCodes) {
|
|
467
|
+
if (code === types_1.EventTiming.Immediate) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const fallbackClocks = DEFAULT_WHEN_FALLBACK_CLOCKS[code];
|
|
471
|
+
if (!fallbackClocks) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
for (const clock of fallbackClocks) {
|
|
475
|
+
addClock(clock);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return entries.sort((a, b) => {
|
|
479
|
+
if (a.dayShift !== b.dayShift) {
|
|
480
|
+
return a.dayShift - b.dayShift;
|
|
481
|
+
}
|
|
482
|
+
return a.time.localeCompare(b.time);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
422
485
|
function mergeFrequencyDefaults(base, override) {
|
|
423
486
|
var _a, _b, _c, _d;
|
|
424
487
|
if (!base && !override) {
|
|
@@ -571,6 +634,11 @@ function nextDueDoses(dosage, options) {
|
|
|
571
634
|
return a.time.localeCompare(b.time);
|
|
572
635
|
});
|
|
573
636
|
}
|
|
637
|
+
if (expanded.length === 0 &&
|
|
638
|
+
timeOfDayEntries.length === 0 &&
|
|
639
|
+
(!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
|
|
640
|
+
expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
|
|
641
|
+
}
|
|
574
642
|
const includesImmediate = (0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate);
|
|
575
643
|
if (includesImmediate) {
|
|
576
644
|
const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
|
|
@@ -731,6 +799,11 @@ function derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, ti
|
|
|
731
799
|
return a.time.localeCompare(b.time);
|
|
732
800
|
});
|
|
733
801
|
}
|
|
802
|
+
if (expanded.length === 0 &&
|
|
803
|
+
timeOfDayEntries.length === 0 &&
|
|
804
|
+
(!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
|
|
805
|
+
expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
|
|
806
|
+
}
|
|
734
807
|
if ((0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate)) {
|
|
735
808
|
if (recordCandidate(orderedAt) && normalizedCount !== undefined && seen.size >= normalizedCount) {
|
|
736
809
|
return count;
|
|
@@ -981,6 +1054,11 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
981
1054
|
return a.time.localeCompare(b.time);
|
|
982
1055
|
});
|
|
983
1056
|
}
|
|
1057
|
+
if (expanded.length === 0 &&
|
|
1058
|
+
timeOfDayEntries.length === 0 &&
|
|
1059
|
+
(!repeat.frequency || !repeat.period || !repeat.periodUnit)) {
|
|
1060
|
+
expanded.push(...inferWhenFallbackEntries(whenCodes, repeat));
|
|
1061
|
+
}
|
|
984
1062
|
if ((0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate)) {
|
|
985
1063
|
const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
|
|
986
1064
|
if (!orderedAt || orderedAt >= from) {
|
package/dist/types.d.ts
CHANGED
|
@@ -457,6 +457,13 @@ export interface ParseOptions extends FormatOptions {
|
|
|
457
457
|
* `context.mealRelation` when provided.
|
|
458
458
|
*/
|
|
459
459
|
smartMealExpansion?: boolean;
|
|
460
|
+
/**
|
|
461
|
+
* Enables parsing meal dash shorthand like `1-0-1` / `1-0-0-1` into
|
|
462
|
+
* multiple dosage clauses aligned to breakfast/lunch/dinner/(bedtime).
|
|
463
|
+
* Optional trailing `ac` / `pc` maps meal anchors to before/after meal
|
|
464
|
+
* variants.
|
|
465
|
+
*/
|
|
466
|
+
enableMealDashSyntax?: boolean;
|
|
460
467
|
/**
|
|
461
468
|
* Controls which meal pair is assumed for twice-daily meal expansions.
|
|
462
469
|
* Defaults to "breakfast+dinner" to mirror common clinical practice.
|