dabke 0.81.1 → 0.83.0
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/CHANGELOG.md +58 -0
- package/README.md +45 -27
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -1
- package/dist/client.js.map +1 -1
- package/dist/client.types.d.ts +9 -0
- package/dist/client.types.d.ts.map +1 -1
- package/dist/client.types.js +1 -0
- package/dist/client.types.js.map +1 -1
- package/dist/cpsat/model-builder.d.ts +9 -0
- package/dist/cpsat/model-builder.d.ts.map +1 -1
- package/dist/cpsat/model-builder.js +36 -34
- package/dist/cpsat/model-builder.js.map +1 -1
- package/dist/cpsat/response.d.ts +13 -1
- package/dist/cpsat/response.d.ts.map +1 -1
- package/dist/cpsat/response.js +4 -0
- package/dist/cpsat/response.js.map +1 -1
- package/dist/cpsat/rules/cost-utils.d.ts +11 -0
- package/dist/cpsat/rules/cost-utils.d.ts.map +1 -0
- package/dist/cpsat/rules/cost-utils.js +24 -0
- package/dist/cpsat/rules/cost-utils.js.map +1 -0
- package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-multiplier.js +3 -14
- package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.js +3 -7
- package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/index.d.ts +3 -0
- package/dist/cpsat/rules/index.d.ts.map +1 -1
- package/dist/cpsat/rules/index.js +3 -0
- package/dist/cpsat/rules/index.js.map +1 -1
- package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/max-consecutive-days.js +16 -2
- package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/max-days-week.d.ts +44 -0
- package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
- package/dist/cpsat/rules/max-days-week.js +95 -0
- package/dist/cpsat/rules/max-days-week.js.map +1 -0
- package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-day.js +15 -2
- package/dist/cpsat/rules/max-hours-day.js.map +1 -1
- package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-week.js +16 -2
- package/dist/cpsat/rules/max-hours-week.js.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.js +15 -2
- package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.js +15 -2
- package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/min-days-week.d.ts +34 -0
- package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
- package/dist/cpsat/rules/min-days-week.js +84 -0
- package/dist/cpsat/rules/min-days-week.js.map +1 -0
- package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-day.js +15 -2
- package/dist/cpsat/rules/min-hours-day.js.map +1 -1
- package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-week.js +16 -2
- package/dist/cpsat/rules/min-hours-week.js.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.js +72 -2
- package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
- package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
- package/dist/cpsat/rules/minimize-cost.js +2 -23
- package/dist/cpsat/rules/minimize-cost.js.map +1 -1
- package/dist/cpsat/rules/must-assign.d.ts +49 -0
- package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
- package/dist/cpsat/rules/must-assign.js +86 -0
- package/dist/cpsat/rules/must-assign.js.map +1 -0
- package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.js +1 -5
- package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +5 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.js +1 -5
- package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
- package/dist/cpsat/rules/registry.d.ts +28 -2
- package/dist/cpsat/rules/registry.d.ts.map +1 -1
- package/dist/cpsat/rules/registry.js +4 -1
- package/dist/cpsat/rules/registry.js.map +1 -1
- package/dist/cpsat/rules/resolver.js +2 -2
- package/dist/cpsat/rules/resolver.js.map +1 -1
- package/dist/cpsat/rules/rules.types.d.ts +3 -0
- package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.d.ts +18 -1
- package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.js +59 -16
- package/dist/cpsat/rules/scope.types.js.map +1 -1
- package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/time-cost-surcharge.js +2 -1
- package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/time-off.d.ts.map +1 -1
- package/dist/cpsat/rules/time-off.js +6 -3
- package/dist/cpsat/rules/time-off.js.map +1 -1
- package/dist/cpsat/semantic-time.d.ts +44 -42
- package/dist/cpsat/semantic-time.d.ts.map +1 -1
- package/dist/cpsat/semantic-time.js +64 -46
- package/dist/cpsat/semantic-time.js.map +1 -1
- package/dist/cpsat/types.d.ts +37 -27
- package/dist/cpsat/types.d.ts.map +1 -1
- package/dist/cpsat/utils.d.ts.map +1 -1
- package/dist/cpsat/utils.js +7 -12
- package/dist/cpsat/utils.js.map +1 -1
- package/dist/cpsat/validation-reporter.d.ts +10 -7
- package/dist/cpsat/validation-reporter.d.ts.map +1 -1
- package/dist/cpsat/validation-reporter.js +44 -72
- package/dist/cpsat/validation-reporter.js.map +1 -1
- package/dist/cpsat/validation.types.d.ts +54 -44
- package/dist/cpsat/validation.types.d.ts.map +1 -1
- package/dist/cpsat/validation.types.js +15 -10
- package/dist/cpsat/validation.types.js.map +1 -1
- package/dist/datetime.utils.d.ts +3 -203
- package/dist/datetime.utils.d.ts.map +1 -1
- package/dist/datetime.utils.js +1 -288
- package/dist/datetime.utils.js.map +1 -1
- package/dist/index.d.ts +14 -83
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -83
- package/dist/index.js.map +1 -1
- package/dist/schedule/cost.d.ts +204 -0
- package/dist/schedule/cost.d.ts.map +1 -0
- package/dist/schedule/cost.js +187 -0
- package/dist/schedule/cost.js.map +1 -0
- package/dist/schedule/coverage.d.ts +85 -0
- package/dist/schedule/coverage.d.ts.map +1 -0
- package/dist/schedule/coverage.js +33 -0
- package/dist/schedule/coverage.js.map +1 -0
- package/dist/schedule/definition.d.ts +227 -0
- package/dist/schedule/definition.d.ts.map +1 -0
- package/dist/schedule/definition.js +659 -0
- package/dist/schedule/definition.js.map +1 -0
- package/dist/schedule/index.d.ts +67 -0
- package/dist/schedule/index.d.ts.map +1 -0
- package/dist/schedule/index.js +69 -0
- package/dist/schedule/index.js.map +1 -0
- package/dist/schedule/rules.d.ts +353 -0
- package/dist/schedule/rules.d.ts.map +1 -0
- package/dist/schedule/rules.js +352 -0
- package/dist/schedule/rules.js.map +1 -0
- package/dist/schedule/shift-patterns.d.ts +34 -0
- package/dist/schedule/shift-patterns.d.ts.map +1 -0
- package/dist/schedule/shift-patterns.js +41 -0
- package/dist/schedule/shift-patterns.js.map +1 -0
- package/dist/schedule/time-periods.d.ts +69 -0
- package/dist/schedule/time-periods.d.ts.map +1 -0
- package/dist/schedule/time-periods.js +91 -0
- package/dist/schedule/time-periods.js.map +1 -0
- package/dist/types.d.ts +14 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -9
- package/solver/src/solver/app.py +1 -1
- package/solver/src/solver/solver.py +7 -4
- package/src/client.ts +6 -8
- package/src/client.types.ts +9 -0
- package/src/cpsat/model-builder.ts +44 -35
- package/src/cpsat/response.ts +13 -1
- package/src/cpsat/rules/cost-utils.ts +25 -0
- package/src/cpsat/rules/day-cost-multiplier.ts +3 -14
- package/src/cpsat/rules/day-cost-surcharge.ts +3 -8
- package/src/cpsat/rules/index.ts +3 -0
- package/src/cpsat/rules/max-consecutive-days.ts +17 -0
- package/src/cpsat/rules/max-days-week.ts +143 -0
- package/src/cpsat/rules/max-hours-day.ts +21 -1
- package/src/cpsat/rules/max-hours-week.ts +22 -1
- package/src/cpsat/rules/max-shifts-day.ts +21 -1
- package/src/cpsat/rules/min-consecutive-days.ts +16 -1
- package/src/cpsat/rules/min-days-week.ts +120 -0
- package/src/cpsat/rules/min-hours-day.ts +16 -1
- package/src/cpsat/rules/min-hours-week.ts +17 -1
- package/src/cpsat/rules/min-rest-between-shifts.ts +92 -2
- package/src/cpsat/rules/minimize-cost.ts +2 -29
- package/src/cpsat/rules/must-assign.ts +108 -0
- package/src/cpsat/rules/overtime-daily-multiplier.ts +1 -12
- package/src/cpsat/rules/overtime-daily-surcharge.ts +1 -6
- package/src/cpsat/rules/overtime-tiered-multiplier.ts +6 -13
- package/src/cpsat/rules/overtime-weekly-multiplier.ts +1 -12
- package/src/cpsat/rules/overtime-weekly-surcharge.ts +1 -6
- package/src/cpsat/rules/registry.ts +8 -2
- package/src/cpsat/rules/resolver.ts +2 -2
- package/src/cpsat/rules/rules.types.ts +3 -0
- package/src/cpsat/rules/scope.types.ts +73 -20
- package/src/cpsat/rules/time-cost-surcharge.ts +2 -1
- package/src/cpsat/rules/time-off.ts +6 -2
- package/src/cpsat/semantic-time.ts +115 -91
- package/src/cpsat/types.ts +37 -27
- package/src/cpsat/utils.ts +8 -12
- package/src/cpsat/validation-reporter.ts +51 -82
- package/src/cpsat/validation.types.ts +72 -47
- package/src/datetime.utils.ts +3 -334
- package/src/index.ts +35 -107
- package/src/schedule/cost.ts +242 -0
- package/src/schedule/coverage.ts +135 -0
- package/src/schedule/definition.ts +958 -0
- package/src/schedule/index.ts +112 -0
- package/src/schedule/rules.ts +529 -0
- package/src/schedule/shift-patterns.ts +46 -0
- package/src/schedule/time-periods.ts +110 -0
- package/src/types.ts +14 -88
- package/dist/errors.d.ts +0 -12
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -17
- package/dist/errors.js.map +0 -1
- package/dist/llms.d.ts +0 -2
- package/dist/llms.d.ts.map +0 -1
- package/dist/llms.js +0 -3
- package/dist/llms.js.map +0 -1
- package/dist/schedule.d.ts +0 -724
- package/dist/schedule.d.ts.map +0 -1
- package/dist/schedule.js +0 -899
- package/dist/schedule.js.map +0 -1
- package/dist/validation.d.ts +0 -105
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -130
- package/dist/validation.js.map +0 -1
- package/llms.txt +0 -925
- package/src/errors.ts +0 -17
- package/src/llms.ts +0 -3
- package/src/schedule.ts +0 -1419
- package/src/validation.ts +0 -188
package/src/datetime.utils.ts
CHANGED
|
@@ -1,48 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { DayOfWeek, SchedulingPeriod } from "./types.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Converts a JavaScript Date to a CalendarDate
|
|
5
|
-
*/
|
|
6
|
-
export function dateToCalendarDate(date: Date): CalendarDate {
|
|
7
|
-
return {
|
|
8
|
-
year: date.getFullYear(),
|
|
9
|
-
month: date.getMonth() + 1, // JS months are 0-indexed
|
|
10
|
-
day: date.getDate(),
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Converts a DateTime to a JavaScript Date
|
|
16
|
-
* Internal helper function
|
|
17
|
-
*/
|
|
18
|
-
export function dateTimeToDate(dateTime: DateTime): Date {
|
|
19
|
-
return new Date(
|
|
20
|
-
dateTime.year || 0,
|
|
21
|
-
(dateTime.month || 1) - 1,
|
|
22
|
-
dateTime.day || 1,
|
|
23
|
-
dateTime.hours || 0,
|
|
24
|
-
dateTime.minutes || 0,
|
|
25
|
-
dateTime.seconds || 0,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Compares two DateTimes
|
|
31
|
-
* Returns:
|
|
32
|
-
* -1 if dateTime1 < dateTime2
|
|
33
|
-
* 0 if dateTime1 = dateTime2
|
|
34
|
-
* 1 if dateTime1 > dateTime2
|
|
35
|
-
*/
|
|
36
|
-
export function compareDateTimes(dateTime1: DateTime, dateTime2: DateTime): number {
|
|
37
|
-
const date1 = dateTimeToDate(dateTime1);
|
|
38
|
-
const date2 = dateTimeToDate(dateTime2);
|
|
39
|
-
|
|
40
|
-
if (date1 < date2) return -1;
|
|
41
|
-
if (date1 > date2) return 1;
|
|
42
|
-
return 0;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const DAY_OF_WEEK_MAP = {
|
|
3
|
+
export const DAY_OF_WEEK_MAP: Record<DayOfWeek, number> = {
|
|
46
4
|
sunday: 0, // JavaScript Date week starts on Sunday
|
|
47
5
|
monday: 1,
|
|
48
6
|
tuesday: 2,
|
|
@@ -63,15 +21,7 @@ const DAY_NAMES = [
|
|
|
63
21
|
] as const;
|
|
64
22
|
|
|
65
23
|
/**
|
|
66
|
-
* Helper to get the day of week name from a Date (
|
|
67
|
-
*/
|
|
68
|
-
export function toDayOfWeek(date: Date): DayOfWeek {
|
|
69
|
-
// getDay() always returns 0-6
|
|
70
|
-
return DAY_NAMES[date.getDay()] as DayOfWeek;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Helper to get the day of week name from a Date (UTC)
|
|
24
|
+
* Helper to get the day of week name from a Date (UTC).
|
|
75
25
|
* Use this when working with date strings like "2026-01-10" that are timezone-agnostic.
|
|
76
26
|
*/
|
|
77
27
|
export function toDayOfWeekUTC(date: Date): DayOfWeek {
|
|
@@ -79,287 +29,6 @@ export function toDayOfWeekUTC(date: Date): DayOfWeek {
|
|
|
79
29
|
return DAY_NAMES[date.getUTCDay()] as DayOfWeek;
|
|
80
30
|
}
|
|
81
31
|
|
|
82
|
-
/**
|
|
83
|
-
* Formats a date as YYYY-MM-DD string
|
|
84
|
-
*/
|
|
85
|
-
export function formatDateString(date: Date): string {
|
|
86
|
-
const year = date.getFullYear();
|
|
87
|
-
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
88
|
-
const day = date.getDate().toString().padStart(2, "0");
|
|
89
|
-
return `${year}-${month}-${day}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Generates an array of day strings (YYYY-MM-DD) from a time horizon.
|
|
94
|
-
*
|
|
95
|
-
* @param horizon - The time horizon with start (inclusive) and end (inclusive) dates
|
|
96
|
-
* @returns Array of day strings in YYYY-MM-DD format
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```typescript
|
|
100
|
-
* const days = generateDays({
|
|
101
|
-
* start: new Date('2025-01-01'),
|
|
102
|
-
* end: new Date('2025-01-04')
|
|
103
|
-
* });
|
|
104
|
-
* // Returns: ["2025-01-01", "2025-01-02", "2025-01-03", "2025-01-04"]
|
|
105
|
-
* ```
|
|
106
|
-
*/
|
|
107
|
-
export function generateDays(horizon: { start: Date; end: Date }): string[] {
|
|
108
|
-
const days: string[] = [];
|
|
109
|
-
const current = new Date(horizon.start);
|
|
110
|
-
|
|
111
|
-
while (current <= horizon.end) {
|
|
112
|
-
days.push(formatDateString(current));
|
|
113
|
-
current.setDate(current.getDate() + 1);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return days;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Splits a time period into consecutive day ranges.
|
|
121
|
-
*
|
|
122
|
-
* Each range represents a single calendar day within the period from start to end.
|
|
123
|
-
* This is useful for rules that need to apply constraints on a per-day basis,
|
|
124
|
-
* such as maximum or minimum hours per day.
|
|
125
|
-
*
|
|
126
|
-
* @param start - The start date of the period (inclusive)
|
|
127
|
-
* @param end - The end date of the period (exclusive)
|
|
128
|
-
* @returns An array of [startDate, endDate] tuples, where each tuple represents
|
|
129
|
-
* a single day. The last range's end date will be the provided end date.
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```typescript
|
|
133
|
-
* const ranges = splitPeriodIntoDays({
|
|
134
|
-
* start: new Date('2025-01-01'),
|
|
135
|
-
* end: new Date('2025-01-03')
|
|
136
|
-
* });
|
|
137
|
-
* // Returns:
|
|
138
|
-
* // [
|
|
139
|
-
* // [Date('2025-01-01'), Date('2025-01-02')],
|
|
140
|
-
* // [Date('2025-01-02'), Date('2025-01-03')]
|
|
141
|
-
* // ]
|
|
142
|
-
* ```
|
|
143
|
-
*/
|
|
144
|
-
export function splitPeriodIntoDays({ start, end }: { start: Date; end: Date }): [Date, Date][] {
|
|
145
|
-
const ranges: [Date, Date][] = [];
|
|
146
|
-
|
|
147
|
-
let leftBound = new Date(start);
|
|
148
|
-
let done = false;
|
|
149
|
-
|
|
150
|
-
// Loop through each day
|
|
151
|
-
leftBound = new Date(start);
|
|
152
|
-
while (!done) {
|
|
153
|
-
// Create next day date
|
|
154
|
-
let rightBound = new Date(leftBound);
|
|
155
|
-
rightBound.setDate(rightBound.getDate() + 1);
|
|
156
|
-
|
|
157
|
-
if (rightBound >= end) {
|
|
158
|
-
rightBound = end;
|
|
159
|
-
done = true;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
ranges.push([leftBound, rightBound]);
|
|
163
|
-
leftBound = rightBound;
|
|
164
|
-
}
|
|
165
|
-
return ranges;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Splits a time period into consecutive week ranges.
|
|
170
|
-
*
|
|
171
|
-
* Each range represents a week period starting on the specified day of the week.
|
|
172
|
-
* This is useful for rules that need to apply constraints on a per-week basis,
|
|
173
|
-
* such as maximum or minimum hours per week.
|
|
174
|
-
*
|
|
175
|
-
* The first range starts at the provided start date (not necessarily on weekStartsOn).
|
|
176
|
-
* Subsequent ranges align to the weekStartsOn day. The last range's end date will be
|
|
177
|
-
* the provided end date.
|
|
178
|
-
*
|
|
179
|
-
* @param start - The start date of the period (inclusive)
|
|
180
|
-
* @param end - The end date of the period (exclusive)
|
|
181
|
-
* @param weekStartsOn - The day of the week that weeks start on (e.g., "monday", "sunday")
|
|
182
|
-
* @returns An array of [startDate, endDate] tuples, where each tuple represents
|
|
183
|
-
* a week period aligned to the specified week start day.
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```typescript
|
|
187
|
-
* const ranges = splitPeriodIntoWeeks({
|
|
188
|
-
* start: new Date('2025-01-01'), // Wednesday
|
|
189
|
-
* end: new Date('2025-01-15'),
|
|
190
|
-
* weekStartsOn: 'monday'
|
|
191
|
-
* });
|
|
192
|
-
* // Returns ranges starting from Jan 1 (Wed), then aligning to Mondays:
|
|
193
|
-
* // [
|
|
194
|
-
* // [Date('2025-01-01 Wed'), Date('2025-01-06 Mon')],
|
|
195
|
-
* // [Date('2025-01-06 Mon'), Date('2025-01-13 Mon')],
|
|
196
|
-
* // [Date('2025-01-13 Mon'), Date('2025-01-15 Wed')]
|
|
197
|
-
* // ]
|
|
198
|
-
* ```
|
|
199
|
-
*/
|
|
200
|
-
export function splitPeriodIntoWeeks({
|
|
201
|
-
start,
|
|
202
|
-
end,
|
|
203
|
-
weekStartsOn,
|
|
204
|
-
}: {
|
|
205
|
-
start: Date;
|
|
206
|
-
end: Date;
|
|
207
|
-
weekStartsOn: DayOfWeek;
|
|
208
|
-
}): [Date, Date][] {
|
|
209
|
-
const DAYS_OF_WEEK: DayOfWeek[] = [
|
|
210
|
-
"sunday",
|
|
211
|
-
"monday",
|
|
212
|
-
"tuesday",
|
|
213
|
-
"wednesday",
|
|
214
|
-
"thursday",
|
|
215
|
-
"friday",
|
|
216
|
-
"saturday",
|
|
217
|
-
];
|
|
218
|
-
|
|
219
|
-
const ranges: [Date, Date][] = [];
|
|
220
|
-
|
|
221
|
-
let leftBound = new Date(start);
|
|
222
|
-
let done = false;
|
|
223
|
-
|
|
224
|
-
const startDayIndex = DAYS_OF_WEEK.indexOf(weekStartsOn);
|
|
225
|
-
|
|
226
|
-
// Create weekly ranges
|
|
227
|
-
while (!done) {
|
|
228
|
-
const dayOfWeek = leftBound.getDay(); // 0 = Sunday, 1 = Monday, etc.
|
|
229
|
-
const daysToNextWeek = (7 - ((dayOfWeek - startDayIndex) % 7)) % 7;
|
|
230
|
-
const daysToAdd = daysToNextWeek === 0 ? 7 : daysToNextWeek;
|
|
231
|
-
|
|
232
|
-
let rightBound = new Date(leftBound);
|
|
233
|
-
rightBound.setDate(rightBound.getDate() + daysToAdd);
|
|
234
|
-
|
|
235
|
-
if (rightBound >= end) {
|
|
236
|
-
rightBound = end;
|
|
237
|
-
done = true;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
ranges.push([leftBound, rightBound]);
|
|
241
|
-
leftBound = rightBound;
|
|
242
|
-
}
|
|
243
|
-
return ranges;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Checks if two DateTime ranges overlap in both date and time.
|
|
248
|
-
* Ranges overlap if they share any moment in time.
|
|
249
|
-
*
|
|
250
|
-
* Two ranges overlap if: range1.start < range2.end AND range2.start < range1.end
|
|
251
|
-
*
|
|
252
|
-
* @param range1 First time range with start (inclusive) and end (exclusive)
|
|
253
|
-
* @param range2 Second time range with start (inclusive) and end (exclusive)
|
|
254
|
-
* @returns true if ranges overlap, false otherwise
|
|
255
|
-
*
|
|
256
|
-
* @example
|
|
257
|
-
* ```typescript
|
|
258
|
-
* // Same day, overlapping times (9-17 overlaps with 12-20)
|
|
259
|
-
* dateTimeRangesOverlap(
|
|
260
|
-
* {
|
|
261
|
-
* start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },
|
|
262
|
-
* end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }
|
|
263
|
-
* },
|
|
264
|
-
* {
|
|
265
|
-
* start: { year: 2025, month: 6, day: 1, hours: 12, minutes: 0 },
|
|
266
|
-
* end: { year: 2025, month: 6, day: 1, hours: 20, minutes: 0 }
|
|
267
|
-
* }
|
|
268
|
-
* ); // true
|
|
269
|
-
*
|
|
270
|
-
* // Different days - no overlap
|
|
271
|
-
* dateTimeRangesOverlap(
|
|
272
|
-
* {
|
|
273
|
-
* start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },
|
|
274
|
-
* end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }
|
|
275
|
-
* },
|
|
276
|
-
* {
|
|
277
|
-
* start: { year: 2025, month: 6, day: 2, hours: 9, minutes: 0 },
|
|
278
|
-
* end: { year: 2025, month: 6, day: 2, hours: 17, minutes: 0 }
|
|
279
|
-
* }
|
|
280
|
-
* ); // false
|
|
281
|
-
*
|
|
282
|
-
* // Works naturally with Shift objects
|
|
283
|
-
* dateTimeRangesOverlap(
|
|
284
|
-
* { start: shift1.startDateTime, end: shift1.endDateTime },
|
|
285
|
-
* { start: shift2.startDateTime, end: shift2.endDateTime }
|
|
286
|
-
* );
|
|
287
|
-
* ```
|
|
288
|
-
*/
|
|
289
|
-
export function dateTimeRangesOverlap(range1: DateTimeRange, range2: DateTimeRange): boolean {
|
|
290
|
-
// Use existing compareDateTimes for temporal comparison
|
|
291
|
-
// Ranges overlap if: start1 < end2 AND start2 < end1
|
|
292
|
-
return (
|
|
293
|
-
compareDateTimes(range1.start, range2.end) < 0 && compareDateTimes(range2.start, range1.end) < 0
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Calculates the number of complete days between two dates
|
|
299
|
-
* @param start The start date
|
|
300
|
-
* @param end The end date
|
|
301
|
-
* @returns Number of complete days from start to end (can be negative if end < start)
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```typescript
|
|
305
|
-
* daysBetween(new Date('2025-01-01'), new Date('2025-01-05')); // 4
|
|
306
|
-
* ```
|
|
307
|
-
*/
|
|
308
|
-
export function daysBetween(start: Date, end: Date): number {
|
|
309
|
-
const ms = end.getTime() - start.getTime();
|
|
310
|
-
return Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Adds a number of minutes to a base date and returns a DateTime.
|
|
315
|
-
* Treats the base date as a reference point (typically midnight of horizon start),
|
|
316
|
-
* and the minutes parameter as absolute minutes from that point.
|
|
317
|
-
*
|
|
318
|
-
* @param baseDate The starting date (used as reference point)
|
|
319
|
-
* @param minutes Absolute minutes from the base date
|
|
320
|
-
* @returns DateTime representing the result
|
|
321
|
-
*
|
|
322
|
-
* @example
|
|
323
|
-
* ```typescript
|
|
324
|
-
* // Add 90 minutes from midnight
|
|
325
|
-
* addMinutesToDate(new Date('2025-01-01'), 90);
|
|
326
|
-
* // Returns: { year: 2025, month: 1, day: 1, hours: 1, minutes: 30 }
|
|
327
|
-
*
|
|
328
|
-
* // Add 1500 minutes (spans to next day)
|
|
329
|
-
* addMinutesToDate(new Date('2025-01-01'), 1500);
|
|
330
|
-
* // Returns: { year: 2025, month: 1, day: 2, hours: 1, minutes: 0 }
|
|
331
|
-
* ```
|
|
332
|
-
*/
|
|
333
|
-
export function addMinutesToDate(baseDate: Date, minutes: number): DateTime {
|
|
334
|
-
const days = Math.floor(minutes / 1440);
|
|
335
|
-
const minutesInDay = minutes % 1440;
|
|
336
|
-
|
|
337
|
-
const targetDate = new Date(baseDate);
|
|
338
|
-
targetDate.setDate(targetDate.getDate() + days);
|
|
339
|
-
|
|
340
|
-
return {
|
|
341
|
-
year: targetDate.getFullYear(),
|
|
342
|
-
month: targetDate.getMonth() + 1,
|
|
343
|
-
day: targetDate.getDate(),
|
|
344
|
-
hours: Math.floor(minutesInDay / 60),
|
|
345
|
-
minutes: minutesInDay % 60,
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Returns the points where a range should be split, filtered to within [start, end).
|
|
351
|
-
* Always includes range start. Sorted ascending.
|
|
352
|
-
*/
|
|
353
|
-
export function splitPoints([start, end]: [number, number], splitAt: number[]): number[] {
|
|
354
|
-
const points = new Set<number>([start]);
|
|
355
|
-
|
|
356
|
-
for (const p of splitAt) {
|
|
357
|
-
if (p > start && p < end) points.add(p);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
return [...points].toSorted((a, b) => a - b);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
32
|
/**
|
|
364
33
|
* Computes the list of day strings (YYYY-MM-DD) from a SchedulingPeriod.
|
|
365
34
|
*
|
package/src/index.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scheduling library powered by constraint programming (CP-SAT).
|
|
3
3
|
*
|
|
4
|
-
* Define teams, shifts, coverage, and rules declaratively. dabke compiles
|
|
5
|
-
* them into a constraint model and solves for an optimized schedule.
|
|
6
|
-
*
|
|
7
4
|
* @remarks
|
|
8
5
|
* ## Core Concepts
|
|
9
6
|
*
|
|
10
|
-
* **Schedule
|
|
7
|
+
* **Schedule**: The primary API. Small, composable functions
|
|
11
8
|
* ({@link time}, {@link cover}, {@link shift}, rule functions) produce a
|
|
12
|
-
* complete scheduling configuration via {@link
|
|
9
|
+
* complete scheduling configuration via {@link schedule}. Each concept
|
|
13
10
|
* is a single function call with full type safety.
|
|
14
11
|
*
|
|
15
12
|
* **Times vs Shift Patterns**: These are two distinct concepts.
|
|
@@ -28,73 +25,9 @@
|
|
|
28
25
|
* - Scoping: apply rules globally, per person, per role, per skill, or per time period
|
|
29
26
|
* - Priority: `MANDATORY` (hard constraint) vs `LOW`/`MEDIUM`/`HIGH` (soft preferences)
|
|
30
27
|
*
|
|
31
|
-
* **Solving**: {@link
|
|
32
|
-
*
|
|
33
|
-
* {@link
|
|
34
|
-
* {@link HttpSolverClient} sends it to the CP-SAT solver.
|
|
35
|
-
*
|
|
36
|
-
* @example Define a schedule
|
|
37
|
-
* ```typescript
|
|
38
|
-
* import {
|
|
39
|
-
* defineSchedule, t, time, cover, shift,
|
|
40
|
-
* maxHoursPerWeek, minRestBetweenShifts, timeOff,
|
|
41
|
-
* weekdays, weekend,
|
|
42
|
-
* } from "dabke";
|
|
43
|
-
*
|
|
44
|
-
* const schedule = defineSchedule({
|
|
45
|
-
* roles: ["nurse", "doctor"],
|
|
46
|
-
* skills: ["charge_nurse"],
|
|
47
|
-
*
|
|
48
|
-
* times: {
|
|
49
|
-
* morning_round: time({ startTime: t(7), endTime: t(9) }),
|
|
50
|
-
* day_ward: time({ startTime: t(7), endTime: t(15) }),
|
|
51
|
-
* night_ward: time({ startTime: t(23), endTime: t(7) }),
|
|
52
|
-
* },
|
|
53
|
-
*
|
|
54
|
-
* coverage: [
|
|
55
|
-
* cover("morning_round", "doctor", 1),
|
|
56
|
-
* cover("day_ward", "nurse", 3, { dayOfWeek: weekdays }),
|
|
57
|
-
* cover("day_ward", "nurse", 2, { dayOfWeek: weekend }),
|
|
58
|
-
* cover("night_ward", "nurse", 2),
|
|
59
|
-
* cover("night_ward", "charge_nurse", 1),
|
|
60
|
-
* ],
|
|
61
|
-
*
|
|
62
|
-
* shiftPatterns: [
|
|
63
|
-
* shift("day", t(7), t(15)),
|
|
64
|
-
* shift("night", t(23), t(7)),
|
|
65
|
-
* ],
|
|
66
|
-
*
|
|
67
|
-
* rules: [
|
|
68
|
-
* maxHoursPerWeek(40),
|
|
69
|
-
* minRestBetweenShifts(11),
|
|
70
|
-
* timeOff({ appliesTo: "alice", dayOfWeek: weekend }),
|
|
71
|
-
* ],
|
|
72
|
-
* });
|
|
73
|
-
* ```
|
|
74
|
-
*
|
|
75
|
-
* @example Solve a schedule
|
|
76
|
-
* ```typescript
|
|
77
|
-
* import { ModelBuilder, HttpSolverClient, parseSolverResponse } from "dabke";
|
|
78
|
-
*
|
|
79
|
-
* const config = schedule.createSchedulerConfig({
|
|
80
|
-
* schedulingPeriod: {
|
|
81
|
-
* dateRange: { start: "2026-02-09", end: "2026-02-15" },
|
|
82
|
-
* },
|
|
83
|
-
* members: [
|
|
84
|
-
* { id: "alice", roles: ["nurse"], skills: ["charge_nurse"] },
|
|
85
|
-
* { id: "bob", roles: ["nurse"] },
|
|
86
|
-
* { id: "carol", roles: ["doctor"] },
|
|
87
|
-
* ],
|
|
88
|
-
* });
|
|
89
|
-
*
|
|
90
|
-
* const builder = new ModelBuilder(config);
|
|
91
|
-
* const { request, canSolve, validation } = builder.compile();
|
|
92
|
-
* if (canSolve) {
|
|
93
|
-
* const client = new HttpSolverClient(fetch, "http://localhost:8080");
|
|
94
|
-
* const response = await client.solve(request);
|
|
95
|
-
* const result = parseSolverResponse(response);
|
|
96
|
-
* }
|
|
97
|
-
* ```
|
|
28
|
+
* **Solving**: {@link Schedule.compile} compiles the config into a
|
|
29
|
+
* solver request; {@link Schedule.solve} sends it to the CP-SAT solver
|
|
30
|
+
* and returns a {@link SolveResult}.
|
|
98
31
|
*
|
|
99
32
|
* @packageDocumentation
|
|
100
33
|
*/
|
|
@@ -103,22 +36,10 @@
|
|
|
103
36
|
// Time primitives
|
|
104
37
|
// ============================================================================
|
|
105
38
|
|
|
106
|
-
export type { TimeOfDay, DayOfWeek,
|
|
39
|
+
export type { TimeOfDay, DayOfWeek, SchedulingPeriod } from "./types.js";
|
|
107
40
|
|
|
108
41
|
export { DayOfWeekSchema } from "./types.js";
|
|
109
42
|
|
|
110
|
-
// ============================================================================
|
|
111
|
-
// Date/time utilities
|
|
112
|
-
// ============================================================================
|
|
113
|
-
|
|
114
|
-
export { dateTimeToDate } from "./datetime.utils.js";
|
|
115
|
-
|
|
116
|
-
// ============================================================================
|
|
117
|
-
// Errors
|
|
118
|
-
// ============================================================================
|
|
119
|
-
|
|
120
|
-
export { ORSchedulingError } from "./errors.js";
|
|
121
|
-
|
|
122
43
|
// ============================================================================
|
|
123
44
|
// Solver client
|
|
124
45
|
// ============================================================================
|
|
@@ -149,7 +70,6 @@ export type {
|
|
|
149
70
|
CompilationResult,
|
|
150
71
|
CompilationRule,
|
|
151
72
|
RuleValidationContext,
|
|
152
|
-
CostContext,
|
|
153
73
|
CostEntry,
|
|
154
74
|
CostContribution,
|
|
155
75
|
} from "./cpsat/model-builder.js";
|
|
@@ -162,15 +82,22 @@ export { parseSolverResponse, resolveAssignments } from "./cpsat/response.js";
|
|
|
162
82
|
|
|
163
83
|
export type { ShiftAssignment, ResolvedShiftAssignment, SolverResult } from "./cpsat/response.js";
|
|
164
84
|
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Cost calculation
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
export { calculateScheduleCost, COST_CATEGORY } from "./cpsat/cost.js";
|
|
90
|
+
|
|
91
|
+
export type { CostBreakdown, MemberCostDetail, CostCalculationConfig } from "./cpsat/cost.js";
|
|
92
|
+
|
|
165
93
|
// ============================================================================
|
|
166
94
|
// Rules (registry types)
|
|
167
95
|
// ============================================================================
|
|
168
96
|
|
|
169
97
|
export type {
|
|
170
|
-
CpsatRuleRegistry,
|
|
171
|
-
CpsatRuleName,
|
|
172
98
|
CpsatRuleConfigEntry,
|
|
173
99
|
CpsatRuleFactories,
|
|
100
|
+
CreateCpsatRuleFunction,
|
|
174
101
|
} from "./cpsat/rules/rules.types.js";
|
|
175
102
|
|
|
176
103
|
export type { RecurringPeriod } from "./cpsat/rules/scope.types.js";
|
|
@@ -186,9 +113,7 @@ export type {
|
|
|
186
113
|
SalariedPay,
|
|
187
114
|
SchedulingMember,
|
|
188
115
|
ShiftPattern,
|
|
189
|
-
CoverageRequirement,
|
|
190
116
|
Priority,
|
|
191
|
-
ModelBuilderOptions,
|
|
192
117
|
} from "./cpsat/types.js";
|
|
193
118
|
|
|
194
119
|
// ============================================================================
|
|
@@ -201,35 +126,32 @@ export { OBJECTIVE_WEIGHTS } from "./cpsat/utils.js";
|
|
|
201
126
|
// Validation
|
|
202
127
|
// ============================================================================
|
|
203
128
|
|
|
204
|
-
export type { ValidationReporter } from "./cpsat/validation-reporter.js";
|
|
205
|
-
|
|
206
129
|
export type {
|
|
130
|
+
ValidationGroup,
|
|
207
131
|
ScheduleValidation,
|
|
208
132
|
ScheduleError,
|
|
209
|
-
ScheduleViolation,
|
|
210
|
-
SchedulePassed,
|
|
211
133
|
CoverageError,
|
|
212
|
-
CoverageViolation,
|
|
213
|
-
CoveragePassed,
|
|
214
134
|
RuleError,
|
|
135
|
+
SolverError,
|
|
136
|
+
ScheduleViolation,
|
|
137
|
+
CoverageViolation,
|
|
215
138
|
RuleViolation,
|
|
139
|
+
SchedulePassed,
|
|
140
|
+
CoveragePassed,
|
|
216
141
|
RulePassed,
|
|
217
|
-
SolverError,
|
|
218
|
-
ValidationContext,
|
|
219
142
|
ValidationSummary,
|
|
220
|
-
GroupKey,
|
|
221
143
|
} from "./cpsat/validation.types.js";
|
|
222
144
|
|
|
223
145
|
export { summarizeValidation } from "./cpsat/validation-reporter.js";
|
|
224
146
|
|
|
225
|
-
export { groupKey } from "./cpsat/validation.types.js";
|
|
226
|
-
|
|
227
147
|
// ============================================================================
|
|
228
|
-
// Schedule
|
|
148
|
+
// Schedule API
|
|
229
149
|
// ============================================================================
|
|
230
150
|
|
|
231
151
|
export {
|
|
232
|
-
|
|
152
|
+
schedule,
|
|
153
|
+
partialSchedule,
|
|
154
|
+
Schedule,
|
|
233
155
|
t,
|
|
234
156
|
time,
|
|
235
157
|
cover,
|
|
@@ -238,14 +160,18 @@ export {
|
|
|
238
160
|
maxHoursPerWeek,
|
|
239
161
|
minHoursPerDay,
|
|
240
162
|
minHoursPerWeek,
|
|
163
|
+
maxDaysPerWeek,
|
|
164
|
+
minDaysPerWeek,
|
|
241
165
|
maxShiftsPerDay,
|
|
242
166
|
maxConsecutiveDays,
|
|
243
167
|
minConsecutiveDays,
|
|
244
168
|
minRestBetweenShifts,
|
|
169
|
+
mustAssign,
|
|
245
170
|
preference,
|
|
246
171
|
preferLocation,
|
|
247
172
|
timeOff,
|
|
248
173
|
assignTogether,
|
|
174
|
+
defineRule,
|
|
249
175
|
minimizeCost,
|
|
250
176
|
dayMultiplier,
|
|
251
177
|
daySurcharge,
|
|
@@ -257,19 +183,21 @@ export {
|
|
|
257
183
|
tieredOvertimeMultiplier,
|
|
258
184
|
weekdays,
|
|
259
185
|
weekend,
|
|
260
|
-
} from "./schedule.js";
|
|
186
|
+
} from "./schedule/index.js";
|
|
261
187
|
|
|
262
188
|
export type {
|
|
263
189
|
CoverageEntry,
|
|
264
190
|
CoverageOptions,
|
|
265
191
|
CoverageVariant,
|
|
266
192
|
RuleEntry,
|
|
193
|
+
RuleResolveContext,
|
|
267
194
|
RuleOptions,
|
|
268
195
|
EntityOnlyRuleOptions,
|
|
269
196
|
TimeOffOptions,
|
|
270
197
|
AssignTogetherOptions,
|
|
271
198
|
CostRuleOptions,
|
|
272
|
-
RuntimeArgs,
|
|
273
|
-
ScheduleDefinition,
|
|
274
199
|
ScheduleConfig,
|
|
275
|
-
|
|
200
|
+
SolveResult,
|
|
201
|
+
SolveStatus,
|
|
202
|
+
SolveOptions,
|
|
203
|
+
} from "./schedule/index.js";
|