ezmedicationinput 0.1.43 → 0.1.44
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 +4 -1
- package/dist/advice-rules.json +772 -0
- package/dist/advice-terminology.json +104 -0
- package/dist/advice.d.ts +16 -0
- package/dist/advice.js +1375 -0
- package/dist/event-trigger.d.ts +14 -0
- package/dist/event-trigger.js +501 -0
- package/dist/fhir-translations.d.ts +5 -0
- package/dist/fhir-translations.js +117 -0
- package/dist/fhir.d.ts +6 -4
- package/dist/fhir.js +566 -134
- package/dist/format.d.ts +4 -2
- package/dist/format.js +515 -218
- package/dist/i18n.d.ts +2 -2
- package/dist/i18n.js +641 -199
- package/dist/index.d.ts +0 -1
- package/dist/index.js +219 -168
- package/dist/internal-types.d.ts +5 -5
- package/dist/ir.d.ts +4 -0
- package/dist/ir.js +178 -0
- package/dist/lexer/lex.d.ts +2 -0
- package/dist/lexer/lex.js +401 -0
- package/dist/lexer/meaning.d.ts +71 -0
- package/dist/lexer/meaning.js +619 -0
- package/dist/lexer/surface.d.ts +2 -0
- package/dist/lexer/surface.js +62 -0
- package/dist/lexer/token-types.d.ts +36 -0
- package/dist/lexer/token-types.js +19 -0
- package/dist/maps.d.ts +6 -12
- package/dist/maps.js +793 -247
- package/dist/parser-state.d.ts +101 -0
- package/dist/parser-state.js +441 -0
- package/dist/parser.d.ts +7 -7
- package/dist/parser.js +3598 -1974
- package/dist/prn.d.ts +4 -0
- package/dist/prn.js +59 -0
- package/dist/schedule.js +230 -32
- package/dist/site-phrases.d.ts +35 -0
- package/dist/site-phrases.js +344 -0
- package/dist/timing-summary.d.ts +13 -3
- package/dist/timing-summary.js +7 -7
- package/dist/types.d.ts +237 -32
- package/dist/types.js +49 -1
- package/dist/utils/text.d.ts +3 -0
- package/dist/utils/text.js +48 -0
- package/package.json +1 -1
package/dist/prn.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CanonicalPrnReasonExpr } from "./types";
|
|
2
|
+
export declare function getCanonicalPrnReasonText(reason: CanonicalPrnReasonExpr | undefined): string | undefined;
|
|
3
|
+
export declare function joinCanonicalPrnReasonTexts(reasons: CanonicalPrnReasonExpr[] | undefined, conjunction?: string): string | undefined;
|
|
4
|
+
export declare function getPreferredCanonicalPrnReasonText(reason: CanonicalPrnReasonExpr | undefined, reasons: CanonicalPrnReasonExpr[] | undefined, conjunction?: string): string | undefined;
|
package/dist/prn.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCanonicalPrnReasonText = getCanonicalPrnReasonText;
|
|
4
|
+
exports.joinCanonicalPrnReasonTexts = joinCanonicalPrnReasonTexts;
|
|
5
|
+
exports.getPreferredCanonicalPrnReasonText = getPreferredCanonicalPrnReasonText;
|
|
6
|
+
function getCanonicalPrnReasonText(reason) {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
return (_a = reason === null || reason === void 0 ? void 0 : reason.text) !== null && _a !== void 0 ? _a : (_b = reason === null || reason === void 0 ? void 0 : reason.coding) === null || _b === void 0 ? void 0 : _b.display;
|
|
9
|
+
}
|
|
10
|
+
function joinCanonicalPrnReasonTexts(reasons, conjunction = "or") {
|
|
11
|
+
var _a;
|
|
12
|
+
if (!(reasons === null || reasons === void 0 ? void 0 : reasons.length)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const texts = [];
|
|
16
|
+
for (const reason of reasons) {
|
|
17
|
+
const text = (_a = getCanonicalPrnReasonText(reason)) === null || _a === void 0 ? void 0 : _a.trim();
|
|
18
|
+
if (!text) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
texts.push(text);
|
|
22
|
+
}
|
|
23
|
+
switch (texts.length) {
|
|
24
|
+
case 0:
|
|
25
|
+
return undefined;
|
|
26
|
+
case 1:
|
|
27
|
+
return texts[0];
|
|
28
|
+
case 2:
|
|
29
|
+
return `${texts[0]} ${conjunction} ${texts[1]}`;
|
|
30
|
+
default: {
|
|
31
|
+
let combined = "";
|
|
32
|
+
for (let index = 0; index < texts.length; index += 1) {
|
|
33
|
+
if (index === 0) {
|
|
34
|
+
combined = texts[index];
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (index === texts.length - 1) {
|
|
38
|
+
combined += ` ${conjunction} ${texts[index]}`;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
combined += `, ${texts[index]}`;
|
|
42
|
+
}
|
|
43
|
+
return combined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getPreferredCanonicalPrnReasonText(reason, reasons, conjunction = "or") {
|
|
48
|
+
var _a;
|
|
49
|
+
const direct = (_a = getCanonicalPrnReasonText(reason)) === null || _a === void 0 ? void 0 : _a.trim();
|
|
50
|
+
if (!(reasons === null || reasons === void 0 ? void 0 : reasons.length)) {
|
|
51
|
+
return direct;
|
|
52
|
+
}
|
|
53
|
+
if (!direct) {
|
|
54
|
+
return joinCanonicalPrnReasonTexts(reasons, conjunction);
|
|
55
|
+
}
|
|
56
|
+
return /[,/;]/.test(direct)
|
|
57
|
+
? joinCanonicalPrnReasonTexts(reasons, conjunction)
|
|
58
|
+
: direct;
|
|
59
|
+
}
|
package/dist/schedule.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.nextDueDoses = nextDueDoses;
|
|
4
4
|
exports.calculateTotalUnits = calculateTotalUnits;
|
|
5
5
|
const types_1 = require("./types");
|
|
6
|
+
const advice_1 = require("./advice");
|
|
6
7
|
const array_1 = require("./utils/array");
|
|
7
8
|
const units_1 = require("./utils/units");
|
|
8
9
|
const strength_1 = require("./utils/strength");
|
|
@@ -349,6 +350,151 @@ function applyOffset(clock, offsetMinutes) {
|
|
|
349
350
|
dayShift
|
|
350
351
|
};
|
|
351
352
|
}
|
|
353
|
+
function parseBoundsDurationUnit(quantity) {
|
|
354
|
+
var _a, _b, _c;
|
|
355
|
+
const candidate = (_b = (_a = quantity === null || quantity === void 0 ? void 0 : quantity.code) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()) !== null && _b !== void 0 ? _b : (_c = quantity === null || quantity === void 0 ? void 0 : quantity.unit) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase();
|
|
356
|
+
switch (candidate) {
|
|
357
|
+
case "s":
|
|
358
|
+
case "sec":
|
|
359
|
+
case "second":
|
|
360
|
+
case "seconds":
|
|
361
|
+
return types_1.FhirPeriodUnit.Second;
|
|
362
|
+
case "min":
|
|
363
|
+
case "mins":
|
|
364
|
+
case "minute":
|
|
365
|
+
case "minutes":
|
|
366
|
+
return types_1.FhirPeriodUnit.Minute;
|
|
367
|
+
case "h":
|
|
368
|
+
case "hr":
|
|
369
|
+
case "hrs":
|
|
370
|
+
case "hour":
|
|
371
|
+
case "hours":
|
|
372
|
+
return types_1.FhirPeriodUnit.Hour;
|
|
373
|
+
case "d":
|
|
374
|
+
case "day":
|
|
375
|
+
case "days":
|
|
376
|
+
return types_1.FhirPeriodUnit.Day;
|
|
377
|
+
case "wk":
|
|
378
|
+
case "wks":
|
|
379
|
+
case "week":
|
|
380
|
+
case "weeks":
|
|
381
|
+
return types_1.FhirPeriodUnit.Week;
|
|
382
|
+
case "mo":
|
|
383
|
+
case "month":
|
|
384
|
+
case "months":
|
|
385
|
+
return types_1.FhirPeriodUnit.Month;
|
|
386
|
+
case "a":
|
|
387
|
+
case "yr":
|
|
388
|
+
case "yrs":
|
|
389
|
+
case "year":
|
|
390
|
+
case "years":
|
|
391
|
+
return types_1.FhirPeriodUnit.Year;
|
|
392
|
+
default:
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function resolveRepeatBoundsDuration(repeat) {
|
|
397
|
+
var _a, _b, _c, _d;
|
|
398
|
+
if (!repeat) {
|
|
399
|
+
return {};
|
|
400
|
+
}
|
|
401
|
+
if (((_a = repeat.boundsDuration) === null || _a === void 0 ? void 0 : _a.value) !== undefined) {
|
|
402
|
+
return {
|
|
403
|
+
value: repeat.boundsDuration.value,
|
|
404
|
+
unit: parseBoundsDurationUnit(repeat.boundsDuration)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (!repeat.boundsRange) {
|
|
408
|
+
return {};
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
value: (_b = repeat.boundsRange.low) === null || _b === void 0 ? void 0 : _b.value,
|
|
412
|
+
max: (_c = repeat.boundsRange.high) === null || _c === void 0 ? void 0 : _c.value,
|
|
413
|
+
unit: (_d = parseBoundsDurationUnit(repeat.boundsRange.low)) !== null && _d !== void 0 ? _d : parseBoundsDurationUnit(repeat.boundsRange.high)
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
function resolveRepeatDurationCapEnd(repeat, anchor, timeZone) {
|
|
417
|
+
var _a, _b;
|
|
418
|
+
const bounds = resolveRepeatBoundsDuration(repeat);
|
|
419
|
+
const durationValue = (_a = bounds.max) !== null && _a !== void 0 ? _a : bounds.value;
|
|
420
|
+
const durationUnit = bounds.unit;
|
|
421
|
+
if (durationValue === undefined ||
|
|
422
|
+
!Number.isFinite(durationValue) ||
|
|
423
|
+
durationValue <= 0 ||
|
|
424
|
+
!durationUnit) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const stepper = createIntervalStepper({ period: durationValue, periodUnit: durationUnit }, timeZone);
|
|
428
|
+
if (!stepper) {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
return (_b = stepper(anchor)) !== null && _b !== void 0 ? _b : null;
|
|
432
|
+
}
|
|
433
|
+
function resolveDayFilteredSeriesRepeat(repeat, enforceDayFilter) {
|
|
434
|
+
var _a, _b, _c, _d;
|
|
435
|
+
if (!enforceDayFilter) {
|
|
436
|
+
return undefined;
|
|
437
|
+
}
|
|
438
|
+
if (repeat.frequency) {
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
if (((_b = (_a = repeat.when) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 0 || ((_d = (_c = repeat.timeOfDay) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) > 0) {
|
|
442
|
+
return undefined;
|
|
443
|
+
}
|
|
444
|
+
switch (repeat.periodUnit) {
|
|
445
|
+
case types_1.FhirPeriodUnit.Week:
|
|
446
|
+
case types_1.FhirPeriodUnit.Month:
|
|
447
|
+
case types_1.FhirPeriodUnit.Year:
|
|
448
|
+
return repeat.period ? repeat : undefined;
|
|
449
|
+
case undefined:
|
|
450
|
+
return repeat.period === undefined
|
|
451
|
+
? Object.assign(Object.assign({}, repeat), { period: 1, periodUnit: types_1.FhirPeriodUnit.Week }) : undefined;
|
|
452
|
+
default:
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
function isSingleAdministrationRepeat(repeat) {
|
|
457
|
+
var _a, _b, _c, _d, _e, _f;
|
|
458
|
+
return (repeat.count === 1 &&
|
|
459
|
+
repeat.frequency === undefined &&
|
|
460
|
+
repeat.frequencyMax === undefined &&
|
|
461
|
+
repeat.period === undefined &&
|
|
462
|
+
repeat.periodMax === undefined &&
|
|
463
|
+
repeat.periodUnit === undefined &&
|
|
464
|
+
((_b = (_a = repeat.dayOfWeek) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0 &&
|
|
465
|
+
((_d = (_c = repeat.when) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) === 0 &&
|
|
466
|
+
((_f = (_e = repeat.timeOfDay) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) === 0);
|
|
467
|
+
}
|
|
468
|
+
function hasUnresolvedRelationalInstruction(dosage) {
|
|
469
|
+
var _a, _b, _c, _d, _e, _f;
|
|
470
|
+
const texts = [];
|
|
471
|
+
if ((_a = dosage.patientInstruction) === null || _a === void 0 ? void 0 : _a.trim()) {
|
|
472
|
+
texts.push(dosage.patientInstruction.trim());
|
|
473
|
+
}
|
|
474
|
+
for (const instruction of (_b = dosage.additionalInstruction) !== null && _b !== void 0 ? _b : []) {
|
|
475
|
+
const text = ((_c = instruction.text) === null || _c === void 0 ? void 0 : _c.trim()) || ((_f = (_e = (_d = instruction.coding) === null || _d === void 0 ? void 0 : _d.find((coding) => { var _a; return (_a = coding.display) === null || _a === void 0 ? void 0 : _a.trim(); })) === null || _e === void 0 ? void 0 : _e.display) === null || _f === void 0 ? void 0 : _f.trim());
|
|
476
|
+
if (text) {
|
|
477
|
+
texts.push(text);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
for (const text of texts) {
|
|
481
|
+
const parsed = (0, advice_1.parseAdditionalInstructions)(text, { start: 0, end: text.length }, { defaultPredicate: "take" });
|
|
482
|
+
for (const instruction of parsed) {
|
|
483
|
+
for (const frame of instruction.frames) {
|
|
484
|
+
if (frame.relation) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
function minDate(left, right) {
|
|
493
|
+
if (!right) {
|
|
494
|
+
return left;
|
|
495
|
+
}
|
|
496
|
+
return right.getTime() < left.getTime() ? right : left;
|
|
497
|
+
}
|
|
352
498
|
/** Provides the default meal pairing used for AC/PC expansions. */
|
|
353
499
|
function getDefaultMealPairs(config) {
|
|
354
500
|
return [types_1.EventTiming.Breakfast, types_1.EventTiming.Lunch, types_1.EventTiming.Dinner];
|
|
@@ -633,6 +779,7 @@ function nextDueDoses(dosage, options) {
|
|
|
633
779
|
};
|
|
634
780
|
const timing = dosage.timing;
|
|
635
781
|
const repeat = timing === null || timing === void 0 ? void 0 : timing.repeat;
|
|
782
|
+
const courseEnd = timing && repeat ? resolveRepeatDurationCapEnd(repeat, baseTime, timeZone) : null;
|
|
636
783
|
if (needsDerivedPriorCount &&
|
|
637
784
|
orderedAt &&
|
|
638
785
|
timing &&
|
|
@@ -643,6 +790,9 @@ function nextDueDoses(dosage, options) {
|
|
|
643
790
|
if (!timing || !repeat) {
|
|
644
791
|
return [];
|
|
645
792
|
}
|
|
793
|
+
if (courseEnd && from >= courseEnd) {
|
|
794
|
+
return [];
|
|
795
|
+
}
|
|
646
796
|
const rawCount = repeat.count;
|
|
647
797
|
const normalizedCount = rawCount === undefined ? undefined : Math.max(0, Math.floor(rawCount));
|
|
648
798
|
if (normalizedCount === 0) {
|
|
@@ -653,10 +803,21 @@ function nextDueDoses(dosage, options) {
|
|
|
653
803
|
return [];
|
|
654
804
|
}
|
|
655
805
|
const effectiveLimit = remainingCount !== undefined ? Math.min(limit, remainingCount) : limit;
|
|
806
|
+
if (isSingleAdministrationRepeat(repeat)) {
|
|
807
|
+
if (hasUnresolvedRelationalInstruction(dosage)) {
|
|
808
|
+
return [];
|
|
809
|
+
}
|
|
810
|
+
const anchor = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
|
|
811
|
+
if ((orderedAt && orderedAt < from) || (courseEnd && anchor >= courseEnd)) {
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
return [formatZonedIso(anchor, timeZone)].slice(0, effectiveLimit);
|
|
815
|
+
}
|
|
656
816
|
const results = [];
|
|
657
817
|
const seen = new Set();
|
|
658
818
|
const dayFilter = new Set(((_g = repeat.dayOfWeek) !== null && _g !== void 0 ? _g : []).map((day) => day.toLowerCase()));
|
|
659
819
|
const enforceDayFilter = dayFilter.size > 0;
|
|
820
|
+
const dayFilteredSeriesRepeat = resolveDayFilteredSeriesRepeat(repeat, enforceDayFilter);
|
|
660
821
|
const whenCodes = (_h = repeat.when) !== null && _h !== void 0 ? _h : [];
|
|
661
822
|
const timeOfDayEntries = (_j = repeat.timeOfDay) !== null && _j !== void 0 ? _j : [];
|
|
662
823
|
if (whenCodes.length > 0 || timeOfDayEntries.length > 0) {
|
|
@@ -680,7 +841,7 @@ function nextDueDoses(dosage, options) {
|
|
|
680
841
|
const includesImmediate = (0, array_1.arrayIncludes)(whenCodes, types_1.EventTiming.Immediate);
|
|
681
842
|
if (includesImmediate) {
|
|
682
843
|
const immediateSource = orderedAt !== null && orderedAt !== void 0 ? orderedAt : from;
|
|
683
|
-
if (!orderedAt || orderedAt >= from) {
|
|
844
|
+
if ((!orderedAt || orderedAt >= from) && (!courseEnd || immediateSource < courseEnd)) {
|
|
684
845
|
const instantIso = formatZonedIso(immediateSource, timeZone);
|
|
685
846
|
if (!seen.has(instantIso)) {
|
|
686
847
|
seen.add(instantIso);
|
|
@@ -703,7 +864,9 @@ function nextDueDoses(dosage, options) {
|
|
|
703
864
|
let currentDay = startOfLocalDay(from, timeZone);
|
|
704
865
|
let iterations = 0;
|
|
705
866
|
const maxIterations = effectiveLimit * 31;
|
|
706
|
-
while (results.length < effectiveLimit &&
|
|
867
|
+
while (results.length < effectiveLimit &&
|
|
868
|
+
iterations < maxIterations &&
|
|
869
|
+
(!courseEnd || currentDay < courseEnd)) {
|
|
707
870
|
const weekday = getLocalWeekday(currentDay, timeZone);
|
|
708
871
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
709
872
|
for (const entry of expanded) {
|
|
@@ -720,6 +883,9 @@ function nextDueDoses(dosage, options) {
|
|
|
720
883
|
if (orderedAt && zoned < orderedAt) {
|
|
721
884
|
continue;
|
|
722
885
|
}
|
|
886
|
+
if (courseEnd && zoned >= courseEnd) {
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
723
889
|
const iso = formatZonedIso(zoned, timeZone);
|
|
724
890
|
if (!seen.has(iso)) {
|
|
725
891
|
seen.add(iso);
|
|
@@ -748,20 +914,18 @@ function nextDueDoses(dosage, options) {
|
|
|
748
914
|
if (treatAsInterval && supportsDayFilteredInterval) {
|
|
749
915
|
// True interval schedules advance from the order start in fixed units. The
|
|
750
916
|
// timing.code remains advisory so we only rely on the period/unit fields.
|
|
751
|
-
const candidates = generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt);
|
|
917
|
+
const candidates = generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt, courseEnd);
|
|
752
918
|
return candidates;
|
|
753
919
|
}
|
|
754
|
-
if (
|
|
755
|
-
repeat.period &&
|
|
756
|
-
repeat.periodUnit &&
|
|
757
|
-
(repeat.periodUnit === "wk" || repeat.periodUnit === "mo" || repeat.periodUnit === "a")) {
|
|
920
|
+
if (dayFilteredSeriesRepeat) {
|
|
758
921
|
return generateDayFilteredPeriodSeries({
|
|
759
|
-
repeat,
|
|
922
|
+
repeat: dayFilteredSeriesRepeat,
|
|
760
923
|
timeZone,
|
|
761
924
|
dayFilter,
|
|
762
925
|
anchorDay: startOfLocalDay(baseTime, timeZone),
|
|
763
926
|
startDay: from,
|
|
764
927
|
from,
|
|
928
|
+
to: courseEnd !== null && courseEnd !== void 0 ? courseEnd : undefined,
|
|
765
929
|
orderedAt,
|
|
766
930
|
limit: effectiveLimit,
|
|
767
931
|
defaultClock: toLocalClock(baseTime, timeZone)
|
|
@@ -778,7 +942,9 @@ function nextDueDoses(dosage, options) {
|
|
|
778
942
|
let currentDay = startOfLocalDay(from, timeZone);
|
|
779
943
|
let iterations = 0;
|
|
780
944
|
const maxIterations = effectiveLimit * 31;
|
|
781
|
-
while (results.length < effectiveLimit &&
|
|
945
|
+
while (results.length < effectiveLimit &&
|
|
946
|
+
iterations < maxIterations &&
|
|
947
|
+
(!courseEnd || currentDay < courseEnd)) {
|
|
782
948
|
const weekday = getLocalWeekday(currentDay, timeZone);
|
|
783
949
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
784
950
|
for (const clock of clocks) {
|
|
@@ -792,6 +958,9 @@ function nextDueDoses(dosage, options) {
|
|
|
792
958
|
if (orderedAt && zoned < orderedAt) {
|
|
793
959
|
continue;
|
|
794
960
|
}
|
|
961
|
+
if (courseEnd && zoned >= courseEnd) {
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
795
964
|
const iso = formatZonedIso(zoned, timeZone);
|
|
796
965
|
if (!seen.has(iso)) {
|
|
797
966
|
seen.add(iso);
|
|
@@ -822,6 +991,7 @@ function derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, ti
|
|
|
822
991
|
}
|
|
823
992
|
const dayFilter = new Set(((_a = repeat.dayOfWeek) !== null && _a !== void 0 ? _a : []).map((day) => day.toLowerCase()));
|
|
824
993
|
const enforceDayFilter = dayFilter.size > 0;
|
|
994
|
+
const dayFilteredSeriesRepeat = resolveDayFilteredSeriesRepeat(repeat, enforceDayFilter);
|
|
825
995
|
const seen = new Set();
|
|
826
996
|
let count = 0;
|
|
827
997
|
const recordCandidate = (candidate) => {
|
|
@@ -931,12 +1101,9 @@ function derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, ti
|
|
|
931
1101
|
}
|
|
932
1102
|
return count;
|
|
933
1103
|
}
|
|
934
|
-
if (
|
|
935
|
-
repeat.period &&
|
|
936
|
-
repeat.periodUnit &&
|
|
937
|
-
(repeat.periodUnit === "wk" || repeat.periodUnit === "mo" || repeat.periodUnit === "a")) {
|
|
1104
|
+
if (dayFilteredSeriesRepeat) {
|
|
938
1105
|
const generated = generateDayFilteredPeriodSeries({
|
|
939
|
-
repeat,
|
|
1106
|
+
repeat: dayFilteredSeriesRepeat,
|
|
940
1107
|
timeZone,
|
|
941
1108
|
dayFilter,
|
|
942
1109
|
anchorDay: startOfLocalDay(orderedAt, timeZone),
|
|
@@ -983,7 +1150,7 @@ function derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, ti
|
|
|
983
1150
|
* Generates an interval-based series by stepping forward from the base time
|
|
984
1151
|
* until the requested number of timestamps have been produced.
|
|
985
1152
|
*/
|
|
986
|
-
function generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt) {
|
|
1153
|
+
function generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone, dayFilter, enforceDayFilter, orderedAt, upperBound) {
|
|
987
1154
|
const increment = createIntervalStepper(repeat, timeZone);
|
|
988
1155
|
if (!increment) {
|
|
989
1156
|
return [];
|
|
@@ -1001,7 +1168,9 @@ function generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone
|
|
|
1001
1168
|
current = next;
|
|
1002
1169
|
guard += 1;
|
|
1003
1170
|
}
|
|
1004
|
-
while (results.length < effectiveLimit &&
|
|
1171
|
+
while (results.length < effectiveLimit &&
|
|
1172
|
+
guard < maxIterations &&
|
|
1173
|
+
(!upperBound || current < upperBound)) {
|
|
1005
1174
|
const weekday = getLocalWeekday(current, timeZone);
|
|
1006
1175
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
1007
1176
|
if (current < from) {
|
|
@@ -1023,6 +1192,9 @@ function generateIntervalSeries(baseTime, from, effectiveLimit, repeat, timeZone
|
|
|
1023
1192
|
current = next;
|
|
1024
1193
|
continue;
|
|
1025
1194
|
}
|
|
1195
|
+
if (upperBound && current >= upperBound) {
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1026
1198
|
const iso = formatZonedIso(current, timeZone);
|
|
1027
1199
|
if (!seen.has(iso)) {
|
|
1028
1200
|
seen.add(iso);
|
|
@@ -1214,11 +1386,38 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1214
1386
|
const repeat = timing === null || timing === void 0 ? void 0 : timing.repeat;
|
|
1215
1387
|
if (!timing || !repeat)
|
|
1216
1388
|
return 0;
|
|
1389
|
+
const normalizedCount = repeat.count === undefined
|
|
1390
|
+
? undefined
|
|
1391
|
+
: Math.max(0, Math.floor(repeat.count));
|
|
1392
|
+
if (normalizedCount === 0) {
|
|
1393
|
+
return 0;
|
|
1394
|
+
}
|
|
1217
1395
|
const dayFilter = new Set(((_a = repeat.dayOfWeek) !== null && _a !== void 0 ? _a : []).map((day) => day.toLowerCase()));
|
|
1218
1396
|
const enforceDayFilter = dayFilter.size > 0;
|
|
1397
|
+
const dayFilteredSeriesRepeat = resolveDayFilteredSeriesRepeat(repeat, enforceDayFilter);
|
|
1219
1398
|
const seen = new Set();
|
|
1220
1399
|
let count = 0;
|
|
1221
1400
|
const timeZone = config.timeZone;
|
|
1401
|
+
const priorCount = normalizedCount !== undefined && orderedAt && from > orderedAt
|
|
1402
|
+
? derivePriorCountFromHistory(timing, repeat, config, orderedAt, from, timeZone)
|
|
1403
|
+
: 0;
|
|
1404
|
+
const countLimit = normalizedCount === undefined
|
|
1405
|
+
? (limit !== null && limit !== void 0 ? limit : Number.POSITIVE_INFINITY)
|
|
1406
|
+
: Math.min(limit !== null && limit !== void 0 ? limit : normalizedCount, Math.max(0, normalizedCount - priorCount));
|
|
1407
|
+
if (countLimit <= 0) {
|
|
1408
|
+
return 0;
|
|
1409
|
+
}
|
|
1410
|
+
const hardLimit = Number.isFinite(countLimit) ? countLimit : 365 * 31;
|
|
1411
|
+
if (isSingleAdministrationRepeat(repeat)) {
|
|
1412
|
+
if (hasUnresolvedRelationalInstruction(dosage)) {
|
|
1413
|
+
return 0;
|
|
1414
|
+
}
|
|
1415
|
+
const anchor = orderedAt !== null && orderedAt !== void 0 ? orderedAt : baseTime;
|
|
1416
|
+
if (anchor < from || anchor >= to) {
|
|
1417
|
+
return 0;
|
|
1418
|
+
}
|
|
1419
|
+
return 1;
|
|
1420
|
+
}
|
|
1222
1421
|
const recordCandidate = (candidate) => {
|
|
1223
1422
|
if (!candidate)
|
|
1224
1423
|
return false;
|
|
@@ -1266,8 +1465,8 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1266
1465
|
return count;
|
|
1267
1466
|
let currentDay = startOfLocalDay(from, timeZone);
|
|
1268
1467
|
let iterations = 0;
|
|
1269
|
-
const maxIterations =
|
|
1270
|
-
while (count <
|
|
1468
|
+
const maxIterations = hardLimit * 31;
|
|
1469
|
+
while (count < countLimit && currentDay < to && iterations < maxIterations) {
|
|
1271
1470
|
const weekday = getLocalWeekday(currentDay, timeZone);
|
|
1272
1471
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
1273
1472
|
for (const entry of expanded) {
|
|
@@ -1297,7 +1496,7 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1297
1496
|
return count;
|
|
1298
1497
|
let current = baseTime;
|
|
1299
1498
|
let guard = 0;
|
|
1300
|
-
const maxIterations =
|
|
1499
|
+
const maxIterations = hardLimit * 1000;
|
|
1301
1500
|
// Advance to "from"
|
|
1302
1501
|
while (current < from && guard < maxIterations) {
|
|
1303
1502
|
const next = increment(current);
|
|
@@ -1306,7 +1505,7 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1306
1505
|
current = next;
|
|
1307
1506
|
guard++;
|
|
1308
1507
|
}
|
|
1309
|
-
while (current < to && count <
|
|
1508
|
+
while (current < to && count < countLimit && guard < maxIterations) {
|
|
1310
1509
|
const weekday = getLocalWeekday(current, timeZone);
|
|
1311
1510
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
1312
1511
|
recordCandidate(current);
|
|
@@ -1325,8 +1524,8 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1325
1524
|
return count;
|
|
1326
1525
|
let currentDay = startOfLocalDay(from, timeZone);
|
|
1327
1526
|
let iterations = 0;
|
|
1328
|
-
const maxIterations =
|
|
1329
|
-
while (count <
|
|
1527
|
+
const maxIterations = hardLimit * 31;
|
|
1528
|
+
while (count < countLimit && currentDay < to && iterations < maxIterations) {
|
|
1330
1529
|
const weekday = getLocalWeekday(currentDay, timeZone);
|
|
1331
1530
|
if (!enforceDayFilter || dayFilter.has(weekday)) {
|
|
1332
1531
|
for (const clock of clocks) {
|
|
@@ -1340,12 +1539,9 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1340
1539
|
}
|
|
1341
1540
|
}
|
|
1342
1541
|
// Fallback for dayOfWeek with period/periodUnit but no explicit frequency/clocks
|
|
1343
|
-
if (
|
|
1344
|
-
repeat.period &&
|
|
1345
|
-
repeat.periodUnit &&
|
|
1346
|
-
(repeat.periodUnit === "wk" || repeat.periodUnit === "mo" || repeat.periodUnit === "a")) {
|
|
1542
|
+
if (dayFilteredSeriesRepeat) {
|
|
1347
1543
|
const generated = generateDayFilteredPeriodSeries({
|
|
1348
|
-
repeat,
|
|
1544
|
+
repeat: dayFilteredSeriesRepeat,
|
|
1349
1545
|
timeZone,
|
|
1350
1546
|
dayFilter,
|
|
1351
1547
|
anchorDay: startOfLocalDay(baseTime, timeZone),
|
|
@@ -1353,7 +1549,7 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1353
1549
|
from,
|
|
1354
1550
|
to,
|
|
1355
1551
|
orderedAt,
|
|
1356
|
-
limit:
|
|
1552
|
+
limit: hardLimit,
|
|
1357
1553
|
defaultClock: toLocalClock(baseTime, timeZone)
|
|
1358
1554
|
});
|
|
1359
1555
|
return count + generated.length;
|
|
@@ -1361,9 +1557,10 @@ function countScheduleEvents(dosage, from, to, config, baseTime, orderedAt, limi
|
|
|
1361
1557
|
return count;
|
|
1362
1558
|
}
|
|
1363
1559
|
function calculateTotalUnitsSingle(options) {
|
|
1364
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
1560
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1365
1561
|
const { dosage, durationValue, durationUnit, roundToMultiple, context } = options;
|
|
1366
1562
|
const from = coerceDate(options.from, "from");
|
|
1563
|
+
const orderedAtDate = options.orderedAt === undefined ? null : coerceDate(options.orderedAt, "orderedAt");
|
|
1367
1564
|
const providedConfig = options.config;
|
|
1368
1565
|
const timeZone = (_a = options.timeZone) !== null && _a !== void 0 ? _a : providedConfig === null || providedConfig === void 0 ? void 0 : providedConfig.timeZone;
|
|
1369
1566
|
if (!timeZone) {
|
|
@@ -1388,8 +1585,9 @@ function calculateTotalUnitsSingle(options) {
|
|
|
1388
1585
|
else {
|
|
1389
1586
|
endDay = from;
|
|
1390
1587
|
}
|
|
1391
|
-
|
|
1392
|
-
const
|
|
1588
|
+
endDay = minDate(endDay, resolveRepeatDurationCapEnd((_f = dosage.timing) === null || _f === void 0 ? void 0 : _f.repeat, orderedAtDate !== null && orderedAtDate !== void 0 ? orderedAtDate : from, timeZone));
|
|
1589
|
+
const count = countScheduleEvents(dosage, from, endDay, config, orderedAtDate !== null && orderedAtDate !== void 0 ? orderedAtDate : from, orderedAtDate, 2000);
|
|
1590
|
+
const doseQuantity = (_k = (_j = (_h = (_g = dosage.doseAndRate) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.doseQuantity) === null || _j === void 0 ? void 0 : _j.value) !== null && _k !== void 0 ? _k : 0;
|
|
1393
1591
|
let totalUnits = count * doseQuantity;
|
|
1394
1592
|
if (roundToMultiple && roundToMultiple > 0) {
|
|
1395
1593
|
totalUnits = Math.ceil(totalUnits / roundToMultiple) * roundToMultiple;
|
|
@@ -1398,7 +1596,7 @@ function calculateTotalUnitsSingle(options) {
|
|
|
1398
1596
|
// Handle containers
|
|
1399
1597
|
const containerValue = context === null || context === void 0 ? void 0 : context.containerValue;
|
|
1400
1598
|
const containerUnit = context === null || context === void 0 ? void 0 : context.containerUnit;
|
|
1401
|
-
const doseUnit = (
|
|
1599
|
+
const doseUnit = (_o = (_m = (_l = dosage.doseAndRate) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.doseQuantity) === null || _o === void 0 ? void 0 : _o.unit;
|
|
1402
1600
|
if (containerValue && containerValue > 0) {
|
|
1403
1601
|
let effectiveUnits = totalUnits;
|
|
1404
1602
|
if (containerUnit && doseUnit && containerUnit !== doseUnit) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Token } from "./parser-state";
|
|
2
|
+
import { BodySiteDefinition, ParseOptions, RouteCode } from "./types";
|
|
3
|
+
export interface SitePhraseServices {
|
|
4
|
+
customSiteHints?: Set<string>;
|
|
5
|
+
siteConnectors: ReadonlySet<string>;
|
|
6
|
+
siteFillerWords: ReadonlySet<string>;
|
|
7
|
+
isInstructionLikeText?: (text: string) => boolean;
|
|
8
|
+
normalizeTokenLower: (token: Token) => string;
|
|
9
|
+
isBodySiteHint: (word: string, customSiteHints?: Set<string>) => boolean;
|
|
10
|
+
hasExplicitSiteIntroduction: (startIndex: number) => boolean;
|
|
11
|
+
isNumericToken: (value: string) => boolean;
|
|
12
|
+
isOrdinalToken: (value: string) => boolean;
|
|
13
|
+
mapFrequencyAdverb: (value: string) => string | undefined;
|
|
14
|
+
mapIntervalUnit: (value: string) => string | undefined;
|
|
15
|
+
normalizeUnit: (value: string, options?: ParseOptions) => string | undefined;
|
|
16
|
+
hasRouteLikeWord: (value: string, options?: ParseOptions) => boolean;
|
|
17
|
+
hasFrequencyLikeWord: (value: string) => boolean;
|
|
18
|
+
getNextActiveToken: (index: number) => Token | undefined;
|
|
19
|
+
getPreviousActiveToken: (index: number) => Token | undefined;
|
|
20
|
+
hasApplicationVerbBefore: (index: number) => boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface SiteLookupServices {
|
|
23
|
+
lookupBodySiteDefinition: (map: Record<string, BodySiteDefinition> | undefined, canonical: string) => BodySiteDefinition | undefined;
|
|
24
|
+
}
|
|
25
|
+
export interface SitePhraseCandidate {
|
|
26
|
+
tokenIndices: number[];
|
|
27
|
+
source: "explicit" | "residual";
|
|
28
|
+
}
|
|
29
|
+
export declare function isTimingOnlySitePhrase(words: string[]): boolean;
|
|
30
|
+
export declare function hasExternalSurfaceModifier(siteText: string): boolean;
|
|
31
|
+
export declare function extractExplicitSiteCandidate(tokens: Token[], consumed: Set<number>, startIndex: number, options: ParseOptions | undefined, services: SitePhraseServices): SitePhraseCandidate | undefined;
|
|
32
|
+
export declare function selectBestResidualSiteCandidate(groups: Array<{
|
|
33
|
+
tokens: Token[];
|
|
34
|
+
}>, prnSiteSuffixIndices: Set<number>, services: SitePhraseServices): SitePhraseCandidate | undefined;
|
|
35
|
+
export declare function inferRouteHintFromSitePhrase(siteText: string, options: ParseOptions | undefined, services: SiteLookupServices): RouteCode | undefined;
|