ezmedicationinput 0.1.1 → 0.1.3
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 +60 -20
- package/dist/context.js +10 -5
- package/dist/fhir.d.ts +1 -1
- package/dist/fhir.js +40 -32
- package/dist/format.d.ts +3 -2
- package/dist/format.js +371 -86
- package/dist/i18n.d.ts +31 -0
- package/dist/i18n.js +664 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +53 -19
- package/dist/internal-types.d.ts +33 -0
- package/dist/internal-types.js +2 -0
- package/dist/maps.d.ts +2 -0
- package/dist/maps.js +543 -350
- package/dist/package.json +3 -0
- package/dist/parser.d.ts +2 -31
- package/dist/parser.js +809 -142
- package/dist/safety.js +7 -4
- package/dist/schedule.js +148 -76
- package/dist/suggest.js +391 -79
- package/dist/types.d.ts +26 -8
- package/dist/types.js +12 -9
- package/dist/utils/array.d.ts +1 -0
- package/dist/utils/array.js +11 -0
- package/dist/utils/enum.d.ts +2 -0
- package/dist/utils/enum.js +7 -0
- package/dist/utils/object.d.ts +7 -0
- package/dist/utils/object.js +34 -0
- package/package.json +2 -2
package/dist/parser.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tokenize = tokenize;
|
|
4
|
+
exports.parseInternal = parseInternal;
|
|
5
|
+
const maps_1 = require("./maps");
|
|
6
|
+
const context_1 = require("./context");
|
|
7
|
+
const safety_1 = require("./safety");
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const object_1 = require("./utils/object");
|
|
10
|
+
const array_1 = require("./utils/array");
|
|
5
11
|
const BODY_SITE_HINTS = new Set([
|
|
6
12
|
"left",
|
|
7
13
|
"right",
|
|
@@ -31,75 +37,492 @@ const BODY_SITE_HINTS = new Set([
|
|
|
31
37
|
"upper",
|
|
32
38
|
"lower",
|
|
33
39
|
"forearm",
|
|
34
|
-
"back"
|
|
40
|
+
"back",
|
|
41
|
+
"mouth",
|
|
42
|
+
"tongue",
|
|
43
|
+
"tongues",
|
|
44
|
+
"cheek",
|
|
45
|
+
"cheeks",
|
|
46
|
+
"gum",
|
|
47
|
+
"gums",
|
|
48
|
+
"tooth",
|
|
49
|
+
"teeth",
|
|
50
|
+
"nose",
|
|
51
|
+
"nares",
|
|
52
|
+
"hair",
|
|
53
|
+
"skin",
|
|
54
|
+
"scalp",
|
|
55
|
+
"face",
|
|
56
|
+
"forehead",
|
|
57
|
+
"chin",
|
|
58
|
+
"neck",
|
|
59
|
+
"buttock",
|
|
60
|
+
"buttocks",
|
|
61
|
+
"gluteal",
|
|
62
|
+
"glute",
|
|
63
|
+
"muscle",
|
|
64
|
+
"muscles",
|
|
65
|
+
"vein",
|
|
66
|
+
"veins",
|
|
67
|
+
"vagina",
|
|
68
|
+
"vaginal",
|
|
69
|
+
"rectum",
|
|
70
|
+
"rectal",
|
|
71
|
+
"anus",
|
|
72
|
+
"perineum"
|
|
73
|
+
]);
|
|
74
|
+
const SITE_CONNECTORS = new Set(["to", "in", "into", "on", "onto", "at"]);
|
|
75
|
+
const SITE_FILLER_WORDS = new Set([
|
|
76
|
+
"the",
|
|
77
|
+
"a",
|
|
78
|
+
"an",
|
|
79
|
+
"your",
|
|
80
|
+
"his",
|
|
81
|
+
"her",
|
|
82
|
+
"their",
|
|
83
|
+
"my"
|
|
84
|
+
]);
|
|
85
|
+
const HOUSEHOLD_VOLUME_UNIT_SET = new Set(maps_1.HOUSEHOLD_VOLUME_UNITS.map((unit) => unit.toLowerCase()));
|
|
86
|
+
const OCULAR_DIRECTION_WORDS = new Set([
|
|
87
|
+
"left",
|
|
88
|
+
"right",
|
|
89
|
+
"both",
|
|
90
|
+
"either",
|
|
91
|
+
"each",
|
|
92
|
+
"bilateral"
|
|
93
|
+
]);
|
|
94
|
+
const OCULAR_SITE_WORDS = new Set([
|
|
95
|
+
"eye",
|
|
96
|
+
"eyes",
|
|
97
|
+
"eyelid",
|
|
98
|
+
"eyelids",
|
|
99
|
+
"ocular",
|
|
100
|
+
"ophthalmic",
|
|
101
|
+
"oculus"
|
|
35
102
|
]);
|
|
36
103
|
const COMBO_EVENT_TIMINGS = {
|
|
37
|
-
"early morning": EventTiming["Early Morning"],
|
|
38
|
-
"late morning": EventTiming["Late Morning"],
|
|
39
|
-
"early afternoon": EventTiming["Early Afternoon"],
|
|
40
|
-
"late afternoon": EventTiming["Late Afternoon"],
|
|
41
|
-
"early evening": EventTiming["Early Evening"],
|
|
42
|
-
"late evening": EventTiming["Late Evening"],
|
|
43
|
-
"after sleep": EventTiming["After Sleep"],
|
|
44
|
-
"upon waking": EventTiming.Wake
|
|
104
|
+
"early morning": types_1.EventTiming["Early Morning"],
|
|
105
|
+
"late morning": types_1.EventTiming["Late Morning"],
|
|
106
|
+
"early afternoon": types_1.EventTiming["Early Afternoon"],
|
|
107
|
+
"late afternoon": types_1.EventTiming["Late Afternoon"],
|
|
108
|
+
"early evening": types_1.EventTiming["Early Evening"],
|
|
109
|
+
"late evening": types_1.EventTiming["Late Evening"],
|
|
110
|
+
"after sleep": types_1.EventTiming["After Sleep"],
|
|
111
|
+
"upon waking": types_1.EventTiming.Wake
|
|
45
112
|
};
|
|
46
113
|
// Tracking explicit breakfast/lunch/dinner markers lets the meal-expansion
|
|
47
114
|
// logic bail early when the clinician already specified precise events.
|
|
48
115
|
const SPECIFIC_MEAL_TIMINGS = new Set([
|
|
49
|
-
EventTiming["Before Breakfast"],
|
|
50
|
-
EventTiming["Before Lunch"],
|
|
51
|
-
EventTiming["Before Dinner"],
|
|
52
|
-
EventTiming["After Breakfast"],
|
|
53
|
-
EventTiming["After Lunch"],
|
|
54
|
-
EventTiming["After Dinner"],
|
|
55
|
-
EventTiming.Breakfast,
|
|
56
|
-
EventTiming.Lunch,
|
|
57
|
-
EventTiming.Dinner
|
|
116
|
+
types_1.EventTiming["Before Breakfast"],
|
|
117
|
+
types_1.EventTiming["Before Lunch"],
|
|
118
|
+
types_1.EventTiming["Before Dinner"],
|
|
119
|
+
types_1.EventTiming["After Breakfast"],
|
|
120
|
+
types_1.EventTiming["After Lunch"],
|
|
121
|
+
types_1.EventTiming["After Dinner"],
|
|
122
|
+
types_1.EventTiming.Breakfast,
|
|
123
|
+
types_1.EventTiming.Lunch,
|
|
124
|
+
types_1.EventTiming.Dinner
|
|
58
125
|
]);
|
|
59
126
|
// Ocular shorthand tokens commonly used in ophthalmic sigs.
|
|
60
127
|
const EYE_SITE_TOKENS = {
|
|
61
|
-
od: { site: "right eye", route: RouteCode["Ophthalmic route"] },
|
|
62
|
-
re: { site: "right eye", route: RouteCode["Ophthalmic route"] },
|
|
63
|
-
os: { site: "left eye", route: RouteCode["Ophthalmic route"] },
|
|
64
|
-
le: { site: "left eye", route: RouteCode["Ophthalmic route"] },
|
|
65
|
-
ou: { site: "both eyes", route: RouteCode["Ophthalmic route"] },
|
|
66
|
-
be: { site: "both eyes", route: RouteCode["Ophthalmic route"] },
|
|
128
|
+
od: { site: "right eye", route: types_1.RouteCode["Ophthalmic route"] },
|
|
129
|
+
re: { site: "right eye", route: types_1.RouteCode["Ophthalmic route"] },
|
|
130
|
+
os: { site: "left eye", route: types_1.RouteCode["Ophthalmic route"] },
|
|
131
|
+
le: { site: "left eye", route: types_1.RouteCode["Ophthalmic route"] },
|
|
132
|
+
ou: { site: "both eyes", route: types_1.RouteCode["Ophthalmic route"] },
|
|
133
|
+
be: { site: "both eyes", route: types_1.RouteCode["Ophthalmic route"] },
|
|
67
134
|
vod: {
|
|
68
135
|
site: "right eye",
|
|
69
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
136
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
70
137
|
},
|
|
71
138
|
vos: {
|
|
72
139
|
site: "left eye",
|
|
73
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
140
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
74
141
|
},
|
|
75
142
|
ivtod: {
|
|
76
143
|
site: "right eye",
|
|
77
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
144
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
78
145
|
},
|
|
79
146
|
ivtre: {
|
|
80
147
|
site: "right eye",
|
|
81
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
148
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
82
149
|
},
|
|
83
150
|
ivtos: {
|
|
84
151
|
site: "left eye",
|
|
85
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
152
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
86
153
|
},
|
|
87
154
|
ivtle: {
|
|
88
155
|
site: "left eye",
|
|
89
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
156
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
90
157
|
},
|
|
91
158
|
ivtou: {
|
|
92
159
|
site: "both eyes",
|
|
93
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
160
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
94
161
|
},
|
|
95
162
|
ivtbe: {
|
|
96
163
|
site: "both eyes",
|
|
97
|
-
route: RouteCode["Intravitreal route (qualifier value)"]
|
|
164
|
+
route: types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
98
165
|
}
|
|
99
166
|
};
|
|
100
|
-
|
|
167
|
+
const OPHTHALMIC_ROUTE_CODES = new Set([
|
|
168
|
+
types_1.RouteCode["Ophthalmic route"],
|
|
169
|
+
types_1.RouteCode["Intravitreal route (qualifier value)"]
|
|
170
|
+
]);
|
|
171
|
+
const OPHTHALMIC_CONTEXT_TOKENS = new Set([
|
|
172
|
+
"drop",
|
|
173
|
+
"drops",
|
|
174
|
+
"gtt",
|
|
175
|
+
"gtts",
|
|
176
|
+
"eye",
|
|
177
|
+
"eyes",
|
|
178
|
+
"eyelid",
|
|
179
|
+
"eyelids",
|
|
180
|
+
"ocular",
|
|
181
|
+
"ophthalmic",
|
|
182
|
+
"ophth",
|
|
183
|
+
"oculus",
|
|
184
|
+
"os",
|
|
185
|
+
"ou",
|
|
186
|
+
"re",
|
|
187
|
+
"le",
|
|
188
|
+
"be"
|
|
189
|
+
]);
|
|
190
|
+
function normalizeTokenLower(token) {
|
|
191
|
+
return token.lower.replace(/\./g, "");
|
|
192
|
+
}
|
|
193
|
+
function hasOphthalmicContextHint(tokens, index) {
|
|
194
|
+
for (let offset = -3; offset <= 3; offset++) {
|
|
195
|
+
if (offset === 0) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const neighbor = tokens[index + offset];
|
|
199
|
+
if (!neighbor) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const normalized = normalizeTokenLower(neighbor);
|
|
203
|
+
if (OPHTHALMIC_CONTEXT_TOKENS.has(normalized) || normalized.includes("eye")) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
function shouldInterpretOdAsOnceDaily(internal, tokens, index, treatAsSite) {
|
|
210
|
+
var _a;
|
|
211
|
+
if (treatAsSite) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const hasCadenceAssigned = internal.frequency !== undefined ||
|
|
215
|
+
internal.frequencyMax !== undefined ||
|
|
216
|
+
internal.period !== undefined ||
|
|
217
|
+
internal.periodMax !== undefined ||
|
|
218
|
+
internal.timingCode !== undefined;
|
|
219
|
+
const hasPriorSiteContext = hasBodySiteContextBefore(internal, tokens, index);
|
|
220
|
+
const hasUpcomingSiteContext = hasBodySiteContextAfter(internal, tokens, index);
|
|
221
|
+
const previous = tokens[index - 1];
|
|
222
|
+
const previousNormalized = previous ? normalizeTokenLower(previous) : undefined;
|
|
223
|
+
const previousIsOd = previousNormalized === "od";
|
|
224
|
+
const previousConsumed = previousIsOd && internal.consumed.has(previous.index);
|
|
225
|
+
const previousOdProvidedSite = previousConsumed && /eye/i.test((_a = internal.siteText) !== null && _a !== void 0 ? _a : "");
|
|
226
|
+
if (previousOdProvidedSite) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
const previousEyeToken = previousNormalized && previousNormalized !== "od"
|
|
230
|
+
? EYE_SITE_TOKENS[previousNormalized]
|
|
231
|
+
: undefined;
|
|
232
|
+
if (previousEyeToken && internal.consumed.has(previous.index)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (previousNormalized === "od" &&
|
|
236
|
+
internal.siteSource === "abbreviation" &&
|
|
237
|
+
internal.siteText &&
|
|
238
|
+
/eye/i.test(internal.siteText)) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (hasPriorSiteContext || hasUpcomingSiteContext) {
|
|
242
|
+
return !hasCadenceAssigned;
|
|
243
|
+
}
|
|
244
|
+
if (hasCadenceAssigned) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (internal.routeCode && !OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) {
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (internal.unit && internal.unit !== "drop") {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
if (internal.siteText && !/eye/i.test(internal.siteText)) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
const hasNonOdToken = tokens.some((token, tokenIndex) => {
|
|
257
|
+
if (tokenIndex === index) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
return normalizeTokenLower(token) !== "od";
|
|
261
|
+
});
|
|
262
|
+
if (!hasNonOdToken) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const ophthalmicContext = hasOphthalmicContextHint(tokens, index) ||
|
|
266
|
+
(internal.routeCode !== undefined && OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) ||
|
|
267
|
+
(internal.siteText !== undefined && /eye/i.test(internal.siteText));
|
|
268
|
+
if (ophthalmicContext && hasSpelledOcularSiteBefore(tokens, index)) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
return !ophthalmicContext;
|
|
272
|
+
}
|
|
273
|
+
function hasBodySiteContextBefore(internal, tokens, index) {
|
|
274
|
+
const currentToken = tokens[index];
|
|
275
|
+
const currentTokenIndex = currentToken ? currentToken.index : index;
|
|
276
|
+
if (internal.siteText) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
for (const tokenIndex of internal.siteTokenIndices) {
|
|
280
|
+
if (tokenIndex < currentTokenIndex) {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (let i = 0; i < index; i++) {
|
|
285
|
+
const token = tokens[i];
|
|
286
|
+
if (!token) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (internal.consumed.has(token.index)) {
|
|
290
|
+
if (internal.siteTokenIndices.has(token.index) && token.index < currentTokenIndex) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const normalized = normalizeTokenLower(token);
|
|
296
|
+
if (BODY_SITE_HINTS.has(normalized)) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (EYE_SITE_TOKENS[normalized]) {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
function hasBodySiteContextAfter(internal, tokens, index) {
|
|
306
|
+
const currentToken = tokens[index];
|
|
307
|
+
const currentTokenIndex = currentToken ? currentToken.index : index;
|
|
308
|
+
for (const tokenIndex of internal.siteTokenIndices) {
|
|
309
|
+
if (tokenIndex > currentTokenIndex) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
let seenConnector = false;
|
|
314
|
+
for (let i = index + 1; i < tokens.length; i++) {
|
|
315
|
+
const token = tokens[i];
|
|
316
|
+
if (!token) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (internal.consumed.has(token.index)) {
|
|
320
|
+
if (internal.siteTokenIndices.has(token.index) && token.index > currentTokenIndex) {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const normalized = normalizeTokenLower(token);
|
|
326
|
+
if (SITE_CONNECTORS.has(normalized)) {
|
|
327
|
+
seenConnector = true;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (SITE_FILLER_WORDS.has(normalized)) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (BODY_SITE_HINTS.has(normalized)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
if (seenConnector) {
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
if (!seenConnector) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
function hasSpelledOcularSiteBefore(tokens, index) {
|
|
346
|
+
let hasOcularWord = false;
|
|
347
|
+
let hasDirectionalCue = false;
|
|
348
|
+
for (let i = 0; i < index; i++) {
|
|
349
|
+
const token = tokens[i];
|
|
350
|
+
if (!token) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const normalized = normalizeTokenLower(token);
|
|
354
|
+
if (SITE_CONNECTORS.has(normalized) || OCULAR_DIRECTION_WORDS.has(normalized)) {
|
|
355
|
+
hasDirectionalCue = true;
|
|
356
|
+
}
|
|
357
|
+
if (OCULAR_SITE_WORDS.has(normalized) || normalized.includes("eye")) {
|
|
358
|
+
hasOcularWord = true;
|
|
359
|
+
}
|
|
360
|
+
if (hasDirectionalCue && hasOcularWord) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
function shouldTreatEyeTokenAsSite(internal, tokens, index, context) {
|
|
367
|
+
var _a;
|
|
368
|
+
const currentToken = tokens[index];
|
|
369
|
+
const normalizedSelf = normalizeTokenLower(currentToken);
|
|
370
|
+
const eyeMeta = EYE_SITE_TOKENS[normalizedSelf];
|
|
371
|
+
if (internal.routeCode && !OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
if (internal.siteText) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
if (internal.siteSource === "abbreviation") {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
const dosageForm = (_a = context === null || context === void 0 ? void 0 : context.dosageForm) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
381
|
+
const contextImpliesOphthalmic = Boolean(dosageForm && /(eye|ophth|ocular|intravit)/i.test(dosageForm));
|
|
382
|
+
const eyeRouteImpliesOphthalmic = (eyeMeta === null || eyeMeta === void 0 ? void 0 : eyeMeta.route) === types_1.RouteCode["Intravitreal route (qualifier value)"];
|
|
383
|
+
const ophthalmicContext = hasOphthalmicContextHint(tokens, index) ||
|
|
384
|
+
(internal.routeCode !== undefined && OPHTHALMIC_ROUTE_CODES.has(internal.routeCode)) ||
|
|
385
|
+
contextImpliesOphthalmic ||
|
|
386
|
+
eyeRouteImpliesOphthalmic;
|
|
387
|
+
if (hasBodySiteContextAfter(internal, tokens, index)) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
if (!ophthalmicContext) {
|
|
391
|
+
const hasOtherActiveTokens = tokens.some((token, tokenIndex) => tokenIndex !== index && !internal.consumed.has(token.index));
|
|
392
|
+
const onlyEyeTokens = tokens.every((token, tokenIndex) => {
|
|
393
|
+
if (tokenIndex === index || internal.consumed.has(token.index)) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
return normalizeTokenLower(token) === "od";
|
|
397
|
+
});
|
|
398
|
+
if (!hasOtherActiveTokens) {
|
|
399
|
+
return internal.unit === undefined && internal.routeCode === undefined;
|
|
400
|
+
}
|
|
401
|
+
if (onlyEyeTokens) {
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
for (let i = 0; i < index; i++) {
|
|
407
|
+
const candidate = tokens[i];
|
|
408
|
+
if (internal.consumed.has(candidate.index)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const normalized = normalizeTokenLower(candidate);
|
|
412
|
+
if (SITE_CONNECTORS.has(normalized)) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (BODY_SITE_HINTS.has(normalized)) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (EYE_SITE_TOKENS[normalized]) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
if (maps_1.DEFAULT_ROUTE_SYNONYMS[normalized]) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
function tryParseNumericCadence(internal, tokens, index) {
|
|
428
|
+
const token = tokens[index];
|
|
429
|
+
if (!/^[0-9]+(?:\.[0-9]+)?$/.test(token.lower)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (internal.frequency !== undefined ||
|
|
433
|
+
internal.frequencyMax !== undefined ||
|
|
434
|
+
internal.period !== undefined ||
|
|
435
|
+
internal.periodMax !== undefined) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
let nextIndex = index + 1;
|
|
439
|
+
const connectors = [];
|
|
440
|
+
while (true) {
|
|
441
|
+
const connector = tokens[nextIndex];
|
|
442
|
+
if (!connector || internal.consumed.has(connector.index)) {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
const normalized = normalizeTokenLower(connector);
|
|
446
|
+
if (normalized === "per" || normalized === "a" || normalized === "each" || normalized === "every") {
|
|
447
|
+
connectors.push(connector);
|
|
448
|
+
nextIndex += 1;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
if (!connectors.length) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
const unitToken = tokens[nextIndex];
|
|
457
|
+
if (!unitToken || internal.consumed.has(unitToken.index)) {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
const unitCode = mapIntervalUnit(normalizeTokenLower(unitToken));
|
|
461
|
+
if (!unitCode) {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
const value = parseFloat(token.original);
|
|
465
|
+
if (!Number.isFinite(value)) {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
internal.frequency = value;
|
|
469
|
+
internal.period = 1;
|
|
470
|
+
internal.periodUnit = unitCode;
|
|
471
|
+
if (value === 1 && unitCode === types_1.FhirPeriodUnit.Day && !internal.timingCode) {
|
|
472
|
+
internal.timingCode = "QD";
|
|
473
|
+
}
|
|
474
|
+
mark(internal.consumed, token);
|
|
475
|
+
for (const connector of connectors) {
|
|
476
|
+
mark(internal.consumed, connector);
|
|
477
|
+
}
|
|
478
|
+
mark(internal.consumed, unitToken);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
const SITE_UNIT_ROUTE_HINTS = [
|
|
482
|
+
{ pattern: /\beye(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
|
|
483
|
+
{ pattern: /\beyelid(s)?\b/i, route: types_1.RouteCode["Ophthalmic route"] },
|
|
484
|
+
{ pattern: /\bintravitreal\b/i, route: types_1.RouteCode["Intravitreal route (qualifier value)"] },
|
|
485
|
+
{ pattern: /\bear(s)?\b/i, route: types_1.RouteCode["Otic route"] },
|
|
486
|
+
{ pattern: /\bnostril(s)?\b/i, route: types_1.RouteCode["Nasal route"] },
|
|
487
|
+
{ pattern: /\bnares?\b/i, route: types_1.RouteCode["Nasal route"] },
|
|
488
|
+
{ pattern: /\bnose\b/i, route: types_1.RouteCode["Nasal route"] },
|
|
489
|
+
{ pattern: /\bmouth\b/i, route: types_1.RouteCode["Oral route"] },
|
|
490
|
+
{ pattern: /\boral\b/i, route: types_1.RouteCode["Oral route"] },
|
|
491
|
+
{ pattern: /\bunder (the )?tongue\b/i, route: types_1.RouteCode["Sublingual route"] },
|
|
492
|
+
{ pattern: /\btongue\b/i, route: types_1.RouteCode["Sublingual route"] },
|
|
493
|
+
{ pattern: /\bcheek(s)?\b/i, route: types_1.RouteCode["Buccal route"] },
|
|
494
|
+
{ pattern: /\blung(s)?\b/i, route: types_1.RouteCode["Respiratory tract route (qualifier value)"] },
|
|
495
|
+
{ pattern: /\brespiratory tract\b/i, route: types_1.RouteCode["Respiratory tract route (qualifier value)"] },
|
|
496
|
+
{ pattern: /\bskin\b/i, route: types_1.RouteCode["Topical route"] },
|
|
497
|
+
{ pattern: /\bscalp\b/i, route: types_1.RouteCode["Topical route"] },
|
|
498
|
+
{ pattern: /\bface\b/i, route: types_1.RouteCode["Topical route"] },
|
|
499
|
+
{ pattern: /\bhand(s)?\b/i, route: types_1.RouteCode["Topical route"] },
|
|
500
|
+
{ pattern: /(\bfoot\b|\bfeet\b)/i, route: types_1.RouteCode["Topical route"] },
|
|
501
|
+
{ pattern: /\belbow(s)?\b/i, route: types_1.RouteCode["Topical route"] },
|
|
502
|
+
{ pattern: /\bknee(s)?\b/i, route: types_1.RouteCode["Topical route"] },
|
|
503
|
+
{ pattern: /\bleg(s)?\b/i, route: types_1.RouteCode["Topical route"] },
|
|
504
|
+
{ pattern: /\barm(s)?\b/i, route: types_1.RouteCode["Topical route"] },
|
|
505
|
+
{ pattern: /\bpatch(es)?\b/i, route: types_1.RouteCode["Transdermal route"] },
|
|
506
|
+
{ pattern: /\babdomen\b/i, route: types_1.RouteCode["Subcutaneous route"] },
|
|
507
|
+
{ pattern: /\bbelly\b/i, route: types_1.RouteCode["Subcutaneous route"] },
|
|
508
|
+
{ pattern: /\bstomach\b/i, route: types_1.RouteCode["Subcutaneous route"] },
|
|
509
|
+
{ pattern: /\bthigh(s)?\b/i, route: types_1.RouteCode["Subcutaneous route"] },
|
|
510
|
+
{ pattern: /\bupper arm\b/i, route: types_1.RouteCode["Subcutaneous route"] },
|
|
511
|
+
{ pattern: /\bbuttock(s)?\b/i, route: types_1.RouteCode["Intramuscular route"] },
|
|
512
|
+
{ pattern: /\bglute(al)?\b/i, route: types_1.RouteCode["Intramuscular route"] },
|
|
513
|
+
{ pattern: /\bdeltoid\b/i, route: types_1.RouteCode["Intramuscular route"] },
|
|
514
|
+
{ pattern: /\bmuscle(s)?\b/i, route: types_1.RouteCode["Intramuscular route"] },
|
|
515
|
+
{ pattern: /\bvein(s)?\b/i, route: types_1.RouteCode["Intravenous route"] },
|
|
516
|
+
{ pattern: /\brectum\b/i, route: types_1.RouteCode["Per rectum"] },
|
|
517
|
+
{ pattern: /\banus\b/i, route: types_1.RouteCode["Per rectum"] },
|
|
518
|
+
{ pattern: /\brectal\b/i, route: types_1.RouteCode["Per rectum"] },
|
|
519
|
+
{ pattern: /\bvagina\b/i, route: types_1.RouteCode["Per vagina"] },
|
|
520
|
+
{ pattern: /\bvaginal\b/i, route: types_1.RouteCode["Per vagina"] }
|
|
521
|
+
];
|
|
522
|
+
function tokenize(input) {
|
|
101
523
|
const separators = /[(),]/g;
|
|
102
524
|
let normalized = input.trim().replace(separators, " ");
|
|
525
|
+
normalized = normalized.replace(/(\d+(?:\.\d+)?)\s*\/\s*(d|day|days|wk|w|week|weeks|mo|month|months|hr|hrs|hour|hours|h|min|mins|minute|minutes)\b/gi, (_match, value, unit) => `${value} per ${unit}`);
|
|
103
526
|
normalized = normalized.replace(/(\d+)\s*\/\s*(\d+)/g, (match, num, den) => {
|
|
104
527
|
const numerator = parseFloat(num);
|
|
105
528
|
const denominator = parseFloat(den);
|
|
@@ -154,7 +577,7 @@ function mark(consumed, token) {
|
|
|
154
577
|
consumed.add(token.index);
|
|
155
578
|
}
|
|
156
579
|
function addWhen(target, code) {
|
|
157
|
-
if (!
|
|
580
|
+
if (!(0, array_1.arrayIncludes)(target, code)) {
|
|
158
581
|
target.push(code);
|
|
159
582
|
}
|
|
160
583
|
}
|
|
@@ -167,74 +590,184 @@ function removeWhen(target, code) {
|
|
|
167
590
|
index = target.indexOf(code);
|
|
168
591
|
}
|
|
169
592
|
}
|
|
593
|
+
const DEFAULT_EVENT_TIMING_WEIGHTS = {
|
|
594
|
+
[types_1.EventTiming.Immediate]: 0,
|
|
595
|
+
[types_1.EventTiming.Wake]: 6 * 3600,
|
|
596
|
+
[types_1.EventTiming["After Sleep"]]: 6 * 3600 + 15 * 60,
|
|
597
|
+
[types_1.EventTiming["Early Morning"]]: 7 * 3600,
|
|
598
|
+
[types_1.EventTiming["Before Meal"]]: 7 * 3600 + 30 * 60,
|
|
599
|
+
[types_1.EventTiming["Before Breakfast"]]: 7 * 3600 + 45 * 60,
|
|
600
|
+
[types_1.EventTiming.Morning]: 8 * 3600,
|
|
601
|
+
[types_1.EventTiming.Breakfast]: 8 * 3600 + 15 * 60,
|
|
602
|
+
[types_1.EventTiming.Meal]: 8 * 3600 + 30 * 60,
|
|
603
|
+
[types_1.EventTiming["After Breakfast"]]: 9 * 3600,
|
|
604
|
+
[types_1.EventTiming["After Meal"]]: 9 * 3600 + 15 * 60,
|
|
605
|
+
[types_1.EventTiming["Late Morning"]]: 10 * 3600 + 30 * 60,
|
|
606
|
+
[types_1.EventTiming["Before Lunch"]]: 11 * 3600 + 45 * 60,
|
|
607
|
+
[types_1.EventTiming.Noon]: 12 * 3600,
|
|
608
|
+
[types_1.EventTiming.Lunch]: 12 * 3600 + 15 * 60,
|
|
609
|
+
[types_1.EventTiming["After Lunch"]]: 12 * 3600 + 45 * 60,
|
|
610
|
+
[types_1.EventTiming["Early Afternoon"]]: 13 * 3600 + 30 * 60,
|
|
611
|
+
[types_1.EventTiming.Afternoon]: 15 * 3600,
|
|
612
|
+
[types_1.EventTiming["Late Afternoon"]]: 16 * 3600 + 30 * 60,
|
|
613
|
+
[types_1.EventTiming["Before Dinner"]]: 17 * 3600 + 30 * 60,
|
|
614
|
+
[types_1.EventTiming.Dinner]: 18 * 3600,
|
|
615
|
+
[types_1.EventTiming["After Dinner"]]: 19 * 3600,
|
|
616
|
+
[types_1.EventTiming["Early Evening"]]: 19 * 3600 + 30 * 60,
|
|
617
|
+
[types_1.EventTiming.Evening]: 20 * 3600,
|
|
618
|
+
[types_1.EventTiming["Late Evening"]]: 21 * 3600,
|
|
619
|
+
[types_1.EventTiming.Night]: 22 * 3600,
|
|
620
|
+
[types_1.EventTiming["Before Sleep"]]: 22 * 3600 + 30 * 60,
|
|
621
|
+
};
|
|
622
|
+
function parseClockToSeconds(clock) {
|
|
623
|
+
const match = clock.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
|
624
|
+
if (!match) {
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
const hour = Number(match[1]);
|
|
628
|
+
const minute = Number(match[2]);
|
|
629
|
+
const second = match[3] ? Number(match[3]) : 0;
|
|
630
|
+
if (!Number.isFinite(hour) ||
|
|
631
|
+
!Number.isFinite(minute) ||
|
|
632
|
+
!Number.isFinite(second) ||
|
|
633
|
+
hour < 0 ||
|
|
634
|
+
hour > 23 ||
|
|
635
|
+
minute < 0 ||
|
|
636
|
+
minute > 59 ||
|
|
637
|
+
second < 0 ||
|
|
638
|
+
second > 59) {
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
return hour * 3600 + minute * 60 + second;
|
|
642
|
+
}
|
|
643
|
+
function computeWhenWeight(code, options) {
|
|
644
|
+
var _a, _b;
|
|
645
|
+
const clock = (_a = options === null || options === void 0 ? void 0 : options.eventClock) === null || _a === void 0 ? void 0 : _a[code];
|
|
646
|
+
if (clock) {
|
|
647
|
+
const seconds = parseClockToSeconds(clock);
|
|
648
|
+
if (seconds !== undefined) {
|
|
649
|
+
return seconds;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return (_b = DEFAULT_EVENT_TIMING_WEIGHTS[code]) !== null && _b !== void 0 ? _b : 10000;
|
|
653
|
+
}
|
|
654
|
+
function sortWhenValues(internal, options) {
|
|
655
|
+
if (internal.when.length < 2) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const weighted = internal.when.map((code, index) => ({
|
|
659
|
+
code,
|
|
660
|
+
weight: computeWhenWeight(code, options),
|
|
661
|
+
index,
|
|
662
|
+
}));
|
|
663
|
+
weighted.sort((a, b) => {
|
|
664
|
+
if (a.weight !== b.weight) {
|
|
665
|
+
return a.weight - b.weight;
|
|
666
|
+
}
|
|
667
|
+
return a.index - b.index;
|
|
668
|
+
});
|
|
669
|
+
internal.when.splice(0, internal.when.length, ...weighted.map((entry) => entry.code));
|
|
670
|
+
}
|
|
170
671
|
// Translate the requested expansion context into the appropriate sequence of
|
|
171
672
|
// EventTiming values (e.g., AC -> ACM/ACD/ACV) for the detected frequency.
|
|
172
673
|
function computeMealExpansions(base, frequency, pairPreference) {
|
|
173
674
|
if (frequency < 1 || frequency > 4) {
|
|
174
675
|
return undefined;
|
|
175
676
|
}
|
|
176
|
-
const bedtime = EventTiming["Before Sleep"];
|
|
677
|
+
const bedtime = types_1.EventTiming["Before Sleep"];
|
|
177
678
|
const beforePair = pairPreference === "breakfast+lunch"
|
|
178
|
-
? [EventTiming["Before Breakfast"], EventTiming["Before Lunch"]]
|
|
179
|
-
: [EventTiming["Before Breakfast"], EventTiming["Before Dinner"]];
|
|
679
|
+
? [types_1.EventTiming["Before Breakfast"], types_1.EventTiming["Before Lunch"]]
|
|
680
|
+
: [types_1.EventTiming["Before Breakfast"], types_1.EventTiming["Before Dinner"]];
|
|
180
681
|
const afterPair = pairPreference === "breakfast+lunch"
|
|
181
|
-
? [EventTiming["After Breakfast"], EventTiming["After Lunch"]]
|
|
182
|
-
: [EventTiming["After Breakfast"], EventTiming["After Dinner"]];
|
|
682
|
+
? [types_1.EventTiming["After Breakfast"], types_1.EventTiming["After Lunch"]]
|
|
683
|
+
: [types_1.EventTiming["After Breakfast"], types_1.EventTiming["After Dinner"]];
|
|
183
684
|
const withPair = pairPreference === "breakfast+lunch"
|
|
184
|
-
? [EventTiming.Breakfast, EventTiming.Lunch]
|
|
185
|
-
: [EventTiming.Breakfast, EventTiming.Dinner];
|
|
685
|
+
? [types_1.EventTiming.Breakfast, types_1.EventTiming.Lunch]
|
|
686
|
+
: [types_1.EventTiming.Breakfast, types_1.EventTiming.Dinner];
|
|
186
687
|
if (base === "before") {
|
|
187
688
|
if (frequency === 1)
|
|
188
|
-
return [EventTiming["Before Breakfast"]];
|
|
689
|
+
return [types_1.EventTiming["Before Breakfast"]];
|
|
189
690
|
if (frequency === 2)
|
|
190
691
|
return beforePair;
|
|
191
692
|
if (frequency === 3) {
|
|
192
693
|
return [
|
|
193
|
-
EventTiming["Before Breakfast"],
|
|
194
|
-
EventTiming["Before Lunch"],
|
|
195
|
-
EventTiming["Before Dinner"]
|
|
694
|
+
types_1.EventTiming["Before Breakfast"],
|
|
695
|
+
types_1.EventTiming["Before Lunch"],
|
|
696
|
+
types_1.EventTiming["Before Dinner"]
|
|
196
697
|
];
|
|
197
698
|
}
|
|
198
699
|
return [
|
|
199
|
-
EventTiming["Before Breakfast"],
|
|
200
|
-
EventTiming["Before Lunch"],
|
|
201
|
-
EventTiming["Before Dinner"],
|
|
700
|
+
types_1.EventTiming["Before Breakfast"],
|
|
701
|
+
types_1.EventTiming["Before Lunch"],
|
|
702
|
+
types_1.EventTiming["Before Dinner"],
|
|
202
703
|
bedtime
|
|
203
704
|
];
|
|
204
705
|
}
|
|
205
706
|
if (base === "after") {
|
|
206
707
|
if (frequency === 1)
|
|
207
|
-
return [EventTiming["After Breakfast"]];
|
|
708
|
+
return [types_1.EventTiming["After Breakfast"]];
|
|
208
709
|
if (frequency === 2)
|
|
209
710
|
return afterPair;
|
|
210
711
|
if (frequency === 3) {
|
|
211
712
|
return [
|
|
212
|
-
EventTiming["After Breakfast"],
|
|
213
|
-
EventTiming["After Lunch"],
|
|
214
|
-
EventTiming["After Dinner"]
|
|
713
|
+
types_1.EventTiming["After Breakfast"],
|
|
714
|
+
types_1.EventTiming["After Lunch"],
|
|
715
|
+
types_1.EventTiming["After Dinner"]
|
|
215
716
|
];
|
|
216
717
|
}
|
|
217
718
|
return [
|
|
218
|
-
EventTiming["After Breakfast"],
|
|
219
|
-
EventTiming["After Lunch"],
|
|
220
|
-
EventTiming["After Dinner"],
|
|
719
|
+
types_1.EventTiming["After Breakfast"],
|
|
720
|
+
types_1.EventTiming["After Lunch"],
|
|
721
|
+
types_1.EventTiming["After Dinner"],
|
|
221
722
|
bedtime
|
|
222
723
|
];
|
|
223
724
|
}
|
|
224
725
|
// base === "with"
|
|
225
726
|
if (frequency === 1)
|
|
226
|
-
return [EventTiming.Breakfast];
|
|
727
|
+
return [types_1.EventTiming.Breakfast];
|
|
227
728
|
if (frequency === 2)
|
|
228
729
|
return withPair;
|
|
229
730
|
if (frequency === 3) {
|
|
230
|
-
return [EventTiming.Breakfast, EventTiming.Lunch, EventTiming.Dinner];
|
|
731
|
+
return [types_1.EventTiming.Breakfast, types_1.EventTiming.Lunch, types_1.EventTiming.Dinner];
|
|
231
732
|
}
|
|
232
|
-
return [EventTiming.Breakfast, EventTiming.Lunch, EventTiming.Dinner, bedtime];
|
|
733
|
+
return [types_1.EventTiming.Breakfast, types_1.EventTiming.Lunch, types_1.EventTiming.Dinner, bedtime];
|
|
734
|
+
}
|
|
735
|
+
function reconcileMealTimingSpecificity(internal) {
|
|
736
|
+
if (!internal.when.length) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const convertSpecifics = (base, mappings) => {
|
|
740
|
+
if (!(0, array_1.arrayIncludes)(internal.when, base)) {
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
let replaced = false;
|
|
744
|
+
for (const [general, specific] of mappings) {
|
|
745
|
+
if ((0, array_1.arrayIncludes)(internal.when, general)) {
|
|
746
|
+
removeWhen(internal.when, general);
|
|
747
|
+
addWhen(internal.when, specific);
|
|
748
|
+
replaced = true;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (replaced) {
|
|
752
|
+
removeWhen(internal.when, base);
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
convertSpecifics(types_1.EventTiming["Before Meal"], [
|
|
756
|
+
[types_1.EventTiming.Breakfast, types_1.EventTiming["Before Breakfast"]],
|
|
757
|
+
[types_1.EventTiming.Lunch, types_1.EventTiming["Before Lunch"]],
|
|
758
|
+
[types_1.EventTiming.Dinner, types_1.EventTiming["Before Dinner"]],
|
|
759
|
+
]);
|
|
760
|
+
convertSpecifics(types_1.EventTiming["After Meal"], [
|
|
761
|
+
[types_1.EventTiming.Breakfast, types_1.EventTiming["After Breakfast"]],
|
|
762
|
+
[types_1.EventTiming.Lunch, types_1.EventTiming["After Lunch"]],
|
|
763
|
+
[types_1.EventTiming.Dinner, types_1.EventTiming["After Dinner"]],
|
|
764
|
+
]);
|
|
233
765
|
}
|
|
234
766
|
// Optionally replace generic meal tokens with concrete breakfast/lunch/dinner
|
|
235
767
|
// EventTiming codes when the cadence makes the intent obvious.
|
|
236
768
|
function expandMealTimings(internal, options) {
|
|
237
|
-
|
|
769
|
+
var _a;
|
|
770
|
+
if (!(options === null || options === void 0 ? void 0 : options.smartMealExpansion)) {
|
|
238
771
|
return;
|
|
239
772
|
}
|
|
240
773
|
if (!internal.when.length) {
|
|
@@ -249,7 +782,7 @@ function expandMealTimings(internal, options) {
|
|
|
249
782
|
}
|
|
250
783
|
if (internal.period !== undefined &&
|
|
251
784
|
internal.periodUnit !== undefined &&
|
|
252
|
-
(internal.periodUnit !== FhirPeriodUnit.Day || internal.period !== 1)) {
|
|
785
|
+
(internal.periodUnit !== types_1.FhirPeriodUnit.Day || internal.period !== 1)) {
|
|
253
786
|
return;
|
|
254
787
|
}
|
|
255
788
|
if (internal.period !== undefined &&
|
|
@@ -257,30 +790,30 @@ function expandMealTimings(internal, options) {
|
|
|
257
790
|
internal.period !== 1) {
|
|
258
791
|
return;
|
|
259
792
|
}
|
|
260
|
-
if (internal.periodUnit && internal.periodUnit !== FhirPeriodUnit.Day) {
|
|
793
|
+
if (internal.periodUnit && internal.periodUnit !== types_1.FhirPeriodUnit.Day) {
|
|
261
794
|
return;
|
|
262
795
|
}
|
|
263
796
|
if (internal.frequencyMax !== undefined || internal.periodMax !== undefined) {
|
|
264
797
|
return;
|
|
265
798
|
}
|
|
266
|
-
const pairPreference = options.twoPerDayPair
|
|
799
|
+
const pairPreference = (_a = options.twoPerDayPair) !== null && _a !== void 0 ? _a : "breakfast+dinner";
|
|
267
800
|
const replacements = [];
|
|
268
|
-
if (internal.when.
|
|
801
|
+
if ((0, array_1.arrayIncludes)(internal.when, types_1.EventTiming["Before Meal"])) {
|
|
269
802
|
const specifics = computeMealExpansions("before", frequency, pairPreference);
|
|
270
803
|
if (specifics) {
|
|
271
|
-
replacements.push({ general: EventTiming["Before Meal"], specifics });
|
|
804
|
+
replacements.push({ general: types_1.EventTiming["Before Meal"], specifics });
|
|
272
805
|
}
|
|
273
806
|
}
|
|
274
|
-
if (internal.when.
|
|
807
|
+
if ((0, array_1.arrayIncludes)(internal.when, types_1.EventTiming["After Meal"])) {
|
|
275
808
|
const specifics = computeMealExpansions("after", frequency, pairPreference);
|
|
276
809
|
if (specifics) {
|
|
277
|
-
replacements.push({ general: EventTiming["After Meal"], specifics });
|
|
810
|
+
replacements.push({ general: types_1.EventTiming["After Meal"], specifics });
|
|
278
811
|
}
|
|
279
812
|
}
|
|
280
|
-
if (internal.when.
|
|
813
|
+
if ((0, array_1.arrayIncludes)(internal.when, types_1.EventTiming.Meal)) {
|
|
281
814
|
const specifics = computeMealExpansions("with", frequency, pairPreference);
|
|
282
815
|
if (specifics) {
|
|
283
|
-
replacements.push({ general: EventTiming.Meal, specifics });
|
|
816
|
+
replacements.push({ general: types_1.EventTiming.Meal, specifics });
|
|
284
817
|
}
|
|
285
818
|
}
|
|
286
819
|
for (const { general, specifics } of replacements) {
|
|
@@ -292,15 +825,15 @@ function expandMealTimings(internal, options) {
|
|
|
292
825
|
}
|
|
293
826
|
function setRoute(internal, code, text) {
|
|
294
827
|
internal.routeCode = code;
|
|
295
|
-
internal.routeText = text
|
|
828
|
+
internal.routeText = text !== null && text !== void 0 ? text : maps_1.ROUTE_TEXT[code];
|
|
296
829
|
}
|
|
297
830
|
/**
|
|
298
831
|
* Convert hour-based values into minutes when fractional quantities appear so
|
|
299
832
|
* the resulting FHIR repeat payloads avoid unwieldy decimals.
|
|
300
833
|
*/
|
|
301
834
|
function normalizePeriodValue(value, unit) {
|
|
302
|
-
if (unit === FhirPeriodUnit.Hour && (!Number.isInteger(value) || value < 1)) {
|
|
303
|
-
return { value: Math.round(value * 60 * 1000) / 1000, unit: FhirPeriodUnit.Minute };
|
|
835
|
+
if (unit === types_1.FhirPeriodUnit.Hour && (!Number.isInteger(value) || value < 1)) {
|
|
836
|
+
return { value: Math.round(value * 60 * 1000) / 1000, unit: types_1.FhirPeriodUnit.Minute };
|
|
304
837
|
}
|
|
305
838
|
return { value, unit };
|
|
306
839
|
}
|
|
@@ -309,28 +842,28 @@ function normalizePeriodValue(value, unit) {
|
|
|
309
842
|
* demand conversion into minutes.
|
|
310
843
|
*/
|
|
311
844
|
function normalizePeriodRange(low, high, unit) {
|
|
312
|
-
if (unit === FhirPeriodUnit.Hour && (!Number.isInteger(low) || !Number.isInteger(high) || low < 1 || high < 1)) {
|
|
845
|
+
if (unit === types_1.FhirPeriodUnit.Hour && (!Number.isInteger(low) || !Number.isInteger(high) || low < 1 || high < 1)) {
|
|
313
846
|
return {
|
|
314
847
|
low: Math.round(low * 60 * 1000) / 1000,
|
|
315
848
|
high: Math.round(high * 60 * 1000) / 1000,
|
|
316
|
-
unit: FhirPeriodUnit.Minute
|
|
849
|
+
unit: types_1.FhirPeriodUnit.Minute
|
|
317
850
|
};
|
|
318
851
|
}
|
|
319
852
|
return { low, high, unit };
|
|
320
853
|
}
|
|
321
854
|
function periodUnitSuffix(unit) {
|
|
322
855
|
switch (unit) {
|
|
323
|
-
case FhirPeriodUnit.Minute:
|
|
856
|
+
case types_1.FhirPeriodUnit.Minute:
|
|
324
857
|
return "min";
|
|
325
|
-
case FhirPeriodUnit.Hour:
|
|
858
|
+
case types_1.FhirPeriodUnit.Hour:
|
|
326
859
|
return "h";
|
|
327
|
-
case FhirPeriodUnit.Day:
|
|
860
|
+
case types_1.FhirPeriodUnit.Day:
|
|
328
861
|
return "d";
|
|
329
|
-
case FhirPeriodUnit.Week:
|
|
862
|
+
case types_1.FhirPeriodUnit.Week:
|
|
330
863
|
return "wk";
|
|
331
|
-
case FhirPeriodUnit.Month:
|
|
864
|
+
case types_1.FhirPeriodUnit.Month:
|
|
332
865
|
return "mo";
|
|
333
|
-
case FhirPeriodUnit.Year:
|
|
866
|
+
case types_1.FhirPeriodUnit.Year:
|
|
334
867
|
return "a";
|
|
335
868
|
default:
|
|
336
869
|
return undefined;
|
|
@@ -342,8 +875,8 @@ function maybeAssignTimingCode(internal, value, unit) {
|
|
|
342
875
|
return;
|
|
343
876
|
}
|
|
344
877
|
const key = `q${value}${suffix}`;
|
|
345
|
-
const descriptor = TIMING_ABBREVIATIONS[key];
|
|
346
|
-
if (descriptor
|
|
878
|
+
const descriptor = maps_1.TIMING_ABBREVIATIONS[key];
|
|
879
|
+
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.code) && !internal.timingCode) {
|
|
347
880
|
internal.timingCode = descriptor.code;
|
|
348
881
|
}
|
|
349
882
|
}
|
|
@@ -352,18 +885,19 @@ function maybeAssignTimingCode(internal, value, unit) {
|
|
|
352
885
|
* period clearly represents common cadences (daily/weekly/monthly).
|
|
353
886
|
*/
|
|
354
887
|
function applyPeriod(internal, period, unit) {
|
|
888
|
+
var _a, _b, _c;
|
|
355
889
|
const normalized = normalizePeriodValue(period, unit);
|
|
356
890
|
internal.period = normalized.value;
|
|
357
891
|
internal.periodUnit = normalized.unit;
|
|
358
892
|
maybeAssignTimingCode(internal, normalized.value, normalized.unit);
|
|
359
|
-
if (normalized.unit === FhirPeriodUnit.Day && normalized.value === 1) {
|
|
360
|
-
internal.frequency = internal.frequency
|
|
893
|
+
if (normalized.unit === types_1.FhirPeriodUnit.Day && normalized.value === 1) {
|
|
894
|
+
internal.frequency = (_a = internal.frequency) !== null && _a !== void 0 ? _a : 1;
|
|
361
895
|
}
|
|
362
|
-
if (normalized.unit === FhirPeriodUnit.Week && normalized.value === 1) {
|
|
363
|
-
internal.timingCode = internal.timingCode
|
|
896
|
+
if (normalized.unit === types_1.FhirPeriodUnit.Week && normalized.value === 1) {
|
|
897
|
+
internal.timingCode = (_b = internal.timingCode) !== null && _b !== void 0 ? _b : "WK";
|
|
364
898
|
}
|
|
365
|
-
if (normalized.unit === FhirPeriodUnit.Month && normalized.value === 1) {
|
|
366
|
-
internal.timingCode = internal.timingCode
|
|
899
|
+
if (normalized.unit === types_1.FhirPeriodUnit.Month && normalized.value === 1) {
|
|
900
|
+
internal.timingCode = (_c = internal.timingCode) !== null && _c !== void 0 ? _c : "MO";
|
|
367
901
|
}
|
|
368
902
|
}
|
|
369
903
|
/**
|
|
@@ -406,7 +940,7 @@ function tryParseCompactQ(internal, tokens, index) {
|
|
|
406
940
|
}
|
|
407
941
|
function applyFrequencyDescriptor(internal, token, descriptor, options) {
|
|
408
942
|
if (descriptor.discouraged) {
|
|
409
|
-
const check = checkDiscouraged(token.original, options);
|
|
943
|
+
const check = (0, safety_1.checkDiscouraged)(token.original, options);
|
|
410
944
|
if (check.warning) {
|
|
411
945
|
internal.warnings.push(check.warning);
|
|
412
946
|
}
|
|
@@ -447,11 +981,11 @@ function parseMealContext(internal, tokens, index, code) {
|
|
|
447
981
|
applyWhenToken(internal, token, code);
|
|
448
982
|
return;
|
|
449
983
|
}
|
|
450
|
-
const meal = MEAL_KEYWORDS[next.lower];
|
|
984
|
+
const meal = maps_1.MEAL_KEYWORDS[next.lower];
|
|
451
985
|
if (meal) {
|
|
452
|
-
const whenCode = code === EventTiming["After Meal"]
|
|
986
|
+
const whenCode = code === types_1.EventTiming["After Meal"]
|
|
453
987
|
? meal.pc
|
|
454
|
-
: code === EventTiming["Before Meal"]
|
|
988
|
+
: code === types_1.EventTiming["Before Meal"]
|
|
455
989
|
? meal.ac
|
|
456
990
|
: code;
|
|
457
991
|
applyWhenToken(internal, token, whenCode);
|
|
@@ -519,19 +1053,19 @@ function mapIntervalUnit(token) {
|
|
|
519
1053
|
token === "minute" ||
|
|
520
1054
|
token === "minutes" ||
|
|
521
1055
|
token === "m") {
|
|
522
|
-
return FhirPeriodUnit.Minute;
|
|
1056
|
+
return types_1.FhirPeriodUnit.Minute;
|
|
523
1057
|
}
|
|
524
1058
|
if (token === "h" || token === "hr" || token === "hrs" || token === "hour" || token === "hours") {
|
|
525
|
-
return FhirPeriodUnit.Hour;
|
|
1059
|
+
return types_1.FhirPeriodUnit.Hour;
|
|
526
1060
|
}
|
|
527
1061
|
if (token === "d" || token === "day" || token === "days") {
|
|
528
|
-
return FhirPeriodUnit.Day;
|
|
1062
|
+
return types_1.FhirPeriodUnit.Day;
|
|
529
1063
|
}
|
|
530
1064
|
if (token === "wk" || token === "w" || token === "week" || token === "weeks") {
|
|
531
|
-
return FhirPeriodUnit.Week;
|
|
1065
|
+
return types_1.FhirPeriodUnit.Week;
|
|
532
1066
|
}
|
|
533
1067
|
if (token === "mo" || token === "month" || token === "months") {
|
|
534
|
-
return FhirPeriodUnit.Month;
|
|
1068
|
+
return types_1.FhirPeriodUnit.Month;
|
|
535
1069
|
}
|
|
536
1070
|
return undefined;
|
|
537
1071
|
}
|
|
@@ -547,7 +1081,8 @@ function parseNumericRange(token) {
|
|
|
547
1081
|
}
|
|
548
1082
|
return { low, high };
|
|
549
1083
|
}
|
|
550
|
-
|
|
1084
|
+
function parseInternal(input, options) {
|
|
1085
|
+
var _a, _b, _c, _d, _e, _f;
|
|
551
1086
|
const tokens = tokenize(input);
|
|
552
1087
|
const internal = {
|
|
553
1088
|
input,
|
|
@@ -555,11 +1090,12 @@ export function parseInternal(input, options) {
|
|
|
555
1090
|
consumed: new Set(),
|
|
556
1091
|
dayOfWeek: [],
|
|
557
1092
|
when: [],
|
|
558
|
-
warnings: []
|
|
1093
|
+
warnings: [],
|
|
1094
|
+
siteTokenIndices: new Set()
|
|
559
1095
|
};
|
|
560
|
-
const context = options
|
|
561
|
-
const customRouteMap = options
|
|
562
|
-
? new Map(
|
|
1096
|
+
const context = (_a = options === null || options === void 0 ? void 0 : options.context) !== null && _a !== void 0 ? _a : undefined;
|
|
1097
|
+
const customRouteMap = (options === null || options === void 0 ? void 0 : options.routeMap)
|
|
1098
|
+
? new Map((0, object_1.objectEntries)(options.routeMap).map(([key, value]) => [
|
|
563
1099
|
key.toLowerCase(),
|
|
564
1100
|
value
|
|
565
1101
|
]))
|
|
@@ -577,12 +1113,12 @@ export function parseInternal(input, options) {
|
|
|
577
1113
|
prnReasonStart = i + 1;
|
|
578
1114
|
break;
|
|
579
1115
|
}
|
|
580
|
-
if (token.lower === "as" && tokens[i + 1]
|
|
1116
|
+
if (token.lower === "as" && ((_b = tokens[i + 1]) === null || _b === void 0 ? void 0 : _b.lower) === "needed") {
|
|
581
1117
|
internal.asNeeded = true;
|
|
582
1118
|
mark(internal.consumed, token);
|
|
583
1119
|
mark(internal.consumed, tokens[i + 1]);
|
|
584
1120
|
let reasonIndex = i + 2;
|
|
585
|
-
if (tokens[reasonIndex]
|
|
1121
|
+
if (((_c = tokens[reasonIndex]) === null || _c === void 0 ? void 0 : _c.lower) === "for") {
|
|
586
1122
|
mark(internal.consumed, tokens[reasonIndex]);
|
|
587
1123
|
reasonIndex += 1;
|
|
588
1124
|
}
|
|
@@ -603,7 +1139,7 @@ export function parseInternal(input, options) {
|
|
|
603
1139
|
}
|
|
604
1140
|
internal.frequency = freq;
|
|
605
1141
|
internal.period = 1;
|
|
606
|
-
internal.periodUnit = FhirPeriodUnit.Day;
|
|
1142
|
+
internal.periodUnit = types_1.FhirPeriodUnit.Day;
|
|
607
1143
|
mark(internal.consumed, token);
|
|
608
1144
|
}
|
|
609
1145
|
}
|
|
@@ -616,14 +1152,17 @@ export function parseInternal(input, options) {
|
|
|
616
1152
|
continue;
|
|
617
1153
|
}
|
|
618
1154
|
const phrase = slice.map((part) => part.lower).join(" ");
|
|
619
|
-
const customCode = customRouteMap
|
|
1155
|
+
const customCode = customRouteMap === null || customRouteMap === void 0 ? void 0 : customRouteMap.get(phrase);
|
|
620
1156
|
const synonym = customCode
|
|
621
|
-
? { code: customCode, text: ROUTE_TEXT[customCode] }
|
|
622
|
-
: DEFAULT_ROUTE_SYNONYMS[phrase];
|
|
1157
|
+
? { code: customCode, text: maps_1.ROUTE_TEXT[customCode] }
|
|
1158
|
+
: maps_1.DEFAULT_ROUTE_SYNONYMS[phrase];
|
|
623
1159
|
if (synonym) {
|
|
624
1160
|
setRoute(internal, synonym.code, synonym.text);
|
|
625
1161
|
for (const part of slice) {
|
|
626
1162
|
mark(internal.consumed, part);
|
|
1163
|
+
if (BODY_SITE_HINTS.has(part.lower)) {
|
|
1164
|
+
internal.siteTokenIndices.add(part.index);
|
|
1165
|
+
}
|
|
627
1166
|
}
|
|
628
1167
|
return true;
|
|
629
1168
|
}
|
|
@@ -635,12 +1174,13 @@ export function parseInternal(input, options) {
|
|
|
635
1174
|
if (internal.consumed.has(token.index)) {
|
|
636
1175
|
continue;
|
|
637
1176
|
}
|
|
1177
|
+
const normalizedLower = normalizeTokenLower(token);
|
|
638
1178
|
if (token.lower === "bld" || token.lower === "b-l-d") {
|
|
639
|
-
const check = checkDiscouraged(token.original, options);
|
|
1179
|
+
const check = (0, safety_1.checkDiscouraged)(token.original, options);
|
|
640
1180
|
if (check.warning) {
|
|
641
1181
|
internal.warnings.push(check.warning);
|
|
642
1182
|
}
|
|
643
|
-
applyWhenToken(internal, token, EventTiming.Meal);
|
|
1183
|
+
applyWhenToken(internal, token, types_1.EventTiming.Meal);
|
|
644
1184
|
continue;
|
|
645
1185
|
}
|
|
646
1186
|
if (token.lower === "q") {
|
|
@@ -648,8 +1188,25 @@ export function parseInternal(input, options) {
|
|
|
648
1188
|
continue;
|
|
649
1189
|
}
|
|
650
1190
|
}
|
|
1191
|
+
if (tryParseNumericCadence(internal, tokens, i)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
const eyeSite = EYE_SITE_TOKENS[normalizedLower];
|
|
1195
|
+
const treatEyeTokenAsSite = eyeSite
|
|
1196
|
+
? shouldTreatEyeTokenAsSite(internal, tokens, i, context)
|
|
1197
|
+
: false;
|
|
1198
|
+
if (normalizedLower === "od") {
|
|
1199
|
+
const descriptor = maps_1.TIMING_ABBREVIATIONS.od;
|
|
1200
|
+
if (descriptor &&
|
|
1201
|
+
shouldInterpretOdAsOnceDaily(internal, tokens, i, treatEyeTokenAsSite)) {
|
|
1202
|
+
applyFrequencyDescriptor(internal, token, descriptor, options);
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
651
1206
|
// Frequency abbreviation map
|
|
652
|
-
const freqDescriptor =
|
|
1207
|
+
const freqDescriptor = normalizedLower === "od"
|
|
1208
|
+
? undefined
|
|
1209
|
+
: (_d = maps_1.TIMING_ABBREVIATIONS[token.lower]) !== null && _d !== void 0 ? _d : maps_1.TIMING_ABBREVIATIONS[normalizedLower];
|
|
653
1210
|
if (freqDescriptor) {
|
|
654
1211
|
applyFrequencyDescriptor(internal, token, freqDescriptor, options);
|
|
655
1212
|
continue;
|
|
@@ -660,34 +1217,34 @@ export function parseInternal(input, options) {
|
|
|
660
1217
|
// Event timing tokens
|
|
661
1218
|
if (token.lower === "pc" || token.lower === "ac") {
|
|
662
1219
|
parseMealContext(internal, tokens, i, token.lower === "pc"
|
|
663
|
-
? EventTiming["After Meal"]
|
|
664
|
-
: EventTiming["Before Meal"]);
|
|
1220
|
+
? types_1.EventTiming["After Meal"]
|
|
1221
|
+
: types_1.EventTiming["Before Meal"]);
|
|
665
1222
|
continue;
|
|
666
1223
|
}
|
|
667
1224
|
const nextToken = tokens[i + 1];
|
|
668
1225
|
if (nextToken && !internal.consumed.has(nextToken.index)) {
|
|
669
1226
|
const combo = `${token.lower} ${nextToken.lower}`;
|
|
670
|
-
const comboWhen = COMBO_EVENT_TIMINGS[combo]
|
|
1227
|
+
const comboWhen = (_e = COMBO_EVENT_TIMINGS[combo]) !== null && _e !== void 0 ? _e : maps_1.EVENT_TIMING_TOKENS[combo];
|
|
671
1228
|
if (comboWhen) {
|
|
672
1229
|
applyWhenToken(internal, token, comboWhen);
|
|
673
1230
|
mark(internal.consumed, nextToken);
|
|
674
1231
|
continue;
|
|
675
1232
|
}
|
|
676
1233
|
}
|
|
677
|
-
const customWhen = options
|
|
1234
|
+
const customWhen = (_f = options === null || options === void 0 ? void 0 : options.whenMap) === null || _f === void 0 ? void 0 : _f[token.lower];
|
|
678
1235
|
if (customWhen) {
|
|
679
1236
|
applyWhenToken(internal, token, customWhen);
|
|
680
1237
|
continue;
|
|
681
1238
|
}
|
|
682
|
-
const whenCode = EVENT_TIMING_TOKENS[token.lower];
|
|
1239
|
+
const whenCode = maps_1.EVENT_TIMING_TOKENS[token.lower];
|
|
683
1240
|
if (whenCode) {
|
|
684
1241
|
applyWhenToken(internal, token, whenCode);
|
|
685
1242
|
continue;
|
|
686
1243
|
}
|
|
687
1244
|
// Day of week
|
|
688
|
-
const day = DAY_OF_WEEK_TOKENS[token.lower];
|
|
1245
|
+
const day = maps_1.DAY_OF_WEEK_TOKENS[token.lower];
|
|
689
1246
|
if (day) {
|
|
690
|
-
if (!internal.dayOfWeek
|
|
1247
|
+
if (!(0, array_1.arrayIncludes)(internal.dayOfWeek, day)) {
|
|
691
1248
|
internal.dayOfWeek.push(day);
|
|
692
1249
|
}
|
|
693
1250
|
mark(internal.consumed, token);
|
|
@@ -697,9 +1254,9 @@ export function parseInternal(input, options) {
|
|
|
697
1254
|
if (tryRouteSynonym(i)) {
|
|
698
1255
|
continue;
|
|
699
1256
|
}
|
|
700
|
-
|
|
701
|
-
if (eyeSite) {
|
|
1257
|
+
if (eyeSite && treatEyeTokenAsSite) {
|
|
702
1258
|
internal.siteText = eyeSite.site;
|
|
1259
|
+
internal.siteSource = "abbreviation";
|
|
703
1260
|
if (eyeSite.route && !internal.routeCode) {
|
|
704
1261
|
setRoute(internal, eyeSite.route);
|
|
705
1262
|
}
|
|
@@ -750,7 +1307,7 @@ export function parseInternal(input, options) {
|
|
|
750
1307
|
continue;
|
|
751
1308
|
}
|
|
752
1309
|
// Words for frequency
|
|
753
|
-
const wordFreq = WORD_FREQUENCIES[token.lower];
|
|
1310
|
+
const wordFreq = maps_1.WORD_FREQUENCIES[token.lower];
|
|
754
1311
|
if (wordFreq) {
|
|
755
1312
|
internal.frequency = wordFreq.frequency;
|
|
756
1313
|
internal.period = 1;
|
|
@@ -759,7 +1316,7 @@ export function parseInternal(input, options) {
|
|
|
759
1316
|
continue;
|
|
760
1317
|
}
|
|
761
1318
|
// Skip generic connectors
|
|
762
|
-
if (token.lower === "per" || token.lower === "a" || token.lower === "every") {
|
|
1319
|
+
if (token.lower === "per" || token.lower === "a" || token.lower === "every" || token.lower === "each") {
|
|
763
1320
|
mark(internal.consumed, token);
|
|
764
1321
|
continue;
|
|
765
1322
|
}
|
|
@@ -778,13 +1335,19 @@ export function parseInternal(input, options) {
|
|
|
778
1335
|
}
|
|
779
1336
|
}
|
|
780
1337
|
if (internal.unit === undefined) {
|
|
781
|
-
internal.unit = inferUnitFromContext(context);
|
|
1338
|
+
internal.unit = enforceHouseholdUnitPolicy((0, context_1.inferUnitFromContext)(context), options);
|
|
1339
|
+
}
|
|
1340
|
+
if (internal.unit === undefined) {
|
|
1341
|
+
const fallbackUnit = enforceHouseholdUnitPolicy(inferUnitFromRouteHints(internal), options);
|
|
1342
|
+
if (fallbackUnit) {
|
|
1343
|
+
internal.unit = fallbackUnit;
|
|
1344
|
+
}
|
|
782
1345
|
}
|
|
783
1346
|
// Frequency defaults when timing code implies it
|
|
784
1347
|
if (internal.frequency === undefined &&
|
|
785
1348
|
internal.period === undefined &&
|
|
786
1349
|
internal.timingCode) {
|
|
787
|
-
const descriptor = TIMING_ABBREVIATIONS[internal.timingCode.toLowerCase()];
|
|
1350
|
+
const descriptor = maps_1.TIMING_ABBREVIATIONS[internal.timingCode.toLowerCase()];
|
|
788
1351
|
if (descriptor) {
|
|
789
1352
|
if (descriptor.frequency !== undefined) {
|
|
790
1353
|
internal.frequency = descriptor.frequency;
|
|
@@ -804,7 +1367,7 @@ export function parseInternal(input, options) {
|
|
|
804
1367
|
}
|
|
805
1368
|
if (!internal.timingCode &&
|
|
806
1369
|
internal.frequency !== undefined &&
|
|
807
|
-
internal.periodUnit === FhirPeriodUnit.Day &&
|
|
1370
|
+
internal.periodUnit === types_1.FhirPeriodUnit.Day &&
|
|
808
1371
|
(internal.period === undefined || internal.period === 1)) {
|
|
809
1372
|
if (internal.frequency === 2) {
|
|
810
1373
|
internal.timingCode = "BID";
|
|
@@ -816,23 +1379,82 @@ export function parseInternal(input, options) {
|
|
|
816
1379
|
internal.timingCode = "QID";
|
|
817
1380
|
}
|
|
818
1381
|
}
|
|
1382
|
+
reconcileMealTimingSpecificity(internal);
|
|
819
1383
|
// Expand generic meal markers into specific EventTiming codes when asked to.
|
|
820
1384
|
expandMealTimings(internal, options);
|
|
1385
|
+
sortWhenValues(internal, options);
|
|
821
1386
|
// Determine site text from leftover tokens (excluding PRN reason tokens)
|
|
822
1387
|
const leftoverTokens = tokens.filter((t) => !internal.consumed.has(t.index));
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
if (
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1388
|
+
const siteCandidateIndices = new Set();
|
|
1389
|
+
for (const token of leftoverTokens) {
|
|
1390
|
+
if (BODY_SITE_HINTS.has(token.lower)) {
|
|
1391
|
+
siteCandidateIndices.add(token.index);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
for (const idx of internal.siteTokenIndices) {
|
|
1395
|
+
siteCandidateIndices.add(idx);
|
|
1396
|
+
}
|
|
1397
|
+
if (siteCandidateIndices.size > 0) {
|
|
1398
|
+
const indicesToInclude = new Set(siteCandidateIndices);
|
|
1399
|
+
for (const idx of siteCandidateIndices) {
|
|
1400
|
+
let prev = idx - 1;
|
|
1401
|
+
while (prev >= 0) {
|
|
1402
|
+
const token = tokens[prev];
|
|
1403
|
+
if (!token) {
|
|
1404
|
+
break;
|
|
832
1405
|
}
|
|
1406
|
+
const lower = token.lower;
|
|
1407
|
+
if (SITE_CONNECTORS.has(lower) || BODY_SITE_HINTS.has(lower)) {
|
|
1408
|
+
indicesToInclude.add(token.index);
|
|
1409
|
+
prev -= 1;
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
let next = idx + 1;
|
|
1415
|
+
while (next < tokens.length) {
|
|
1416
|
+
const token = tokens[next];
|
|
1417
|
+
if (!token) {
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
const lower = token.lower;
|
|
1421
|
+
if (SITE_CONNECTORS.has(lower) || BODY_SITE_HINTS.has(lower)) {
|
|
1422
|
+
indicesToInclude.add(token.index);
|
|
1423
|
+
next += 1;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
break;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
const sortedIndices = Array.from(indicesToInclude).sort((a, b) => a - b);
|
|
1430
|
+
const displayWords = [];
|
|
1431
|
+
for (const index of sortedIndices) {
|
|
1432
|
+
const token = tokens[index];
|
|
1433
|
+
if (!token) {
|
|
1434
|
+
continue;
|
|
833
1435
|
}
|
|
834
|
-
|
|
835
|
-
|
|
1436
|
+
const lower = token.lower;
|
|
1437
|
+
if (!SITE_CONNECTORS.has(lower) && !SITE_FILLER_WORDS.has(lower)) {
|
|
1438
|
+
displayWords.push(token.original);
|
|
1439
|
+
}
|
|
1440
|
+
mark(internal.consumed, token);
|
|
1441
|
+
}
|
|
1442
|
+
const normalizedSite = displayWords
|
|
1443
|
+
.filter((word) => !SITE_CONNECTORS.has(word.trim().toLowerCase()))
|
|
1444
|
+
.join(" ")
|
|
1445
|
+
.trim();
|
|
1446
|
+
if (normalizedSite) {
|
|
1447
|
+
internal.siteText = normalizedSite;
|
|
1448
|
+
if (!internal.siteSource) {
|
|
1449
|
+
internal.siteSource = "text";
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
if (!internal.routeCode && internal.siteText) {
|
|
1454
|
+
for (const { pattern, route } of SITE_UNIT_ROUTE_HINTS) {
|
|
1455
|
+
if (pattern.test(internal.siteText)) {
|
|
1456
|
+
setRoute(internal, route);
|
|
1457
|
+
break;
|
|
836
1458
|
}
|
|
837
1459
|
}
|
|
838
1460
|
}
|
|
@@ -851,20 +1473,65 @@ export function parseInternal(input, options) {
|
|
|
851
1473
|
internal.asNeededReason = reasonTokens.join(" ");
|
|
852
1474
|
}
|
|
853
1475
|
}
|
|
854
|
-
if (internal.routeCode === RouteCode["Intravitreal route (qualifier value)"] &&
|
|
1476
|
+
if (internal.routeCode === types_1.RouteCode["Intravitreal route (qualifier value)"] &&
|
|
855
1477
|
(!internal.siteText || !/eye/i.test(internal.siteText))) {
|
|
856
1478
|
internal.warnings.push("Intravitreal administrations require an eye site (e.g., OD/OS/OU).");
|
|
857
1479
|
}
|
|
858
1480
|
return internal;
|
|
859
1481
|
}
|
|
860
1482
|
function normalizeUnit(token, options) {
|
|
861
|
-
|
|
1483
|
+
var _a;
|
|
1484
|
+
const override = enforceHouseholdUnitPolicy((_a = options === null || options === void 0 ? void 0 : options.unitMap) === null || _a === void 0 ? void 0 : _a[token], options);
|
|
862
1485
|
if (override) {
|
|
863
1486
|
return override;
|
|
864
1487
|
}
|
|
865
|
-
const defaultUnit = DEFAULT_UNIT_SYNONYMS[token];
|
|
1488
|
+
const defaultUnit = enforceHouseholdUnitPolicy(maps_1.DEFAULT_UNIT_SYNONYMS[token], options);
|
|
866
1489
|
if (defaultUnit) {
|
|
867
1490
|
return defaultUnit;
|
|
868
1491
|
}
|
|
869
1492
|
return undefined;
|
|
870
1493
|
}
|
|
1494
|
+
function enforceHouseholdUnitPolicy(unit, options) {
|
|
1495
|
+
if (unit &&
|
|
1496
|
+
(options === null || options === void 0 ? void 0 : options.allowHouseholdVolumeUnits) === false &&
|
|
1497
|
+
HOUSEHOLD_VOLUME_UNIT_SET.has(unit.toLowerCase())) {
|
|
1498
|
+
return undefined;
|
|
1499
|
+
}
|
|
1500
|
+
return unit;
|
|
1501
|
+
}
|
|
1502
|
+
function inferUnitFromRouteHints(internal) {
|
|
1503
|
+
if (internal.routeCode) {
|
|
1504
|
+
const unit = maps_1.DEFAULT_UNIT_BY_ROUTE[internal.routeCode];
|
|
1505
|
+
if (unit) {
|
|
1506
|
+
return unit;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (internal.routeText) {
|
|
1510
|
+
const normalized = internal.routeText.trim().toLowerCase();
|
|
1511
|
+
const synonym = maps_1.DEFAULT_ROUTE_SYNONYMS[normalized];
|
|
1512
|
+
if (synonym) {
|
|
1513
|
+
const unit = maps_1.DEFAULT_UNIT_BY_ROUTE[synonym.code];
|
|
1514
|
+
if (unit) {
|
|
1515
|
+
return unit;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (internal.siteText) {
|
|
1520
|
+
const unit = inferUnitFromSiteText(internal.siteText);
|
|
1521
|
+
if (unit) {
|
|
1522
|
+
return unit;
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return undefined;
|
|
1526
|
+
}
|
|
1527
|
+
function inferUnitFromSiteText(siteText) {
|
|
1528
|
+
for (const { pattern, route } of SITE_UNIT_ROUTE_HINTS) {
|
|
1529
|
+
if (pattern.test(siteText)) {
|
|
1530
|
+
const unit = maps_1.DEFAULT_UNIT_BY_ROUTE[route];
|
|
1531
|
+
if (unit) {
|
|
1532
|
+
return unit;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return undefined;
|
|
1537
|
+
}
|