ezmedicationinput 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/format.js +5 -1
- package/dist/i18n.js +63 -3
- package/dist/maps.js +7 -0
- package/dist/parser.js +39 -16
- package/dist/suggest.js +56 -2
- package/package.json +1 -1
package/dist/format.js
CHANGED
|
@@ -587,6 +587,7 @@ function formatLong(internal) {
|
|
|
587
587
|
const frequencyPart = describeFrequency(internal);
|
|
588
588
|
const eventParts = collectWhenPhrases(internal);
|
|
589
589
|
if ((_b = internal.timeOfDay) === null || _b === void 0 ? void 0 : _b.length) {
|
|
590
|
+
const timeStrings = [];
|
|
590
591
|
for (const time of internal.timeOfDay) {
|
|
591
592
|
const parts = time.split(":");
|
|
592
593
|
const h = parseInt(parts[0], 10);
|
|
@@ -594,7 +595,10 @@ function formatLong(internal) {
|
|
|
594
595
|
const isAm = h < 12;
|
|
595
596
|
const displayH = h % 12 || 12;
|
|
596
597
|
const displayM = m < 10 ? `0${m}` : `${m}`;
|
|
597
|
-
|
|
598
|
+
timeStrings.push(`${displayH}:${displayM}${isAm ? " am" : " pm"}`);
|
|
599
|
+
}
|
|
600
|
+
if (timeStrings.length > 0) {
|
|
601
|
+
eventParts.push(`at ${timeStrings.join(", ")}`);
|
|
598
602
|
}
|
|
599
603
|
}
|
|
600
604
|
const timing = combineFrequencyAndEvents(frequencyPart, eventParts);
|
package/dist/i18n.js
CHANGED
|
@@ -116,6 +116,16 @@ const WHEN_TEXT_THAI = {
|
|
|
116
116
|
[types_1.EventTiming["After Sleep"]]: "หลังจากนอน",
|
|
117
117
|
[types_1.EventTiming.Immediate]: "ทันที"
|
|
118
118
|
};
|
|
119
|
+
const INSTRUCTION_TEXT_THAI = {
|
|
120
|
+
"Take with or after food": "รับประทานพร้อมหรือหลังอาหาร",
|
|
121
|
+
"With or after food": "พร้อมหรือหลังอาหาร",
|
|
122
|
+
"Take before food": "รับประทานก่อนอาหาร",
|
|
123
|
+
"Take on an empty stomach": "รับประทานขณะท้องว่าง",
|
|
124
|
+
"Take with plenty of water": "รับประทานพร้อมน้ำดื่มจำนวนมาก",
|
|
125
|
+
"Dissolve or mix with water before taking": "ละลายหรือผสมน้ำก่อนรับประทาน",
|
|
126
|
+
"Avoid alcoholic drinks": "หลีกเลี่ยงเครื่องดื่มแอลกอฮอล์",
|
|
127
|
+
"May cause drowsiness; do not drive if affected": "อาจทำให้ง่วงซึม; ห้ามขับขี่ยานพาหนะหรือทำงานกับเครื่องจักรหากมีอาการ",
|
|
128
|
+
};
|
|
119
129
|
const DAY_NAMES_THAI = {
|
|
120
130
|
mon: "วันจันทร์",
|
|
121
131
|
tue: "วันอังคาร",
|
|
@@ -634,6 +644,7 @@ function formatAsNeededThai(internal) {
|
|
|
634
644
|
return "ใช้เมื่อจำเป็น";
|
|
635
645
|
}
|
|
636
646
|
function formatShortThai(internal) {
|
|
647
|
+
var _a;
|
|
637
648
|
const parts = [];
|
|
638
649
|
const dose = formatDoseThaiShort(internal);
|
|
639
650
|
if (dose) {
|
|
@@ -671,6 +682,10 @@ function formatShortThai(internal) {
|
|
|
671
682
|
parts.push(events.join(" "));
|
|
672
683
|
}
|
|
673
684
|
}
|
|
685
|
+
if ((_a = internal.timeOfDay) === null || _a === void 0 ? void 0 : _a.length) {
|
|
686
|
+
const times = internal.timeOfDay.map((t) => t.slice(0, 5)).join(",");
|
|
687
|
+
parts.push(times);
|
|
688
|
+
}
|
|
674
689
|
if (internal.dayOfWeek.length) {
|
|
675
690
|
const days = internal.dayOfWeek
|
|
676
691
|
.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; })
|
|
@@ -687,13 +702,27 @@ function formatShortThai(internal) {
|
|
|
687
702
|
return parts.filter(Boolean).join(" ");
|
|
688
703
|
}
|
|
689
704
|
function formatLongThai(internal) {
|
|
690
|
-
var _a;
|
|
705
|
+
var _a, _b;
|
|
691
706
|
const grammar = resolveRouteGrammarThai(internal);
|
|
692
707
|
const dosePart = (_a = formatDoseThaiLong(internal)) !== null && _a !== void 0 ? _a : "ยา";
|
|
693
708
|
const sitePart = formatSiteThai(internal, grammar);
|
|
694
709
|
const routePart = buildRoutePhraseThai(internal, grammar, Boolean(sitePart));
|
|
695
710
|
const frequencyPart = describeFrequencyThai(internal);
|
|
696
711
|
const eventParts = collectWhenPhrasesThai(internal);
|
|
712
|
+
if ((_b = internal.timeOfDay) === null || _b === void 0 ? void 0 : _b.length) {
|
|
713
|
+
const timeStrings = [];
|
|
714
|
+
for (const time of internal.timeOfDay) {
|
|
715
|
+
const parts = time.split(":");
|
|
716
|
+
const h = parseInt(parts[0], 10);
|
|
717
|
+
const m = parseInt(parts[1], 10);
|
|
718
|
+
const displayM = m < 10 ? `0${m}` : `${m}`;
|
|
719
|
+
const displayH = h < 10 ? `0${h}` : `${h}`;
|
|
720
|
+
timeStrings.push(`${displayH}:${displayM}`);
|
|
721
|
+
}
|
|
722
|
+
if (timeStrings.length > 0) {
|
|
723
|
+
eventParts.push(`เวลา ${timeStrings.join(", ")}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
697
726
|
const timing = combineFrequencyAndEventsThai(frequencyPart, eventParts);
|
|
698
727
|
const dayPart = describeDayOfWeekThai(internal);
|
|
699
728
|
const countPart = internal.count !== undefined
|
|
@@ -724,9 +753,40 @@ function formatLongThai(internal) {
|
|
|
724
753
|
}
|
|
725
754
|
const body = segments.filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
726
755
|
if (!body) {
|
|
727
|
-
|
|
756
|
+
const instructionText = formatAdditionalInstructionsThai(internal);
|
|
757
|
+
if (!instructionText) {
|
|
758
|
+
return `${grammar.verb}.`;
|
|
759
|
+
}
|
|
760
|
+
return `${grammar.verb}. ${instructionText}`.trim();
|
|
761
|
+
}
|
|
762
|
+
const instructionText = formatAdditionalInstructionsThai(internal);
|
|
763
|
+
const baseSentence = `${grammar.verb} ${body}.`;
|
|
764
|
+
return instructionText ? `${baseSentence} ${instructionText}` : baseSentence;
|
|
765
|
+
}
|
|
766
|
+
function formatAdditionalInstructionsThai(internal) {
|
|
767
|
+
var _a;
|
|
768
|
+
if (!((_a = internal.additionalInstructions) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
769
|
+
return undefined;
|
|
770
|
+
}
|
|
771
|
+
const phrases = internal.additionalInstructions
|
|
772
|
+
.map((instruction) => {
|
|
773
|
+
var _a;
|
|
774
|
+
const original = instruction.text || ((_a = instruction.coding) === null || _a === void 0 ? void 0 : _a.display);
|
|
775
|
+
if (!original)
|
|
776
|
+
return undefined;
|
|
777
|
+
const normalized = original.trim();
|
|
778
|
+
return INSTRUCTION_TEXT_THAI[normalized] || normalized;
|
|
779
|
+
})
|
|
780
|
+
.filter((text) => Boolean(text))
|
|
781
|
+
.map((text) => text.trim())
|
|
782
|
+
.filter((text) => text.length > 0);
|
|
783
|
+
if (!phrases.length) {
|
|
784
|
+
return undefined;
|
|
728
785
|
}
|
|
729
|
-
return
|
|
786
|
+
return phrases
|
|
787
|
+
.map((phrase) => (/[.!?]$/.test(phrase) ? phrase : `${phrase}.`))
|
|
788
|
+
.join(" ")
|
|
789
|
+
.trim();
|
|
730
790
|
}
|
|
731
791
|
function stripTrailingZero(value) {
|
|
732
792
|
const text = value.toString();
|
package/dist/maps.js
CHANGED
|
@@ -673,6 +673,9 @@ exports.EVENT_TIMING_TOKENS = {
|
|
|
673
673
|
breakfast: types_1.EventTiming.Breakfast,
|
|
674
674
|
bfast: types_1.EventTiming.Breakfast,
|
|
675
675
|
brkfst: types_1.EventTiming.Breakfast,
|
|
676
|
+
meal: types_1.EventTiming.Meal,
|
|
677
|
+
meals: types_1.EventTiming.Meal,
|
|
678
|
+
food: types_1.EventTiming.Meal,
|
|
676
679
|
brk: types_1.EventTiming.Breakfast,
|
|
677
680
|
cd: types_1.EventTiming.Lunch,
|
|
678
681
|
lunch: types_1.EventTiming.Lunch,
|
|
@@ -717,6 +720,10 @@ registerMealKeywords(["dinner", "dinnertime", "supper", "suppertime"], {
|
|
|
717
720
|
pc: types_1.EventTiming["After Dinner"],
|
|
718
721
|
ac: types_1.EventTiming["Before Dinner"]
|
|
719
722
|
});
|
|
723
|
+
registerMealKeywords(["meal", "meals", "food"], {
|
|
724
|
+
pc: types_1.EventTiming["After Meal"],
|
|
725
|
+
ac: types_1.EventTiming["Before Meal"]
|
|
726
|
+
});
|
|
720
727
|
exports.MEAL_KEYWORDS = (0, object_1.objectFromEntries)(MEAL_KEYWORD_ENTRIES);
|
|
721
728
|
exports.DISCOURAGED_TOKENS = {
|
|
722
729
|
qd: "QD",
|
package/dist/parser.js
CHANGED
|
@@ -781,15 +781,40 @@ function tryParseTimeBasedSchedule(internal, tokens, index) {
|
|
|
781
781
|
const token = tokens[index];
|
|
782
782
|
if (internal.consumed.has(token.index))
|
|
783
783
|
return false;
|
|
784
|
-
|
|
785
|
-
|
|
784
|
+
// Handle connectors like "and at" or just "and" before a time.
|
|
785
|
+
// This prevents rogue "and" from leaking into Additional Instructions
|
|
786
|
+
// when it serves as a connector between schedule parts.
|
|
787
|
+
let isAndPrefix = false;
|
|
788
|
+
let isAtPrefix = token.lower === "@" || token.lower === "at";
|
|
789
|
+
if (token.lower === "and" && !isAtPrefix) {
|
|
790
|
+
const next = tokens[index + 1];
|
|
791
|
+
if (next && !internal.consumed.has(next.index)) {
|
|
792
|
+
const nextLower = next.lower;
|
|
793
|
+
// If "and" is followed by "at", "@", or a number, it's a connector for this time block
|
|
794
|
+
if (nextLower === "@" || nextLower === "at" || /^\d/.test(nextLower)) {
|
|
795
|
+
isAndPrefix = true;
|
|
796
|
+
if (nextLower === "@" || nextLower === "at") {
|
|
797
|
+
isAtPrefix = true;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (!isAtPrefix && !isAndPrefix && !/^\d/.test(token.lower))
|
|
786
803
|
return false;
|
|
787
|
-
let nextIndex =
|
|
804
|
+
let nextIndex = index;
|
|
805
|
+
if (isAndPrefix)
|
|
806
|
+
nextIndex++;
|
|
807
|
+
if (isAtPrefix)
|
|
808
|
+
nextIndex++;
|
|
788
809
|
const times = [];
|
|
789
810
|
const consumedIndices = [];
|
|
790
811
|
const timeTokens = [];
|
|
791
|
-
if (
|
|
812
|
+
if (isAndPrefix)
|
|
792
813
|
consumedIndices.push(index);
|
|
814
|
+
if (isAtPrefix) {
|
|
815
|
+
// If we have "and at", at is the second token (index + 1)
|
|
816
|
+
consumedIndices.push(isAndPrefix ? index + 1 : index);
|
|
817
|
+
}
|
|
793
818
|
while (nextIndex < tokens.length) {
|
|
794
819
|
const nextToken = tokens[nextIndex];
|
|
795
820
|
if (!nextToken || internal.consumed.has(nextToken.index))
|
|
@@ -1918,15 +1943,19 @@ function parseInternal(input, options) {
|
|
|
1918
1943
|
: types_1.EventTiming["Before Meal"]);
|
|
1919
1944
|
continue;
|
|
1920
1945
|
}
|
|
1921
|
-
if (token.lower === "at" || token.lower === "@" || token.lower === "on") {
|
|
1946
|
+
if (token.lower === "at" || token.lower === "@" || token.lower === "on" || token.lower === "with") {
|
|
1922
1947
|
if (parseAnchorSequence(internal, tokens, i)) {
|
|
1923
1948
|
continue;
|
|
1924
1949
|
}
|
|
1925
1950
|
if (tryParseTimeBasedSchedule(internal, tokens, i)) {
|
|
1926
1951
|
continue;
|
|
1927
1952
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1953
|
+
// If none of the above consume it, and it's a known anchor prefix, mark it
|
|
1954
|
+
// but only if it's not "with" which might be part of other phrases later.
|
|
1955
|
+
if (token.lower !== "with") {
|
|
1956
|
+
mark(internal.consumed, token);
|
|
1957
|
+
continue;
|
|
1958
|
+
}
|
|
1930
1959
|
}
|
|
1931
1960
|
const nextToken = tokens[i + 1];
|
|
1932
1961
|
if (nextToken && !internal.consumed.has(nextToken.index)) {
|
|
@@ -1938,12 +1967,6 @@ function parseInternal(input, options) {
|
|
|
1938
1967
|
mark(internal.consumed, nextToken);
|
|
1939
1968
|
continue;
|
|
1940
1969
|
}
|
|
1941
|
-
// Issue 2: Support "with meal" and "with food" combos explicitly if needed
|
|
1942
|
-
if (token.lower === "with" && (lowerNext === "meal" || lowerNext === "food")) {
|
|
1943
|
-
applyWhenToken(internal, token, types_1.EventTiming.Meal);
|
|
1944
|
-
mark(internal.consumed, nextToken);
|
|
1945
|
-
continue;
|
|
1946
|
-
}
|
|
1947
1970
|
}
|
|
1948
1971
|
const customWhen = (_g = options === null || options === void 0 ? void 0 : options.whenMap) === null || _g === void 0 ? void 0 : _g[token.lower];
|
|
1949
1972
|
if (customWhen) {
|
|
@@ -2813,12 +2836,12 @@ function findAdditionalInstructionDefinition(text, canonical) {
|
|
|
2813
2836
|
if (!entry.canonical) {
|
|
2814
2837
|
continue;
|
|
2815
2838
|
}
|
|
2839
|
+
// Check for exact canonical match first
|
|
2816
2840
|
if (entry.canonical === canonical) {
|
|
2817
2841
|
return entry.definition;
|
|
2818
2842
|
}
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
}
|
|
2843
|
+
// Avoid broad includes checks (like "with" matching "with meal")
|
|
2844
|
+
// to prevent leakage of common connectors into additional instructions.
|
|
2822
2845
|
for (const term of entry.terms) {
|
|
2823
2846
|
const normalizedTerm = (0, maps_1.normalizeAdditionalInstructionKey)(term);
|
|
2824
2847
|
if (!normalizedTerm) {
|
package/dist/suggest.js
CHANGED
|
@@ -347,6 +347,43 @@ function canonicalizeLowercaseForMatching(value) {
|
|
|
347
347
|
function canonicalizeForMatching(value) {
|
|
348
348
|
return canonicalizeLowercaseForMatching(value.toLowerCase());
|
|
349
349
|
}
|
|
350
|
+
function buildTimeTokens(input) {
|
|
351
|
+
const tokens = new Set();
|
|
352
|
+
// Add common times
|
|
353
|
+
for (let i = 1; i <= 12; i++) {
|
|
354
|
+
tokens.add(`at ${i}:00 am`);
|
|
355
|
+
tokens.add(`at ${i}:00 pm`);
|
|
356
|
+
}
|
|
357
|
+
// Analyze input for specific time requests to provide more granular suggestions
|
|
358
|
+
const match = input.match(/(?:at|@)\s*(\d{1,2})(?::(\d{0,2}))?/i);
|
|
359
|
+
if (match) {
|
|
360
|
+
const h = parseInt(match[1], 10);
|
|
361
|
+
if (h >= 1 && h <= 12) {
|
|
362
|
+
const m = match[2] || "00";
|
|
363
|
+
if (m.length === 1) {
|
|
364
|
+
tokens.add(`at ${h}:${m}0 am`);
|
|
365
|
+
tokens.add(`at ${h}:${m}0 pm`);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
tokens.add(`at ${h}:${m} am`);
|
|
369
|
+
tokens.add(`at ${h}:${m} pm`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else if (h > 12 && h < 24) {
|
|
373
|
+
// Input seems to be 24h, but we format as am/pm usually.
|
|
374
|
+
// Let's add the 24h format as well if that's what they are typing?
|
|
375
|
+
// Or convert to am/pm? Let's add both for robustness.
|
|
376
|
+
const m = match[2] || "00";
|
|
377
|
+
if (m.length === 1) {
|
|
378
|
+
tokens.add(`at ${h}:${m}0`);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
tokens.add(`at ${h}:${m}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return [...tokens];
|
|
386
|
+
}
|
|
350
387
|
function tokensMatch(prefixTokens, candidateTokens) {
|
|
351
388
|
if (prefixTokens.length === 0) {
|
|
352
389
|
return true;
|
|
@@ -461,7 +498,7 @@ function getCandidateFingerprint(candidateLower) {
|
|
|
461
498
|
}
|
|
462
499
|
return fingerprint;
|
|
463
500
|
}
|
|
464
|
-
function generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences, limit, matcher) {
|
|
501
|
+
function generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, timeTokens, whenSequences, limit, matcher) {
|
|
465
502
|
const suggestions = [];
|
|
466
503
|
const seen = new Set();
|
|
467
504
|
const doseVariantMap = new Map();
|
|
@@ -502,6 +539,7 @@ function generateCandidateDirections(pairs, doseValues, prnReasons, intervalToke
|
|
|
502
539
|
const whenSuffixes = whenSequences === PRECOMPUTED_WHEN_SEQUENCES
|
|
503
540
|
? PRECOMPUTED_WHEN_SEQUENCE_SUFFIXES
|
|
504
541
|
: whenSequences.map((sequence) => ` ${sequence.join(" ")}`);
|
|
542
|
+
const timeSuffixes = timeTokens.map((token) => ` ${token}`);
|
|
505
543
|
for (let pairIndex = 0; pairIndex < pairs.length; pairIndex += 1) {
|
|
506
544
|
const pair = pairs[pairIndex];
|
|
507
545
|
const unitVariants = getUnitVariants(pair.unit);
|
|
@@ -593,6 +631,21 @@ function generateCandidateDirections(pairs, doseValues, prnReasons, intervalToke
|
|
|
593
631
|
return suggestions;
|
|
594
632
|
}
|
|
595
633
|
}
|
|
634
|
+
for (let timeIndex = 0; timeIndex < timeSuffixes.length; timeIndex += 1) {
|
|
635
|
+
const timeSuffix = timeSuffixes[timeIndex];
|
|
636
|
+
for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
|
|
637
|
+
const doseBases = unitDoseVariants[unitIndex];
|
|
638
|
+
for (let doseIndex = 0; doseIndex < doseBases.length; doseIndex += 1) {
|
|
639
|
+
const base = doseBases[doseIndex];
|
|
640
|
+
if (push(base.value + timeSuffix, base.lower + timeSuffix)) {
|
|
641
|
+
return suggestions;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (push(route + timeSuffix, routeLower + timeSuffix)) {
|
|
646
|
+
return suggestions;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
596
649
|
for (let reasonIndex = 0; reasonIndex < prnSuffixes.length; reasonIndex += 1) {
|
|
597
650
|
const reasonSuffix = prnSuffixes[reasonIndex];
|
|
598
651
|
for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
|
|
@@ -712,7 +765,8 @@ function suggestSig(input, options) {
|
|
|
712
765
|
const doseValues = buildDoseValues(input);
|
|
713
766
|
const prnReasons = buildPrnReasons(options === null || options === void 0 ? void 0 : options.prnReasons);
|
|
714
767
|
const intervalTokens = buildIntervalTokens(input);
|
|
768
|
+
const timeTokens = buildTimeTokens(input);
|
|
715
769
|
const whenSequences = PRECOMPUTED_WHEN_SEQUENCES;
|
|
716
770
|
const matcher = (candidate, candidateLower) => matchesPrefix(candidate, candidateLower, prefixContext);
|
|
717
|
-
return generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences, limit, matcher);
|
|
771
|
+
return generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, timeTokens, whenSequences, limit, matcher);
|
|
718
772
|
}
|