ezmedicationinput 0.1.0 → 0.1.2

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.
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.suggestSig = suggestSig;
4
+ const context_1 = require("./context");
5
+ const DEFAULT_LIMIT = 10;
6
+ const DEFAULT_UNIT_ROUTE_ORDER = [
7
+ { unit: "tab", route: "po" },
8
+ { unit: "cap", route: "po" },
9
+ { unit: "mL", route: "po" },
10
+ { unit: "mg", route: "po" },
11
+ { unit: "puff", route: "inh" },
12
+ { unit: "spray", route: "in" },
13
+ { unit: "drop", route: "oph" },
14
+ { unit: "suppository", route: "pr" },
15
+ { unit: "patch", route: "transdermal" },
16
+ { unit: "g", route: "topical" }
17
+ ];
18
+ const DEFAULT_ROUTE_BY_UNIT = {
19
+ tab: "po",
20
+ tabs: "po",
21
+ tablet: "po",
22
+ cap: "po",
23
+ capsule: "po",
24
+ ml: "po",
25
+ mg: "po",
26
+ puff: "inh",
27
+ puffs: "inh",
28
+ spray: "in",
29
+ sprays: "in",
30
+ drop: "oph",
31
+ drops: "oph",
32
+ suppository: "pr",
33
+ suppositories: "pr",
34
+ patch: "transdermal",
35
+ patches: "transdermal",
36
+ g: "topical"
37
+ };
38
+ const FREQUENCY_CODES = ["qd", "bid", "tid", "qid"];
39
+ const INTERVAL_CODES = ["q4h", "q6h", "q8h"];
40
+ const WHEN_TOKENS = ["ac", "pc", "hs", "am", "pm"];
41
+ const CORE_WHEN_TOKENS = ["pc", "ac", "hs"];
42
+ const FREQUENCY_NUMBERS = [1, 2, 3, 4];
43
+ const FREQ_TOKEN_BY_NUMBER = {
44
+ 1: "qd",
45
+ 2: "bid",
46
+ 3: "tid",
47
+ 4: "qid",
48
+ };
49
+ const DEFAULT_PRN_REASONS = [
50
+ "pain",
51
+ "nausea",
52
+ "itching",
53
+ "anxiety",
54
+ "sleep",
55
+ "cough",
56
+ "fever",
57
+ "spasm",
58
+ "constipation",
59
+ "dyspnea",
60
+ ];
61
+ const DEFAULT_DOSE_COUNTS = ["1", "2"];
62
+ function normalizeKey(value) {
63
+ return value.trim().toLowerCase();
64
+ }
65
+ function normalizeSpacing(value) {
66
+ return value
67
+ .trim()
68
+ .replace(/\s+/g, " ");
69
+ }
70
+ function buildUnitRoutePairs(contextUnit) {
71
+ const pairs = [];
72
+ const seen = new Set();
73
+ const addPair = (unit, route) => {
74
+ var _a;
75
+ if (!unit) {
76
+ return;
77
+ }
78
+ const cleanUnit = unit.trim();
79
+ if (!cleanUnit) {
80
+ return;
81
+ }
82
+ const normalizedUnit = cleanUnit.toLowerCase();
83
+ const resolvedRoute = (_a = route !== null && route !== void 0 ? route : DEFAULT_ROUTE_BY_UNIT[normalizedUnit]) !== null && _a !== void 0 ? _a : "po";
84
+ const key = `${normalizedUnit}::${resolvedRoute.toLowerCase()}`;
85
+ if (seen.has(key)) {
86
+ return;
87
+ }
88
+ seen.add(key);
89
+ pairs.push({ unit: cleanUnit, route: resolvedRoute });
90
+ };
91
+ if (contextUnit) {
92
+ const normalized = normalizeKey(contextUnit);
93
+ addPair(contextUnit, DEFAULT_ROUTE_BY_UNIT[normalized]);
94
+ }
95
+ for (const pair of DEFAULT_UNIT_ROUTE_ORDER) {
96
+ addPair(pair.unit, pair.route);
97
+ }
98
+ return pairs;
99
+ }
100
+ function buildPrnReasons(customReasons) {
101
+ const reasons = new Set();
102
+ const add = (reason) => {
103
+ if (!reason) {
104
+ return;
105
+ }
106
+ const normalized = normalizeSpacing(reason.toLowerCase());
107
+ if (!normalized) {
108
+ return;
109
+ }
110
+ reasons.add(normalized);
111
+ };
112
+ if (customReasons) {
113
+ for (const reason of customReasons) {
114
+ add(reason);
115
+ }
116
+ }
117
+ for (const reason of DEFAULT_PRN_REASONS) {
118
+ add(reason);
119
+ }
120
+ return [...reasons];
121
+ }
122
+ function extractDoseValuesFromInput(input) {
123
+ const matches = input.match(/\d+(?:\.\d+)?(?:\/\d+(?:\.\d+)?)?/g);
124
+ if (!matches) {
125
+ return [];
126
+ }
127
+ const values = new Set();
128
+ for (const match of matches) {
129
+ if (!match) {
130
+ continue;
131
+ }
132
+ values.add(match);
133
+ }
134
+ return [...values];
135
+ }
136
+ function buildDoseValues(input) {
137
+ const dynamicValues = extractDoseValuesFromInput(input);
138
+ const values = new Set();
139
+ for (const value of dynamicValues) {
140
+ values.add(value);
141
+ }
142
+ for (const value of DEFAULT_DOSE_COUNTS) {
143
+ values.add(value);
144
+ }
145
+ return [...values];
146
+ }
147
+ function generateCandidateSignatures(pairs, doseValues, prnReasons) {
148
+ const suggestions = [];
149
+ const seen = new Set();
150
+ const push = (value) => {
151
+ const normalized = normalizeSpacing(value);
152
+ if (!normalized) {
153
+ return;
154
+ }
155
+ const key = normalizeKey(normalized);
156
+ if (seen.has(key)) {
157
+ return;
158
+ }
159
+ seen.add(key);
160
+ suggestions.push(normalized);
161
+ };
162
+ for (const pair of pairs) {
163
+ for (const code of FREQUENCY_CODES) {
164
+ for (const dose of doseValues) {
165
+ push(`${dose} ${pair.unit} ${pair.route} ${code}`);
166
+ }
167
+ push(`${pair.route} ${code}`);
168
+ }
169
+ for (const interval of INTERVAL_CODES) {
170
+ for (const dose of doseValues) {
171
+ push(`${dose} ${pair.unit} ${pair.route} ${interval}`);
172
+ for (const reason of prnReasons) {
173
+ push(`${dose} ${pair.unit} ${pair.route} ${interval} prn ${reason}`);
174
+ }
175
+ }
176
+ push(`${pair.route} ${interval}`);
177
+ }
178
+ for (const freq of FREQUENCY_NUMBERS) {
179
+ const freqToken = FREQ_TOKEN_BY_NUMBER[freq];
180
+ push(`1x${freq} ${pair.route} ${freqToken}`);
181
+ for (const when of CORE_WHEN_TOKENS) {
182
+ push(`1x${freq} ${pair.route} ${when}`);
183
+ }
184
+ }
185
+ for (const when of WHEN_TOKENS) {
186
+ for (const dose of doseValues) {
187
+ push(`${dose} ${pair.unit} ${pair.route} ${when}`);
188
+ }
189
+ push(`${pair.route} ${when}`);
190
+ }
191
+ for (const reason of prnReasons) {
192
+ push(`1 ${pair.unit} ${pair.route} prn ${reason}`);
193
+ }
194
+ }
195
+ return suggestions;
196
+ }
197
+ function matchesPrefix(candidate, prefix, prefixCompact) {
198
+ if (!prefix) {
199
+ return true;
200
+ }
201
+ const normalizedCandidate = candidate.toLowerCase();
202
+ if (normalizedCandidate.startsWith(prefix)) {
203
+ return true;
204
+ }
205
+ const compactCandidate = normalizedCandidate.replace(/\s+/g, "");
206
+ return compactCandidate.startsWith(prefixCompact);
207
+ }
208
+ function suggestSig(input, options) {
209
+ var _a, _b;
210
+ const limit = (_a = options === null || options === void 0 ? void 0 : options.limit) !== null && _a !== void 0 ? _a : DEFAULT_LIMIT;
211
+ const prefix = normalizeSpacing(input.toLowerCase());
212
+ const prefixCompact = prefix.replace(/\s+/g, "");
213
+ const contextUnit = (0, context_1.inferUnitFromContext)((_b = options === null || options === void 0 ? void 0 : options.context) !== null && _b !== void 0 ? _b : undefined);
214
+ const pairs = buildUnitRoutePairs(contextUnit);
215
+ const doseValues = buildDoseValues(input);
216
+ const prnReasons = buildPrnReasons(options === null || options === void 0 ? void 0 : options.prnReasons);
217
+ const candidates = generateCandidateSignatures(pairs, doseValues, prnReasons);
218
+ const results = [];
219
+ for (const candidate of candidates) {
220
+ if (matchesPrefix(candidate, prefix, prefixCompact)) {
221
+ results.push(candidate);
222
+ }
223
+ if (results.length >= limit) {
224
+ break;
225
+ }
226
+ }
227
+ return results;
228
+ }
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;
@@ -167,6 +168,7 @@ export declare enum SNOMEDCTRouteCodes {
167
168
  "Intrapulmonary route (qualifier value)" = "420201002",
168
169
  "Mucous fistula route (qualifier value)" = "420204005",
169
170
  "Nasoduodenal route (qualifier value)" = "420218003",
171
+ "Body cavity route" = "420254004",
170
172
  "A route that begins within a non-pathologic hollow cavity, such as that of the abdominal cavity or uterus." = "420254004",
171
173
  "Intraventricular route - cardiac (qualifier value)" = "420287000",
172
174
  "Intracerebroventricular route (qualifier value)" = "420719007",
@@ -282,7 +284,11 @@ export interface MedicationContext {
282
284
  containerUnit?: string;
283
285
  defaultUnit?: string;
284
286
  }
285
- export interface ParseOptions {
287
+ export interface FormatOptions {
288
+ locale?: "en" | "th" | string;
289
+ i18n?: SigTranslationConfig;
290
+ }
291
+ export interface ParseOptions extends FormatOptions {
286
292
  /**
287
293
  * Optional medication context that assists with default unit inference.
288
294
  * May be omitted or explicitly set to null when no contextual clues exist.
@@ -298,7 +304,6 @@ export interface ParseOptions {
298
304
  }>;
299
305
  whenMap?: Record<string, EventTiming>;
300
306
  allowDiscouraged?: boolean;
301
- locale?: "en" | "th" | string;
302
307
  /**
303
308
  * When enabled the parser will expand generic meal timing tokens (AC/PC/C)
304
309
  * into specific breakfast/lunch/dinner (and bedtime) EventTiming entries
@@ -347,17 +352,21 @@ export interface FrequencyFallbackTimes {
347
352
  * Shared configuration required to generate next-due dose timestamps.
348
353
  */
349
354
  export interface NextDueDoseConfig {
350
- timeZone: string;
351
- eventClock: EventClockMap;
352
- mealOffsets: MealOffsetMap;
355
+ timeZone?: string;
356
+ eventClock?: EventClockMap;
357
+ mealOffsets?: MealOffsetMap;
353
358
  frequencyDefaults?: FrequencyFallbackTimes;
354
359
  }
355
360
  /**
356
361
  * Options bag for next-due dose generation.
357
362
  */
358
363
  export interface NextDueDoseOptions {
359
- orderedAt: Date | string;
360
364
  from: Date | string;
361
- limit: number;
362
- config: NextDueDoseConfig;
365
+ orderedAt?: Date | string;
366
+ limit?: number;
367
+ timeZone?: string;
368
+ eventClock?: EventClockMap;
369
+ mealOffsets?: MealOffsetMap;
370
+ frequencyDefaults?: FrequencyFallbackTimes;
371
+ config?: NextDueDoseConfig;
363
372
  }
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
- export var EventTiming;
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
- export var SNOMEDCTRouteCodes;
42
+ var SNOMEDCTRouteCodes;
40
43
  (function (SNOMEDCTRouteCodes) {
41
44
  SNOMEDCTRouteCodes["Topical route"] = "6064005";
42
45
  SNOMEDCTRouteCodes["Otic route"] = "10547007";
@@ -148,6 +151,7 @@ export var SNOMEDCTRouteCodes;
148
151
  SNOMEDCTRouteCodes["Intrapulmonary route (qualifier value)"] = "420201002";
149
152
  SNOMEDCTRouteCodes["Mucous fistula route (qualifier value)"] = "420204005";
150
153
  SNOMEDCTRouteCodes["Nasoduodenal route (qualifier value)"] = "420218003";
154
+ SNOMEDCTRouteCodes["Body cavity route"] = "420254004";
151
155
  SNOMEDCTRouteCodes["A route that begins within a non-pathologic hollow cavity, such as that of the abdominal cavity or uterus."] = "420254004";
152
156
  SNOMEDCTRouteCodes["Intraventricular route - cardiac (qualifier value)"] = "420287000";
153
157
  SNOMEDCTRouteCodes["Intracerebroventricular route (qualifier value)"] = "420719007";
@@ -200,8 +204,8 @@ export var SNOMEDCTRouteCodes;
200
204
  SNOMEDCTRouteCodes["Posterior juxtascleral route (qualifier value)"] = "58821000052106";
201
205
  SNOMEDCTRouteCodes["Subretinal route (qualifier value)"] = "58831000052108";
202
206
  SNOMEDCTRouteCodes["Sublabial use"] = "66621000052103";
203
- })(SNOMEDCTRouteCodes || (SNOMEDCTRouteCodes = {}));
204
- export var FhirPeriodUnit;
207
+ })(SNOMEDCTRouteCodes || (exports.SNOMEDCTRouteCodes = SNOMEDCTRouteCodes = {}));
208
+ var FhirPeriodUnit;
205
209
  (function (FhirPeriodUnit) {
206
210
  FhirPeriodUnit["Second"] = "s";
207
211
  FhirPeriodUnit["Minute"] = "min";
@@ -210,8 +214,8 @@ export var FhirPeriodUnit;
210
214
  FhirPeriodUnit["Week"] = "wk";
211
215
  FhirPeriodUnit["Month"] = "mo";
212
216
  FhirPeriodUnit["Year"] = "a";
213
- })(FhirPeriodUnit || (FhirPeriodUnit = {}));
214
- export var FhirDayOfWeek;
217
+ })(FhirPeriodUnit || (exports.FhirPeriodUnit = FhirPeriodUnit = {}));
218
+ var FhirDayOfWeek;
215
219
  (function (FhirDayOfWeek) {
216
220
  FhirDayOfWeek["Monday"] = "mon";
217
221
  FhirDayOfWeek["Tuesday"] = "tue";
@@ -220,5 +224,5 @@ export var FhirDayOfWeek;
220
224
  FhirDayOfWeek["Friday"] = "fri";
221
225
  FhirDayOfWeek["Saturday"] = "sat";
222
226
  FhirDayOfWeek["Sunday"] = "sun";
223
- })(FhirDayOfWeek || (FhirDayOfWeek = {}));
224
- export const RouteCode = SNOMEDCTRouteCodes;
227
+ })(FhirDayOfWeek || (exports.FhirDayOfWeek = FhirDayOfWeek = {}));
228
+ exports.RouteCode = SNOMEDCTRouteCodes;
@@ -0,0 +1 @@
1
+ export declare function arrayIncludes<T>(values: ReadonlyArray<T>, target: T): boolean;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.arrayIncludes = arrayIncludes;
4
+ function arrayIncludes(values, target) {
5
+ for (let index = 0; index < values.length; index += 1) {
6
+ if (values[index] === target) {
7
+ return true;
8
+ }
9
+ }
10
+ return false;
11
+ }
@@ -0,0 +1,2 @@
1
+ export declare function enumEntries<T extends Record<string, string | number>>(enumeration: T): Array<[keyof T, T[keyof T]]>;
2
+ export declare function enumValues<T extends Record<string, string | number>>(enumeration: T): Array<T[keyof T]>;
@@ -0,0 +1,7 @@
1
+ import { objectEntries, objectValues } from "./object";
2
+ export function enumEntries(enumeration) {
3
+ return objectEntries(enumeration);
4
+ }
5
+ export function enumValues(enumeration) {
6
+ return objectValues(enumeration);
7
+ }
@@ -0,0 +1,7 @@
1
+ export type ObjectEntries<T extends Record<string, unknown>> = Array<[
2
+ keyof T,
3
+ T[keyof T]
4
+ ]>;
5
+ export declare function objectEntries<T extends Record<string, unknown>>(value: T): ObjectEntries<T>;
6
+ export declare function objectValues<T extends Record<string, unknown>>(value: T): Array<T[keyof T]>;
7
+ export declare function objectFromEntries<K extends string, V>(entries: Array<[K, V]>): Record<K, V>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.objectEntries = objectEntries;
4
+ exports.objectValues = objectValues;
5
+ exports.objectFromEntries = objectFromEntries;
6
+ function objectEntries(value) {
7
+ const entries = [];
8
+ for (const key in value) {
9
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
10
+ const typedKey = key;
11
+ entries.push([typedKey, value[typedKey]]);
12
+ }
13
+ }
14
+ return entries;
15
+ }
16
+ function objectValues(value) {
17
+ const values = [];
18
+ for (const key in value) {
19
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
20
+ values.push(value[key]);
21
+ }
22
+ }
23
+ return values;
24
+ }
25
+ function objectFromEntries(entries) {
26
+ const result = {};
27
+ for (let index = 0; index < entries.length; index += 1) {
28
+ const entry = entries[index];
29
+ const key = entry[0];
30
+ const value = entry[1];
31
+ result[key] = value;
32
+ }
33
+ return result;
34
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "scripts": {
9
- "build": "tsc -p tsconfig.json",
9
+ "build": "tsc -p tsconfig.json && node scripts/postbuild.cjs",
10
10
  "test": "vitest run"
11
11
  },
12
12
  "files": [