dabke 0.82.0 → 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.
Files changed (83) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +6 -3
  3. package/dist/cpsat/rules/index.d.ts +3 -0
  4. package/dist/cpsat/rules/index.d.ts.map +1 -1
  5. package/dist/cpsat/rules/index.js +3 -0
  6. package/dist/cpsat/rules/index.js.map +1 -1
  7. package/dist/cpsat/rules/max-days-week.d.ts +44 -0
  8. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
  9. package/dist/cpsat/rules/max-days-week.js +95 -0
  10. package/dist/cpsat/rules/max-days-week.js.map +1 -0
  11. package/dist/cpsat/rules/min-days-week.d.ts +34 -0
  12. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
  13. package/dist/cpsat/rules/min-days-week.js +84 -0
  14. package/dist/cpsat/rules/min-days-week.js.map +1 -0
  15. package/dist/cpsat/rules/must-assign.d.ts +49 -0
  16. package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
  17. package/dist/cpsat/rules/must-assign.js +86 -0
  18. package/dist/cpsat/rules/must-assign.js.map +1 -0
  19. package/dist/cpsat/rules/registry.d.ts +4 -1
  20. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  21. package/dist/cpsat/rules/registry.js +4 -1
  22. package/dist/cpsat/rules/registry.js.map +1 -1
  23. package/dist/cpsat/rules/rules.types.d.ts +3 -0
  24. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  25. package/dist/cpsat/rules/scope.types.d.ts +1 -1
  26. package/dist/index.d.ts +5 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/schedule/cost.d.ts +204 -0
  31. package/dist/schedule/cost.d.ts.map +1 -0
  32. package/dist/schedule/cost.js +187 -0
  33. package/dist/schedule/cost.js.map +1 -0
  34. package/dist/schedule/coverage.d.ts +85 -0
  35. package/dist/schedule/coverage.d.ts.map +1 -0
  36. package/dist/schedule/coverage.js +33 -0
  37. package/dist/schedule/coverage.js.map +1 -0
  38. package/dist/schedule/definition.d.ts +227 -0
  39. package/dist/schedule/definition.d.ts.map +1 -0
  40. package/dist/{schedule.js → schedule/definition.js} +9 -673
  41. package/dist/schedule/definition.js.map +1 -0
  42. package/dist/schedule/index.d.ts +67 -0
  43. package/dist/schedule/index.d.ts.map +1 -0
  44. package/dist/schedule/index.js +69 -0
  45. package/dist/schedule/index.js.map +1 -0
  46. package/dist/schedule/rules.d.ts +353 -0
  47. package/dist/schedule/rules.d.ts.map +1 -0
  48. package/dist/schedule/rules.js +352 -0
  49. package/dist/schedule/rules.js.map +1 -0
  50. package/dist/schedule/shift-patterns.d.ts +34 -0
  51. package/dist/schedule/shift-patterns.d.ts.map +1 -0
  52. package/dist/schedule/shift-patterns.js +41 -0
  53. package/dist/schedule/shift-patterns.js.map +1 -0
  54. package/dist/schedule/time-periods.d.ts +69 -0
  55. package/dist/schedule/time-periods.d.ts.map +1 -0
  56. package/dist/schedule/time-periods.js +91 -0
  57. package/dist/schedule/time-periods.js.map +1 -0
  58. package/package.json +4 -9
  59. package/src/cpsat/rules/index.ts +3 -0
  60. package/src/cpsat/rules/max-days-week.ts +143 -0
  61. package/src/cpsat/rules/min-days-week.ts +120 -0
  62. package/src/cpsat/rules/must-assign.ts +108 -0
  63. package/src/cpsat/rules/registry.ts +6 -0
  64. package/src/cpsat/rules/rules.types.ts +3 -0
  65. package/src/cpsat/rules/scope.types.ts +1 -1
  66. package/src/index.ts +8 -3
  67. package/src/schedule/cost.ts +242 -0
  68. package/src/schedule/coverage.ts +135 -0
  69. package/src/schedule/definition.ts +958 -0
  70. package/src/schedule/index.ts +112 -0
  71. package/src/schedule/rules.ts +529 -0
  72. package/src/schedule/shift-patterns.ts +46 -0
  73. package/src/schedule/time-periods.ts +110 -0
  74. package/dist/llms.d.ts +0 -2
  75. package/dist/llms.d.ts.map +0 -1
  76. package/dist/llms.js +0 -3
  77. package/dist/llms.js.map +0 -1
  78. package/dist/schedule.d.ts +0 -917
  79. package/dist/schedule.d.ts.map +0 -1
  80. package/dist/schedule.js.map +0 -1
  81. package/llms.txt +0 -758
  82. package/src/llms.ts +0 -3
  83. package/src/schedule.ts +0 -1960
