dabke 0.78.2 → 0.80.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +68 -31
  3. package/dist/client.types.d.ts +58 -0
  4. package/dist/client.types.d.ts.map +1 -1
  5. package/dist/client.types.js.map +1 -1
  6. package/dist/cpsat/model-builder.d.ts +13 -2
  7. package/dist/cpsat/model-builder.d.ts.map +1 -1
  8. package/dist/cpsat/model-builder.js.map +1 -1
  9. package/dist/cpsat/response.d.ts +12 -3
  10. package/dist/cpsat/response.d.ts.map +1 -1
  11. package/dist/cpsat/response.js.map +1 -1
  12. package/dist/cpsat/rules/assign-together.d.ts +7 -0
  13. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  14. package/dist/cpsat/rules/assign-together.js +1 -0
  15. package/dist/cpsat/rules/assign-together.js.map +1 -1
  16. package/dist/cpsat/rules/employee-assignment-priority.d.ts +11 -37
  17. package/dist/cpsat/rules/employee-assignment-priority.d.ts.map +1 -1
  18. package/dist/cpsat/rules/employee-assignment-priority.js +12 -104
  19. package/dist/cpsat/rules/employee-assignment-priority.js.map +1 -1
  20. package/dist/cpsat/rules/location-preference.d.ts +12 -10
  21. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  22. package/dist/cpsat/rules/location-preference.js +16 -14
  23. package/dist/cpsat/rules/location-preference.js.map +1 -1
  24. package/dist/cpsat/rules/max-consecutive-days.d.ts +12 -13
  25. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  26. package/dist/cpsat/rules/max-consecutive-days.js +11 -12
  27. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  28. package/dist/cpsat/rules/max-hours-day.d.ts +12 -28
  29. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  30. package/dist/cpsat/rules/max-hours-day.js +12 -95
  31. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  32. package/dist/cpsat/rules/max-hours-week.d.ts +14 -34
  33. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  34. package/dist/cpsat/rules/max-hours-week.js +12 -103
  35. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  36. package/dist/cpsat/rules/max-shifts-day.d.ts +14 -39
  37. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  38. package/dist/cpsat/rules/max-shifts-day.js +14 -106
  39. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  40. package/dist/cpsat/rules/min-consecutive-days.d.ts +12 -13
  41. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  42. package/dist/cpsat/rules/min-consecutive-days.js +11 -12
  43. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  44. package/dist/cpsat/rules/min-hours-day.d.ts +12 -13
  45. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  46. package/dist/cpsat/rules/min-hours-day.js +11 -12
  47. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  48. package/dist/cpsat/rules/min-hours-week.d.ts +13 -13
  49. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  50. package/dist/cpsat/rules/min-hours-week.js +10 -14
  51. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  52. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +13 -14
  53. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  54. package/dist/cpsat/rules/min-rest-between-shifts.js +11 -12
  55. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  56. package/dist/cpsat/rules/resolver.d.ts +2 -2
  57. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  58. package/dist/cpsat/rules/resolver.js +55 -30
  59. package/dist/cpsat/rules/resolver.js.map +1 -1
  60. package/dist/cpsat/rules/scope.types.d.ts +267 -0
  61. package/dist/cpsat/rules/scope.types.d.ts.map +1 -0
  62. package/dist/cpsat/rules/scope.types.js +325 -0
  63. package/dist/cpsat/rules/scope.types.js.map +1 -0
  64. package/dist/cpsat/rules/time-off.d.ts +21 -25
  65. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  66. package/dist/cpsat/rules/time-off.js +20 -110
  67. package/dist/cpsat/rules/time-off.js.map +1 -1
  68. package/dist/cpsat/semantic-time.d.ts +2 -0
  69. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  70. package/dist/cpsat/semantic-time.js +2 -4
  71. package/dist/cpsat/semantic-time.js.map +1 -1
  72. package/dist/cpsat/types.d.ts +22 -6
  73. package/dist/cpsat/types.d.ts.map +1 -1
  74. package/dist/cpsat/utils.d.ts +1 -1
  75. package/dist/cpsat/utils.js +1 -1
  76. package/dist/cpsat/validation-reporter.js +1 -1
  77. package/dist/cpsat/validation-reporter.js.map +1 -1
  78. package/dist/datetime.utils.d.ts +14 -14
  79. package/dist/datetime.utils.d.ts.map +1 -1
  80. package/dist/datetime.utils.js +26 -27
  81. package/dist/datetime.utils.js.map +1 -1
  82. package/dist/index.d.ts +4 -3
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/llms.d.ts +1 -1
  87. package/dist/llms.d.ts.map +1 -1
  88. package/dist/llms.js +1 -1
  89. package/dist/llms.js.map +1 -1
  90. package/dist/testing/index.d.ts +1 -1
  91. package/dist/testing/index.js +1 -1
  92. package/dist/testing/solver-container.js +3 -1
  93. package/dist/testing/solver-container.js.map +1 -1
  94. package/dist/types.d.ts +18 -20
  95. package/dist/types.d.ts.map +1 -1
  96. package/llms.txt +516 -263
  97. package/package.json +25 -25
  98. package/src/client.types.ts +58 -0
  99. package/src/cpsat/model-builder.ts +19 -7
  100. package/src/cpsat/response.ts +12 -3
  101. package/src/cpsat/rules/assign-together.ts +7 -0
  102. package/src/cpsat/rules/employee-assignment-priority.ts +28 -128
  103. package/src/cpsat/rules/location-preference.ts +24 -17
  104. package/src/cpsat/rules/max-consecutive-days.ts +19 -15
  105. package/src/cpsat/rules/max-hours-day.ts +29 -119
  106. package/src/cpsat/rules/max-hours-week.ts +42 -135
  107. package/src/cpsat/rules/max-shifts-day.ts +31 -130
  108. package/src/cpsat/rules/min-consecutive-days.ts +19 -15
  109. package/src/cpsat/rules/min-hours-day.ts +19 -15
  110. package/src/cpsat/rules/min-hours-week.ts +28 -26
  111. package/src/cpsat/rules/min-rest-between-shifts.ts +21 -17
  112. package/src/cpsat/rules/resolver.ts +66 -45
  113. package/src/cpsat/rules/scope.types.ts +534 -0
  114. package/src/cpsat/rules/time-off.ts +48 -145
  115. package/src/cpsat/semantic-time.ts +10 -8
  116. package/src/cpsat/types.ts +22 -6
  117. package/src/cpsat/utils.ts +1 -1
  118. package/src/cpsat/validation-reporter.ts +1 -1
  119. package/src/datetime.utils.ts +27 -29
  120. package/src/index.ts +11 -7
  121. package/src/llms.ts +1 -1
  122. package/src/testing/index.ts +1 -1
  123. package/src/testing/solver-container.ts +3 -3
  124. package/src/types.ts +27 -31
  125. package/dist/cpsat/rules/scoping.d.ts +0 -129
  126. package/dist/cpsat/rules/scoping.d.ts.map +0 -1
  127. package/dist/cpsat/rules/scoping.js +0 -190
  128. package/dist/cpsat/rules/scoping.js.map +0 -1
  129. package/src/cpsat/rules/scoping.ts +0 -340
package/llms.txt CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Scheduling library powered by constraint programming (CP-SAT).
6
6
 
7
- Define teams, shifts, coverage, and rules dabke turns them
7
+ Define teams, shifts, coverage, and rules. dabke turns them
8
8
  into an optimized schedule.
9
9
 
10
10
  ## Core Concepts
@@ -109,6 +109,47 @@ Used for checking overlaps and scheduling constraints.
109
109
  - `start: DateTimeWithUtcOffset | DateTimeWithTimeZone`
110
110
  - `end: DateTimeWithUtcOffset | DateTimeWithTimeZone`
111
111
 
