ezmedicationinput 0.1.29 → 0.1.31

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/fhir.js CHANGED
@@ -162,7 +162,7 @@ function toFhir(internal) {
162
162
  return dosage;
163
163
  }
164
164
  function internalFromFhir(dosage) {
165
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13;
165
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14;
166
166
  const internal = {
167
167
  input: (_a = dosage.text) !== null && _a !== void 0 ? _a : "",
168
168
  tokens: [],
@@ -212,34 +212,41 @@ function internalFromFhir(dosage) {
212
212
  }
213
213
  const reasonCoding = (_9 = (_8 = (_7 = dosage.asNeededFor) === null || _7 === void 0 ? void 0 : _7[0]) === null || _8 === void 0 ? void 0 : _8.coding) === null || _9 === void 0 ? void 0 : _9[0];
214
214
  if (reasonCoding === null || reasonCoding === void 0 ? void 0 : reasonCoding.code) {
215
+ const defaultDef = (0, maps_1.findPrnReasonDefinitionByCoding)((_10 = reasonCoding.system) !== null && _10 !== void 0 ? _10 : SNOMED_SYSTEM, reasonCoding.code);
215
216
  internal.asNeededReasonCoding = {
216
217
  code: reasonCoding.code,
217
218
  display: reasonCoding.display,
218
- system: reasonCoding.system
219
+ system: reasonCoding.system,
220
+ i18n: defaultDef === null || defaultDef === void 0 ? void 0 : defaultDef.i18n
219
221
  };
220
222
  }
221
- if ((_10 = dosage.additionalInstruction) === null || _10 === void 0 ? void 0 : _10.length) {
223
+ if ((_11 = dosage.additionalInstruction) === null || _11 === void 0 ? void 0 : _11.length) {
222
224
  internal.additionalInstructions = dosage.additionalInstruction.map((concept) => {
223
- var _a;
224
- return ({
225
+ var _a, _b;
226
+ const coding = (_a = concept.coding) === null || _a === void 0 ? void 0 : _a[0];
227
+ const defaultDef = (coding === null || coding === void 0 ? void 0 : coding.code)
228
+ ? (0, maps_1.findAdditionalInstructionDefinitionByCoding)((_b = coding.system) !== null && _b !== void 0 ? _b : SNOMED_SYSTEM, coding.code)
229
+ : undefined;
230
+ return {
225
231
  text: concept.text,
226
- coding: ((_a = concept.coding) === null || _a === void 0 ? void 0 : _a[0])
232
+ coding: (coding === null || coding === void 0 ? void 0 : coding.code)
227
233
  ? {
228
- code: concept.coding[0].code,
229
- display: concept.coding[0].display,
230
- system: concept.coding[0].system
234
+ code: coding.code,
235
+ display: coding.display,
236
+ system: coding.system,
237
+ i18n: defaultDef === null || defaultDef === void 0 ? void 0 : defaultDef.i18n
231
238
  }
232
239
  : undefined
233
- });
240
+ };
234
241
  });
235
242
  }
236
- const doseAndRate = (_11 = dosage.doseAndRate) === null || _11 === void 0 ? void 0 : _11[0];
243
+ const doseAndRate = (_12 = dosage.doseAndRate) === null || _12 === void 0 ? void 0 : _12[0];
237
244
  if (doseAndRate === null || doseAndRate === void 0 ? void 0 : doseAndRate.doseRange) {
238
245
  const { low, high } = doseAndRate.doseRange;
239
246
  if ((low === null || low === void 0 ? void 0 : low.value) !== undefined && (high === null || high === void 0 ? void 0 : high.value) !== undefined) {
240
247
  internal.doseRange = { low: low.value, high: high.value };
241
248
  }
242
- internal.unit = (_13 = (_12 = low === null || low === void 0 ? void 0 : low.unit) !== null && _12 !== void 0 ? _12 : high === null || high === void 0 ? void 0 : high.unit) !== null && _13 !== void 0 ? _13 : internal.unit;
249
+ internal.unit = (_14 = (_13 = low === null || low === void 0 ? void 0 : low.unit) !== null && _13 !== void 0 ? _13 : high === null || high === void 0 ? void 0 : high.unit) !== null && _14 !== void 0 ? _14 : internal.unit;
243
250
  }
244
251
  else if (doseAndRate === null || doseAndRate === void 0 ? void 0 : doseAndRate.doseQuantity) {
245
252
  const dose = doseAndRate.doseQuantity;
package/dist/maps.d.ts CHANGED
@@ -74,3 +74,11 @@ export interface AdditionalInstructionDictionaryEntry {
74
74
  }
75
75
  export declare const DEFAULT_ADDITIONAL_INSTRUCTION_ENTRIES: AdditionalInstructionDictionaryEntry[];
76
76
  export declare const DEFAULT_ADDITIONAL_INSTRUCTION_DEFINITIONS: Record<string, AdditionalInstructionDefinition>;
77
+ /**
78
+ * Finds a default PRN reason definition by its SNOMED coding.
79
+ */
80
+ export declare function findPrnReasonDefinitionByCoding(system: string, code: string): PrnReasonDefinition | undefined;
81
+ /**
82
+ * Finds a default additional instruction definition by its SNOMED coding.
83
+ */
84
+ export declare function findAdditionalInstructionDefinitionByCoding(system: string, code: string): AdditionalInstructionDefinition | undefined;
package/dist/maps.js CHANGED
@@ -4,6 +4,8 @@ exports.DEFAULT_ADDITIONAL_INSTRUCTION_DEFINITIONS = exports.DEFAULT_ADDITIONAL_
4
4
  exports.normalizeBodySiteKey = normalizeBodySiteKey;
5
5
  exports.normalizePrnReasonKey = normalizePrnReasonKey;
6
6
  exports.normalizeAdditionalInstructionKey = normalizeAdditionalInstructionKey;
7
+ exports.findPrnReasonDefinitionByCoding = findPrnReasonDefinitionByCoding;
8
+ exports.findAdditionalInstructionDefinitionByCoding = findAdditionalInstructionDefinitionByCoding;
7
9
  const types_1 = require("./types");
8
10
  const object_1 = require("./utils/object");
9
11
  const SNOMED_SYSTEM = "http://snomed.info/sct";
@@ -698,6 +700,7 @@ exports.EVENT_TIMING_TOKENS = {
698
700
  night: types_1.EventTiming.Night,
699
701
  hs: types_1.EventTiming["Before Sleep"],
700
702
  bedtime: types_1.EventTiming["Before Sleep"],
703
+ bed: types_1.EventTiming["Before Sleep"],
701
704
  wake: types_1.EventTiming.Wake,
702
705
  waking: types_1.EventTiming.Wake,
703
706
  stat: types_1.EventTiming.Immediate
@@ -1611,3 +1614,17 @@ exports.DEFAULT_ADDITIONAL_INSTRUCTION_DEFINITIONS = (0, object_1.objectFromEntr
1611
1614
  }
1612
1615
  return entries;
1613
1616
  }, []));
1617
+ /**
1618
+ * Finds a default PRN reason definition by its SNOMED coding.
1619
+ */
1620
+ function findPrnReasonDefinitionByCoding(system, code) {
1621
+ var _a;
1622
+ return (_a = DEFAULT_PRN_REASON_SOURCE.find((source) => { var _a, _b; return ((_a = source.definition.coding) === null || _a === void 0 ? void 0 : _a.system) === system && ((_b = source.definition.coding) === null || _b === void 0 ? void 0 : _b.code) === code; })) === null || _a === void 0 ? void 0 : _a.definition;
1623
+ }
1624
+ /**
1625
+ * Finds a default additional instruction definition by its SNOMED coding.
1626
+ */
1627
+ function findAdditionalInstructionDefinitionByCoding(system, code) {
1628
+ var _a;
1629
+ return (_a = DEFAULT_ADDITIONAL_INSTRUCTION_SOURCE.find((source) => { var _a, _b; return ((_a = source.definition.coding) === null || _a === void 0 ? void 0 : _a.system) === system && ((_b = source.definition.coding) === null || _b === void 0 ? void 0 : _b.code) === code; })) === null || _a === void 0 ? void 0 : _a.definition;
1630
+ }
package/dist/parser.js CHANGED
@@ -190,6 +190,9 @@ const COMBO_EVENT_TIMINGS = {
190
190
  "early evening": types_1.EventTiming["Early Evening"],
191
191
  "late evening": types_1.EventTiming["Late Evening"],
192
192
  "after sleep": types_1.EventTiming["After Sleep"],
193
+ "before bed": types_1.EventTiming["Before Sleep"],
194
+ "before bedtime": types_1.EventTiming["Before Sleep"],
195
+ "before sleep": types_1.EventTiming["Before Sleep"],
193
196
  "upon waking": types_1.EventTiming.Wake
194
197
  };
195
198
  const MEAL_CONTEXT_CONNECTORS = new Set(["and", "or", "&", "+", "plus"]);
@@ -781,39 +784,17 @@ function tryParseTimeBasedSchedule(internal, tokens, index) {
781
784
  const token = tokens[index];
782
785
  if (internal.consumed.has(token.index))
783
786
  return false;
784
- // Handle connectors like "and at" or just "and" before a time.
785
- // This prevents rogue "and" from leaking into Additional Instructions
786
- // when it serves as a connector between schedule parts.
787
- let isAndPrefix = false;
788
787
  let isAtPrefix = token.lower === "@" || token.lower === "at";
789
- if (token.lower === "and" && !isAtPrefix) {
790
- const next = tokens[index + 1];
791
- if (next && !internal.consumed.has(next.index)) {
792
- const nextLower = next.lower;
793
- // If "and" is followed by "at", "@", or a number, it's a connector for this time block
794
- if (nextLower === "@" || nextLower === "at" || /^\d/.test(nextLower)) {
795
- isAndPrefix = true;
796
- if (nextLower === "@" || nextLower === "at") {
797
- isAtPrefix = true;
798
- }
799
- }
800
- }
801
- }
802
- if (!isAtPrefix && !isAndPrefix && !/^\d/.test(token.lower))
788
+ if (!isAtPrefix && !/^\d/.test(token.lower))
803
789
  return false;
804
790
  let nextIndex = index;
805
- if (isAndPrefix)
806
- nextIndex++;
807
791
  if (isAtPrefix)
808
792
  nextIndex++;
809
793
  const times = [];
810
794
  const consumedIndices = [];
811
795
  const timeTokens = [];
812
- if (isAndPrefix)
813
- consumedIndices.push(index);
814
796
  if (isAtPrefix) {
815
- // If we have "and at", at is the second token (index + 1)
816
- consumedIndices.push(isAndPrefix ? index + 1 : index);
797
+ consumedIndices.push(index);
817
798
  }
818
799
  while (nextIndex < tokens.length) {
819
800
  const nextToken = tokens[nextIndex];
@@ -1518,6 +1499,20 @@ function applyWhenToken(internal, token, code) {
1518
1499
  addWhen(internal.when, code);
1519
1500
  mark(internal.consumed, token);
1520
1501
  }
1502
+ function isTimingAnchorOrPrefix(tokens, index) {
1503
+ const token = tokens[index];
1504
+ if (!token)
1505
+ return false;
1506
+ const lower = token.lower;
1507
+ const nextToken = tokens[index + 1];
1508
+ const comboKey = nextToken ? `${lower} ${nextToken.lower}` : undefined;
1509
+ return Boolean(maps_1.EVENT_TIMING_TOKENS[lower] ||
1510
+ maps_1.TIMING_ABBREVIATIONS[lower] ||
1511
+ (comboKey && COMBO_EVENT_TIMINGS[comboKey]) ||
1512
+ (lower === "pc" || lower === "ac" || lower === "after" || lower === "before") ||
1513
+ (lower === "at" || lower === "@" || lower === "on" || lower === "with") ||
1514
+ /^\d/.test(lower));
1515
+ }
1521
1516
  function parseAnchorSequence(internal, tokens, index, prefixCode) {
1522
1517
  var _a;
1523
1518
  const token = tokens[index];
@@ -1936,7 +1931,25 @@ function parseInternal(input, options) {
1936
1931
  if (tryParseCompactQ(internal, tokens, i)) {
1937
1932
  continue;
1938
1933
  }
1934
+ // Skip connectors if they are followed by recognized timing tokens or prefixes
1935
+ if (MEAL_CONTEXT_CONNECTORS.has(token.lower) || token.lower === ",") {
1936
+ if (isTimingAnchorOrPrefix(tokens, i + 1)) {
1937
+ mark(internal.consumed, token);
1938
+ continue;
1939
+ }
1940
+ }
1939
1941
  // Event timing tokens
1942
+ const nextToken = tokens[i + 1];
1943
+ if (nextToken && !internal.consumed.has(nextToken.index)) {
1944
+ const lowerNext = nextToken.lower;
1945
+ const combo = `${token.lower} ${lowerNext}`;
1946
+ const comboWhen = (_f = COMBO_EVENT_TIMINGS[combo]) !== null && _f !== void 0 ? _f : maps_1.EVENT_TIMING_TOKENS[combo];
1947
+ if (comboWhen) {
1948
+ applyWhenToken(internal, token, comboWhen);
1949
+ mark(internal.consumed, nextToken);
1950
+ continue;
1951
+ }
1952
+ }
1940
1953
  if (token.lower === "pc" || token.lower === "ac" || token.lower === "after" || token.lower === "before") {
1941
1954
  parseAnchorSequence(internal, tokens, i, (token.lower === "pc" || token.lower === "after")
1942
1955
  ? types_1.EventTiming["After Meal"]
@@ -1957,17 +1970,6 @@ function parseInternal(input, options) {
1957
1970
  continue;
1958
1971
  }
1959
1972
  }
1960
- const nextToken = tokens[i + 1];
1961
- if (nextToken && !internal.consumed.has(nextToken.index)) {
1962
- const lowerNext = nextToken.lower;
1963
- const combo = `${token.lower} ${lowerNext}`;
1964
- const comboWhen = (_f = COMBO_EVENT_TIMINGS[combo]) !== null && _f !== void 0 ? _f : maps_1.EVENT_TIMING_TOKENS[combo];
1965
- if (comboWhen) {
1966
- applyWhenToken(internal, token, comboWhen);
1967
- mark(internal.consumed, nextToken);
1968
- continue;
1969
- }
1970
- }
1971
1973
  const customWhen = (_g = options === null || options === void 0 ? void 0 : options.whenMap) === null || _g === void 0 ? void 0 : _g[token.lower];
1972
1974
  if (customWhen) {
1973
1975
  applyWhenToken(internal, token, customWhen);
@@ -3126,7 +3128,7 @@ function collectAdditionalInstructions(internal, tokens) {
3126
3128
  if (/\s/.test(ch)) {
3127
3129
  continue;
3128
3130
  }
3129
- if (/-|;|:|\.|,/.test(ch)) {
3131
+ if (/-|;|:|\.|\,/.test(ch)) {
3130
3132
  separatorDetected = true;
3131
3133
  }
3132
3134
  break;
@@ -3135,9 +3137,6 @@ function collectAdditionalInstructions(internal, tokens) {
3135
3137
  const sourceText = range
3136
3138
  ? internal.input.slice(range.start, range.end)
3137
3139
  : joined;
3138
- if (!separatorDetected && !/[-;:.]/.test(sourceText)) {
3139
- return;
3140
- }
3141
3140
  const normalized = sourceText
3142
3141
  .replace(/\s*[-:]+\s*/g, "; ")
3143
3142
  .replace(/\s*(?:\r?\n)+\s*/g, "; ")
@@ -3146,6 +3145,18 @@ function collectAdditionalInstructions(internal, tokens) {
3146
3145
  .split(/(?:;|\.)/)
3147
3146
  .map((segment) => segment.trim())
3148
3147
  .filter((segment) => segment.length > 0);
3148
+ // If no punctuation was detected, we only collect if at least one segment matches a known definition.
3149
+ // This avoids capturing random trailing text as instructions unless it's codified.
3150
+ if (!separatorDetected && !/[-;:.]/.test(sourceText)) {
3151
+ const hasKnownDefinition = segments.some((phrase) => {
3152
+ const canonical = (0, maps_1.normalizeAdditionalInstructionKey)(phrase);
3153
+ return (maps_1.DEFAULT_ADDITIONAL_INSTRUCTION_DEFINITIONS[canonical] ||
3154
+ findAdditionalInstructionDefinition(phrase, canonical));
3155
+ });
3156
+ if (!hasKnownDefinition) {
3157
+ return;
3158
+ }
3159
+ }
3149
3160
  const phrases = segments.length ? segments : [joined];
3150
3161
  const seen = new Set();
3151
3162
  const instructions = [];
@@ -3174,7 +3185,7 @@ function collectAdditionalInstructions(internal, tokens) {
3174
3185
  : undefined
3175
3186
  });
3176
3187
  }
3177
- else {
3188
+ else if (!MEAL_CONTEXT_CONNECTORS.has(phrase.toLowerCase())) {
3178
3189
  instructions.push({ text: phrase });
3179
3190
  }
3180
3191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",