ezmedicationinput 0.1.5 → 0.1.6

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.
Files changed (2) hide show
  1. package/dist/suggest.js +205 -56
  2. package/package.json +1 -1
package/dist/suggest.js CHANGED
@@ -206,11 +206,40 @@ function normalizeSpacing(value) {
206
206
  .trim()
207
207
  .replace(/\s+/g, " ");
208
208
  }
209
+ function removeWhitespaceCharacters(value) {
210
+ for (let index = 0; index < value.length; index += 1) {
211
+ const code = value.charCodeAt(index);
212
+ if (code <= 32) {
213
+ const result = [];
214
+ for (let inner = 0; inner < value.length; inner += 1) {
215
+ const currentCode = value.charCodeAt(inner);
216
+ if (currentCode > 32) {
217
+ result.push(value.charAt(inner));
218
+ }
219
+ }
220
+ return result.join("");
221
+ }
222
+ }
223
+ return value;
224
+ }
225
+ function removeDashes(value) {
226
+ if (value.indexOf("-") === -1) {
227
+ return value;
228
+ }
229
+ const result = [];
230
+ for (let index = 0; index < value.length; index += 1) {
231
+ const char = value.charAt(index);
232
+ if (char !== "-") {
233
+ result.push(char);
234
+ }
235
+ }
236
+ return result.join("");
237
+ }
209
238
  function getUnitVariants(unit) {
210
239
  var _a;
211
240
  const canonical = (_a = resolveCanonicalUnit(unit)) !== null && _a !== void 0 ? _a : normalizeSpacing(unit);
212
241
  const normalizedCanonical = normalizeKey(canonical);
213
- const variants = new Set();
242
+ const variants = new Map();
214
243
  const push = (candidate) => {
215
244
  if (!candidate) {
216
245
  return;
@@ -219,7 +248,11 @@ function getUnitVariants(unit) {
219
248
  if (!normalizedCandidate) {
220
249
  return;
221
250
  }
222
- variants.add(normalizedCandidate);
251
+ const lower = normalizedCandidate.toLowerCase();
252
+ if (variants.has(lower)) {
253
+ return;
254
+ }
255
+ variants.set(lower, { value: normalizedCandidate, lower });
223
256
  };
224
257
  push(canonical);
225
258
  push(unit);
@@ -229,7 +262,7 @@ function getUnitVariants(unit) {
229
262
  push(candidate);
230
263
  }
231
264
  }
232
- return [...variants];
265
+ return [...variants.values()];
233
266
  }
234
267
  function buildIntervalTokens(input) {
235
268
  const intervals = new Set();
@@ -289,16 +322,21 @@ function buildWhenSequences() {
289
322
  return sequences;
290
323
  }
291
324
  const PRECOMPUTED_WHEN_SEQUENCES = buildWhenSequences();
292
- function tokenizeForMatching(value) {
325
+ function tokenizeLowercaseForMatching(value) {
293
326
  return value
294
- .toLowerCase()
295
327
  .split(/\s+/)
296
328
  .map((token) => token.replace(/^[^a-z0-9-]+|[^a-z0-9-]+$/g, ""))
297
329
  .filter((token) => token.length > 0)
298
330
  .filter((token) => !OPTIONAL_MATCH_TOKENS.has(token));
299
331
  }
332
+ function tokenizeForMatching(value) {
333
+ return tokenizeLowercaseForMatching(value.toLowerCase());
334
+ }
335
+ function canonicalizeLowercaseForMatching(value) {
336
+ return tokenizeLowercaseForMatching(value).join(" ");
337
+ }
300
338
  function canonicalizeForMatching(value) {
301
- return tokenizeForMatching(value).join(" ");
339
+ return canonicalizeLowercaseForMatching(value.toLowerCase());
302
340
  }
303
341
  function tokensMatch(prefixTokens, candidateTokens) {
304
342
  if (prefixTokens.length === 0) {
@@ -343,12 +381,13 @@ function buildUnitRoutePairs(contextUnit, options) {
343
381
  if (!cleanRoute) {
344
382
  return;
345
383
  }
346
- const key = `${normalizedUnit}::${normalizeKey(cleanRoute)}`;
384
+ const routeLower = cleanRoute.toLowerCase();
385
+ const key = `${normalizedUnit}::${routeLower}`;
347
386
  if (seen.has(key)) {
348
387
  return;
349
388
  }
350
389
  seen.add(key);
351
- pairs.push({ unit: canonicalUnit, route: cleanRoute });
390
+ pairs.push({ unit: canonicalUnit, route: cleanRoute, routeLower });
352
391
  };
353
392
  addPair(contextUnit);
354
393
  for (const preference of DEFAULT_UNIT_ROUTE_ORDER) {
@@ -404,53 +443,93 @@ function buildDoseValues(input) {
404
443
  }
405
444
  return [...values];
406
445
  }
446
+ const CANDIDATE_FINGERPRINT_CACHE = new Map();
447
+ function getCandidateFingerprint(candidateLower) {
448
+ let fingerprint = CANDIDATE_FINGERPRINT_CACHE.get(candidateLower);
449
+ if (!fingerprint) {
450
+ fingerprint = {};
451
+ CANDIDATE_FINGERPRINT_CACHE.set(candidateLower, fingerprint);
452
+ }
453
+ return fingerprint;
454
+ }
407
455
  function generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences, limit, matcher) {
408
456
  const suggestions = [];
409
457
  const seen = new Set();
410
- const push = (value) => {
411
- const normalized = normalizeSpacing(value);
458
+ const doseVariantMap = new Map();
459
+ for (const dose of doseValues) {
460
+ const normalized = normalizeSpacing(dose);
412
461
  if (!normalized) {
462
+ continue;
463
+ }
464
+ const lower = normalized.toLowerCase();
465
+ if (!doseVariantMap.has(lower)) {
466
+ doseVariantMap.set(lower, { value: normalized, lower });
467
+ }
468
+ }
469
+ const doseVariants = [...doseVariantMap.values()];
470
+ const push = (value, lower) => {
471
+ if (!lower) {
413
472
  return false;
414
473
  }
415
- const key = normalizeKey(normalized);
416
- if (seen.has(key)) {
474
+ if (seen.has(lower)) {
417
475
  return false;
418
476
  }
419
- seen.add(key);
420
- if (!matcher(normalized)) {
477
+ if (!matcher(value, lower)) {
421
478
  return false;
422
479
  }
423
- suggestions.push(normalized);
480
+ seen.add(lower);
481
+ suggestions.push(value);
424
482
  return suggestions.length >= limit;
425
483
  };
426
484
  for (const pair of pairs) {
427
485
  const unitVariants = getUnitVariants(pair.unit);
486
+ const route = pair.route;
487
+ const routeLower = pair.routeLower;
428
488
  for (const code of FREQUENCY_CODES) {
489
+ const codeSuffix = ` ${code}`;
429
490
  for (const unitVariant of unitVariants) {
430
- for (const dose of doseValues) {
431
- if (push(`${dose} ${unitVariant} ${pair.route} ${code}`)) {
491
+ const unitRoute = `${unitVariant.value} ${route}`;
492
+ const unitRouteLower = `${unitVariant.lower} ${routeLower}`;
493
+ for (const doseVariant of doseVariants) {
494
+ const candidate = `${doseVariant.value} ${unitRoute}${codeSuffix}`;
495
+ const candidateLower = `${doseVariant.lower} ${unitRouteLower}${codeSuffix}`;
496
+ if (push(candidate, candidateLower)) {
432
497
  return suggestions;
433
498
  }
434
499
  }
435
500
  }
436
- if (push(`${pair.route} ${code}`)) {
501
+ const candidate = `${route}${codeSuffix}`;
502
+ const candidateLower = `${routeLower}${codeSuffix}`;
503
+ if (push(candidate, candidateLower)) {
437
504
  return suggestions;
438
505
  }
439
506
  }
440
507
  for (const interval of intervalTokens) {
508
+ const intervalSuffix = ` ${interval}`;
441
509
  for (const unitVariant of unitVariants) {
442
- for (const dose of doseValues) {
443
- if (push(`${dose} ${unitVariant} ${pair.route} ${interval}`)) {
510
+ const unitRoute = `${unitVariant.value} ${route}`;
511
+ const unitRouteLower = `${unitVariant.lower} ${routeLower}`;
512
+ for (const doseVariant of doseVariants) {
513
+ const base = `${doseVariant.value} ${unitRoute}`;
514
+ const baseLower = `${doseVariant.lower} ${unitRouteLower}`;
515
+ const intervalCandidate = `${base}${intervalSuffix}`;
516
+ const intervalCandidateLower = `${baseLower}${intervalSuffix}`;
517
+ if (push(intervalCandidate, intervalCandidateLower)) {
444
518
  return suggestions;
445
519
  }
446
520
  for (const reason of prnReasons) {
447
- if (push(`${dose} ${unitVariant} ${pair.route} ${interval} prn ${reason}`)) {
521
+ const reasonSuffix = `${intervalSuffix} prn ${reason}`;
522
+ const reasonCandidate = `${base}${reasonSuffix}`;
523
+ const reasonCandidateLower = `${baseLower}${reasonSuffix}`;
524
+ if (push(reasonCandidate, reasonCandidateLower)) {
448
525
  return suggestions;
449
526
  }
450
527
  }
451
528
  }
452
529
  }
453
- if (push(`${pair.route} ${interval}`)) {
530
+ const candidate = `${route}${intervalSuffix}`;
531
+ const candidateLower = `${routeLower}${intervalSuffix}`;
532
+ if (push(candidate, candidateLower)) {
454
533
  return suggestions;
455
534
  }
456
535
  }
@@ -459,77 +538,130 @@ function generateCandidateDirections(pairs, doseValues, prnReasons, intervalToke
459
538
  if (!freqToken) {
460
539
  continue;
461
540
  }
462
- if (push(`1x${freq} ${pair.route} ${freqToken}`)) {
541
+ const base = `1x${freq} ${route}`;
542
+ const baseLower = `1x${freq} ${routeLower}`;
543
+ const freqCandidate = `${base} ${freqToken}`;
544
+ const freqCandidateLower = `${baseLower} ${freqToken}`;
545
+ if (push(freqCandidate, freqCandidateLower)) {
463
546
  return suggestions;
464
547
  }
465
548
  for (const when of CORE_WHEN_TOKENS) {
466
- if (push(`1x${freq} ${pair.route} ${when}`)) {
549
+ const whenCandidate = `${base} ${when}`;
550
+ const whenCandidateLower = `${baseLower} ${when}`;
551
+ if (push(whenCandidate, whenCandidateLower)) {
467
552
  return suggestions;
468
553
  }
469
554
  }
470
555
  }
471
556
  for (const whenSequence of whenSequences) {
472
- const suffix = whenSequence.join(" ");
557
+ const suffix = ` ${whenSequence.join(" ")}`;
473
558
  for (const unitVariant of unitVariants) {
474
- for (const dose of doseValues) {
475
- if (push(`${dose} ${unitVariant} ${pair.route} ${suffix}`)) {
559
+ const unitRoute = `${unitVariant.value} ${route}`;
560
+ const unitRouteLower = `${unitVariant.lower} ${routeLower}`;
561
+ for (const doseVariant of doseVariants) {
562
+ const base = `${doseVariant.value} ${unitRoute}`;
563
+ const baseLower = `${doseVariant.lower} ${unitRouteLower}`;
564
+ const candidate = `${base}${suffix}`;
565
+ const candidateLower = `${baseLower}${suffix}`;
566
+ if (push(candidate, candidateLower)) {
476
567
  return suggestions;
477
568
  }
478
569
  }
479
570
  }
480
- if (push(`${pair.route} ${suffix}`)) {
571
+ const candidate = `${route}${suffix}`;
572
+ const candidateLower = `${routeLower}${suffix}`;
573
+ if (push(candidate, candidateLower)) {
481
574
  return suggestions;
482
575
  }
483
576
  }
484
577
  for (const reason of prnReasons) {
578
+ const reasonSuffix = ` prn ${reason}`;
485
579
  for (const unitVariant of unitVariants) {
486
- for (const dose of doseValues) {
487
- if (push(`${dose} ${unitVariant} ${pair.route} prn ${reason}`)) {
580
+ const unitRoute = `${unitVariant.value} ${route}`;
581
+ const unitRouteLower = `${unitVariant.lower} ${routeLower}`;
582
+ for (const doseVariant of doseVariants) {
583
+ const base = `${doseVariant.value} ${unitRoute}`;
584
+ const baseLower = `${doseVariant.lower} ${unitRouteLower}`;
585
+ const candidate = `${base}${reasonSuffix}`;
586
+ const candidateLower = `${baseLower}${reasonSuffix}`;
587
+ if (push(candidate, candidateLower)) {
488
588
  return suggestions;
489
589
  }
490
590
  }
491
591
  }
492
- if (push(`${pair.route} prn ${reason}`)) {
592
+ const candidate = `${route}${reasonSuffix}`;
593
+ const candidateLower = `${routeLower}${reasonSuffix}`;
594
+ if (push(candidate, candidateLower)) {
493
595
  return suggestions;
494
596
  }
495
597
  }
496
598
  }
497
599
  return suggestions;
498
600
  }
499
- function matchesPrefix(candidate, prefix, prefixCompact, prefixTokens, prefixTokensNoDashes, prefixCanonical, prefixCanonicalCompact, prefixNoDashes, prefixCanonicalNoDashes) {
500
- if (!prefix) {
601
+ function matchesPrefix(_candidate, candidateLower, context) {
602
+ var _a, _b, _c, _d, _e, _f;
603
+ if (!context.raw) {
501
604
  return true;
502
605
  }
503
- const normalizedCandidate = candidate.toLowerCase();
504
- if (normalizedCandidate.startsWith(prefix)) {
606
+ if (!context.hasCanonical && !context.hasTokens) {
505
607
  return true;
506
608
  }
507
- const compactCandidate = normalizedCandidate.replace(/\s+/g, "");
508
- if (compactCandidate.startsWith(prefixCompact)) {
609
+ if (candidateLower.startsWith(context.raw)) {
509
610
  return true;
510
611
  }
511
- const candidateNoDashes = normalizedCandidate.replace(/-/g, "");
512
- if (candidateNoDashes.startsWith(prefixNoDashes)) {
513
- return true;
612
+ const fingerprint = getCandidateFingerprint(candidateLower);
613
+ if (context.requiresCompact) {
614
+ const compactCandidate = (_a = fingerprint.compact) !== null && _a !== void 0 ? _a : (fingerprint.compact = removeWhitespaceCharacters(candidateLower));
615
+ if (compactCandidate.startsWith(context.compact)) {
616
+ return true;
617
+ }
514
618
  }
515
- const canonicalCandidate = canonicalizeForMatching(candidate);
516
- if (canonicalCandidate.startsWith(prefixCanonical)) {
517
- return true;
619
+ if (context.requiresNoDashes) {
620
+ const candidateNoDashes = (_b = fingerprint.noDashes) !== null && _b !== void 0 ? _b : (fingerprint.noDashes = removeDashes(candidateLower));
621
+ if (candidateNoDashes.startsWith(context.noDashes)) {
622
+ return true;
623
+ }
518
624
  }
519
- const canonicalCompact = canonicalCandidate.replace(/\s+/g, "");
520
- if (canonicalCompact.startsWith(prefixCanonicalCompact)) {
521
- return true;
625
+ const getCandidateTokens = () => {
626
+ if (!fingerprint.tokens) {
627
+ fingerprint.tokens = tokenizeLowercaseForMatching(candidateLower);
628
+ }
629
+ return fingerprint.tokens;
630
+ };
631
+ if (context.hasCanonical) {
632
+ const canonicalCandidate = (_c = fingerprint.canonical) !== null && _c !== void 0 ? _c : (fingerprint.canonical = getCandidateTokens().join(" "));
633
+ if (canonicalCandidate.startsWith(context.canonical)) {
634
+ return true;
635
+ }
636
+ if (context.requiresCanonicalCompact) {
637
+ const canonicalCompact = (_d = fingerprint.canonicalCompact) !== null && _d !== void 0 ? _d : (fingerprint.canonicalCompact = removeWhitespaceCharacters(canonicalCandidate));
638
+ if (canonicalCompact.startsWith(context.canonicalCompact)) {
639
+ return true;
640
+ }
641
+ }
642
+ if (context.requiresCanonicalNoDashes) {
643
+ const canonicalNoDashes = (_e = fingerprint.canonicalNoDashes) !== null && _e !== void 0 ? _e : (fingerprint.canonicalNoDashes = removeDashes(canonicalCandidate));
644
+ if (canonicalNoDashes.startsWith(context.canonicalNoDashes)) {
645
+ return true;
646
+ }
647
+ }
522
648
  }
523
- const candidateTokens = tokenizeForMatching(candidate);
524
- if (tokensMatch(prefixTokens, candidateTokens)) {
525
- return true;
649
+ if (context.hasTokens) {
650
+ const resolvedTokens = getCandidateTokens();
651
+ if (tokensMatch(context.tokens, resolvedTokens)) {
652
+ return true;
653
+ }
654
+ if (context.requiresTokenNoDashes) {
655
+ const candidateTokensNoDashes = (_f = fingerprint.tokensNoDashes) !== null && _f !== void 0 ? _f : (fingerprint.tokensNoDashes = resolvedTokens.map((token) => removeDashes(token)));
656
+ if (tokensMatch(context.tokensNoDashes, candidateTokensNoDashes)) {
657
+ return true;
658
+ }
659
+ }
526
660
  }
527
- const canonicalNoDashes = canonicalCandidate.replace(/-/g, "");
528
- if (canonicalNoDashes.startsWith(prefixCanonicalNoDashes)) {
661
+ else if (context.requiresTokenNoDashes) {
529
662
  return true;
530
663
  }
531
- const candidateTokensNoDashes = candidateTokens.map((token) => token.replace(/-/g, ""));
532
- return tokensMatch(prefixTokensNoDashes, candidateTokensNoDashes);
664
+ return false;
533
665
  }
534
666
  function suggestSig(input, options) {
535
667
  var _a, _b;
@@ -540,17 +672,34 @@ function suggestSig(input, options) {
540
672
  const prefix = normalizeSpacing(input.toLowerCase());
541
673
  const prefixCompact = prefix.replace(/\s+/g, "");
542
674
  const prefixNoDashes = prefix.replace(/-/g, "");
543
- const prefixCanonical = canonicalizeForMatching(prefix);
675
+ const prefixTokens = tokenizeLowercaseForMatching(prefix);
676
+ const prefixCanonical = prefixTokens.join(" ");
544
677
  const prefixCanonicalCompact = prefixCanonical.replace(/\s+/g, "");
545
678
  const prefixCanonicalNoDashes = prefixCanonical.replace(/-/g, "");
546
- const prefixTokens = tokenizeForMatching(prefixCanonical);
547
679
  const prefixTokensNoDashes = prefixTokens.map((token) => token.replace(/-/g, ""));
680
+ const prefixContext = {
681
+ raw: prefix,
682
+ compact: prefixCompact,
683
+ noDashes: prefixNoDashes,
684
+ canonical: prefixCanonical,
685
+ canonicalCompact: prefixCanonicalCompact,
686
+ canonicalNoDashes: prefixCanonicalNoDashes,
687
+ tokens: prefixTokens,
688
+ tokensNoDashes: prefixTokensNoDashes,
689
+ hasCanonical: prefixCanonical.length > 0,
690
+ hasTokens: prefixTokens.length > 0,
691
+ requiresCompact: prefixCompact !== prefix,
692
+ requiresNoDashes: prefixNoDashes !== prefix,
693
+ requiresCanonicalCompact: prefixCanonicalCompact !== prefixCanonical,
694
+ requiresCanonicalNoDashes: prefixCanonicalNoDashes !== prefixCanonical,
695
+ requiresTokenNoDashes: prefixTokens.some((token, index) => token !== prefixTokensNoDashes[index]),
696
+ };
548
697
  const contextUnit = (0, context_1.inferUnitFromContext)((_b = options === null || options === void 0 ? void 0 : options.context) !== null && _b !== void 0 ? _b : undefined);
549
698
  const pairs = buildUnitRoutePairs(contextUnit, options);
550
699
  const doseValues = buildDoseValues(input);
551
700
  const prnReasons = buildPrnReasons(options === null || options === void 0 ? void 0 : options.prnReasons);
552
701
  const intervalTokens = buildIntervalTokens(input);
553
702
  const whenSequences = PRECOMPUTED_WHEN_SEQUENCES;
554
- const matcher = (candidate) => matchesPrefix(candidate, prefix, prefixCompact, prefixTokens, prefixTokensNoDashes, prefixCanonical, prefixCanonicalCompact, prefixNoDashes, prefixCanonicalNoDashes);
703
+ const matcher = (candidate, candidateLower) => matchesPrefix(candidate, candidateLower, prefixContext);
555
704
  return generateCandidateDirections(pairs, doseValues, prnReasons, intervalTokens, whenSequences, limit, matcher);
556
705
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ezmedicationinput",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Parse concise medication sigs into FHIR R5 Dosage JSON",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",