112
+ ### SchedulingPeriod
113
+ Defines a scheduling period as a date range with optional filters.
114
+
115
+ The `dateRange` specifies the overall scheduling window. Use `daysOfWeek`
116
+ and/or `dates` to narrow which days within the range are included.
117
+ Filters compose: a day must pass all specified filters to be included.
118
+
119
+ **Example:**
120
+ All days in a week
121
+ ```typescript
122
+ const period: SchedulingPeriod = {
123
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
124
+ };
125
+ ```
126
+
127
+ **Example:**
128
+ Only specific days of the week (restaurant closed Mon/Tue)
129
+ ```typescript
130
+ const period: SchedulingPeriod = {
131
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
132
+ daysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
133
+ };
134
+ ```
135
+
136
+ **Example:**
137
+ Only specific dates within the range
138
+ ```typescript
139
+ const period: SchedulingPeriod = {
140
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
141
+ dates: ['2025-02-05', '2025-02-07'],
142
+ };
143
+ ```
144
+
145
+ **Properties:**
146
+ - `dateRange: { start: string; end: string; }` - The overall scheduling window (start and end are inclusive).
147
+ Dates should be in YYYY-MM-DD format.
148
+ - `daysOfWeek?: DayOfWeek[] | undefined` - Include only these days of the week.
149
+ If omitted, all days of the week are included.
150
+ - `dates?: string[] | undefined` - Include only these specific dates (YYYY-MM-DD) within the range.
151
+ If omitted, all dates in the range are included (subject to daysOfWeek filter).
152
+
112
153
  ### SolverClient
113
154
  **Properties:**
114
155
  - `solve: (request: SolverRequest, options?: { signal?: AbortSignal; }) => Promise<SolverResponse>`
@@ -132,10 +173,10 @@ daysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
132
173
  ```
133
174
 
134
175
  **Properties:**
135
- - `employees: SchedulingEmployee[]`
136
- - `shiftPatterns: ShiftPattern[]`
137
- - `schedulingPeriod: { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }` - Defines when scheduling should occur. Can specify either a date range
138
- (with optional day-of-week filtering) or a list of specific dates.
176
+ - `employees: SchedulingEmployee[]` - Team members available for scheduling.
177
+ - `shiftPatterns: ShiftPattern[]` - Available shift patterns (time slots) that employees can be assigned to.
178
+ - `schedulingPeriod: SchedulingPeriod` - Defines when scheduling should occur as a date range with optional
179
+ `daysOfWeek` and `dates` filters that compose to narrow which days are included.
139
180
  - `coverage: CoverageRequirement[]`
140
181
  - `rules?: CompilationRule[] | undefined` - Pre-compiled rules; use this for custom rules that are not part of the registry.
141
182
  - `ruleConfigs?: CpsatRuleConfigEntry[] | undefined` - Named rule configurations that will be compiled using the provided rule factories.
@@ -149,9 +190,15 @@ daysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
149
190
  - `canSolve: boolean`
150
191
 
151
192
  ### CompilationRule
193
+ A rule that adds constraints or objectives to the solver model.
194
+
195
+ Rules implement `compile` to emit solver constraints during model building,
196
+ and optionally `validate` to check the solution after solving.
197
+ Use the `create*Rule` functions to create built-in rules.
198
+
152
199
  **Properties:**
153
- - `compile: (builder: ModelBuilder) => void`
154
- - `validate?: ((assignments: ResolvedShiftAssignment[], reporter: ValidationReporter, context: RuleValidationContext) => void) | undefined`
200
+ - `compile: (builder: ModelBuilder) => void` - Emit constraints and objectives into the model builder.
201
+ - `validate?: ((assignments: ResolvedShiftAssignment[], reporter: ValidationReporter, context: RuleValidationContext) => void) | undefined` - Validate the solved schedule and report violations.
155
202
 
156
203
  ### RuleValidationContext
157
204
  Context provided to rules during post-solve validation.
@@ -162,52 +209,62 @@ Context provided to rules during post-solve validation.
162
209
  - `shiftPatterns: ShiftPattern[]`
163
210
 
164
211
  ### ShiftAssignment
165
- A shift assignment extracted from the solver response.
212
+ A raw assignment from the solver: which employee works which shift on which day.
166
213
 
167
214
  **Properties:**
168
- - `employeeId: string`
169
- - `shiftPatternId: string`
170
- - `day: string`
215
+ - `employeeId: string` - The assigned employee's ID.
216
+ - `shiftPatternId: string` - The shift pattern this employee is assigned to.
217
+ - `day: string` - The date of the assignment (YYYY-MM-DD).
171
218
 
172
219
  ### ResolvedShiftAssignment
173
220
  A shift assignment with resolved times.
174
221
 
175
222
  **Properties:**
176
- - `employeeId: string`
177
- - `day: string`
178
- - `startTime: TimeOfDay`
179
- - `endTime: TimeOfDay`
223
+ - `employeeId: string` - The assigned employee's ID.
224
+ - `day: string` - The date of the assignment (YYYY-MM-DD).
225
+ - `startTime: TimeOfDay` - When the shift starts.
226
+ - `endTime: TimeOfDay` - When the shift ends.
180
227
 
181
228
  ### SolverResult
182
229
  Parsed solver result with assignments and metadata.
183
230
 
184
231
  **Properties:**
185
- - `status: "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "TIMEOUT" | "ERROR"`
186
- - `assignments: ShiftAssignment[]`
187
- - `statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined`
188
- - `error?: string | undefined`
232
+ - `status: "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "TIMEOUT" | "ERROR"` - The solver outcome: OPTIMAL, FEASIBLE, INFEASIBLE, TIMEOUT, or ERROR.
233
+ - `assignments: ShiftAssignment[]` - The shift assignments extracted from the solution.
234
+ - `statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined` - Solver performance statistics (branches, conflicts, solve time).
235
+ - `error?: string | undefined` - Error message if the solver returned an error status.
236
+
237
+ ### RecurringPeriod
238
+ Recurring calendar period for time scoping.
239
+
240
+ **Properties:**
241
+ - `name: string`
242
+ - `startMonth: number`
243
+ - `startDay: number`
244
+ - `endMonth: number`
245
+ - `endDay: number`
189
246
 
190
247
  ### CpsatRuleRegistry
191
248
  **Properties:**
192
249
  - `"assign-together": { groupEmployeeIds: [string, string, ...string[]]; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; }`
