ezmedicationinput 0.1.43 → 0.1.45

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/suggest.js DELETED
@@ -1,907 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.suggestSig = suggestSig;
4
- const context_1 = require("./context");
5
- const maps_1 = require("./maps");
6
- const types_1 = require("./types");
7
- const DEFAULT_LIMIT = 10;
8
- const HOUSEHOLD_VOLUME_UNIT_SET = new Set(maps_1.HOUSEHOLD_VOLUME_UNITS.map((unit) => unit.trim().toLowerCase()));
9
- const ROUTE_TOKEN_BY_CODE = {
10
- [types_1.RouteCode["Oral route"]]: "po",
11
- [types_1.RouteCode["Respiratory tract route (qualifier value)"]]: "inh",
12
- [types_1.RouteCode["Nasal route"]]: "in",
13
- [types_1.RouteCode["Ophthalmic route"]]: "oph",
14
- [types_1.RouteCode["Per rectum"]]: "pr",
15
- [types_1.RouteCode["Transdermal route"]]: "transdermal",
16
- [types_1.RouteCode["Topical route"]]: "topical",
17
- };
18
- const DEFAULT_UNIT_ROUTE_ORDER = [
19
- { unit: "tab", routeCode: types_1.RouteCode["Oral route"] },
20
- { unit: "cap", routeCode: types_1.RouteCode["Oral route"] },
21
- { unit: "tsp", routeCode: types_1.RouteCode["Oral route"] },
22
- { unit: "tbsp", routeCode: types_1.RouteCode["Oral route"] },
23
- { unit: "mL", routeCode: types_1.RouteCode["Oral route"] },
24
- { unit: "L", routeCode: types_1.RouteCode["Oral route"] },
25
- { unit: "mcL", routeCode: types_1.RouteCode["Oral route"] },
26
- { unit: "nL", routeCode: types_1.RouteCode["Oral route"] },
27
- { unit: "mg", routeCode: types_1.RouteCode["Oral route"] },
28
- { unit: "mcg", routeCode: types_1.RouteCode["Oral route"] },
29
- { unit: "ng", routeCode: types_1.RouteCode["Oral route"] },
30
- { unit: "g", routeCode: types_1.RouteCode["Topical route"] },
31
- { unit: "kg", routeCode: types_1.RouteCode["Topical route"] },
32
- { unit: "puff", routeCode: types_1.RouteCode["Respiratory tract route (qualifier value)"] },
33
- { unit: "spray", routeCode: types_1.RouteCode["Nasal route"] },
34
- { unit: "drop", routeCode: types_1.RouteCode["Ophthalmic route"] },
35
- { unit: "suppository", routeCode: types_1.RouteCode["Per rectum"] },
36
- { unit: "patch", routeCode: types_1.RouteCode["Transdermal route"] },
37
- ];
38
- const ROUTE_TOKEN_BY_UNIT = (() => {
39
- var _a, _b, _c;
40
- const map = new Map();
41
- const assign = (unit, token) => {
42
- if (!unit || !token) {
43
- return;
44
- }
45
- const normalizedUnit = normalizeKey(unit);
46
- if (!normalizedUnit || map.has(normalizedUnit)) {
47
- return;
48
- }
49
- map.set(normalizedUnit, normalizeSpacing(token));
50
- };
51
- for (const routeCodeKey in maps_1.DEFAULT_UNIT_BY_ROUTE) {
52
- if (!Object.prototype.hasOwnProperty.call(maps_1.DEFAULT_UNIT_BY_ROUTE, routeCodeKey)) {
53
- continue;
54
- }
55
- const routeCode = routeCodeKey;
56
- const unit = maps_1.DEFAULT_UNIT_BY_ROUTE[routeCode];
57
- if (!unit) {
58
- continue;
59
- }
60
- const token = (_a = ROUTE_TOKEN_BY_CODE[routeCode]) !== null && _a !== void 0 ? _a : maps_1.ROUTE_TEXT[routeCode];
61
- assign(unit, token);
62
- }
63
- for (const preference of DEFAULT_UNIT_ROUTE_ORDER) {
64
- const token = (_c = (_b = preference.routeToken) !== null && _b !== void 0 ? _b : ROUTE_TOKEN_BY_CODE[preference.routeCode]) !== null && _c !== void 0 ? _c : maps_1.ROUTE_TEXT[preference.routeCode];
65
- assign(preference.unit, token);
66
- }
67
- return map;
68
- })();
69
- const BASE_INTERVAL_CODES = Object.keys(maps_1.TIMING_ABBREVIATIONS)
70
- .filter((token) => /^q\d+h$/.test(token))
71
- .sort((a, b) => Number.parseInt(a.slice(1, -1), 10) - Number.parseInt(b.slice(1, -1), 10));
72
- const DEFAULT_INTERVAL_RANGES = ["q2-4h", "q4-6h", "q6-8h", "q8-12h"];
73
- const BASE_WHEN_TOKEN_CANDIDATES = [
74
- "ac",
75
- "pc",
76
- "hs",
77
- "am",
78
- "pm",
79
- "morn",
80
- "morning",
81
- "noon",
82
- "afternoon",
83
- "evening",
84
- "night",
85
- "bedtime",
86
- "wake",
87
- "waking",
88
- "breakfast",
89
- "lunch",
90
- "dinner",
91
- "stat",
92
- ];
93
- const WHEN_TOKENS = BASE_WHEN_TOKEN_CANDIDATES.filter((token) => maps_1.EVENT_TIMING_TOKENS[token] !== undefined);
94
- const WHEN_COMBINATIONS = [
95
- "am",
96
- "morning",
97
- "morn",
98
- "noon",
99
- "afternoon",
100
- "pm",
101
- "evening",
102
- "night",
103
- "hs",
104
- "bedtime",
105
- ].filter((token) => maps_1.EVENT_TIMING_TOKENS[token] !== undefined);
106
- const CORE_WHEN_TOKENS = ["pc", "ac", "hs"].filter((token) => maps_1.EVENT_TIMING_TOKENS[token] !== undefined);
107
- const FREQUENCY_CODES = ["qd", "od", "bid", "tid", "qid"].filter((token) => maps_1.TIMING_ABBREVIATIONS[token] !== undefined);
108
- const FREQUENCY_CODE_SUFFIXES = FREQUENCY_CODES.map((code) => ` ${code}`);
109
- const FREQ_TOKEN_BY_NUMBER = {};
110
- for (const [frequency, token] of [
111
- [1, "qd"],
112
- [2, "bid"],
113
- [3, "tid"],
114
- [4, "qid"],
115
- ]) {
116
- if (maps_1.TIMING_ABBREVIATIONS[token]) {
117
- FREQ_TOKEN_BY_NUMBER[frequency] = token;
118
- }
119
- }
120
- const FREQUENCY_NUMBERS = Object.keys(FREQ_TOKEN_BY_NUMBER)
121
- .map((value) => Number.parseInt(value, 10))
122
- .sort((a, b) => a - b);
123
- const DEFAULT_PRN_REASONS = [
124
- "pain",
125
- "nausea",
126
- "itching",
127
- "anxiety",
128
- "sleep",
129
- "cough",
130
- "fever",
131
- "spasm",
132
- "constipation",
133
- "dyspnea",
134
- ];
135
- const DEFAULT_DOSE_COUNTS = ["1", "2"];
136
- const OPTIONAL_MATCH_TOKENS = new Set([
137
- "to",
138
- "into",
139
- "in",
140
- "on",
141
- "onto",
142
- "per",
143
- "for",
144
- "the",
145
- "od",
146
- "os",
147
- "ou",
148
- ]);
149
- const ROUTE_TOKEN_FRAGMENTS = new Set();
150
- for (const phrase of Object.keys(maps_1.DEFAULT_ROUTE_SYNONYMS)) {
151
- for (const fragment of phrase.split(/\s+/)) {
152
- const normalized = fragment.trim();
153
- if (normalized) {
154
- ROUTE_TOKEN_FRAGMENTS.add(normalized);
155
- }
156
- }
157
- }
158
- const SKIPPABLE_CANDIDATE_TOKENS = new Set([
159
- ...Array.from(OPTIONAL_MATCH_TOKENS),
160
- ...Array.from(ROUTE_TOKEN_FRAGMENTS),
161
- ]);
162
- const UNIT_LOOKUP = (() => {
163
- const canonicalByKey = new Map();
164
- const variantsByCanonical = new Map();
165
- const registerVariant = (canonical, variant) => {
166
- const normalizedCanonical = normalizeKey(canonical);
167
- if (!normalizedCanonical) {
168
- return;
169
- }
170
- let variants = variantsByCanonical.get(normalizedCanonical);
171
- if (!variants) {
172
- variants = new Set();
173
- variantsByCanonical.set(normalizedCanonical, variants);
174
- }
175
- variants.add(normalizeSpacing(canonical));
176
- variants.add(normalizeSpacing(variant));
177
- };
178
- for (const token in maps_1.DEFAULT_UNIT_SYNONYMS) {
179
- if (!Object.prototype.hasOwnProperty.call(maps_1.DEFAULT_UNIT_SYNONYMS, token)) {
180
- continue;
181
- }
182
- const canonicalValue = maps_1.DEFAULT_UNIT_SYNONYMS[token];
183
- const canonical = normalizeSpacing(canonicalValue);
184
- registerVariant(canonical, canonical);
185
- registerVariant(canonical, token);
186
- canonicalByKey.set(normalizeKey(token), canonical);
187
- canonicalByKey.set(normalizeKey(canonical), canonical);
188
- }
189
- return { canonicalByKey, variantsByCanonical };
190
- })();
191
- function resolveCanonicalUnit(unit) {
192
- var _a;
193
- if (!unit) {
194
- return undefined;
195
- }
196
- const normalized = normalizeKey(unit);
197
- if (!normalized) {
198
- return undefined;
199
- }
200
- return (_a = UNIT_LOOKUP.canonicalByKey.get(normalized)) !== null && _a !== void 0 ? _a : normalizeSpacing(unit);
201
- }
202
- function normalizeKey(value) {
203
- return value.trim().toLowerCase();
204
- }
205
- function normalizeSpacing(value) {
206
- return value
207
- .trim()
208
- .replace(/\s+/g, " ");
209
- }
210
- function removeWhitespaceCharacters(value) {
211
- for (let index = 0; index < value.length; index += 1) {
212
- const code = value.charCodeAt(index);
213
- if (code <= 32) {
214
- const result = [];
215
- for (let inner = 0; inner < value.length; inner += 1) {
216
- const currentCode = value.charCodeAt(inner);
217
- if (currentCode > 32) {
218
- result.push(value.charAt(inner));
219
- }
220
- }
221
- return result.join("");
222
- }
223
- }
224
- return value;
225
- }
226
- function removeDashes(value) {
227
- if (value.indexOf("-") === -1) {
228
- return value;
229
- }
230
- const result = [];
231
- for (let index = 0; index < value.length; index += 1) {
232
- const char = value.charAt(index);
233
- if (char !== "-") {
234
- result.push(char);
235
- }
236
- }
237
- return result.join("");
238
- }
239
- const UNIT_VARIANT_CACHE = new Map();
240
- function getUnitVariants(unit) {
241
- var _a;
242
- const canonical = (_a = resolveCanonicalUnit(unit)) !== null && _a !== void 0 ? _a : normalizeSpacing(unit);
243
- const normalizedCanonical = normalizeKey(canonical);
244
- const cached = UNIT_VARIANT_CACHE.get(normalizedCanonical);
245
- if (cached) {
246
- return cached;
247
- }
248
- const variants = new Map();
249
- const push = (candidate) => {
250
- if (!candidate) {
251
- return;
252
- }
253
- const normalizedCandidate = normalizeSpacing(candidate);
254
- if (!normalizedCandidate) {
255
- return;
256
- }
257
- const lower = normalizedCandidate.toLowerCase();
258
- if (variants.has(lower)) {
259
- return;
260
- }
261
- variants.set(lower, { value: normalizedCandidate, lower });
262
- };
263
- push(canonical);
264
- push(unit);
265
- const canonicalVariants = UNIT_LOOKUP.variantsByCanonical.get(normalizedCanonical);
266
- if (canonicalVariants) {
267
- for (const candidate of canonicalVariants) {
268
- push(candidate);
269
- }
270
- }
271
- const result = [...variants.values()];
272
- UNIT_VARIANT_CACHE.set(normalizedCanonical, result);
273
- return result;
274
- }
275
- function buildIntervalTokens(input) {
276
- const intervals = new Set();
277
- const add = (token) => {
278
- if (!token) {
279
- return;
280
- }
281
- const normalized = token.trim().toLowerCase();
282
- if (!normalized) {
283
- return;
284
- }
285
- intervals.add(normalized);
286
- };
287
- for (const token of BASE_INTERVAL_CODES) {
288
- add(token);
289
- }
290
- for (const token of DEFAULT_INTERVAL_RANGES) {
291
- add(token);
292
- }
293
- const normalizedInput = input.toLowerCase();
294
- const rawTokens = normalizedInput.split(/[^a-z0-9-]+/g);
295
- for (const rawToken of rawTokens) {
296
- if (!rawToken) {
297
- continue;
298
- }
299
- const match = rawToken.match(/^q(\d{1,2})(?:-(\d{1,2}))?(h?)$/);
300
- if (!match) {
301
- continue;
302
- }
303
- const first = Number.parseInt(match[1], 10);
304
- const second = match[2] ? Number.parseInt(match[2], 10) : undefined;
305
- if (Number.isNaN(first) || first <= 0 || first > 48) {
306
- continue;
307
- }
308
- if (second !== undefined) {
309
- if (Number.isNaN(second) || second < first || second > 48) {
310
- continue;
311
- }
312
- }
313
- const normalized = `q${first}${second ? `-${second}` : ""}h`;
314
- add(normalized);
315
- }
316
- return [...intervals];
317
- }
318
- function buildWhenSequences() {
319
- const sequences = [];
320
- for (const token of WHEN_TOKENS) {
321
- sequences.push([token]);
322
- }
323
- for (let i = 0; i < WHEN_COMBINATIONS.length; i++) {
324
- const first = WHEN_COMBINATIONS[i];
325
- for (let j = i + 1; j < WHEN_COMBINATIONS.length; j++) {
326
- const second = WHEN_COMBINATIONS[j];
327
- sequences.push([first, second]);
328
- }
329
- }
330
- return sequences;
331
- }
332
- const PRECOMPUTED_WHEN_SEQUENCES = buildWhenSequences();
333
- const PRECOMPUTED_WHEN_SEQUENCE_SUFFIXES = PRECOMPUTED_WHEN_SEQUENCES.map((sequence) => ` ${sequence.join(" ")}`);
334
- function tokenizeLowercaseForMatching(value) {
335
- return value
336
- .split(/\s+/)
337
- .map((token) => token.replace(/^[^a-z0-9-]+|[^a-z0-9-]+$/g, ""))
338
- .filter((token) => token.length > 0)
339
- .filter((token) => !OPTIONAL_MATCH_TOKENS.has(token));
340
- }
341
- function tokenizeForMatching(value) {
342
- return tokenizeLowercaseForMatching(value.toLowerCase());
343
- }
344
- function canonicalizeLowercaseForMatching(value) {
345
- return tokenizeLowercaseForMatching(value).join(" ");
346
- }
347
- function canonicalizeForMatching(value) {
348
- return canonicalizeLowercaseForMatching(value.toLowerCase());
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
- }
387
- function tokensMatch(prefixTokens, candidateTokens) {
388
- if (prefixTokens.length === 0) {
389
- return true;
390
- }
391
- let prefixIndex = 0;
392
- for (const candidateToken of candidateTokens) {
393
- if (prefixIndex >= prefixTokens.length) {
394
- return true;
395
- }
396
- const prefixToken = prefixTokens[prefixIndex];
397
- if (candidateToken.startsWith(prefixToken)) {
398
- prefixIndex += 1;
399
- if (prefixIndex >= prefixTokens.length) {
400
- return true;
401
- }
402
- continue;
403
- }
404
- if (!SKIPPABLE_CANDIDATE_TOKENS.has(candidateToken)) {
405
- return false;
406
- }
407
- }
408
- return prefixIndex >= prefixTokens.length;
409
- }
410
- function buildUnitRoutePairs(contextUnit, options) {
411
- var _a, _b;
412
- const pairs = [];
413
- const seen = new Set();
414
- const addPair = (unit, routeOverride) => {
415
- var _a;
416
- const canonicalUnit = resolveCanonicalUnit(unit);
417
- if (!canonicalUnit) {
418
- return;
419
- }
420
- const normalizedUnit = normalizeKey(canonicalUnit);
421
- if ((options === null || options === void 0 ? void 0 : options.allowHouseholdVolumeUnits) === false &&
422
- HOUSEHOLD_VOLUME_UNIT_SET.has(normalizedUnit)) {
423
- return;
424
- }
425
- const resolvedRoute = (_a = routeOverride !== null && routeOverride !== void 0 ? routeOverride : ROUTE_TOKEN_BY_UNIT.get(normalizedUnit)) !== null && _a !== void 0 ? _a : "po";
426
- const cleanRoute = normalizeSpacing(resolvedRoute);
427
- if (!cleanRoute) {
428
- return;
429
- }
430
- const routeLower = cleanRoute.toLowerCase();
431
- const key = `${normalizedUnit}::${routeLower}`;
432
- if (seen.has(key)) {
433
- return;
434
- }
435
- seen.add(key);
436
- pairs.push({ unit: canonicalUnit, route: cleanRoute, routeLower });
437
- };
438
- addPair(contextUnit);
439
- for (const preference of DEFAULT_UNIT_ROUTE_ORDER) {
440
- const routeToken = (_b = (_a = preference.routeToken) !== null && _a !== void 0 ? _a : ROUTE_TOKEN_BY_CODE[preference.routeCode]) !== null && _b !== void 0 ? _b : maps_1.ROUTE_TEXT[preference.routeCode];
441
- addPair(preference.unit, routeToken);
442
- }
443
- return pairs;
444
- }
445
- function buildPrnReasons(customReasons) {
446
- const reasons = new Set();
447
- const add = (reason) => {
448
- if (!reason) {
449
- return;
450
- }
451
- const normalized = normalizeSpacing(reason.toLowerCase());
452
- if (!normalized) {
453
- return;
454
- }
455
- reasons.add(normalized);
456
- };
457
- if (customReasons) {
458
- for (const reason of customReasons) {
459
- add(reason);
460
- }
461
- }
462
- for (const reason of DEFAULT_PRN_REASONS) {
463
- add(reason);
464
- }
465
- return [...reasons];
466
- }
467
- function extractDoseValuesFromInput(input) {
468
- const matches = input.match(/\d+(?:\.\d+)?(?:\/\d+(?:\.\d+)?)?/g);
469
- if (!matches) {
470
- return [];
471
- }
472
- const values = new Set();
473
- for (const match of matches) {
474
- if (!match) {
475
- continue;
476
- }
477
- values.add(match);
478
- }
479
- return [...values];
480
- }
481
- function buildDoseValues(input) {
482
- const dynamicValues = extractDoseValuesFromInput(input);
483
- const values = new Set();
484
- for (const value of dynamicValues) {
485
- values.add(value);
486
- }
487
- for (const value of DEFAULT_DOSE_COUNTS) {
488
- values.add(value);
489
- }
490
- return [...values];
491
- }
492
- const CANDIDATE_FINGERPRINT_CACHE = new Map();
493
- function getCandidateFingerprint(candidateLower) {
494
- let fingerprint = CANDIDATE_FINGERPRINT_CACHE.get(candidateLower);
495
- if (!fingerprint) {
496
- fingerprint = {};
497
- CANDIDATE_FINGERPRINT_CACHE.set(candidateLower, fingerprint);
498
- }
499
- return fingerprint;
500
- }
501
- function generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, timeTokens, whenSequences, limit, matcher) {
502
- const suggestions = [];
503
- const seen = new Set();
504
- const doseVariantMap = new Map();
505
- for (const dose of doseValues) {
506
- const normalized = normalizeSpacing(dose);
507
- if (!normalized) {
508
- continue;
509
- }
510
- const lower = normalized.toLowerCase();
511
- if (!doseVariantMap.has(lower)) {
512
- doseVariantMap.set(lower, { value: normalized, lower });
513
- }
514
- }
515
- const doseVariants = [...doseVariantMap.values()];
516
- const push = (value, lower) => {
517
- if (!lower) {
518
- return false;
519
- }
520
- if (seen.has(lower)) {
521
- return false;
522
- }
523
- if (!matcher(value, lower)) {
524
- return false;
525
- }
526
- seen.add(lower);
527
- suggestions.push(value);
528
- return suggestions.length >= limit;
529
- };
530
- const codeSuffixes = FREQUENCY_CODE_SUFFIXES;
531
- const prnSuffixes = new Array(prnReasons.length);
532
- for (let i = 0; i < prnReasons.length; i += 1) {
533
- prnSuffixes[i] = ` prn ${prnReasons[i]}`;
534
- }
535
- const intervalSuffixes = new Array(intervalTokens.length);
536
- for (let i = 0; i < intervalTokens.length; i += 1) {
537
- intervalSuffixes[i] = ` ${intervalTokens[i]}`;
538
- }
539
- const whenSuffixes = whenSequences === PRECOMPUTED_WHEN_SEQUENCES
540
- ? PRECOMPUTED_WHEN_SEQUENCE_SUFFIXES
541
- : whenSequences.map((sequence) => ` ${sequence.join(" ")}`);
542
- const timeSuffixes = timeTokens.map((token) => ` ${token}`);
543
- for (let pairIndex = 0; pairIndex < pairs.length; pairIndex += 1) {
544
- const pair = pairs[pairIndex];
545
- const unitVariants = getUnitVariants(pair.unit);
546
- const route = pair.route;
547
- const routeLower = pair.routeLower;
548
- const unitDoseVariants = new Array(unitVariants.length);
549
- for (let unitIndex = 0; unitIndex < unitVariants.length; unitIndex += 1) {
550
- const unitVariant = unitVariants[unitIndex];
551
- const unitRouteValue = `${unitVariant.value} ${route}`;
552
- const unitRouteLower = `${unitVariant.lower} ${routeLower}`;
553
- const doseBases = new Array(doseVariants.length);
554
- for (let doseIndex = 0; doseIndex < doseVariants.length; doseIndex += 1) {
555
- const doseVariant = doseVariants[doseIndex];
556
- doseBases[doseIndex] = {
557
- value: `${doseVariant.value} ${unitRouteValue}`,
558
- lower: `${doseVariant.lower} ${unitRouteLower}`,
559
- };
560
- }
561
- unitDoseVariants[unitIndex] = doseBases;
562
- }
563
- for (let codeIndex = 0; codeIndex < codeSuffixes.length; codeIndex += 1) {
564
- const codeSuffix = codeSuffixes[codeIndex];
565
- for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
566
- const doseBases = unitDoseVariants[unitIndex];
567
- for (let doseIndex = 0; doseIndex < doseBases.length; doseIndex += 1) {
568
- const base = doseBases[doseIndex];
569
- if (push(base.value + codeSuffix, base.lower + codeSuffix)) {
570
- return suggestions;
571
- }
572
- }
573
- }
574
- if (push(route + codeSuffix, routeLower + codeSuffix)) {
575
- return suggestions;
576
- }
577
- }
578
- for (let intervalIndex = 0; intervalIndex < intervalSuffixes.length; intervalIndex += 1) {
579
- const intervalSuffix = intervalSuffixes[intervalIndex];
580
- for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
581
- const doseBases = unitDoseVariants[unitIndex];
582
- for (let doseIndex = 0; doseIndex < doseBases.length; doseIndex += 1) {
583
- const base = doseBases[doseIndex];
584
- const baseIntervalValue = base.value + intervalSuffix;
585
- const baseIntervalLower = base.lower + intervalSuffix;
586
- if (push(baseIntervalValue, baseIntervalLower)) {
587
- return suggestions;
588
- }
589
- for (let reasonIndex = 0; reasonIndex < prnSuffixes.length; reasonIndex += 1) {
590
- const reasonSuffix = prnSuffixes[reasonIndex];
591
- if (push(baseIntervalValue + reasonSuffix, baseIntervalLower + reasonSuffix)) {
592
- return suggestions;
593
- }
594
- }
595
- }
596
- }
597
- if (push(route + intervalSuffix, routeLower + intervalSuffix)) {
598
- return suggestions;
599
- }
600
- }
601
- for (let freqIndex = 0; freqIndex < FREQUENCY_NUMBERS.length; freqIndex += 1) {
602
- const freq = FREQUENCY_NUMBERS[freqIndex];
603
- const freqToken = FREQ_TOKEN_BY_NUMBER[freq];
604
- if (!freqToken) {
605
- continue;
606
- }
607
- const baseValue = `1x${freq} ${route}`;
608
- const baseLower = `1x${freq} ${routeLower}`;
609
- if (push(`${baseValue} ${freqToken}`, `${baseLower} ${freqToken}`)) {
610
- return suggestions;
611
- }
612
- for (let whenIndex = 0; whenIndex < CORE_WHEN_TOKENS.length; whenIndex += 1) {
613
- const whenToken = CORE_WHEN_TOKENS[whenIndex];
614
- if (push(`${baseValue} ${whenToken}`, `${baseLower} ${whenToken}`)) {
615
- return suggestions;
616
- }
617
- }
618
- }
619
- for (let whenIndex = 0; whenIndex < whenSuffixes.length; whenIndex += 1) {
620
- const whenSuffix = whenSuffixes[whenIndex];
621
- for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
622
- const doseBases = unitDoseVariants[unitIndex];
623
- for (let doseIndex = 0; doseIndex < doseBases.length; doseIndex += 1) {
624
- const base = doseBases[doseIndex];
625
- if (push(base.value + whenSuffix, base.lower + whenSuffix)) {
626
- return suggestions;
627
- }
628
- }
629
- }
630
- if (push(route + whenSuffix, routeLower + whenSuffix)) {
631
- return suggestions;
632
- }
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
- }
649
- for (let reasonIndex = 0; reasonIndex < prnSuffixes.length; reasonIndex += 1) {
650
- const reasonSuffix = prnSuffixes[reasonIndex];
651
- for (let unitIndex = 0; unitIndex < unitDoseVariants.length; unitIndex += 1) {
652
- const doseBases = unitDoseVariants[unitIndex];
653
- for (let doseIndex = 0; doseIndex < doseBases.length; doseIndex += 1) {
654
- const base = doseBases[doseIndex];
655
- if (push(base.value + reasonSuffix, base.lower + reasonSuffix)) {
656
- return suggestions;
657
- }
658
- }
659
- }
660
- if (push(route + reasonSuffix, routeLower + reasonSuffix)) {
661
- return suggestions;
662
- }
663
- }
664
- }
665
- return suggestions;
666
- }
667
- function matchesPrefix(_candidate, candidateLower, context) {
668
- var _a, _b, _c, _d, _e, _f;
669
- if (!context.raw) {
670
- return true;
671
- }
672
- if (!context.hasCanonical && !context.hasTokens) {
673
- return true;
674
- }
675
- if (candidateLower.startsWith(context.raw)) {
676
- return true;
677
- }
678
- const fingerprint = getCandidateFingerprint(candidateLower);
679
- if (context.requiresCompact) {
680
- const compactCandidate = (_a = fingerprint.compact) !== null && _a !== void 0 ? _a : (fingerprint.compact = removeWhitespaceCharacters(candidateLower));
681
- if (compactCandidate.startsWith(context.compact)) {
682
- return true;
683
- }
684
- }
685
- if (context.requiresNoDashes) {
686
- const candidateNoDashes = (_b = fingerprint.noDashes) !== null && _b !== void 0 ? _b : (fingerprint.noDashes = removeDashes(candidateLower));
687
- if (candidateNoDashes.startsWith(context.noDashes)) {
688
- return true;
689
- }
690
- }
691
- const getCandidateTokens = () => {
692
- if (!fingerprint.tokens) {
693
- fingerprint.tokens = tokenizeLowercaseForMatching(candidateLower);
694
- }
695
- return fingerprint.tokens;
696
- };
697
- if (context.hasCanonical) {
698
- const canonicalCandidate = (_c = fingerprint.canonical) !== null && _c !== void 0 ? _c : (fingerprint.canonical = getCandidateTokens().join(" "));
699
- if (canonicalCandidate.startsWith(context.canonical)) {
700
- return true;
701
- }
702
- if (context.requiresCanonicalCompact) {
703
- const canonicalCompact = (_d = fingerprint.canonicalCompact) !== null && _d !== void 0 ? _d : (fingerprint.canonicalCompact = removeWhitespaceCharacters(canonicalCandidate));
704
- if (canonicalCompact.startsWith(context.canonicalCompact)) {
705
- return true;
706
- }
707
- }
708
- if (context.requiresCanonicalNoDashes) {
709
- const canonicalNoDashes = (_e = fingerprint.canonicalNoDashes) !== null && _e !== void 0 ? _e : (fingerprint.canonicalNoDashes = removeDashes(canonicalCandidate));
710
- if (canonicalNoDashes.startsWith(context.canonicalNoDashes)) {
711
- return true;
712
- }
713
- }
714
- }
715
- if (context.hasTokens) {
716
- const resolvedTokens = getCandidateTokens();
717
- if (tokensMatch(context.tokens, resolvedTokens)) {
718
- return true;
719
- }
720
- if (context.requiresTokenNoDashes) {
721
- const candidateTokensNoDashes = (_f = fingerprint.tokensNoDashes) !== null && _f !== void 0 ? _f : (fingerprint.tokensNoDashes = resolvedTokens.map((token) => removeDashes(token)));
722
- if (tokensMatch(context.tokensNoDashes, candidateTokensNoDashes)) {
723
- return true;
724
- }
725
- }
726
- }
727
- else if (context.requiresTokenNoDashes) {
728
- return true;
729
- }
730
- return false;
731
- }
732
- function collectMatchedCandidates(candidates, limit, matcher) {
733
- const suggestions = [];
734
- const seen = new Set();
735
- for (const candidate of candidates) {
736
- const value = normalizeSpacing(candidate);
737
- if (!value) {
738
- continue;
739
- }
740
- const lower = value.toLowerCase();
741
- if (seen.has(lower)) {
742
- continue;
743
- }
744
- if (!matcher(value, lower)) {
745
- continue;
746
- }
747
- seen.add(lower);
748
- suggestions.push(value);
749
- if (suggestions.length >= limit) {
750
- break;
751
- }
752
- }
753
- return suggestions;
754
- }
755
- function buildMealDashCoreVariants(prefixCore) {
756
- var _a;
757
- if (!prefixCore.includes("-") || prefixCore.includes("--")) {
758
- return [];
759
- }
760
- const slots = prefixCore.split("-");
761
- if (slots.length < 2 || slots.length > 4) {
762
- return [];
763
- }
764
- if (!/^[0-9]+(?:\.[0-9]+)?$/.test((_a = slots[0]) !== null && _a !== void 0 ? _a : "")) {
765
- return [];
766
- }
767
- for (let i = 1; i < slots.length; i += 1) {
768
- const slot = slots[i];
769
- if (slot.length === 0) {
770
- continue;
771
- }
772
- if (!/^[0-9]+(?:\.[0-9]+)?$/.test(slot)) {
773
- return [];
774
- }
775
- }
776
- const variants = [];
777
- const seen = new Set();
778
- const addVariant = (value) => {
779
- if (!seen.has(value)) {
780
- seen.add(value);
781
- variants.push(value);
782
- }
783
- };
784
- const first = slots[0];
785
- const fillBase = (targetLength) => {
786
- const values = new Array(targetLength).fill("0");
787
- values[0] = first;
788
- for (let i = 1; i < targetLength; i += 1) {
789
- if (i < slots.length && slots[i] !== "") {
790
- values[i] = slots[i];
791
- }
792
- }
793
- return values;
794
- };
795
- const base3 = fillBase(3);
796
- const missingThird = slots.length < 3 || slots[2] === "";
797
- if (missingThird && (slots.length === 1 || slots[1] === "" || slots[1] === "0")) {
798
- const mirror = [...base3];
799
- mirror[2] = first;
800
- addVariant(mirror.join("-"));
801
- }
802
- addVariant(base3.join("-"));
803
- const base4 = fillBase(4);
804
- const missingFourth = slots.length < 4 || slots[3] === "";
805
- if (missingFourth &&
806
- (slots.length === 1 ||
807
- slots[1] === "" ||
808
- (slots[1] === "0" && (slots.length < 3 || slots[2] === "" || slots[2] === "0")))) {
809
- const mirror = [...base4];
810
- mirror[3] = first;
811
- addVariant(mirror.join("-"));
812
- }
813
- addVariant(base4.join("-"));
814
- return variants;
815
- }
816
- function suggestMealDashSyntax(prefix, limit, matcher) {
817
- if (!prefix.includes("-")) {
818
- return undefined;
819
- }
820
- const match = prefix.match(/^(\d+(?:-\d*){0,3})(?:\s+(ac|pc))?$/);
821
- if (!match) {
822
- return undefined;
823
- }
824
- const core = match[1];
825
- const relation = match[2];
826
- const coreVariants = buildMealDashCoreVariants(core);
827
- if (coreVariants.length === 0) {
828
- return undefined;
829
- }
830
- const suffixes = relation ? [` ${relation}`] : ["", " ac", " pc"];
831
- const candidates = [];
832
- for (const variant of coreVariants) {
833
- for (const suffix of suffixes) {
834
- candidates.push(`${variant}${suffix}`);
835
- }
836
- }
837
- return collectMatchedCandidates(candidates, limit, matcher);
838
- }
839
- function suggestCompactOralMealTiming(prefix, limit, matcher) {
840
- var _a, _b;
841
- const match = prefix.match(/^(\d+(?:\.\d+)?)\s*(?:po\s*(c|ac|pc)|po(c|ac|pc))$/);
842
- if (!match) {
843
- return undefined;
844
- }
845
- const dose = normalizeSpacing(match[1]);
846
- const timing = ((_b = (_a = match[2]) !== null && _a !== void 0 ? _a : match[3]) !== null && _b !== void 0 ? _b : "").toLowerCase();
847
- const orderedTimings = timing === "c"
848
- ? ["c", "ac", "pc"]
849
- : timing === "ac"
850
- ? ["ac", "c", "pc"]
851
- : timing === "pc"
852
- ? ["pc", "c", "ac"]
853
- : ["c", "ac", "pc"];
854
- const candidates = orderedTimings.map((token) => `${dose} po ${token}`);
855
- return collectMatchedCandidates(candidates, limit, matcher);
856
- }
857
- function suggestSig(input, options) {
858
- var _a, _b;
859
- const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : DEFAULT_LIMIT;
860
- if (limit <= 0) {
861
- return [];
862
- }
863
- const prefix = normalizeSpacing(input.toLowerCase());
864
- const prefixCompact = prefix.replace(/\s+/g, "");
865
- const prefixNoDashes = prefix.replace(/-/g, "");
866
- const prefixTokens = tokenizeLowercaseForMatching(prefix);
867
- const prefixCanonical = prefixTokens.join(" ");
868
- const prefixCanonicalCompact = prefixCanonical.replace(/\s+/g, "");
869
- const prefixCanonicalNoDashes = prefixCanonical.replace(/-/g, "");
870
- const prefixTokensNoDashes = prefixTokens.map((token) => token.replace(/-/g, ""));
871
- const prefixContext = {
872
- raw: prefix,
873
- compact: prefixCompact,
874
- noDashes: prefixNoDashes,
875
- canonical: prefixCanonical,
876
- canonicalCompact: prefixCanonicalCompact,
877
- canonicalNoDashes: prefixCanonicalNoDashes,
878
- tokens: prefixTokens,
879
- tokensNoDashes: prefixTokensNoDashes,
880
- hasCanonical: prefixCanonical.length > 0,
881
- hasTokens: prefixTokens.length > 0,
882
- requiresCompact: prefixCompact !== prefix,
883
- requiresNoDashes: prefixNoDashes !== prefix,
884
- requiresCanonicalCompact: prefixCanonicalCompact !== prefixCanonical,
885
- requiresCanonicalNoDashes: prefixCanonicalNoDashes !== prefixCanonical,
886
- requiresTokenNoDashes: prefixTokens.some((token, index) => token !== prefixTokensNoDashes[index]),
887
- };
888
- const contextUnit = (0, context_1.inferUnitFromContext)((_b = options === null || options === void 0 ? void 0 : options.context) !== null && _b !== void 0 ? _b : undefined);
889
- const pairs = buildUnitRoutePairs(contextUnit, options);
890
- const doseValues = buildDoseValues(input);
891
- const prnReasons = buildPrnReasons(options === null || options === void 0 ? void 0 : options.prnReasons);
892
- const intervalTokens = buildIntervalTokens(input);
893
- const timeTokens = buildTimeTokens(input);
894
- const whenSequences = PRECOMPUTED_WHEN_SEQUENCES;
895
- const matcher = (candidate, candidateLower) => matchesPrefix(candidate, candidateLower, prefixContext);
896
- const compactOralSuggestions = suggestCompactOralMealTiming(prefix, limit, matcher);
897
- if (compactOralSuggestions && compactOralSuggestions.length > 0) {
898
- return compactOralSuggestions;
899
- }
900
- if (options === null || options === void 0 ? void 0 : options.enableMealDashSyntax) {
901
- const mealDashSuggestions = suggestMealDashSyntax(prefix, limit, matcher);
902
- if (mealDashSuggestions && mealDashSuggestions.length > 0) {
903
- return mealDashSuggestions;
904
- }
905
- }
906
- return generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, timeTokens, whenSequences, limit, matcher);
907
- }