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/suggest.js
CHANGED
|
@@ -1,48 +1,124 @@
|
|
|
1
|
-
|
|
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");
|
|
2
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
|
+
};
|
|
3
18
|
const DEFAULT_UNIT_ROUTE_ORDER = [
|
|
4
|
-
{ unit: "tab",
|
|
5
|
-
{ unit: "cap",
|
|
6
|
-
{ unit: "
|
|
7
|
-
{ unit: "
|
|
8
|
-
{ unit: "
|
|
9
|
-
{ unit: "
|
|
10
|
-
{ unit: "
|
|
11
|
-
{ unit: "
|
|
12
|
-
{ unit: "
|
|
13
|
-
{ unit: "
|
|
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"] },
|
|
14
37
|
];
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
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 FREQ_TOKEN_BY_NUMBER = {};
|
|
109
|
+
for (const [frequency, token] of [
|
|
110
|
+
[1, "qd"],
|
|
111
|
+
[2, "bid"],
|
|
112
|
+
[3, "tid"],
|
|
113
|
+
[4, "qid"],
|
|
114
|
+
]) {
|
|
115
|
+
if (maps_1.TIMING_ABBREVIATIONS[token]) {
|
|
116
|
+
FREQ_TOKEN_BY_NUMBER[frequency] = token;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const FREQUENCY_NUMBERS = Object.keys(FREQ_TOKEN_BY_NUMBER)
|
|
120
|
+
.map((value) => Number.parseInt(value, 10))
|
|
121
|
+
.sort((a, b) => a - b);
|
|
46
122
|
const DEFAULT_PRN_REASONS = [
|
|
47
123
|
"pain",
|
|
48
124
|
"nausea",
|
|
@@ -56,6 +132,72 @@ const DEFAULT_PRN_REASONS = [
|
|
|
56
132
|
"dyspnea",
|
|
57
133
|
];
|
|
58
134
|
const DEFAULT_DOSE_COUNTS = ["1", "2"];
|
|
135
|
+
const OPTIONAL_MATCH_TOKENS = new Set([
|
|
136
|
+
"to",
|
|
137
|
+
"into",
|
|
138
|
+
"in",
|
|
139
|
+
"on",
|
|
140
|
+
"onto",
|
|
141
|
+
"per",
|
|
142
|
+
"for",
|
|
143
|
+
"the",
|
|
144
|
+
"od",
|
|
145
|
+
"os",
|
|
146
|
+
"ou",
|
|
147
|
+
]);
|
|
148
|
+
const ROUTE_TOKEN_FRAGMENTS = new Set();
|
|
149
|
+
for (const phrase of Object.keys(maps_1.DEFAULT_ROUTE_SYNONYMS)) {
|
|
150
|
+
for (const fragment of phrase.split(/\s+/)) {
|
|
151
|
+
const normalized = fragment.trim();
|
|
152
|
+
if (normalized) {
|
|
153
|
+
ROUTE_TOKEN_FRAGMENTS.add(normalized);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const SKIPPABLE_CANDIDATE_TOKENS = new Set([
|
|
158
|
+
...Array.from(OPTIONAL_MATCH_TOKENS),
|
|
159
|
+
...Array.from(ROUTE_TOKEN_FRAGMENTS),
|
|
160
|
+
]);
|
|
161
|
+
const UNIT_LOOKUP = (() => {
|
|
162
|
+
const canonicalByKey = new Map();
|
|
163
|
+
const variantsByCanonical = new Map();
|
|
164
|
+
const registerVariant = (canonical, variant) => {
|
|
165
|
+
const normalizedCanonical = normalizeKey(canonical);
|
|
166
|
+
if (!normalizedCanonical) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let variants = variantsByCanonical.get(normalizedCanonical);
|
|
170
|
+
if (!variants) {
|
|
171
|
+
variants = new Set();
|
|
172
|
+
variantsByCanonical.set(normalizedCanonical, variants);
|
|
173
|
+
}
|
|
174
|
+
variants.add(normalizeSpacing(canonical));
|
|
175
|
+
variants.add(normalizeSpacing(variant));
|
|
176
|
+
};
|
|
177
|
+
for (const token in maps_1.DEFAULT_UNIT_SYNONYMS) {
|
|
178
|
+
if (!Object.prototype.hasOwnProperty.call(maps_1.DEFAULT_UNIT_SYNONYMS, token)) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const canonicalValue = maps_1.DEFAULT_UNIT_SYNONYMS[token];
|
|
182
|
+
const canonical = normalizeSpacing(canonicalValue);
|
|
183
|
+
registerVariant(canonical, canonical);
|
|
184
|
+
registerVariant(canonical, token);
|
|
185
|
+
canonicalByKey.set(normalizeKey(token), canonical);
|
|
186
|
+
canonicalByKey.set(normalizeKey(canonical), canonical);
|
|
187
|
+
}
|
|
188
|
+
return { canonicalByKey, variantsByCanonical };
|
|
189
|
+
})();
|
|
190
|
+
function resolveCanonicalUnit(unit) {
|
|
191
|
+
var _a;
|
|
192
|
+
if (!unit) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
const normalized = normalizeKey(unit);
|
|
196
|
+
if (!normalized) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return (_a = UNIT_LOOKUP.canonicalByKey.get(normalized)) !== null && _a !== void 0 ? _a : normalizeSpacing(unit);
|
|
200
|
+
}
|
|
59
201
|
function normalizeKey(value) {
|
|
60
202
|
return value.trim().toLowerCase();
|
|
61
203
|
}
|
|
@@ -64,32 +206,153 @@ function normalizeSpacing(value) {
|
|
|
64
206
|
.trim()
|
|
65
207
|
.replace(/\s+/g, " ");
|
|
66
208
|
}
|
|
67
|
-
function
|
|
209
|
+
function getUnitVariants(unit) {
|
|
210
|
+
var _a;
|
|
211
|
+
const canonical = (_a = resolveCanonicalUnit(unit)) !== null && _a !== void 0 ? _a : normalizeSpacing(unit);
|
|
212
|
+
const normalizedCanonical = normalizeKey(canonical);
|
|
213
|
+
const variants = new Set();
|
|
214
|
+
const push = (candidate) => {
|
|
215
|
+
if (!candidate) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const normalizedCandidate = normalizeSpacing(candidate);
|
|
219
|
+
if (!normalizedCandidate) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
variants.add(normalizedCandidate);
|
|
223
|
+
};
|
|
224
|
+
push(canonical);
|
|
225
|
+
push(unit);
|
|
226
|
+
const canonicalVariants = UNIT_LOOKUP.variantsByCanonical.get(normalizedCanonical);
|
|
227
|
+
if (canonicalVariants) {
|
|
228
|
+
for (const candidate of canonicalVariants) {
|
|
229
|
+
push(candidate);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return [...variants];
|
|
233
|
+
}
|
|
234
|
+
function buildIntervalTokens(input) {
|
|
235
|
+
const intervals = new Set();
|
|
236
|
+
const add = (token) => {
|
|
237
|
+
if (!token) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const normalized = token.trim().toLowerCase();
|
|
241
|
+
if (!normalized) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
intervals.add(normalized);
|
|
245
|
+
};
|
|
246
|
+
for (const token of BASE_INTERVAL_CODES) {
|
|
247
|
+
add(token);
|
|
248
|
+
}
|
|
249
|
+
for (const token of DEFAULT_INTERVAL_RANGES) {
|
|
250
|
+
add(token);
|
|
251
|
+
}
|
|
252
|
+
const normalizedInput = input.toLowerCase();
|
|
253
|
+
const rawTokens = normalizedInput.split(/[^a-z0-9-]+/g);
|
|
254
|
+
for (const rawToken of rawTokens) {
|
|
255
|
+
if (!rawToken) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const match = rawToken.match(/^q(\d{1,2})(?:-(\d{1,2}))?(h?)$/);
|
|
259
|
+
if (!match) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const first = Number.parseInt(match[1], 10);
|
|
263
|
+
const second = match[2] ? Number.parseInt(match[2], 10) : undefined;
|
|
264
|
+
if (Number.isNaN(first) || first <= 0 || first > 48) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (second !== undefined) {
|
|
268
|
+
if (Number.isNaN(second) || second < first || second > 48) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const normalized = `q${first}${second ? `-${second}` : ""}h`;
|
|
273
|
+
add(normalized);
|
|
274
|
+
}
|
|
275
|
+
return [...intervals];
|
|
276
|
+
}
|
|
277
|
+
function buildWhenSequences() {
|
|
278
|
+
const sequences = [];
|
|
279
|
+
for (const token of WHEN_TOKENS) {
|
|
280
|
+
sequences.push([token]);
|
|
281
|
+
}
|
|
282
|
+
for (let i = 0; i < WHEN_COMBINATIONS.length; i++) {
|
|
283
|
+
const first = WHEN_COMBINATIONS[i];
|
|
284
|
+
for (let j = i + 1; j < WHEN_COMBINATIONS.length; j++) {
|
|
285
|
+
const second = WHEN_COMBINATIONS[j];
|
|
286
|
+
sequences.push([first, second]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return sequences;
|
|
290
|
+
}
|
|
291
|
+
function tokenizeForMatching(value) {
|
|
292
|
+
return value
|
|
293
|
+
.toLowerCase()
|
|
294
|
+
.split(/\s+/)
|
|
295
|
+
.map((token) => token.replace(/^[^a-z0-9-]+|[^a-z0-9-]+$/g, ""))
|
|
296
|
+
.filter((token) => token.length > 0)
|
|
297
|
+
.filter((token) => !OPTIONAL_MATCH_TOKENS.has(token));
|
|
298
|
+
}
|
|
299
|
+
function canonicalizeForMatching(value) {
|
|
300
|
+
return tokenizeForMatching(value).join(" ");
|
|
301
|
+
}
|
|
302
|
+
function tokensMatch(prefixTokens, candidateTokens) {
|
|
303
|
+
if (prefixTokens.length === 0) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
let prefixIndex = 0;
|
|
307
|
+
for (const candidateToken of candidateTokens) {
|
|
308
|
+
if (prefixIndex >= prefixTokens.length) {
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
const prefixToken = prefixTokens[prefixIndex];
|
|
312
|
+
if (candidateToken.startsWith(prefixToken)) {
|
|
313
|
+
prefixIndex += 1;
|
|
314
|
+
if (prefixIndex >= prefixTokens.length) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!SKIPPABLE_CANDIDATE_TOKENS.has(candidateToken)) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return prefixIndex >= prefixTokens.length;
|
|
324
|
+
}
|
|
325
|
+
function buildUnitRoutePairs(contextUnit, options) {
|
|
326
|
+
var _a, _b;
|
|
68
327
|
const pairs = [];
|
|
69
328
|
const seen = new Set();
|
|
70
|
-
const addPair = (unit,
|
|
71
|
-
|
|
329
|
+
const addPair = (unit, routeOverride) => {
|
|
330
|
+
var _a;
|
|
331
|
+
const canonicalUnit = resolveCanonicalUnit(unit);
|
|
332
|
+
if (!canonicalUnit) {
|
|
72
333
|
return;
|
|
73
334
|
}
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
335
|
+
const normalizedUnit = normalizeKey(canonicalUnit);
|
|
336
|
+
if ((options === null || options === void 0 ? void 0 : options.allowHouseholdVolumeUnits) === false &&
|
|
337
|
+
HOUSEHOLD_VOLUME_UNIT_SET.has(normalizedUnit)) {
|
|
76
338
|
return;
|
|
77
339
|
}
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
340
|
+
const resolvedRoute = (_a = routeOverride !== null && routeOverride !== void 0 ? routeOverride : ROUTE_TOKEN_BY_UNIT.get(normalizedUnit)) !== null && _a !== void 0 ? _a : "po";
|
|
341
|
+
const cleanRoute = normalizeSpacing(resolvedRoute);
|
|
342
|
+
if (!cleanRoute) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const key = `${normalizedUnit}::${normalizeKey(cleanRoute)}`;
|
|
81
346
|
if (seen.has(key)) {
|
|
82
347
|
return;
|
|
83
348
|
}
|
|
84
349
|
seen.add(key);
|
|
85
|
-
pairs.push({ unit:
|
|
350
|
+
pairs.push({ unit: canonicalUnit, route: cleanRoute });
|
|
86
351
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for (const pair of DEFAULT_UNIT_ROUTE_ORDER) {
|
|
92
|
-
addPair(pair.unit, pair.route);
|
|
352
|
+
addPair(contextUnit);
|
|
353
|
+
for (const preference of DEFAULT_UNIT_ROUTE_ORDER) {
|
|
354
|
+
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];
|
|
355
|
+
addPair(preference.unit, routeToken);
|
|
93
356
|
}
|
|
94
357
|
return pairs;
|
|
95
358
|
}
|
|
@@ -140,7 +403,7 @@ function buildDoseValues(input) {
|
|
|
140
403
|
}
|
|
141
404
|
return [...values];
|
|
142
405
|
}
|
|
143
|
-
function
|
|
406
|
+
function generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences) {
|
|
144
407
|
const suggestions = [];
|
|
145
408
|
const seen = new Set();
|
|
146
409
|
const push = (value) => {
|
|
@@ -156,41 +419,57 @@ function generateCandidateSignatures(pairs, doseValues, prnReasons) {
|
|
|
156
419
|
suggestions.push(normalized);
|
|
157
420
|
};
|
|
158
421
|
for (const pair of pairs) {
|
|
422
|
+
const unitVariants = getUnitVariants(pair.unit);
|
|
159
423
|
for (const code of FREQUENCY_CODES) {
|
|
160
|
-
for (const
|
|
161
|
-
|
|
424
|
+
for (const unitVariant of unitVariants) {
|
|
425
|
+
for (const dose of doseValues) {
|
|
426
|
+
push(`${dose} ${unitVariant} ${pair.route} ${code}`);
|
|
427
|
+
}
|
|
162
428
|
}
|
|
163
429
|
push(`${pair.route} ${code}`);
|
|
164
430
|
}
|
|
165
|
-
for (const interval of
|
|
166
|
-
for (const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
431
|
+
for (const interval of intervalTokens) {
|
|
432
|
+
for (const unitVariant of unitVariants) {
|
|
433
|
+
for (const dose of doseValues) {
|
|
434
|
+
push(`${dose} ${unitVariant} ${pair.route} ${interval}`);
|
|
435
|
+
for (const reason of prnReasons) {
|
|
436
|
+
push(`${dose} ${unitVariant} ${pair.route} ${interval} prn ${reason}`);
|
|
437
|
+
}
|
|
170
438
|
}
|
|
171
439
|
}
|
|
172
440
|
push(`${pair.route} ${interval}`);
|
|
173
441
|
}
|
|
174
442
|
for (const freq of FREQUENCY_NUMBERS) {
|
|
175
443
|
const freqToken = FREQ_TOKEN_BY_NUMBER[freq];
|
|
444
|
+
if (!freqToken) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
176
447
|
push(`1x${freq} ${pair.route} ${freqToken}`);
|
|
177
448
|
for (const when of CORE_WHEN_TOKENS) {
|
|
178
449
|
push(`1x${freq} ${pair.route} ${when}`);
|
|
179
450
|
}
|
|
180
451
|
}
|
|
181
|
-
for (const
|
|
182
|
-
|
|
183
|
-
|
|
452
|
+
for (const whenSequence of whenSequences) {
|
|
453
|
+
const suffix = whenSequence.join(" ");
|
|
454
|
+
for (const unitVariant of unitVariants) {
|
|
455
|
+
for (const dose of doseValues) {
|
|
456
|
+
push(`${dose} ${unitVariant} ${pair.route} ${suffix}`);
|
|
457
|
+
}
|
|
184
458
|
}
|
|
185
|
-
push(`${pair.route} ${
|
|
459
|
+
push(`${pair.route} ${suffix}`);
|
|
186
460
|
}
|
|
187
461
|
for (const reason of prnReasons) {
|
|
188
|
-
|
|
462
|
+
for (const unitVariant of unitVariants) {
|
|
463
|
+
for (const dose of doseValues) {
|
|
464
|
+
push(`${dose} ${unitVariant} ${pair.route} prn ${reason}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
push(`${pair.route} prn ${reason}`);
|
|
189
468
|
}
|
|
190
469
|
}
|
|
191
470
|
return suggestions;
|
|
192
471
|
}
|
|
193
|
-
function matchesPrefix(candidate, prefix, prefixCompact) {
|
|
472
|
+
function matchesPrefix(candidate, prefix, prefixCompact, prefixTokens, prefixTokensNoDashes, prefixCanonical, prefixCanonicalCompact, prefixNoDashes, prefixCanonicalNoDashes) {
|
|
194
473
|
if (!prefix) {
|
|
195
474
|
return true;
|
|
196
475
|
}
|
|
@@ -199,20 +478,53 @@ function matchesPrefix(candidate, prefix, prefixCompact) {
|
|
|
199
478
|
return true;
|
|
200
479
|
}
|
|
201
480
|
const compactCandidate = normalizedCandidate.replace(/\s+/g, "");
|
|
202
|
-
|
|
481
|
+
if (compactCandidate.startsWith(prefixCompact)) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
const candidateNoDashes = normalizedCandidate.replace(/-/g, "");
|
|
485
|
+
if (candidateNoDashes.startsWith(prefixNoDashes)) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
const canonicalCandidate = canonicalizeForMatching(candidate);
|
|
489
|
+
if (canonicalCandidate.startsWith(prefixCanonical)) {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
const canonicalCompact = canonicalCandidate.replace(/\s+/g, "");
|
|
493
|
+
if (canonicalCompact.startsWith(prefixCanonicalCompact)) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
const candidateTokens = tokenizeForMatching(candidate);
|
|
497
|
+
if (tokensMatch(prefixTokens, candidateTokens)) {
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
const canonicalNoDashes = canonicalCandidate.replace(/-/g, "");
|
|
501
|
+
if (canonicalNoDashes.startsWith(prefixCanonicalNoDashes)) {
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
const candidateTokensNoDashes = candidateTokens.map((token) => token.replace(/-/g, ""));
|
|
505
|
+
return tokensMatch(prefixTokensNoDashes, candidateTokensNoDashes);
|
|
203
506
|
}
|
|
204
|
-
|
|
205
|
-
|
|
507
|
+
function suggestSig(input, options) {
|
|
508
|
+
var _a, _b;
|
|
509
|
+
const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : DEFAULT_LIMIT;
|
|
206
510
|
const prefix = normalizeSpacing(input.toLowerCase());
|
|
207
511
|
const prefixCompact = prefix.replace(/\s+/g, "");
|
|
208
|
-
const
|
|
209
|
-
const
|
|
512
|
+
const prefixNoDashes = prefix.replace(/-/g, "");
|
|
513
|
+
const prefixCanonical = canonicalizeForMatching(prefix);
|
|
514
|
+
const prefixCanonicalCompact = prefixCanonical.replace(/\s+/g, "");
|
|
515
|
+
const prefixCanonicalNoDashes = prefixCanonical.replace(/-/g, "");
|
|
516
|
+
const prefixTokens = tokenizeForMatching(prefixCanonical);
|
|
517
|
+
const prefixTokensNoDashes = prefixTokens.map((token) => token.replace(/-/g, ""));
|
|
518
|
+
const contextUnit = (0, context_1.inferUnitFromContext)((_b = options === null || options === void 0 ? void 0 : options.context) !== null && _b !== void 0 ? _b : undefined);
|
|
519
|
+
const pairs = buildUnitRoutePairs(contextUnit, options);
|
|
210
520
|
const doseValues = buildDoseValues(input);
|
|
211
|
-
const prnReasons = buildPrnReasons(options
|
|
212
|
-
const
|
|
521
|
+
const prnReasons = buildPrnReasons(options === null || options === void 0 ? void 0 : options.prnReasons);
|
|
522
|
+
const intervalTokens = buildIntervalTokens(input);
|
|
523
|
+
const whenSequences = buildWhenSequences();
|
|
524
|
+
const candidates = generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences);
|
|
213
525
|
const results = [];
|
|
214
526
|
for (const candidate of candidates) {
|
|
215
|
-
if (matchesPrefix(candidate, prefix, prefixCompact)) {
|
|
527
|
+
if (matchesPrefix(candidate, prefix, prefixCompact, prefixTokens, prefixTokensNoDashes, prefixCanonical, prefixCanonicalCompact, prefixNoDashes, prefixCanonicalNoDashes)) {
|
|
216
528
|
results.push(candidate);
|
|
217
529
|
}
|
|
218
530
|
if (results.length >= limit) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SigTranslationConfig } from "./i18n";
|
|
1
2
|
export interface FhirCoding {
|
|
2
3
|
system?: string;
|
|
3
4
|
code?: string;
|
|
@@ -283,7 +284,11 @@ export interface MedicationContext {
|
|
|
283
284
|
containerUnit?: string;
|
|
284
285
|
defaultUnit?: string;
|
|
285
286
|
}
|
|
286
|
-
export interface
|
|
287
|
+
export interface FormatOptions {
|
|
288
|
+
locale?: "en" | "th" | string;
|
|
289
|
+
i18n?: SigTranslationConfig;
|
|
290
|
+
}
|
|
291
|
+
export interface ParseOptions extends FormatOptions {
|
|
287
292
|
/**
|
|
288
293
|
* Optional medication context that assists with default unit inference.
|
|
289
294
|
* May be omitted or explicitly set to null when no contextual clues exist.
|
|
@@ -298,8 +303,12 @@ export interface ParseOptions {
|
|
|
298
303
|
intervalWeeks?: number;
|
|
299
304
|
}>;
|
|
300
305
|
whenMap?: Record<string, EventTiming>;
|
|
306
|
+
/**
|
|
307
|
+
* Allows supplying institution-specific event clock anchors so parsed
|
|
308
|
+
* EventTiming arrays can be ordered chronologically for that locale.
|
|
309
|
+
*/
|
|
310
|
+
eventClock?: EventClockMap;
|
|
301
311
|
allowDiscouraged?: boolean;
|
|
302
|
-
locale?: "en" | "th" | string;
|
|
303
312
|
/**
|
|
304
313
|
* When enabled the parser will expand generic meal timing tokens (AC/PC/C)
|
|
305
314
|
* into specific breakfast/lunch/dinner (and bedtime) EventTiming entries
|
|
@@ -311,6 +320,11 @@ export interface ParseOptions {
|
|
|
311
320
|
* Defaults to "breakfast+dinner" to mirror common clinical practice.
|
|
312
321
|
*/
|
|
313
322
|
twoPerDayPair?: "breakfast+dinner" | "breakfast+lunch";
|
|
323
|
+
/**
|
|
324
|
+
* Allows disabling recognition of household volume units such as teaspoon
|
|
325
|
+
* and tablespoon when set to false. Defaults to true.
|
|
326
|
+
*/
|
|
327
|
+
allowHouseholdVolumeUnits?: boolean;
|
|
314
328
|
}
|
|
315
329
|
export interface ParseResult {
|
|
316
330
|
fhir: FhirDosage;
|
|
@@ -348,17 +362,21 @@ export interface FrequencyFallbackTimes {
|
|
|
348
362
|
* Shared configuration required to generate next-due dose timestamps.
|
|
349
363
|
*/
|
|
350
364
|
export interface NextDueDoseConfig {
|
|
351
|
-
timeZone
|
|
352
|
-
eventClock
|
|
353
|
-
mealOffsets
|
|
365
|
+
timeZone?: string;
|
|
366
|
+
eventClock?: EventClockMap;
|
|
367
|
+
mealOffsets?: MealOffsetMap;
|
|
354
368
|
frequencyDefaults?: FrequencyFallbackTimes;
|
|
355
369
|
}
|
|
356
370
|
/**
|
|
357
371
|
* Options bag for next-due dose generation.
|
|
358
372
|
*/
|
|
359
373
|
export interface NextDueDoseOptions {
|
|
360
|
-
orderedAt: Date | string;
|
|
361
374
|
from: Date | string;
|
|
362
|
-
|
|
363
|
-
|
|
375
|
+
orderedAt?: Date | string;
|
|
376
|
+
limit?: number;
|
|
377
|
+
timeZone?: string;
|
|
378
|
+
eventClock?: EventClockMap;
|
|
379
|
+
mealOffsets?: MealOffsetMap;
|
|
380
|
+
frequencyDefaults?: FrequencyFallbackTimes;
|
|
381
|
+
config?: NextDueDoseConfig;
|
|
364
382
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RouteCode = exports.FhirDayOfWeek = exports.FhirPeriodUnit = exports.SNOMEDCTRouteCodes = exports.EventTiming = void 0;
|
|
1
4
|
/**
|
|
2
5
|
* Follows https://build.fhir.org/valueset-event-timing.html
|
|
3
6
|
* Real-world event relating to the schedule.
|
|
4
7
|
*/
|
|
5
|
-
|
|
8
|
+
var EventTiming;
|
|
6
9
|
(function (EventTiming) {
|
|
7
10
|
EventTiming["Before Sleep"] = "HS";
|
|
8
11
|
EventTiming["Wake"] = "WAKE";
|
|
@@ -31,12 +34,12 @@ export var EventTiming;
|
|
|
31
34
|
EventTiming["Night"] = "NIGHT";
|
|
32
35
|
EventTiming["After Sleep"] = "PHS";
|
|
33
36
|
EventTiming["Immediate"] = "IMD";
|
|
34
|
-
})(EventTiming || (EventTiming = {}));
|
|
37
|
+
})(EventTiming || (exports.EventTiming = EventTiming = {}));
|
|
35
38
|
/**
|
|
36
39
|
* SNOMED CT route codes aligned with the official "route of administration values" value set.
|
|
37
40
|
* Keeping the enumeration exhaustive ensures downstream consumers can rely on strong typing.
|
|
38
41
|
*/
|
|
39
|
-
|
|
42
|
+
var SNOMEDCTRouteCodes;
|
|
40
43
|
(function (SNOMEDCTRouteCodes) {
|
|
41
44
|
SNOMEDCTRouteCodes["Topical route"] = "6064005";
|
|
42
45
|
SNOMEDCTRouteCodes["Otic route"] = "10547007";
|
|
@@ -201,8 +204,8 @@ export var SNOMEDCTRouteCodes;
|
|
|
201
204
|
SNOMEDCTRouteCodes["Posterior juxtascleral route (qualifier value)"] = "58821000052106";
|
|
202
205
|
SNOMEDCTRouteCodes["Subretinal route (qualifier value)"] = "58831000052108";
|
|
203
206
|
SNOMEDCTRouteCodes["Sublabial use"] = "66621000052103";
|
|
204
|
-
})(SNOMEDCTRouteCodes || (SNOMEDCTRouteCodes = {}));
|
|
205
|
-
|
|
207
|
+
})(SNOMEDCTRouteCodes || (exports.SNOMEDCTRouteCodes = SNOMEDCTRouteCodes = {}));
|
|
208
|
+
var FhirPeriodUnit;
|
|
206
209
|
(function (FhirPeriodUnit) {
|
|
207
210
|
FhirPeriodUnit["Second"] = "s";
|
|
208
211
|
FhirPeriodUnit["Minute"] = "min";
|
|
@@ -211,8 +214,8 @@ export var FhirPeriodUnit;
|
|
|
211
214
|
FhirPeriodUnit["Week"] = "wk";
|
|
212
215
|
FhirPeriodUnit["Month"] = "mo";
|
|
213
216
|
FhirPeriodUnit["Year"] = "a";
|
|
214
|
-
})(FhirPeriodUnit || (FhirPeriodUnit = {}));
|
|
215
|
-
|
|
217
|
+
})(FhirPeriodUnit || (exports.FhirPeriodUnit = FhirPeriodUnit = {}));
|
|
218
|
+
var FhirDayOfWeek;
|
|
216
219
|
(function (FhirDayOfWeek) {
|
|
217
220
|
FhirDayOfWeek["Monday"] = "mon";
|
|
218
221
|
FhirDayOfWeek["Tuesday"] = "tue";
|
|
@@ -221,5 +224,5 @@ export var FhirDayOfWeek;
|
|
|
221
224
|
FhirDayOfWeek["Friday"] = "fri";
|
|
222
225
|
FhirDayOfWeek["Saturday"] = "sat";
|
|
223
226
|
FhirDayOfWeek["Sunday"] = "sun";
|
|
224
|
-
})(FhirDayOfWeek || (FhirDayOfWeek = {}));
|
|
225
|
-
|
|
227
|
+
})(FhirDayOfWeek || (exports.FhirDayOfWeek = FhirDayOfWeek = {}));
|
|
228
|
+
exports.RouteCode = SNOMEDCTRouteCodes;
|