package/llms.txt DELETED
@@ -1,758 +0,0 @@
1
- # dabke
2
-
3
- > Scheduling library powered by constraint programming (CP-SAT)
4
-
5
- ---
6
-
7
- ## Schedule Definition
8
-
9
- ### `SolveResult`
10
-
11
- Result of `Schedule.solve`.
12
-
13
- **Properties:**
14
- - `status: SolveStatus` — Outcome of the solve attempt.
15
- - `assignments: ShiftAssignment[]` — Shift assignments (empty when infeasible or no solution).
16
- - `validation: ScheduleValidation` — Validation diagnostics from compilation.
17
- - `cost?: CostBreakdown` — Cost breakdown (present when cost rules are used and a solution is found).
18
-
19
- ### `SolveOptions`
20
-
21
- Options for `Schedule.solve` and `Schedule.compile`.
22
-
23
- **Properties:**
24
- - `dateRange: { start: string; end: string }` — The date range to schedule.
25
- - `pinned?: ShiftAssignment[]` — Fixed assignments from a prior solve (e.g., rolling schedule).
26
- These are injected as fixed variables in the solver.
27
-
28
- Not yet implemented. Providing pinned assignments throws an error.
29
-
30
- ### `ScheduleConfig`
31
-
32
- Configuration for `schedule`.
33
-
34
- Coverage entries for the same semantic time and target stack additively.
35
- An unscoped entry applies every day; adding a weekend-only entry on top
36
- doubles the count on those days. Use mutually exclusive `dayOfWeek` on
37
- both entries to avoid stacking. See `cover` for details.
38
-
39
- `roleIds`, `times`, `coverage`, and `shiftPatterns` are required.
40
- These four fields form the minimum solvable schedule.
41
-
42
- **Properties:**
43
- - `roleIds: R` — Declared role IDs.
44
- - `skillIds?: S` — Declared skill IDs.
45
- - `times: T` — Named semantic time periods.
46
- - `coverage: CoverageEntry<keyof T & string, R[number] | NonNullable<S>[number]>[]` — Staffing requirements per time period (entries stack additively).
47
- - `shiftPatterns: ShiftPattern[]` — Available shift patterns.
48
- - `rules?: RuleEntry[]` — Scheduling rules and constraints.
49
- - `ruleFactories?: Record<string, CreateCpsatRuleFunction>` — Custom rule factories. Keys are rule names, values are functions
50
- that take a config object and return a `CompilationRule`.
51
- Built-in rule names cannot be overridden.
52
- - `members?: SchedulingMember[]` — Team members (typically added via `.with()` at runtime).
53
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Days of the week the business operates (inclusion filter).
54
- - `weekStartsOn?: DayOfWeek` — Which day starts the week for weekly rules. Defaults to `"monday"`.
55
-
56
- ### `schedule`
57
-
58
- Create a schedule definition.
59
-
60
- Returns an immutable `Schedule` that can be composed via `.with()`
61
- and solved via `.solve()`.
62
-
63
- ```typescript
64
- const venue = schedule({
65
- roleIds: ["waiter", "runner", "manager"],
66
- skillIds: ["senior"],
67
- times: {
68
- lunch: time({ startTime: t(12), endTime: t(15) }),
69
- dinner: time(
70
- { startTime: t(17), endTime: t(21) },
71
- { startTime: t(18), endTime: t(22), dayOfWeek: weekend },
72
- ),
73
- },
74
- coverage: [
75
- cover("lunch", "waiter", 2),
76
- cover("dinner", "waiter", 4, { dayOfWeek: weekdays }),
77
- cover("dinner", "waiter", 5, { dayOfWeek: weekend }),
78
- cover("dinner", "manager", 1),
79
- ],
80
- shiftPatterns: [
81
- shift("lunch_shift", t(11, 30), t(15)),
82
- shift("evening", t(17), t(22)),
83
- ],
84
- rules: [
85
- maxHoursPerDay(10),
86
- maxHoursPerWeek(48),
87
- minRestBetweenShifts(11),
88
- ],
89
- });
90
- ```
91
-
92
- ### `partialSchedule`
93
-
94
- Create a partial schedule for composition via `.with()`.
95
-
96
- Unlike `schedule`, all fields are optional. Use this for
97
- schedules that layer rules, coverage, or other config onto a
98
- complete base schedule.
99
-
100
- ```typescript
101
- const companyPolicy = partialSchedule({
102
- rules: [maxHoursPerWeek(40), minRestBetweenShifts(11)],
103
- });
104
-
105
- const ready = venue.with(companyPolicy, teamMembers);
106
- ```
107
-
108
- ---
109
-
110
- ## Time Periods
111
-
112
- ### `t`
113
-
114
- Creates a `TimeOfDay` value.
115
-
116
- Hours only
117
- ```ts
118
- t(9) // { hours: 9, minutes: 0 }
119
- ```
120
-
121
- **Parameters:**
122
- - `hours: number` — Hour component (0-23)
123
- - `minutes: number` — Minute component (0-59)
124
-
125
- **Returns:** `TimeOfDay`
126
-
127
- ### `weekdays`
128
-
129
- Monday through Friday.
130
-
131
- ```typescript
132
- readonly ["monday", "tuesday", "wednesday", "thursday", "friday"]
133
- ```
134
-
135
- ### `weekend`
136
-
137
- Saturday and Sunday.
138
-
139
- ```typescript
140
- readonly ["saturday", "sunday"]
141
- ```
142
-
143
- ### `time`
144
-
145
- Define a named semantic time period.
146
-
147
- Each entry has `startTime`/`endTime` and optional `dayOfWeek` or `dates`
148
- scoping. Entries without scoping are the default.
149
-
150
- ```typescript
151
- times: {
152
- // Simple: same times every day
153
- lunch: time({ startTime: t(12), endTime: t(15) }),
154
-
155
- // Variants: different times on weekends
156
- dinner: time(
157
- { startTime: t(17), endTime: t(21) },
158
- { startTime: t(18), endTime: t(22), dayOfWeek: weekend },
159
- ),
160
-
161
- // Point-in-time window (keyholder at opening)
162
- opening: time({ startTime: t(8, 30), endTime: t(9) }),
163
- }
164
- ```
165
-
166
- ---
167
-
168
- ## Coverage
169
-
170
- ### `CoverageVariant`
171
-
172
- A day-specific count within a variant `cover` call.
173
-
174
- Each variant specifies a count and optional day/date scope. During
175
- resolution, the most specific matching variant wins for each day
176
- (`dates` > `dayOfWeek` > default), mirroring `SemanticTimeVariant`.
177
- At most one variant may be unscoped (the default).
178
-
179
- ```typescript
180
- // Default: 4 agents. Christmas Eve: 2.
181
- cover("peak_hours", "agent",
182
- { count: 4 },
183
- { count: 2, dates: ["2025-12-24"] },
184
- )
185
- ```
186
-
187
- **Properties:**
188
- - `count: number` — Number of people needed.
189
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restrict this variant to specific days of the week.
190
- - `dates?: string[]` — Restrict this variant to specific dates (YYYY-MM-DD).
191
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
192
-
193
- ### `CoverageOptions`
194
-
195
- Options for a `cover` call.
196
-
197
- Day/date scoping controls which days this coverage entry applies to.
198
- An entry without `dayOfWeek` or `dates` applies every day in the
199
- scheduling period.
200
-
201
- **Properties:**
202
- - `skillIds?: [string, ...string[]]` — Additional skill ID filter (AND logic with the target role).
203
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restrict to specific days of the week.
204
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
205
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
206
-
207
- ### `cover`
208
-
209
- Defines a staffing requirement for a semantic time period.
210
-
211
- Entries for the same time and role **stack additively**.
212
- For weekday vs weekend staffing, use mutually exclusive `dayOfWeek`
213
- on both entries.
214
-
215
- ```typescript
216
- coverage: [
217
- // 2 waiters during lunch
218
- cover("lunch", "waiter", 2),
219
-
220
- // 1 manager OR supervisor during dinner
221
- cover("dinner", ["manager", "supervisor"], 1),
222
-
223
- // 1 person with keyholder skill at opening
224
- cover("opening", "keyholder", 1),
225
-
226
- // 1 senior waiter (role + skill AND)
227
- cover("lunch", "waiter", 1, { skillIds: ["senior"] }),
228
-
229
- // Different counts by day (mutually exclusive dayOfWeek!)
230
- cover("lunch", "waiter", 2, { dayOfWeek: weekdays }),
231
- cover("lunch", "waiter", 3, { dayOfWeek: weekend }),
232
- ]
233
- ```
234
-
235
- **Parameters:**
236
- - `timeName: T` — Name of a declared semantic time
237
- - `target: R | [R, ...R[]]` — Role name (string), array of role names (OR logic), or skill name
238
- - `count: number` — Number of people needed
239
- - `opts?: CoverageOptions` — Options: `skillIds` (AND filter), `dayOfWeek`, `dates`, `priority`
240
-
241
- **Returns:** `CoverageEntry<T, R>`
242
-
243
- ---
244
-
245
- ## Shift Patterns
246
-
247
- ### `shift`
248
-
249
- Define a shift pattern: a time slot available for employee assignment.
250
-
251
- Each pattern repeats daily unless filtered by `dayOfWeek`.
252
-
253
- ```typescript
254
- shiftPatterns: [
255
- shift("morning", t(11, 30), t(15)),
256
- shift("evening", t(17), t(22)),
257
-
258
- // Role-restricted shift
259
- shift("kitchen", t(6), t(14), { roleIds: ["chef", "prep_cook"] }),
260
-
261
- // Day-restricted shift
262
- shift("saturday_short", t(9), t(14), { dayOfWeek: ["saturday"] }),
263
-
264
- // Location-specific shift
265
- shift("terrace_lunch", t(12), t(16), { locationId: "terrace" }),
266
- ]
267
- ```
268
-
269
- ---
270
-
271
- ## Rules
272
-
273
- ### `RecurringPeriod`
274
-
275
- Recurring calendar period for time scoping.
276
-
277
- **Properties:**
278
- - `name: string`
279
- - `startMonth: number`
280
- - `startDay: number`
281
- - `endMonth: number`
282
- - `endDay: number`
283
-
284
- ### `RuleOptions`
285
-
286
- Scoping options shared by most rule functions.
287
-
288
- Default priority is `MANDATORY`. Use `appliesTo` to scope to a
289
- role, skill, or member ID. Use time scoping options (`dayOfWeek`,
290
- `dateRange`, `dates`) to limit when the rule applies.
291
- Not all rules support all scoping options. Entity-only rules
292
- (e.g., `maxConsecutiveDays`) ignore time scoping.
293
-
294
- **Properties:**
295
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
296
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restrict to specific days of the week.
297
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
298
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
299
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
300
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
301
-
302
- ### `EntityOnlyRuleOptions`
303
-
304
- Options for rules that support entity scoping only (no time scoping).
305
-
306
- Used by rules whose semantics are inherently per-day or per-week
307
- (e.g., `minHoursPerDay`, `maxConsecutiveDays`) and cannot
308
- be meaningfully restricted to a date range or day of week.
309
-
310
- **Properties:**
311
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
312
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
313
-
314
- ### `TimeOffOptions`
315
-
316
- Options for `timeOff`.
317
-
318
- At least one time scoping field is required (`dayOfWeek`, `dateRange`,
319
- `dates`, or `recurringPeriods`). Use `from`/`until` to block only part
320
- of a day.
321
-
322
- **Properties:**
323
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
324
- - `from?: TimeOfDay` — Off from this time until end of day.
325
- - `until?: TimeOfDay` — Off from start of day until this time.
326
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restrict to specific days of the week.
327
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
328
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
329
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
330
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
331
-
332
- ### `AssignTogetherOptions`
333
-
334
- Options for `assignTogether`.
335
-
336
- **Properties:**
337
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
338
-
339
- ### `RuleResolveContext`
340
-
341
- Context passed to a rule's resolve function during compilation.
342
-
343
- Contains the declared roles, skills, and member IDs so the resolver
344
- can translate user-facing fields (like `appliesTo`) into internal
345
- scoping fields.
346
-
347
- **Properties:**
348
- - `roles: ReadonlySet<string>`
349
- - `skills: ReadonlySet<string>`
350
- - `memberIds: ReadonlySet<string>`
351
-
352
- ### `defineRule`
353
-
354
- Creates a rule entry for use in `ScheduleConfig.rules`.
355
-
356
- Built-in rules use the helpers (`maxHoursPerDay`, `timeOff`, etc.).
357
- Custom rules can use `defineRule` to create entries that plug into the
358
- same resolution and compilation pipeline.
359
-
360
- **Parameters:**
361
- - `name: string` — Rule name. Must match a key in the rule factory registry.
362
- - `fields: Record<string, unknown>` — Rule-specific configuration fields.
363
- - `resolve?: (ctx: RuleResolveContext) => Record<string, unknown> & { name: string }` — Optional custom resolver. When omitted, the default
364
- resolution applies: `appliesTo` is mapped to `roleIds`/`skillIds`/`memberIds`,
365
- `dates` is renamed to `specificDates`, and all other fields pass through.
366
-
367
- **Returns:** `RuleEntry`
368
-
369
- ### `maxHoursPerDay`
370
-
371
- Limits hours per day.
372
-
373
- ```typescript
374
- maxHoursPerDay(10)
375
- maxHoursPerDay(4, { appliesTo: "student", dayOfWeek: weekdays })
376
- ```
377
-
378
- ### `maxHoursPerWeek`
379
-
380
- Limits hours per scheduling week.
381
-
382
- ```typescript
383
- maxHoursPerWeek(48)
384
- maxHoursPerWeek(20, { appliesTo: "student" })
385
- ```
386
-
387
- ### `minHoursPerDay`
388
-
389
- Minimum hours when assigned on a day.
390
-
391
- ```typescript
392
- minHoursPerDay(4)
393
- ```
394
-
395
- ### `minHoursPerWeek`
396
-
397
- Minimum hours per scheduling week.
398
-
399
- ```typescript
400
- minHoursPerWeek(20, { priority: "HIGH" })
401
- ```
402
-
403
- ### `maxShiftsPerDay`
404
-
405
- Maximum distinct shifts per day.
406
-
407
- ```typescript
408
- maxShiftsPerDay(1)
409
- maxShiftsPerDay(2, { appliesTo: "student", dayOfWeek: weekend })
410
- ```
411
-
412
- ### `maxConsecutiveDays`
413
-
414
- Maximum consecutive working days.
415
-
416
- ```typescript
417
- maxConsecutiveDays(5)
418
- ```
419
-
420
- ### `minConsecutiveDays`
421
-
422
- Once working, continue for at least this many consecutive days.
423
-
424
- ```typescript
425
- minConsecutiveDays(2, { priority: "HIGH" })
426
- ```
427
-
428
- ### `minRestBetweenShifts`
429
-
430
- Minimum rest hours between shifts.
431
-
432
- ```typescript
433
- minRestBetweenShifts(10)
434
- ```
435
-
436
- ### `preference`
437
-
438
- Prefer (`"high"`) or avoid (`"low"`) assigning. Requires `appliesTo`.
439
-
440
- ```typescript
441
- preference("high", { appliesTo: "waiter" })
442
- preference("low", { appliesTo: "student", dayOfWeek: weekdays })
443
- ```
444
-
445
- ### `preferLocation`
446
-
447
- Prefer assigning to shifts at a specific location. Requires `appliesTo`.
448
-
449
- ```typescript
450
- preferLocation("terrace", { appliesTo: "alice" })
451
- ```
452
-
453
- ### `timeOff`
454
-
455
- Block assignments during specified periods.
456
- Requires at least one time scope (`dayOfWeek`, `dateRange`, `dates`, or `from`/`until`).
457
-
458
- ```typescript
459
- // Full days off
460
- timeOff({ appliesTo: "alice", dateRange: { start: "2024-02-01", end: "2024-02-05" } })
461
-
462
- // Every weekend off
463
- timeOff({ appliesTo: "mauro", dayOfWeek: weekend })
464
-
465
- // Wednesday afternoons off
466
- timeOff({ appliesTo: "student", dayOfWeek: ["wednesday"], from: t(14) })
467
- ```
468
-
469
- ### `assignTogether`
470
-
471
- Members work the same shifts on days they are both assigned.
472
-
473
- ```typescript
474
- assignTogether(["alice", "bob"])
475
- assignTogether(["alice", "bob", "charlie"], { priority: "HIGH" })
476
- ```
477
-
478
- ---
479
-
480
- ## Cost Optimization
481
-
482
- ### `OvertimeTier`
483
-
484
- A single tier in a tiered overtime configuration.
485
-
486
- ```typescript
487
- { after: number; factor: number; }
488
- ```
489
-
490
- ### `CostRuleOptions`
491
-
492
- Options for cost rules.
493
-
494
- Cost rules are objective terms, not constraints. The `priority` field from
495
- `RuleOptions` does not apply.
496
-
497
- **Properties:**
498
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
499
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restrict to specific days of the week.
500
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
501
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
502
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
503
-
504
- ### `minimizeCost`
505
-
506
- Tells the solver to minimize total labor cost.
507
-
508
- Without this rule, cost modifiers only affect post-solve calculation.
509
- When present, the solver actively prefers cheaper assignments.
510
-
511
- For hourly members, penalizes each assignment proportionally to cost.
512
- For salaried members, adds a fixed weekly salary cost when they have
513
- any assignment that week (zero marginal cost up to contracted hours).
514
-
515
- Cost modifiers adjust the calculation:
516
- - `dayMultiplier(factor, opts?)` - multiply base rate on specific days
517
- - `daySurcharge(amount, opts?)` - flat extra per hour on specific days
518
- - `timeSurcharge(amount, window, opts?)` - flat extra per hour during a time window
519
- - `overtimeMultiplier({ after, factor }, opts?)` - weekly overtime multiplier
520
- - `overtimeSurcharge({ after, amount }, opts?)` - weekly overtime surcharge
521
- - `dailyOvertimeMultiplier({ after, factor }, opts?)` - daily overtime multiplier
522
- - `dailyOvertimeSurcharge({ after, amount }, opts?)` - daily overtime surcharge
523
- - `tieredOvertimeMultiplier(tiers, opts?)` - multiple overtime thresholds
524
-
525
- ```ts
526
- minimizeCost()
527
- ```
528
-
529
- ### `dayMultiplier`
530
-
531
- Multiplies the base rate for assignments on specified days.
532
-
533
- The base cost (1x) is already counted by `minimizeCost`;
534
- this rule adds only the extra portion above 1x.
535
-
536
- Weekend multiplier
537
- ```typescript
538
- dayMultiplier(1.5, { dayOfWeek: weekend })
539
- ```
540
-
541
- ### `daySurcharge`
542
-
543
- Adds a flat extra amount per hour for assignments on specified days.
544
-
545
- The surcharge is independent of the member's base rate.
546
-
547
- Weekend surcharge
548
- ```typescript
549
- daySurcharge(500, { dayOfWeek: weekend })
550
- ```
551
-
552
- ### `timeSurcharge`
553
-
554
- Adds a flat surcharge per hour for the portion of a shift that overlaps a time-of-day window.
555
-
556
- The window supports overnight spans (e.g., 22:00-06:00). The surcharge
557
- is independent of the member's base rate.
558
-
559
- Night differential
560
- ```typescript
561
- timeSurcharge(200, { from: t(22), until: t(6) })
562
- ```
563
-
564
- **Parameters:**
565
- - `amountPerHour: number` — Flat surcharge per hour in smallest currency unit
566
- - `window: { from: TimeOfDay; until: TimeOfDay }` — Time-of-day window
567
- - `opts?: CostRuleOptions` — Entity and time scoping
568
-
569
- **Returns:** `RuleEntry`
570
-
571
- ### `overtimeMultiplier`
572
-
573
- Applies a multiplier to hours beyond a weekly threshold.
574
-
575
- Only the extra portion above 1x is added (the base cost is already
576
- counted by `minimizeCost`).
577
-
578
- ```typescript
579
- overtimeMultiplier({ after: 40, factor: 1.5 })
580
- ```
581
-
582
- ### `overtimeSurcharge`
583
-
584
- Adds a flat surcharge per hour beyond a weekly threshold.
585
-
586
- The surcharge is independent of the member's base rate.
587
-
588
- ```typescript
589
- overtimeSurcharge({ after: 40, amount: 1000 })
590
- ```
591
-
592
- ### `dailyOvertimeMultiplier`
593
-
594
- Applies a multiplier to hours beyond a daily threshold.
595
-
596
- Only the extra portion above 1x is added (the base cost is already
597
- counted by `minimizeCost`).
598
-
599
- ```typescript
600
- dailyOvertimeMultiplier({ after: 8, factor: 1.5 })
601
- ```
602
-
603
- ### `dailyOvertimeSurcharge`
604
-
605
- Adds a flat surcharge per hour beyond a daily threshold.
606
-
607
- The surcharge is independent of the member's base rate.
608
-
609
- ```typescript
610
- dailyOvertimeSurcharge({ after: 8, amount: 500 })
611
- ```
612
-
613
- ### `tieredOvertimeMultiplier`
614
-
615
- Applies multiple overtime thresholds with increasing multipliers.
616
-
617
- Each tier applies only to the hours between its threshold and the next.
618
- Tiers must be sorted by threshold ascending.
619
-
620
- ```typescript
621
- // Hours 0-40: base rate
622
- // Hours 40-48: 1.5x
623
- // Hours 48+: 2.0x
624
- tieredOvertimeMultiplier([
625
- { after: 40, factor: 1.5 },
626
- { after: 48, factor: 2.0 },
627
- ])
628
- ```
629
-
630
- ---
631
-
632
- ## Supporting Types
633
-
634
- ### `DayOfWeek`
635
-
636
- Day of the week identifier.
637
-
638
- ```typescript
639
- "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"
640
- ```
641
-
642
- ### `TimeOfDay`
643
-
644
- Time of day (24-hour format).
645
-
646
- **Properties:**
647
- - `hours: number`
648
- - `minutes: number`
649
-
650
- ### `SchedulingPeriod`
651
-
652
- Defines a scheduling period as a date range with optional filters.
653
-
654
- The `dateRange` specifies the overall scheduling window. Use `dayOfWeek`
655
- and/or `dates` to narrow which days within the range are included.
656
- Filters compose: a day must pass all specified filters to be included.
657
-
658
- All days in a week
659
- ```typescript
660
- const period: SchedulingPeriod = {
661
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
662
- };
663
- ```
664
-
665
- **Properties:**
666
- - `dateRange: { start: string; end: string }` — The overall scheduling window (start and end are inclusive).
667
- Dates should be in YYYY-MM-DD format.
668
- - `dayOfWeek?: readonly DayOfWeek[]` — Include only these days of the week.
669
- If omitted, all days of the week are included.
670
- - `dates?: string[]` — Include only these specific dates (YYYY-MM-DD) within the range.
671
- If omitted, all dates in the range are included (subject to dayOfWeek filter).
672
-
673
- ### `HourlyPay`
674
-
675
- Pay per hour in the caller's smallest currency unit (e.g., pence, cents).
676
-
677
- **Properties:**
678
- - `hourlyRate: number` — Pay per hour in smallest currency unit.
679
-
680
- ### `SalariedPay`
681
-
682
- Annual salary with contracted weekly hours.
683
-
684
- The solver treats salaried members as having a fixed weekly cost
685
- (`annual / 52`) that is incurred once they work any shift in a week.
686
- Additional shifts within the same week have zero marginal cost.
687
-
688
- Note: overtime multiplier rules apply only to hourly members.
689
- Overtime surcharge rules apply to all members regardless of pay type.
690
-
691
- **Properties:**
692
- - `annual: number` — Annual salary in smallest currency unit.
693
- - `hoursPerWeek: number` — Contracted hours per week. Reserved for future overtime support.
694
-
695
- ### `Priority`
696
-
697
- How strictly the solver enforces a rule.
698
-
699
- - `"LOW"`, `"MEDIUM"`, `"HIGH"`: soft constraints with increasing penalty for violations
700
- - `"MANDATORY"`: hard constraint; the solver will not produce a solution that violates it
701
-
702
- ```typescript
703
- "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"
704
- ```
705
-
706
- ### `SchedulingMember`
707
-
708
- A team member available for scheduling.
709
-
710
- Members are assigned to shift patterns by the solver based on
711
- coverage requirements, rules, and constraints.
712
-
713
- **Properties:**
714
- - `id: string` — Unique identifier for this member. Must not contain colons.
715
- - `roleIds: string[]` — Role IDs this member can fill (e.g. "nurse", "doctor").
716
- - `skillIds?: string[]` — Skill IDs this member has (e.g. "charge_nurse", "forklift").
717
- - `pay?: HourlyPay | SalariedPay` — Base pay. Required when cost rules are used.
718
-
719
- ### `ShiftPattern`
720
-
721
- A shift pattern defines WHEN people can work: the time slots available for assignment.
722
-
723
- Shift patterns are templates that repeat across all scheduling days. The solver assigns
724
- team members to these patterns based on coverage requirements and constraints.
725
-
726
- // Simple setup: one shift type, anyone can work it
727
- const patterns: ShiftPattern[] = [
728
- { id: "day", startTime: { hours: 9 }, endTime: { hours: 17 } }
729
- ];
730
-
731
- **Properties:**
732
- - `id: string` — Unique identifier for this shift pattern.
733
- Used in assignments and rule configurations.
734
- - `roleIds?: [string, ...string[]]` — Restricts who can be assigned to this shift based on their role IDs.
735
-
736
- - If omitted: anyone can work this shift
737
- - If provided: only team members whose roles overlap with this list can be assigned
738
-
739
- Most venues have the same shifts for everyone and don't need this.
740
- Use it when different roles have different schedules (e.g., kitchen staff starts
741
- earlier than floor staff).
742
- - `dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]]` — Restricts which days of the week this shift pattern can be used.
743
-
744
- - If omitted: shift can be used on any day
745
- - If provided: shift can only be assigned on the specified days
746
-
747
- ```typescript
748
- // Saturday-only short shift
749
- { id: "saturday_shift", startTime: t(9), endTime: t(14), dayOfWeek: ["saturday"] }
750
-
751
- // Weekday-only full shift
752
- { id: "full_shift", startTime: t(9), endTime: t(18), dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"] }
753
- ```
754
- - `locationId?: string` — Physical location where this shift takes place.
755
- Used for multi-location scheduling and location-based constraints.
756
- - `startTime: TimeOfDay` — When the shift starts (e.g., `{ hours: 9, minutes: 0 }` for 9:00 AM)
757
- - `endTime: TimeOfDay` — When the shift ends (e.g., `{ hours: 17, minutes: 30 }` for 5:30 PM)
758
-