ezmedicationinput 0.1.0 → 0.1.1
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/maps.js +69 -49
- package/dist/parser.js +1 -1
- package/dist/suggest.d.ts +12 -0
- package/dist/suggest.js +223 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FhirDosage, ParseOptions, ParseResult } from "./types";
|
|
2
2
|
export { parseInternal } from "./parser";
|
|
3
|
+
export { suggestSig } from "./suggest";
|
|
3
4
|
export * from "./types";
|
|
4
5
|
export { nextDueDoses } from "./schedule";
|
|
5
6
|
export declare function parseSig(input: string, options?: ParseOptions): ParseResult;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { formatInternal } from "./format";
|
|
|
2
2
|
import { internalFromFhir, toFhir } from "./fhir";
|
|
3
3
|
import { parseInternal } from "./parser";
|
|
4
4
|
export { parseInternal } from "./parser";
|
|
5
|
+
export { suggestSig } from "./suggest";
|
|
5
6
|
export * from "./types";
|
|
6
7
|
export { nextDueDoses } from "./schedule";
|
|
7
8
|
export function parseSig(input, options) {
|
package/dist/maps.js
CHANGED
|
@@ -53,63 +53,83 @@ export const DEFAULT_ROUTE_SYNONYMS = (() => {
|
|
|
53
53
|
}
|
|
54
54
|
map[normalized] = { code, text: ROUTE_TEXT[code] };
|
|
55
55
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
56
|
+
const registerVariants = (value, code) => {
|
|
57
|
+
if (!value)
|
|
58
|
+
return;
|
|
59
|
+
assign(value, code);
|
|
60
|
+
const withoutParens = value
|
|
61
|
+
.replace(/[()]/g, " ")
|
|
62
|
+
.replace(/\s+/g, " ")
|
|
63
|
+
.trim();
|
|
64
|
+
assign(withoutParens, code);
|
|
65
|
+
const withoutCommas = value
|
|
66
|
+
.replace(/,/g, " ")
|
|
67
|
+
.replace(/\s+/g, " ")
|
|
68
|
+
.trim();
|
|
69
|
+
assign(withoutCommas, code);
|
|
70
|
+
const withoutPunctuation = value
|
|
71
|
+
.replace(/[().,-]/g, " ")
|
|
72
|
+
.replace(/\s+/g, " ")
|
|
73
|
+
.trim();
|
|
74
|
+
assign(withoutPunctuation, code);
|
|
75
|
+
};
|
|
76
|
+
registerVariants("po", RouteCode["Oral route"]);
|
|
77
|
+
registerVariants("oral", RouteCode["Oral route"]);
|
|
78
|
+
registerVariants("by mouth", RouteCode["Oral route"]);
|
|
79
|
+
registerVariants("per os", RouteCode["Oral route"]);
|
|
80
|
+
registerVariants("sl", RouteCode["Sublingual route"]);
|
|
81
|
+
registerVariants("s.l.", RouteCode["Sublingual route"]);
|
|
82
|
+
registerVariants("sublingual", RouteCode["Sublingual route"]);
|
|
83
|
+
registerVariants("buccal", RouteCode["Buccal route"]);
|
|
84
|
+
registerVariants("inh", RouteCode["Respiratory tract route (qualifier value)"]);
|
|
85
|
+
registerVariants("inhalation", RouteCode["Respiratory tract route (qualifier value)"]);
|
|
86
|
+
registerVariants("inhaled", RouteCode["Respiratory tract route (qualifier value)"]);
|
|
87
|
+
registerVariants("iv", RouteCode["Intravenous route"]);
|
|
88
|
+
registerVariants("ivp", RouteCode["Intravenous route"]);
|
|
89
|
+
registerVariants("ivpb", RouteCode["Intravenous route"]);
|
|
90
|
+
registerVariants("iv push", RouteCode["Intravenous route"]);
|
|
91
|
+
registerVariants("iv bolus", RouteCode["Intravenous route"]);
|
|
92
|
+
registerVariants("iv drip", RouteCode["Intravenous route"]);
|
|
93
|
+
registerVariants("intravenous", RouteCode["Intravenous route"]);
|
|
94
|
+
registerVariants("im", RouteCode["Intramuscular route"]);
|
|
95
|
+
registerVariants("im injection", RouteCode["Intramuscular route"]);
|
|
96
|
+
registerVariants("intramuscular", RouteCode["Intramuscular route"]);
|
|
97
|
+
registerVariants("sc", RouteCode["Subcutaneous route"]);
|
|
98
|
+
registerVariants("sq", RouteCode["Subcutaneous route"]);
|
|
99
|
+
registerVariants("subq", RouteCode["Subcutaneous route"]);
|
|
100
|
+
registerVariants("subcut", RouteCode["Subcutaneous route"]);
|
|
101
|
+
registerVariants("subcutaneous", RouteCode["Subcutaneous route"]);
|
|
102
|
+
registerVariants("in", RouteCode["Nasal route"]);
|
|
103
|
+
registerVariants("intranasal", RouteCode["Nasal route"]);
|
|
104
|
+
registerVariants("nasal", RouteCode["Nasal route"]);
|
|
105
|
+
registerVariants("top", RouteCode["Topical route"]);
|
|
106
|
+
registerVariants("topical", RouteCode["Topical route"]);
|
|
107
|
+
registerVariants("td", RouteCode["Transdermal route"]);
|
|
108
|
+
registerVariants("patch", RouteCode["Transdermal route"]);
|
|
109
|
+
registerVariants("transdermal", RouteCode["Transdermal route"]);
|
|
110
|
+
registerVariants("pr", RouteCode["Per rectum"]);
|
|
111
|
+
registerVariants("rectal", RouteCode["Per rectum"]);
|
|
112
|
+
registerVariants("pv", RouteCode["Per vagina"]);
|
|
113
|
+
registerVariants("vaginal", RouteCode["Per vagina"]);
|
|
114
|
+
registerVariants("oph", RouteCode["Ophthalmic route"]);
|
|
115
|
+
registerVariants("ophth", RouteCode["Ophthalmic route"]);
|
|
116
|
+
registerVariants("ophthalmic", RouteCode["Ophthalmic route"]);
|
|
117
|
+
registerVariants("ocular", RouteCode["Ophthalmic route"]);
|
|
118
|
+
registerVariants("intravitreal", RouteCode["Intravitreal route (qualifier value)"]);
|
|
119
|
+
registerVariants("intravitreal injection", RouteCode["Intravitreal route (qualifier value)"]);
|
|
120
|
+
registerVariants("ivt", RouteCode["Intravitreal route (qualifier value)"]);
|
|
101
121
|
for (const [routeCode, meta] of ROUTE_SNOMED_ENTRIES) {
|
|
102
122
|
const display = meta.display.toLowerCase();
|
|
103
|
-
|
|
123
|
+
registerVariants(display, routeCode);
|
|
104
124
|
const withoutQualifier = display.replace(/\s*\(qualifier value\)/g, "").trim();
|
|
105
|
-
|
|
125
|
+
registerVariants(withoutQualifier, routeCode);
|
|
106
126
|
const withoutSuffix = withoutQualifier
|
|
107
127
|
.replace(/\b(route|use)\b/g, "")
|
|
108
128
|
.replace(/\s+/g, " ")
|
|
109
129
|
.trim();
|
|
110
|
-
|
|
130
|
+
registerVariants(withoutSuffix, routeCode);
|
|
111
131
|
const withoutPer = withoutSuffix.replace(/^per\s+/, "").trim();
|
|
112
|
-
|
|
132
|
+
registerVariants(withoutPer, routeCode);
|
|
113
133
|
}
|
|
114
134
|
return map;
|
|
115
135
|
})();
|
package/dist/parser.js
CHANGED
|
@@ -609,7 +609,7 @@ export function parseInternal(input, options) {
|
|
|
609
609
|
}
|
|
610
610
|
// Process tokens sequentially
|
|
611
611
|
const tryRouteSynonym = (startIndex) => {
|
|
612
|
-
const maxSpan = Math.min(
|
|
612
|
+
const maxSpan = Math.min(24, tokens.length - startIndex);
|
|
613
613
|
for (let span = maxSpan; span >= 1; span--) {
|
|
614
614
|
const slice = tokens.slice(startIndex, startIndex + span);
|
|
615
615
|
if (slice.some((part) => internal.consumed.has(part.index))) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ParseOptions } from "./types";
|
|
2
|
+
export interface SuggestSigOptions extends ParseOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Maximum number of suggestions to return. Defaults to 10 when not supplied.
|
|
5
|
+
*/
|
|
6
|
+
limit?: number;
|
|
7
|
+
/**
|
|
8
|
+
* Optional custom PRN reasons to use when generating suggestions.
|
|
9
|
+
*/
|
|
10
|
+
prnReasons?: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function suggestSig(input: string, options?: SuggestSigOptions): string[];
|
package/dist/suggest.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { inferUnitFromContext } from "./context";
|
|
2
|
+
const DEFAULT_LIMIT = 10;
|
|
3
|
+
const DEFAULT_UNIT_ROUTE_ORDER = [
|
|
4
|
+
{ unit: "tab", route: "po" },
|
|
5
|
+
{ unit: "cap", route: "po" },
|
|
6
|
+
{ unit: "mL", route: "po" },
|
|
7
|
+
{ unit: "mg", route: "po" },
|
|
8
|
+
{ unit: "puff", route: "inh" },
|
|
9
|
+
{ unit: "spray", route: "in" },
|
|
10
|
+
{ unit: "drop", route: "oph" },
|
|
11
|
+
{ unit: "suppository", route: "pr" },
|
|
12
|
+
{ unit: "patch", route: "transdermal" },
|
|
13
|
+
{ unit: "g", route: "topical" }
|
|
14
|
+
];
|
|
15
|
+
const DEFAULT_ROUTE_BY_UNIT = {
|
|
16
|
+
tab: "po",
|
|
17
|
+
tabs: "po",
|
|
18
|
+
tablet: "po",
|
|
19
|
+
cap: "po",
|
|
20
|
+
capsule: "po",
|
|
21
|
+
ml: "po",
|
|
22
|
+
mg: "po",
|
|
23
|
+
puff: "inh",
|
|
24
|
+
puffs: "inh",
|
|
25
|
+
spray: "in",
|
|
26
|
+
sprays: "in",
|
|
27
|
+
drop: "oph",
|
|
28
|
+
drops: "oph",
|
|
29
|
+
suppository: "pr",
|
|
30
|
+
suppositories: "pr",
|
|
31
|
+
patch: "transdermal",
|
|
32
|
+
patches: "transdermal",
|
|
33
|
+
g: "topical"
|
|
34
|
+
};
|
|
35
|
+
const FREQUENCY_CODES = ["qd", "bid", "tid", "qid"];
|
|
36
|
+
const INTERVAL_CODES = ["q4h", "q6h", "q8h"];
|
|
37
|
+
const WHEN_TOKENS = ["ac", "pc", "hs", "am", "pm"];
|
|
38
|
+
const CORE_WHEN_TOKENS = ["pc", "ac", "hs"];
|
|
39
|
+
const FREQUENCY_NUMBERS = [1, 2, 3, 4];
|
|
40
|
+
const FREQ_TOKEN_BY_NUMBER = {
|
|
41
|
+
1: "qd",
|
|
42
|
+
2: "bid",
|
|
43
|
+
3: "tid",
|
|
44
|
+
4: "qid",
|
|
45
|
+
};
|
|
46
|
+
const DEFAULT_PRN_REASONS = [
|
|
47
|
+
"pain",
|
|
48
|
+
"nausea",
|
|
49
|
+
"itching",
|
|
50
|
+
"anxiety",
|
|
51
|
+
"sleep",
|
|
52
|
+
"cough",
|
|
53
|
+
"fever",
|
|
54
|
+
"spasm",
|
|
55
|
+
"constipation",
|
|
56
|
+
"dyspnea",
|
|
57
|
+
];
|
|
58
|
+
const DEFAULT_DOSE_COUNTS = ["1", "2"];
|
|
59
|
+
function normalizeKey(value) {
|
|
60
|
+
return value.trim().toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
function normalizeSpacing(value) {
|
|
63
|
+
return value
|
|
64
|
+
.trim()
|
|
65
|
+
.replace(/\s+/g, " ");
|
|
66
|
+
}
|
|
67
|
+
function buildUnitRoutePairs(contextUnit) {
|
|
68
|
+
const pairs = [];
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
const addPair = (unit, route) => {
|
|
71
|
+
if (!unit) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const cleanUnit = unit.trim();
|
|
75
|
+
if (!cleanUnit) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const normalizedUnit = cleanUnit.toLowerCase();
|
|
79
|
+
const resolvedRoute = route ?? DEFAULT_ROUTE_BY_UNIT[normalizedUnit] ?? "po";
|
|
80
|
+
const key = `${normalizedUnit}::${resolvedRoute.toLowerCase()}`;
|
|
81
|
+
if (seen.has(key)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
seen.add(key);
|
|
85
|
+
pairs.push({ unit: cleanUnit, route: resolvedRoute });
|
|
86
|
+
};
|
|
87
|
+
if (contextUnit) {
|
|
88
|
+
const normalized = normalizeKey(contextUnit);
|
|
89
|
+
addPair(contextUnit, DEFAULT_ROUTE_BY_UNIT[normalized]);
|
|
90
|
+
}
|
|
91
|
+
for (const pair of DEFAULT_UNIT_ROUTE_ORDER) {
|
|
92
|
+
addPair(pair.unit, pair.route);
|
|
93
|
+
}
|
|
94
|
+
return pairs;
|
|
95
|
+
}
|
|
96
|
+
function buildPrnReasons(customReasons) {
|
|
97
|
+
const reasons = new Set();
|
|
98
|
+
const add = (reason) => {
|
|
99
|
+
if (!reason) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const normalized = normalizeSpacing(reason.toLowerCase());
|
|
103
|
+
if (!normalized) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
reasons.add(normalized);
|
|
107
|
+
};
|
|
108
|
+
if (customReasons) {
|
|
109
|
+
for (const reason of customReasons) {
|
|
110
|
+
add(reason);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const reason of DEFAULT_PRN_REASONS) {
|
|
114
|
+
add(reason);
|
|
115
|
+
}
|
|
116
|
+
return [...reasons];
|
|
117
|
+
}
|
|
118
|
+
function extractDoseValuesFromInput(input) {
|
|
119
|
+
const matches = input.match(/\d+(?:\.\d+)?(?:\/\d+(?:\.\d+)?)?/g);
|
|
120
|
+
if (!matches) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
const values = new Set();
|
|
124
|
+
for (const match of matches) {
|
|
125
|
+
if (!match) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
values.add(match);
|
|
129
|
+
}
|
|
130
|
+
return [...values];
|
|
131
|
+
}
|
|
132
|
+
function buildDoseValues(input) {
|
|
133
|
+
const dynamicValues = extractDoseValuesFromInput(input);
|
|
134
|
+
const values = new Set();
|
|
135
|
+
for (const value of dynamicValues) {
|
|
136
|
+
values.add(value);
|
|
137
|
+
}
|
|
138
|
+
for (const value of DEFAULT_DOSE_COUNTS) {
|
|
139
|
+
values.add(value);
|
|
140
|
+
}
|
|
141
|
+
return [...values];
|
|
142
|
+
}
|
|
143
|
+
function generateCandidateSignatures(pairs, doseValues, prnReasons) {
|
|
144
|
+
const suggestions = [];
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
const push = (value) => {
|
|
147
|
+
const normalized = normalizeSpacing(value);
|
|
148
|
+
if (!normalized) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const key = normalizeKey(normalized);
|
|
152
|
+
if (seen.has(key)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
seen.add(key);
|
|
156
|
+
suggestions.push(normalized);
|
|
157
|
+
};
|
|
158
|
+
for (const pair of pairs) {
|
|
159
|
+
for (const code of FREQUENCY_CODES) {
|
|
160
|
+
for (const dose of doseValues) {
|
|
161
|
+
push(`${dose} ${pair.unit} ${pair.route} ${code}`);
|
|
162
|
+
}
|
|
163
|
+
push(`${pair.route} ${code}`);
|
|
164
|
+
}
|
|
165
|
+
for (const interval of INTERVAL_CODES) {
|
|
166
|
+
for (const dose of doseValues) {
|
|
167
|
+
push(`${dose} ${pair.unit} ${pair.route} ${interval}`);
|
|
168
|
+
for (const reason of prnReasons) {
|
|
169
|
+
push(`${dose} ${pair.unit} ${pair.route} ${interval} prn ${reason}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
push(`${pair.route} ${interval}`);
|
|
173
|
+
}
|
|
174
|
+
for (const freq of FREQUENCY_NUMBERS) {
|
|
175
|
+
const freqToken = FREQ_TOKEN_BY_NUMBER[freq];
|
|
176
|
+
push(`1x${freq} ${pair.route} ${freqToken}`);
|
|
177
|
+
for (const when of CORE_WHEN_TOKENS) {
|
|
178
|
+
push(`1x${freq} ${pair.route} ${when}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const when of WHEN_TOKENS) {
|
|
182
|
+
for (const dose of doseValues) {
|
|
183
|
+
push(`${dose} ${pair.unit} ${pair.route} ${when}`);
|
|
184
|
+
}
|
|
185
|
+
push(`${pair.route} ${when}`);
|
|
186
|
+
}
|
|
187
|
+
for (const reason of prnReasons) {
|
|
188
|
+
push(`1 ${pair.unit} ${pair.route} prn ${reason}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return suggestions;
|
|
192
|
+
}
|
|
193
|
+
function matchesPrefix(candidate, prefix, prefixCompact) {
|
|
194
|
+
if (!prefix) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const normalizedCandidate = candidate.toLowerCase();
|
|
198
|
+
if (normalizedCandidate.startsWith(prefix)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
const compactCandidate = normalizedCandidate.replace(/\s+/g, "");
|
|
202
|
+
return compactCandidate.startsWith(prefixCompact);
|
|
203
|
+
}
|
|
204
|
+
export function suggestSig(input, options) {
|
|
205
|
+
const limit = options?.limit ?? DEFAULT_LIMIT;
|
|
206
|
+
const prefix = normalizeSpacing(input.toLowerCase());
|
|
207
|
+
const prefixCompact = prefix.replace(/\s+/g, "");
|
|
208
|
+
const contextUnit = inferUnitFromContext(options?.context ?? undefined);
|
|
209
|
+
const pairs = buildUnitRoutePairs(contextUnit);
|
|
210
|
+
const doseValues = buildDoseValues(input);
|
|
211
|
+
const prnReasons = buildPrnReasons(options?.prnReasons);
|
|
212
|
+
const candidates = generateCandidateSignatures(pairs, doseValues, prnReasons);
|
|
213
|
+
const results = [];
|
|
214
|
+
for (const candidate of candidates) {
|
|
215
|
+
if (matchesPrefix(candidate, prefix, prefixCompact)) {
|
|
216
|
+
results.push(candidate);
|
|
217
|
+
}
|
|
218
|
+
if (results.length >= limit) {
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return results;
|
|
223
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -167,6 +167,7 @@ export declare enum SNOMEDCTRouteCodes {
|
|
|
167
167
|
"Intrapulmonary route (qualifier value)" = "420201002",
|
|
168
168
|
"Mucous fistula route (qualifier value)" = "420204005",
|
|
169
169
|
"Nasoduodenal route (qualifier value)" = "420218003",
|
|
170
|
+
"Body cavity route" = "420254004",
|
|
170
171
|
"A route that begins within a non-pathologic hollow cavity, such as that of the abdominal cavity or uterus." = "420254004",
|
|
171
172
|
"Intraventricular route - cardiac (qualifier value)" = "420287000",
|
|
172
173
|
"Intracerebroventricular route (qualifier value)" = "420719007",
|
package/dist/types.js
CHANGED
|
@@ -148,6 +148,7 @@ export var SNOMEDCTRouteCodes;
|
|
|
148
148
|
SNOMEDCTRouteCodes["Intrapulmonary route (qualifier value)"] = "420201002";
|
|
149
149
|
SNOMEDCTRouteCodes["Mucous fistula route (qualifier value)"] = "420204005";
|
|
150
150
|
SNOMEDCTRouteCodes["Nasoduodenal route (qualifier value)"] = "420218003";
|
|
151
|
+
SNOMEDCTRouteCodes["Body cavity route"] = "420254004";
|
|
151
152
|
SNOMEDCTRouteCodes["A route that begins within a non-pathologic hollow cavity, such as that of the abdominal cavity or uterus."] = "420254004";
|
|
152
153
|
SNOMEDCTRouteCodes["Intraventricular route - cardiac (qualifier value)"] = "420287000";
|
|
153
154
|
SNOMEDCTRouteCodes["Intracerebroventricular route (qualifier value)"] = "420719007";
|