193
- - `"employee-assignment-priority": { preference: "high" | "low"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
194
- - `"location-preference": { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
195
- - `"max-consecutive-days": { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
196
- - `"max-hours-day": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
197
- - `"max-hours-week": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
198
- - `"max-shifts-day": { shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
199
- - `"min-consecutive-days": { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
200
- - `"min-hours-day": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
201
- - `"min-hours-week": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
202
- - `"min-rest-between-shifts": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
203
- - `"time-off": { priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
250
+ - `"employee-assignment-priority": ({ preference: "high" | "low"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
251
+ - `"location-preference": { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
252
+ - `"max-consecutive-days": { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
253
+ - `"max-hours-day": ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
254
+ - `"max-hours-week": ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
255
+ - `"max-shifts-day": ({ shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
256
+ - `"min-consecutive-days": { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
257
+ - `"min-hours-day": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
258
+ - `"min-hours-week": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">`
259
+ - `"min-rest-between-shifts": { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
260
+ - `"time-off": ({ priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & RequiredTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
204
261
 
205
262
  ### SemanticTimeDef
206
263
  Base definition for a semantic time period.
207
264
 
208
265
  **Properties:**
209
- - `startTime: TimeOfDay`
210
- - `endTime: TimeOfDay`
266
+ - `startTime: TimeOfDay` - When this time period starts.
267
+ - `endTime: TimeOfDay` - When this time period ends.
211
268
 
212
269
  ### SemanticTimeVariant
213
270
  Variant of a semantic time that applies to specific days or dates.
@@ -227,13 +284,18 @@ Accepts both semantic references and concrete one-off requirements.
227
284
  for the given days in the scheduling horizon.
228
285
 
229
286
  ### SchedulingEmployee
287
+ A team member available for scheduling.
288
+
289
+ Employees are assigned to shift patterns by the solver based on
290
+ coverage requirements, rules, and constraints.
291
+
230
292
  **Properties:**
231
- - `id: string`
232
- - `roleIds: string[]`
233
- - `skillIds?: string[] | undefined`
293
+ - `id: string` - Unique identifier for this employee. Must not contain colons.
294
+ - `roleIds: string[]` - Roles this employee can fill (e.g. "waiter", "chef").
295
+ - `skillIds?: string[] | undefined` - Skills this employee has (e.g. "senior", "trainer").
234
296
 
235
297
  ### ShiftPattern
236
- A shift pattern defines WHEN people can work the time slots available for assignment.
298
+ A shift pattern defines WHEN people can work: the time slots available for assignment.
237
299
 
238
300
  Shift patterns are templates that repeat across all scheduling days. The solver assigns
239
301
  team members to these patterns based on coverage requirements and constraints.
@@ -277,8 +339,8 @@ earlier than floor staff).
277
339
  ```
278
340
  - `locationId?: string | undefined` - Physical location where this shift takes place.
279
341
  Used for multi-location scheduling and location-based constraints.
280
- - `startTime: TimeOfDay` - When the shift starts (e.g., { hours: 9, minutes: 0 } for 9:00 AM)
281
- - `endTime: TimeOfDay` - When the shift ends (e.g., { hours: 17, minutes: 30 } for 5:30 PM)
342
+ - `startTime: TimeOfDay` - When the shift starts (e.g., `{ hours: 9, minutes: 0 }` for 9:00 AM)
343
+ - `endTime: TimeOfDay` - When the shift ends (e.g., `{ hours: 17, minutes: 30 }` for 5:30 PM)
282
344
 
283
345
  ### TimeInterval
284
346
  **Properties:**
@@ -287,9 +349,11 @@ Used for multi-location scheduling and location-based constraints.
287
349
  - `endTime: TimeOfDay`
288
350
 
289
351
  ### ModelBuilderOptions
352
+ Optional settings for the model builder.
353
+
290
354
  **Properties:**
291
- - `weekStartsOn?: DayOfWeek | undefined`
292
- - `solverOptions?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined`
355
+ - `weekStartsOn?: DayOfWeek | undefined` - Which day starts the week for weekly rules. Defaults to "monday".
356
+ - `solverOptions?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined` - Solver-level options (time limit, solution limit).
293
357
  - `coverageBucketMinutes?: number | undefined` - Bucket size used when translating coverage requirements into time-indexed constraints.
294
358
  Smaller buckets are more accurate but increase the number of constraints.
295
359
  - `fairDistribution?: boolean | undefined` - Whether to enable fair distribution of shifts across team members.
@@ -299,7 +363,7 @@ any single person works, ensuring work is distributed evenly. Each person
299
363
  works between floor(total/n) and ceil(total/n) shifts.
300
364
 
301
365
  Disable this if you want other rules (like employee-assignment-priority)
302
- to have full control over shift distribution.
366
+ to have full control over shift distribution. Defaults to true.
303
367
 
304
368
  ### ValidationReporter
305
369
  **Properties:**
@@ -467,6 +531,25 @@ Combined validation result for coverage requirements.
467
531
  - `skills: SkillValidationResult`
468
532
  - `errors: string[]` - Human-readable error messages
469
533
 
534
+ ### EntityScopeInput
535
+ Input shape accepted by {@link parseEntityScope}.
536
+
537
+ **Properties:**
538
+ - `employeeIds?: string[] | undefined`
539
+ - `roleIds?: string[] | undefined`
540
+ - `skillIds?: string[] | undefined`
541
+ - `unknown: any`
542
+
543
+ ### TimeScopeInput
544
+ Input shape accepted by {@link parseTimeScope}.
545
+
546
+ **Properties:**
547
+ - `dateRange?: { start: string; end: string; } | undefined`
548
+ - `specificDates?: string[] | undefined`
549
+ - `dayOfWeek?: DayOfWeek[] | undefined`
550
+ - `recurringPeriods?: RecurringPeriod[] | undefined`
551
+ - `unknown: any`
552
+
470
553
  ## Type Aliases
471
554
 
472
555
  ### DayOfWeek
@@ -486,85 +569,99 @@ DateTimeWithUtcOffset | DateTimeWithTimeZone
486
569
  ```
487
570
 
488
571
 
489
- ### SchedulingPeriod
490
- Defines a scheduling period either as a date range or specific dates.
491
-
492
- Use this to specify when scheduling should occur. This is more expressive
493
- than a simple list of days because it can filter by day-of-week.
494
-
495
- **Example:**
496
- Date range with day-of-week filtering (restaurant closed Mon/Tue)
497
- ```typescript
498
- const period: SchedulingPeriod = {
499
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
500
- daysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
501
- };
502
- ```
503
-
504
- **Example:**
505
- Date range for all days
506
- ```typescript
507
- const period: SchedulingPeriod = {
508
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
509
- };
510
- ```
511
-
512
- **Example:**
513
- Specific dates (non-contiguous or custom selection)
514
- ```typescript
515
- const period: SchedulingPeriod = {
516
- specificDates: ['2025-02-05', '2025-02-07', '2025-02-10'],
517
- };
518
- ```
519
-
520
- ```typescript
521
- { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }
522
- ```
572
+ ### SolverRequest
573
+ The full request payload sent to the CP-SAT solver service.
523
574
 
575
+ - `variables` (required): all decision variables
576
+ - `constraints` (required): all constraints
577
+ - `objective` (optional): optimization objective
578
+ - `timeoutSeconds` (optional): solver time limit
524
579
 
525
- ### SolverRequest
526
580
  ```typescript
527
581
  { variables: ({ type: "bool"; name: string; } | { type: "int"; name: string; min: number; max: number; } | { type: "interval"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; })[]; constraints: ({ type: "linear"; terms: { var: string; coeff: number; }[]; op: "<=" | ">=" | "=="; rhs: number; } | { type: "soft_linear"; terms: { var: string; coeff: number; }[]; op: "<=" | ">="; rhs: number; penalty: number; id?: string | undefined; } | { type: "exactly_one"; vars: string[]; } | { type: "at_most_one"; vars: string[]; } | { type: "implication"; if: string; then: string; } | { type: "bool_or"; vars: string[]; } | { type: "bool_and"; vars: string[]; } | { type: "no_overlap"; intervals: string[]; })[]; objective?: { sense: "minimize" | "maximize"; terms: { var: string; coeff: number; }[]; } | undefined; options?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined; }
528
582
  ```
529
583
 
530
584
 
531
585
  ### SolverResponse
586
+ The response payload returned by the CP-SAT solver service.
587
+
588
+ - `status` (required): solve outcome (see {@link SolverStatus})
589
+ - `values` (optional): variable assignments when a solution is found
590
+ - `statistics` (optional): solve time, conflicts, branches
591
+ - `softViolations` (optional): which soft constraints were violated
592
+ - `error` (optional): error message on failure
593
+ - `solutionInfo` (optional): solver diagnostic info
594
+
532
595
  ```typescript
533
596
  { status: "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "TIMEOUT" | "ERROR"; values?: Record<string, number> | undefined; statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined; error?: string | undefined; solutionInfo?: string | undefined; softViolations?: { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }[] | undefined; }
534
597
  ```
535
598
 
536
599
 
537
600
  ### SolverVariable
601
+ A decision variable in the CP-SAT model.
602
+
603
+ - `name` (required): unique variable identifier
604
+ - `lb` (required): lower bound
605
+ - `ub` (required): upper bound
606
+ - `isBoolean` (optional): whether this is a boolean variable
607
+ - `isInterval` (optional): whether this is an interval variable
608
+ - `start`, `end`, `size`, `presenceVar` (optional): interval variable fields
609
+
538
610
  ```typescript
539
611
  { type: "bool"; name: string; } | { type: "int"; name: string; min: number; max: number; } | { type: "interval"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; }
540
612
  ```
541
613
 
542
614
 
543
615
  ### SolverConstraint
616
+ A constraint in the CP-SAT model.
617
+
618
+ - `name` (required): constraint identifier
619
+ - `type` (required): constraint kind (e.g. "linear", "bool_and", "no_overlap")
620
+ - Additional fields vary by constraint type
621
+
544
622
  ```typescript
545
623
  { type: "linear"; terms: { var: string; coeff: number; }[]; op: "<=" | ">=" | "=="; rhs: number; } | { type: "soft_linear"; terms: { var: string; coeff: number; }[]; op: "<=" | ">="; rhs: number; penalty: number; id?: string | undefined; } | { type: "exactly_one"; vars: string[]; } | { type: "at_most_one"; vars: string[]; } | { type: "implication"; if: string; then: string; } | { type: "bool_or"; vars: string[]; } | { type: "bool_and"; vars: string[]; } | { type: "no_overlap"; intervals: string[]; }
546
624
  ```
547
625
 
548
626
 
549
627
  ### SolverTerm
628
+ A single linear term in a constraint or objective.
629
+
630
+ - `var` (required): variable name
631
+ - `coeff` (required): integer coefficient
632
+
550
633
  ```typescript
551
634
  { var: string; coeff: number; }
552
635
  ```
553
636
 
554
637
 
555
638
  ### SolverObjective
639
+ An optimization objective for the solver.
640
+
641
+ - `terms` (required): linear terms to minimize/maximize
642
+ - `minimize` (required): whether to minimize (true) or maximize (false)
643
+
556
644
  ```typescript
557
645
  { sense: "minimize" | "maximize"; terms: { var: string; coeff: number; }[]; }
558
646
  ```
559
647
 
560
648
 
561
649
  ### SolverStatus
650
+ Solver outcome status.
651
+
652
+ One of `"OPTIMAL"`, `"FEASIBLE"`, `"INFEASIBLE"`, `"TIMEOUT"`, or `"ERROR"`.
653
+
562
654
  ```typescript
563
655
  "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "TIMEOUT" | "ERROR"
564
656
  ```
565
657
 
566
658
 
567
659
  ### SoftConstraintViolation
660
+ A soft constraint violation reported by the solver.
661
+
662
+ - `constraintId` (required): which soft constraint was violated
663
+ - `violationAmount` (required): magnitude of the violation
664
+
568
665
  ```typescript
569
666
  { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }
570
667
  ```
@@ -576,75 +673,214 @@ specificDates: ['2025-02-05', '2025-02-07', '2025-02-10'],
576
673
  ```
577
674
 
578
675
 
676
+ ### EntityScopeType
677
+ Entity scope type for a subset of entity keys (at most one).
678
+
679
+ ```typescript
680
+ ExclusiveOne<ActiveEntityFields, InactiveEntityFields, K> | MergeValues<Pick<InactiveEntityFields, K>>
681
+ ```
682
+
683
+
684
+ ### OptionalTimeScopeType
685
+ Time scope type for a subset of time keys (at most one, optional).
686
+
687
+ ```typescript
688
+ ExclusiveOne<ActiveTimeFields, InactiveTimeFields, K> | MergeValues<Pick<InactiveTimeFields, K>>
689
+ ```
690
+
691
+
692
+ ### RequiredTimeScopeType
693
+ Time scope type for a subset of time keys (exactly one, required).
694
+
695
+ ```typescript
696
+ { [K in K]: ActiveTimeFields[K] & MergeValues<Pick<InactiveTimeFields, Exclude<K, K>>>; }[K]
697
+ ```
698
+
699
+
700
+ ### ParsedEntityScope
701
+ Parsed entity scope from a flat config.
702
+ Used internally by scope resolution functions.
703
+
704
+ ```typescript
705
+ { type: "global"; } | { type: "employees"; employeeIds: string[]; } | { type: "roles"; roleIds: string[]; } | { type: "skills"; skillIds: string[]; }
706
+ ```
707
+
708
+
709
+ ### ParsedTimeScope
710
+ Parsed time scope from a flat config.
711
+ Used internally by scope resolution functions.
712
+
713
+ ```typescript
714
+ { type: "none"; } | { type: "dateRange"; start: string; end: string; } | { type: "specificDates"; dates: string[]; } | { type: "dayOfWeek"; days: DayOfWeek[]; } | { type: "recurring"; periods: RecurringPeriod[]; }
715
+ ```
716
+
717
+
579
718
  ### AssignTogetherConfig
719
+ Configuration for {@link createAssignTogetherRule}.
720
+
721
+ - `groupEmployeeIds` (required): employee IDs to assign together (at least two, must be unique)
722
+ - `priority` (required): how strictly the solver enforces this rule
723
+
580
724
  ```typescript
581
725
  { groupEmployeeIds: [string, string, ...string[]]; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; }
582
726
  ```
583
727
 
584
728
 
585
729
  ### EmployeeAssignmentPriorityConfig
730
+ Configuration for {@link createEmployeeAssignmentPriorityRule}.
731
+
732
+ - `preference` (required): `"high"` to prefer assigning or `"low"` to avoid assigning
733
+
734
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
735
+ Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
736
+
586
737
  ```typescript
587
- { preference: "high" | "low"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }
738
+ ({ preference: "high" | "low"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">
588
739
  ```
589
740
 
590
741
 
591
742
  ### LocationPreferenceConfig
743
+ Configuration for {@link createLocationPreferenceRule}.
744
+
745
+ - `locationId` (required): the location ID to prefer for matching shift patterns
746
+ - `priority` (required): how strongly to prefer this location
747
+
748
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
749
+
592
750
  ```typescript
593
- { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
751
+ { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">
594
752
  ```
595
753
 
596
754
 
597
755
  ### MaxConsecutiveDaysConfig
756
+ Configuration for {@link createMaxConsecutiveDaysRule}.
757
+
758
+ - `days` (required): maximum consecutive days allowed
759
+ - `priority` (required): how strictly the solver enforces this rule
760
+
761
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
762
+
598
763
  ```typescript
599
- { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
764
+ { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">
600
765
  ```
601
766
 
602
767
 
603
768
  ### MaxHoursDayConfig
769
+ Configuration for {@link createMaxHoursDayRule}.
770
+
771
+ - `hours` (required): maximum hours allowed per day
772
+ - `priority` (required): how strictly the solver enforces this rule
773
+
774
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
775
+ Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
776
+
604
777
  ```typescript
605
- { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }
778
+ ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">
606
779
  ```
607
780
 
608
781
 
609
782
  ### MaxHoursWeekConfig
783
+ Configuration for {@link createMaxHoursWeekRule}.
784
+
785
+ - `hours` (required): maximum hours allowed per scheduling week
786
+ - `priority` (required): how strictly the solver enforces this rule
787
+ - `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}
788
+
789
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
790
+ Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
791
+
610
792
  ```typescript
611
- { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }
793
+ ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">
612
794
  ```
613
795
 
614
796
 
615
797
  ### MaxShiftsDayConfig
798
+ Configuration for {@link createMaxShiftsDayRule}.
799
+
800
+ - `shifts` (required): maximum number of shifts per day (at least 1)
801
+ - `priority` (required): how strictly the solver enforces this rule
802
+
803
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
804
+ Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
805
+
616
806
  ```typescript
617
- { shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }
807
+ ({ shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">
618
808
  ```
619
809
 
620
810
 
621
811
  ### MinConsecutiveDaysConfig
812
+ Configuration for {@link createMinConsecutiveDaysRule}.
813
+
814
+ - `days` (required): minimum consecutive days required once a person starts working
815
+ - `priority` (required): how strictly the solver enforces this rule
816
+
817
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
818
+
622
819
  ```typescript
623
- { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
820
+ { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">
624
821
  ```
625
822
 
626
823
 
627
824
  ### MinHoursDayConfig
825
+ Configuration for {@link createMinHoursDayRule}.
826
+
827
+ - `hours` (required): minimum hours required per day when scheduled
828
+ - `priority` (required): how strictly the solver enforces this rule
829
+
830
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
831
+
628
832
  ```typescript
629
- { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
833
+ { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">
630
834
  ```
631
835
 
632
836
 
633
837
  ### MinHoursWeekConfig
838
+ Configuration for {@link createMinHoursWeekRule}.
839
+
840
+ - `hours` (required): minimum hours required per scheduling week
841
+ - `priority` (required): how strictly the solver enforces this rule
842
+ - `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}
843
+
844
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
845
+
634
846
  ```typescript
635
- { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
847
+ { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">
636
848
  ```
637
849
 
638
850
 
639
851
  ### MinRestBetweenShiftsConfig
852
+ Configuration for {@link createMinRestBetweenShiftsRule}.
853
+
854
+ - `hours` (required): minimum rest hours required between consecutive shifts
855
+ - `priority` (required): how strictly the solver enforces this rule
856
+
857
+ Entity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`
858
+
640
859
  ```typescript
641
- { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
860
+ { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">
642
861
  ```
643
862
 
644
863
 
645
864
  ### TimeOffConfig
865
+ Configuration for {@link createTimeOffRule}.
866
+
867
+ - `priority` (required): how strictly the solver enforces this rule
868
+ - `startTime` (optional): start of the time-off window within each day; must be paired with `endTime`
869
+ - `endTime` (optional): end of the time-off window within each day; must be paired with `startTime`
870
+
871
+ Entity scoping (at most one):
872
+ - `employeeIds`: restrict to specific employees
873
+ - `roleIds`: restrict to employees with matching roles
874
+ - `skillIds`: restrict to employees with matching skills
875
+
876
+ Time scoping (exactly one required):
877
+ - `dateRange`: contiguous date range
878
+ - `specificDates`: specific dates
879
+ - `dayOfWeek`: days of the week
880
+ - `recurringPeriods`: recurring calendar periods
881
+
646
882
  ```typescript
647
- { priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }
883
+ ({ priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & RequiredTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">
648
884
  ```
649
885
 
650
886
 
@@ -763,6 +999,11 @@ RoleBasedCoverageRequirement | SkillBasedCoverageRequirement
763
999
 
764
1000
 
765
1001
  ### Priority
1002
+ How strictly the solver enforces a rule.
1003
+
1004
+ - `"LOW"`, `"MEDIUM"`, `"HIGH"`: soft constraints with increasing penalty for violations
1005
+ - `"MANDATORY"`: hard constraint; the solver will not produce a solution that violates it
1006
+
766
1007
  ```typescript
767
1008
  "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"
768
1009
  ```
@@ -795,6 +1036,42 @@ string & { readonly [GroupKeyBrand]: never; }
795
1036
  ```
796
1037
 
797
1038
 
1039
+ ### EntityKey
1040
+ ```typescript
1041
+ "employees" | "roles" | "skills"
1042
+ ```
1043
+
1044
+
1045
+ ### TimeKey
1046
+ ```typescript
1047
+ "dateRange" | "specificDates" | "dayOfWeek" | "recurring"
1048
+ ```
1049
+
1050
+
1051
+ ### ExclusiveOne
1052
+ Exactly one of the specified keys must be present.
1053
+ The active key's field is required; all others are `?: never`.
1054
+
1055
+ **Example:**
1056
+ ExclusiveOne<ActiveEntityFields, InactiveEntityFields, "employees" | "roles">
1057
+ =
1058
+ | { employeeIds: [string, ...string[]]; roleIds?: never }
1059
+ | { roleIds: [string, ...string[]]; employeeIds?: never }
1060
+
1061
+ ```typescript
1062
+ { [K in Keys]: Active[K] & MergeValues<Pick<Inactive, Exclude<Keys, K>>>; }[Keys]
1063
+ ```
1064
+
1065
+
1066
+ ### MaybeOne
1067
+ At most one of the specified keys may be present (or none).
1068
+ Same as {@link ExclusiveOne} plus the case where all fields are `?: never`.
1069
+
1070
+ ```typescript
1071
+ ExclusiveOne<Active, Inactive, Keys> | MergeValues<Pick<Inactive, Keys>>
1072
+ ```
1073
+
1074
+
798
1075
  ### InferCpsatRuleConfig
799
1076
  ```typescript
800
1077
  T extends CreateCpsatRuleFunction<infer Config> ? Config : never
@@ -1014,41 +1291,41 @@ daysBetween(new Date('2025-01-01'), new Date('2025-01-05')); // 4
1014
1291
  ### resolveDaysFromPeriod
1015
1292
  Computes the list of day strings (YYYY-MM-DD) from a SchedulingPeriod.
1016
1293
 
1017
- For date ranges, generates all days between start and end (inclusive),
1018
- optionally filtering to specific days of the week.
1019
-
1020
- For specific dates, returns the dates as-is (sorted).
1294
+ Generates all days between start and end (inclusive), applying optional
1295
+ daysOfWeek and dates filters. Filters compose: a day must pass all
1296
+ specified filters to be included.
1021
1297
 
1022
1298
  **Example:**
1023
- Date range with day-of-week filter
1299
+ All days in range
1024
1300
  ```typescript
1025
1301
  const days = resolveDaysFromPeriod({
1026
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
1027
- daysOfWeek: ['wednesday', 'friday'],
1302
+ dateRange: { start: '2025-02-03', end: '2025-02-05' },
1028
1303
  });
1029
- // Returns: ['2025-02-05', '2025-02-07'] (Wed and Fri only)
1304
+ // Returns: ['2025-02-03', '2025-02-04', '2025-02-05']
1030
1305
  ```
1031
1306
 
1032
1307
  **Example:**
1033
- Date range without filter
1308
+ Day-of-week filter
1034
1309
  ```typescript
1035
1310
  const days = resolveDaysFromPeriod({
1036
- dateRange: { start: '2025-02-03', end: '2025-02-05' },
1311
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
1312
+ daysOfWeek: ['wednesday', 'friday'],
1037
1313
  });
1038
- // Returns: ['2025-02-03', '2025-02-04', '2025-02-05']
1314
+ // Returns: ['2025-02-05', '2025-02-07']
1039
1315
  ```
1040
1316
 
1041
1317
  **Example:**
1042
- Specific dates
1318
+ Specific dates filter
1043
1319
  ```typescript
1044
1320
  const days = resolveDaysFromPeriod({
1045
- specificDates: ['2025-02-07', '2025-02-03', '2025-02-10'],
1321
+ dateRange: { start: '2025-02-03', end: '2025-02-10' },
1322
+ dates: ['2025-02-05', '2025-02-07'],
1046
1323
  });
1047
- // Returns: ['2025-02-03', '2025-02-07', '2025-02-10'] (sorted)
1324
+ // Returns: ['2025-02-05', '2025-02-07']
1048
1325
  ```
1049
1326
 
1050
1327
  **Parameters:**
1051
- - `period: { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }`
1328
+ - `period: SchedulingPeriod`
1052
1329
 
1053
1330
  **Returns:** `string[]`
1054
1331
 
@@ -1122,9 +1399,6 @@ builder = new ModelBuilder({ ...config, rules: [rule] });
1122
1399
  ### createEmployeeAssignmentPriorityRule
1123
1400
  Adds objective weight to prefer or avoid assigning team members.
1124
1401
 
1125
- Supports entity scoping (people, roles, skills) and time scoping
1126
- (date ranges, specific dates, days of week, recurring periods).
1127
-
1128
1402
  **Example:**
1129
1403
  Prefer specific team members
1130
1404
  ```ts
@@ -1144,18 +1418,8 @@ preference: "low",
1144
1418
  });
1145
1419
  ```
1146
1420
 
1147
- **Example:**
1148
- Prefer experienced staff on weekends
1149
- ```ts
1150
- createEmployeeAssignmentPriorityRule({
1151
- skillIds: ["senior"],
1152
- dayOfWeek: ["saturday", "sunday"],
1153
- preference: "high",
1154
- });
1155
- ```
1156
-
1157
1421
  **Parameters:**
1158
- - `config: { preference: "high" | "low"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
1422
+ - `config: ({ preference: "high" | "low"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1159
1423
 
1160
1424
  **Returns:** `CompilationRule`
1161
1425
 
@@ -1165,16 +1429,15 @@ Prefers assigning a person to shift patterns matching a specific location.
1165
1429
 
1166
1430
  **Example:**
1167
1431
  ```ts
1168
- const rule = createLocationPreferenceRule({
1432
+ createLocationPreferenceRule({
1169
1433
  locationId: "terrace",
1170
1434
  priority: "HIGH",
1171
1435
  employeeIds: ["alice"],
1172
1436
  });
1173
- builder = new ModelBuilder({ ...config, rules: [rule] });
1174
1437
  ```
1175
1438
 
1176
1439
  **Parameters:**
1177
- - `config: { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1440
+ - `config: { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1178
1441
 
1179
1442
  **Returns:** `CompilationRule`
1180
1443
 
@@ -1184,15 +1447,11 @@ Limits how many consecutive days a person can be assigned.
1184
1447
 
1185
1448
  **Example:**
1186
1449
  ```ts
1187
- const rule = createMaxConsecutiveDaysRule({
1188
- days: 5,
1189
- priority: "MANDATORY",
1190
- });
1191
- builder = new ModelBuilder({ ...config, rules: [rule] });
1450
+ createMaxConsecutiveDaysRule({ days: 5, priority: "MANDATORY" });
1192
1451
  ```
1193
1452
 
1194
1453
  **Parameters:**
1195
- - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1454
+ - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1196
1455
 
1197
1456
  **Returns:** `CompilationRule`
1198
1457
 
@@ -1200,9 +1459,6 @@ builder = new ModelBuilder({ ...config, rules: [rule] });
1200
1459
  ### createMaxHoursDayRule
1201
1460
  Limits how many hours a person can work in a single day.
1202
1461
 
1203
- Supports entity scoping (people, roles, skills) and time scoping
1204
- (date ranges, specific dates, days of week, recurring periods).
1205
-
1206
1462
  **Example:**
1207
1463
  Limit everyone to 8 hours per day
1208
1464
  ```ts
@@ -1224,7 +1480,7 @@ priority: "MANDATORY",
1224
1480
  ```
1225
1481
 
1226
1482
  **Parameters:**
1227
- - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
1483
+ - `config: ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1228
1484
 
1229
1485
  **Returns:** `CompilationRule`
1230
1486
 
@@ -1232,17 +1488,10 @@ priority: "MANDATORY",
1232
1488
  ### createMaxHoursWeekRule
1233
1489
  Caps total hours a person can work within each scheduling week.
1234
1490
 
1235
- Supports entity scoping (people, roles, skills) and time scoping
1236
- (date ranges, specific dates, days of week, recurring periods).
1237
- Time scoping filters which days within each week count toward the limit.
1238
-
1239
1491
  **Example:**
1240
1492
  Limit everyone to 40 hours per week
1241
1493
  ```ts
1242
- createMaxHoursWeekRule({
1243
- hours: 40,
1244
- priority: "HIGH",
1245
- });
1494
+ createMaxHoursWeekRule({ hours: 40, priority: "HIGH" });
1246
1495
  ```
1247
1496
 
1248
1497
  **Example:**
@@ -1253,14 +1502,13 @@ roleIds: ["student"],
1253
1502
  hours: 20,
1254
1503
  recurringPeriods: [
1255
1504
  { name: "fall-term", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },
1256
- { name: "spring-term", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },
1257
1505
  ],
1258
1506
  priority: "MANDATORY",
1259
1507
  });
1260
1508
  ```
1261
1509
 
1262
1510
  **Parameters:**
1263
- - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
1511
+ - `config: ({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1264
1512
 
1265
1513
  **Returns:** `CompilationRule`
1266
1514
 
@@ -1268,14 +1516,11 @@ priority: "MANDATORY",
1268
1516
  ### createMaxShiftsDayRule
1269
1517
  Limits how many shifts a person can work in a single day.
1270
1518
 
1271
- This rule controls the maximum number of distinct shift assignments per day,
1519
+ Controls the maximum number of distinct shift assignments per day,
1272
1520
  regardless of shift duration. For limiting total hours worked, use `max-hours-day`.
1273
1521
 
1274
- Supports entity scoping (people, roles, skills) and time scoping
1275
- (date ranges, specific dates, days of week, recurring periods).
1276
-
1277
1522
  **Example:**
1278
- Limit to one shift per day (common for most schedules)
1523
+ Limit to one shift per day
1279
1524
  ```ts
1280
1525
  createMaxShiftsDayRule({
1281
1526
  shifts: 1,
@@ -1283,16 +1528,6 @@ priority: "MANDATORY",
1283
1528
  });
1284
1529
  ```
1285
1530
 
1286
- **Example:**
1287
- Allow up to two shifts per day for part-time workers
1288
- ```ts
1289
- createMaxShiftsDayRule({
1290
- roleIds: ["part-time"],
1291
- shifts: 2,
1292
- priority: "HIGH",
1293
- });
1294
- ```
1295
-
1296
1531
  **Example:**
1297
1532
  Students can work 2 shifts on weekends only
1298
1533
  ```ts
@@ -1305,7 +1540,7 @@ priority: "MANDATORY",
1305
1540
  ```
1306
1541
 
1307
1542
  **Parameters:**
1308
- - `config: { shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
1543
+ - `config: ({ shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1309
1544
 
1310
1545
  **Returns:** `CompilationRule`
1311
1546
 
@@ -1316,15 +1551,11 @@ number of consecutive days.
1316
1551
 
1317
1552
  **Example:**
1318
1553
  ```ts
1319
- const rule = createMinConsecutiveDaysRule({
1320
- days: 3,
1321
- priority: "MANDATORY",
1322
- });
1323
- builder = new ModelBuilder({ ...config, rules: [rule] });
1554
+ createMinConsecutiveDaysRule({ days: 3, priority: "MANDATORY" });
1324
1555
  ```
1325
1556
 
1326
1557
  **Parameters:**
1327
- - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1558
+ - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1328
1559
 
1329
1560
  **Returns:** `CompilationRule`
1330
1561
 
@@ -1334,15 +1565,11 @@ Ensures a person works at least a minimum number of hours per day.
1334
1565
 
1335
1566
  **Example:**
1336
1567
  ```ts
1337
- const rule = createMinHoursDayRule({
1338
- hours: 6,
1339
- priority: "MANDATORY",
1340
- });
1341
- builder = new ModelBuilder({ ...config, rules: [rule] });
1568
+ createMinHoursDayRule({ hours: 6, priority: "MANDATORY" });
1342
1569
  ```
1343
1570
 
1344
1571
  **Parameters:**
1345
- - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1572
+ - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1346
1573
 
1347
1574
  **Returns:** `CompilationRule`
1348
1575
 
@@ -1352,15 +1579,11 @@ Enforces a minimum total number of hours per scheduling week.
1352
1579
 
1353
1580
  **Example:**
1354
1581
  ```ts
1355
- const rule = createMinHoursWeekRule({
1356
- hours: 30,
1357
- priority: "HIGH",
1358
- });
1359
- builder = new ModelBuilder({ ...config, rules: [rule] });
1582
+ createMinHoursWeekRule({ hours: 30, priority: "HIGH" });
1360
1583
  ```
1361
1584
 
1362
1585
  **Parameters:**
1363
- - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1586
+ - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">`
1364
1587
 
1365
1588
  **Returns:** `CompilationRule`
1366
1589
 
@@ -1370,15 +1593,11 @@ Enforces a minimum rest period between any two shifts a person works.
1370
1593
 
1371
1594
  **Example:**
1372
1595
  ```ts
1373
- const rule = createMinRestBetweenShiftsRule({
1374
- hours: 10,
1375
- priority: "MANDATORY",
1376
- });
1377
- builder = new ModelBuilder({ ...config, rules: [rule] });
1596
+ createMinRestBetweenShiftsRule({ hours: 10, priority: "MANDATORY" });
1378
1597
  ```
1379
1598
 
1380
1599
  **Parameters:**
1381
- - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1600
+ - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1382
1601
 
1383
1602
  **Returns:** `CompilationRule`
1384
1603
 
@@ -1425,7 +1644,7 @@ priority: "MANDATORY",
1425
1644
  ```
1426
1645
 
1427
1646
  **Parameters:**
1428
- - `config: { priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
1647
+ - `config: ({ priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & RequiredTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1429
1648
 
1430
1649
  **Returns:** `CompilationRule`
1431
1650
 
@@ -1666,6 +1885,98 @@ Always includes range start. Sorted ascending.
1666
1885
  **Returns:** `number[]`
1667
1886
 
1668
1887
 
1888
+ ### entityScope
1889
+ Creates a Zod schema for optional entity scoping (at most one of the
1890
+ specified entity variants).
1891
+
1892
+ The returned schema accepts flat fields (`employeeIds`, `roleIds`, `skillIds`)
1893
+ but the TypeScript type enforces mutual exclusivity via `?: never`.
1894
+
1895
+ **Example:**
1896
+ ```ts
1897
+ // Supports all entity scopes
1898
+ entityScope(["employees", "roles", "skills"])
1899
+
1900
+ // Only employee scoping
1901
+ entityScope(["employees"])
1902
+ ```
1903
+
1904
+ **Parameters:**
1905
+ - `keys: K`
1906
+
1907
+ **Returns:** `z.ZodType<EntityScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<EntityScopeType<K[number]>, unknown>>`
1908
+
1909
+
1910
+ ### timeScope
1911
+ Creates a Zod schema for optional time scoping (at most one of the
1912
+ specified time variants, or none).
1913
+
1914
+ **Example:**
1915
+ ```ts
1916
+ // Supports all time scopes, all optional
1917
+ timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"])
1918
+ ```
1919
+
1920
+ **Parameters:**
1921
+ - `keys: K`
1922
+
1923
+ **Returns:** `z.ZodType<OptionalTimeScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<OptionalTimeScopeType<K[number]>, unknown>>`
1924
+
1925
+
1926
+ ### requiredTimeScope
1927
+ Creates a Zod schema for required time scoping (exactly one of the
1928
+ specified time variants must be present).
1929
+
1930
+ **Example:**
1931
+ ```ts
1932
+ // Exactly one time scope required (for time-off)
1933
+ requiredTimeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"])
1934
+ ```
1935
+
1936
+ **Parameters:**
1937
+ - `keys: K`
1938
+
1939
+ **Returns:** `z.ZodType<RequiredTimeScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<RequiredTimeScopeType<K[number]>, unknown>>`
1940
+
1941
+
1942
+ ### parseEntityScope
1943
+ Extracts the entity scope from a parsed flat config.
1944
+
1945
+ **Parameters:**
1946
+ - `config: EntityScopeInput`
1947
+
1948
+ **Returns:** `{ type: "global"; } | { type: "employees"; employeeIds: string[]; } | { type: "roles"; roleIds: string[]; } | { type: "skills"; skillIds: string[]; }`
1949
+
1950
+
1951
+ ### parseTimeScope
1952
+ Extracts the time scope from a parsed flat config.
1953
+
1954
+ **Parameters:**
1955
+ - `config: TimeScopeInput`
1956
+
1957
+ **Returns:** `{ type: "none"; } | { type: "dateRange"; start: string; end: string; } | { type: "specificDates"; dates: string[]; } | { type: "dayOfWeek"; days: DayOfWeek[]; } | { type: "recurring"; periods: RecurringPeriod[]; }`
1958
+
1959
+
1960
+ ### resolveEmployeesFromScope
1961
+ Resolves which employees a rule applies to based on entity scope.
1962
+
1963
+ **Parameters:**
1964
+ - `scope: { type: "global"; } | { type: "employees"; employeeIds: string[]; } | { type: "roles"; roleIds: string[]; } | { type: "skills"; skillIds: string[]; }`
1965
+ - `employees: SchedulingEmployee[]`
1966
+
1967
+ **Returns:** `SchedulingEmployee[]`
1968
+
1969
+
1970
+ ### resolveActiveDaysFromScope
1971
+ Resolves which days a rule applies to based on time scope.
1972
+
1973
+ **Parameters:**
1974
+ - `scope: { type: "none"; } | { type: "dateRange"; start: string; end: string; } | { type: "specificDates"; dates: string[]; } | { type: "dayOfWeek"; days: DayOfWeek[]; } | { type: "recurring"; periods: RecurringPeriod[]; }`
1975
+ - `allDays: string[]`
1976
+
1977
+ **Returns:** `string[]`
1978
+
1979
+
1669
1980
  ### parseDayString
1670
1981
  Parse a day string (YYYY-MM-DD) to a UTC Date.
1671
1982
  Used internally for day-of-week calculations and date comparisons.
@@ -1747,6 +2058,10 @@ Useful for rule configs that need to accept a day-of-week string.
1747
2058
  **Type:** `z.ZodObject<{ status: z.ZodEnum<{ OPTIMAL: "OPTIMAL"; FEASIBLE: "FEASIBLE"; INFEASIBLE: "INFEASIBLE"; TIMEOUT: "TIMEOUT"; ERROR: "ERROR"; }>; values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; statistics: z.ZodOptional<z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>>; error: z.ZodOptional<z.ZodString>; solutionInfo: z.ZodOptional<z.ZodString>; softViolations: z.ZodOptional<z.ZodArray<z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>>>; }, z.core.$strip>`
1748
2059
 
1749
2060
 
2061
+ ### SolverStatusSchema
2062
+ **Type:** `z.ZodEnum<{ OPTIMAL: "OPTIMAL"; FEASIBLE: "FEASIBLE"; INFEASIBLE: "INFEASIBLE"; TIMEOUT: "TIMEOUT"; ERROR: "ERROR"; }>`
2063
+
2064
+
1750
2065
  ### builtInCpsatRuleFactories
1751
2066
  **Type:** `[Complex type: __type]`
1752
2067
 
@@ -1767,7 +2082,7 @@ Weight hierarchy (highest to lowest priority):
1767
2082
  **Example:**
1768
2083
  Using weights in a custom rule
1769
2084
  ```ts
1770
- import { OBJECTIVE_WEIGHTS } from "feasible";
2085
+ import { OBJECTIVE_WEIGHTS } from "dabke";
1771
2086
 
1772
2087
  // Prefer senior staff with same weight as employee-assignment-priority
1773
2088
  b.addPenalty(assignment, -OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);
@@ -1847,10 +2162,6 @@ b.addPenalty(assignment, -2 * OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);
1847
2162
  **Type:** `z.ZodObject<{ timeLimitSeconds: z.ZodOptional<z.ZodNumber>; solutionLimit: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`
1848
2163
 
1849
2164
 
1850
- ### SolverStatusSchema
1851
- **Type:** `z.ZodEnum<{ OPTIMAL: "OPTIMAL"; FEASIBLE: "FEASIBLE"; INFEASIBLE: "INFEASIBLE"; TIMEOUT: "TIMEOUT"; ERROR: "ERROR"; }>`
1852
-
1853
-
1854
2165
  ### SolverStatisticsSchema
1855
2166
  **Type:** `z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`
1856
2167
 
@@ -1892,9 +2203,6 @@ builder = new ModelBuilder({ ...config, rules: [rule] });
1892
2203
 
1893
2204
  Adds objective weight to prefer or avoid assigning team members.
1894
2205
 
1895
- Supports entity scoping (people, roles, skills) and time scoping
1896
- (date ranges, specific dates, days of week, recurring periods).
1897
-
1898
2206
  **Example:**
1899
2207
  Prefer specific team members
1900
2208
  ```ts
@@ -1914,17 +2222,7 @@ preference: "low",
1914
2222
  });
1915
2223
  ```
1916
2224
 
1917
- **Example:**
1918
- Prefer experienced staff on weekends
1919
- ```ts
1920
- createEmployeeAssignmentPriorityRule({
1921
- skillIds: ["senior"],
1922
- dayOfWeek: ["saturday", "sunday"],
1923
- preference: "high",
1924
- });
1925
- ```
1926
-
1927
- **Config:** `{ preference: "high" | "low"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
2225
+ **Config:** `({ preference: "high" | "low"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1928
2226
 
1929
2227
  ### location-preference
1930
2228
 
@@ -1934,15 +2232,14 @@ Prefers assigning a person to shift patterns matching a specific location.
1934
2232
 
1935
2233
  **Example:**
1936
2234
  ```ts
1937
- const rule = createLocationPreferenceRule({
2235
+ createLocationPreferenceRule({
1938
2236
  locationId: "terrace",
1939
2237
  priority: "HIGH",
1940
2238
  employeeIds: ["alice"],
1941
2239
  });
1942
- builder = new ModelBuilder({ ...config, rules: [rule] });
1943
2240
  ```
1944
2241
 
1945
- **Config:** `{ locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2242
+ **Config:** `{ locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1946
2243
 
1947
2244
  ### max-consecutive-days
1948
2245
 
@@ -1952,14 +2249,10 @@ Limits how many consecutive days a person can be assigned.
1952
2249
 
1953
2250
  **Example:**
1954
2251
  ```ts
1955
- const rule = createMaxConsecutiveDaysRule({
1956
- days: 5,
1957
- priority: "MANDATORY",
1958
- });
1959
- builder = new ModelBuilder({ ...config, rules: [rule] });
2252
+ createMaxConsecutiveDaysRule({ days: 5, priority: "MANDATORY" });
1960
2253
  ```
1961
2254
 
1962
- **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2255
+ **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
1963
2256
 
1964
2257
  ### max-hours-day
1965
2258
 
@@ -1967,9 +2260,6 @@ builder = new ModelBuilder({ ...config, rules: [rule] });
1967
2260
 
1968
2261
  Limits how many hours a person can work in a single day.
1969
2262
 
1970
- Supports entity scoping (people, roles, skills) and time scoping
1971
- (date ranges, specific dates, days of week, recurring periods).
1972
-
1973
2263
  **Example:**
1974
2264
  Limit everyone to 8 hours per day
1975
2265
  ```ts
@@ -1990,7 +2280,7 @@ priority: "MANDATORY",
1990
2280
  });
1991
2281
  ```
1992
2282
 
1993
- **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
2283
+ **Config:** `({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
1994
2284
 
1995
2285
  ### max-hours-week
1996
2286
 
@@ -1998,17 +2288,10 @@ priority: "MANDATORY",
1998
2288
 
1999
2289
  Caps total hours a person can work within each scheduling week.
2000
2290
 
2001
- Supports entity scoping (people, roles, skills) and time scoping
2002
- (date ranges, specific dates, days of week, recurring periods).
2003
- Time scoping filters which days within each week count toward the limit.
2004
-
2005
2291
  **Example:**
2006
2292
  Limit everyone to 40 hours per week
2007
2293
  ```ts
2008
- createMaxHoursWeekRule({
2009
- hours: 40,
2010
- priority: "HIGH",
2011
- });
2294
+ createMaxHoursWeekRule({ hours: 40, priority: "HIGH" });
2012
2295
  ```
2013
2296
 
2014
2297
  **Example:**
@@ -2019,13 +2302,12 @@ roleIds: ["student"],
2019
2302
  hours: 20,
2020
2303
  recurringPeriods: [
2021
2304
  { name: "fall-term", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },
2022
- { name: "spring-term", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },
2023
2305
  ],
2024
2306
  priority: "MANDATORY",
2025
2307
  });
2026
2308
  ```
2027
2309
 
2028
- **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
2310
+ **Config:** `({ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
2029
2311
 
2030
2312
  ### max-shifts-day
2031
2313
 
@@ -2033,14 +2315,11 @@ priority: "MANDATORY",
2033
2315
 
2034
2316
  Limits how many shifts a person can work in a single day.
2035
2317
 
2036
- This rule controls the maximum number of distinct shift assignments per day,
2318
+ Controls the maximum number of distinct shift assignments per day,
2037
2319
  regardless of shift duration. For limiting total hours worked, use `max-hours-day`.
2038
2320
 
2039
- Supports entity scoping (people, roles, skills) and time scoping
2040
- (date ranges, specific dates, days of week, recurring periods).
2041
-
2042
2321
  **Example:**
2043
- Limit to one shift per day (common for most schedules)
2322
+ Limit to one shift per day
2044
2323
  ```ts
2045
2324
  createMaxShiftsDayRule({
2046
2325
  shifts: 1,
@@ -2048,16 +2327,6 @@ priority: "MANDATORY",
2048
2327
  });
2049
2328
  ```
2050
2329
 
2051
- **Example:**
2052
- Allow up to two shifts per day for part-time workers
2053
- ```ts
2054
- createMaxShiftsDayRule({
2055
- roleIds: ["part-time"],
2056
- shifts: 2,
2057
- priority: "HIGH",
2058
- });
2059
- ```
2060
-
2061
2330
  **Example:**
2062
2331
  Students can work 2 shifts on weekends only
2063
2332
  ```ts
@@ -2069,7 +2338,7 @@ priority: "MANDATORY",
2069
2338
  });
2070
2339
  ```
2071
2340
 
2072
- **Config:** `{ shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
2341
+ **Config:** `({ shifts: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">) & OptionalTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
2073
2342
 
2074
2343
  ### min-consecutive-days
2075
2344
 
@@ -2080,14 +2349,10 @@ number of consecutive days.
2080
2349
 
2081
2350
  **Example:**
2082
2351
  ```ts
2083
- const rule = createMinConsecutiveDaysRule({
2084
- days: 3,
2085
- priority: "MANDATORY",
2086
- });
2087
- builder = new ModelBuilder({ ...config, rules: [rule] });
2352
+ createMinConsecutiveDaysRule({ days: 3, priority: "MANDATORY" });
2088
2353
  ```
2089
2354
 
2090
- **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2355
+ **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
2091
2356
 
2092
2357
  ### min-hours-day
2093
2358
 
@@ -2097,14 +2362,10 @@ Ensures a person works at least a minimum number of hours per day.
2097
2362
 
2098
2363
  **Example:**
2099
2364
  ```ts
2100
- const rule = createMinHoursDayRule({
2101
- hours: 6,
2102
- priority: "MANDATORY",
2103
- });
2104
- builder = new ModelBuilder({ ...config, rules: [rule] });
2365
+ createMinHoursDayRule({ hours: 6, priority: "MANDATORY" });
2105
2366
  ```
2106
2367
 
2107
- **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2368
+ **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
2108
2369
 
2109
2370
  ### min-hours-week
2110
2371
 
@@ -2114,14 +2375,10 @@ Enforces a minimum total number of hours per scheduling week.
2114
2375
 
2115
2376
  **Example:**
2116
2377
  ```ts
2117
- const rule = createMinHoursWeekRule({
2118
- hours: 30,
2119
- priority: "HIGH",
2120
- });
2121
- builder = new ModelBuilder({ ...config, rules: [rule] });
2378
+ createMinHoursWeekRule({ hours: 30, priority: "HIGH" });
2122
2379
  ```
2123
2380
 
2124
- **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2381
+ **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; weekStartsOn?: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | undefined; } & EntityScopeType<"employees" | "roles" | "skills">`
2125
2382
 
2126
2383
  ### min-rest-between-shifts
2127
2384
 
@@ -2131,14 +2388,10 @@ Enforces a minimum rest period between any two shifts a person works.
2131
2388
 
2132
2389
  **Example:**
2133
2390
  ```ts
2134
- const rule = createMinRestBetweenShiftsRule({
2135
- hours: 10,
2136
- priority: "MANDATORY",
2137
- });
2138
- builder = new ModelBuilder({ ...config, rules: [rule] });
2391
+ createMinRestBetweenShiftsRule({ hours: 10, priority: "MANDATORY" });
2139
2392
  ```
2140
2393
 
2141
- **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2394
+ **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; } & EntityScopeType<"employees" | "roles" | "skills">`
2142
2395
 
2143
2396
  ### time-off
2144
2397
 
@@ -2184,5 +2437,5 @@ priority: "MANDATORY",
2184
2437
  });
2185
2438
  ```
2186
2439
 
2187
- **Config:** `{ priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: ("monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`
2440
+ **Config:** `({ priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<"employees" | "roles" | "skills">) & RequiredTimeScopeType<"dateRange" | "specificDates" | "dayOfWeek" | "recurring">`
2188
2441