dabke 0.78.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 (194) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +21 -0
  3. package/README.md +187 -0
  4. package/dist/client.d.ts +14 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +42 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/client.schemas.d.ts +250 -0
  9. package/dist/client.schemas.d.ts.map +1 -0
  10. package/dist/client.schemas.js +137 -0
  11. package/dist/client.schemas.js.map +1 -0
  12. package/dist/client.types.d.ts +34 -0
  13. package/dist/client.types.d.ts.map +1 -0
  14. package/dist/client.types.js +18 -0
  15. package/dist/client.types.js.map +1 -0
  16. package/dist/cpsat/model-builder.d.ts +128 -0
  17. package/dist/cpsat/model-builder.d.ts.map +1 -0
  18. package/dist/cpsat/model-builder.js +640 -0
  19. package/dist/cpsat/model-builder.js.map +1 -0
  20. package/dist/cpsat/response.d.ts +74 -0
  21. package/dist/cpsat/response.d.ts.map +1 -0
  22. package/dist/cpsat/response.js +92 -0
  23. package/dist/cpsat/response.js.map +1 -0
  24. package/dist/cpsat/rules/assign-together.d.ts +23 -0
  25. package/dist/cpsat/rules/assign-together.d.ts.map +1 -0
  26. package/dist/cpsat/rules/assign-together.js +78 -0
  27. package/dist/cpsat/rules/assign-together.js.map +1 -0
  28. package/dist/cpsat/rules/employee-assignment-priority.d.ts +64 -0
  29. package/dist/cpsat/rules/employee-assignment-priority.d.ts.map +1 -0
  30. package/dist/cpsat/rules/employee-assignment-priority.js +151 -0
  31. package/dist/cpsat/rules/employee-assignment-priority.js.map +1 -0
  32. package/dist/cpsat/rules/index.d.ts +13 -0
  33. package/dist/cpsat/rules/index.d.ts.map +1 -0
  34. package/dist/cpsat/rules/index.js +13 -0
  35. package/dist/cpsat/rules/index.js.map +1 -0
  36. package/dist/cpsat/rules/location-preference.d.ts +29 -0
  37. package/dist/cpsat/rules/location-preference.d.ts.map +1 -0
  38. package/dist/cpsat/rules/location-preference.js +59 -0
  39. package/dist/cpsat/rules/location-preference.js.map +1 -0
  40. package/dist/cpsat/rules/max-consecutive-days.d.ts +28 -0
  41. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -0
  42. package/dist/cpsat/rules/max-consecutive-days.js +70 -0
  43. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -0
  44. package/dist/cpsat/rules/max-hours-day.d.ts +57 -0
  45. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -0
  46. package/dist/cpsat/rules/max-hours-day.js +159 -0
  47. package/dist/cpsat/rules/max-hours-day.js.map +1 -0
  48. package/dist/cpsat/rules/max-hours-week.d.ts +62 -0
  49. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -0
  50. package/dist/cpsat/rules/max-hours-week.js +169 -0
  51. package/dist/cpsat/rules/max-hours-week.js.map +1 -0
  52. package/dist/cpsat/rules/max-shifts-day.d.ts +69 -0
  53. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -0
  54. package/dist/cpsat/rules/max-shifts-day.js +170 -0
  55. package/dist/cpsat/rules/max-shifts-day.js.map +1 -0
  56. package/dist/cpsat/rules/min-consecutive-days.d.ts +29 -0
  57. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -0
  58. package/dist/cpsat/rules/min-consecutive-days.js +104 -0
  59. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -0
  60. package/dist/cpsat/rules/min-hours-day.d.ts +28 -0
  61. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -0
  62. package/dist/cpsat/rules/min-hours-day.js +61 -0
  63. package/dist/cpsat/rules/min-hours-day.js.map +1 -0
  64. package/dist/cpsat/rules/min-hours-week.d.ts +29 -0
  65. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -0
  66. package/dist/cpsat/rules/min-hours-week.js +68 -0
  67. package/dist/cpsat/rules/min-hours-week.js.map +1 -0
  68. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +28 -0
  69. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -0
  70. package/dist/cpsat/rules/min-rest-between-shifts.js +95 -0
  71. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -0
  72. package/dist/cpsat/rules/registry.d.ts +7 -0
  73. package/dist/cpsat/rules/registry.d.ts.map +1 -0
  74. package/dist/cpsat/rules/registry.js +28 -0
  75. package/dist/cpsat/rules/registry.js.map +1 -0
  76. package/dist/cpsat/rules/resolver.d.ts +31 -0
  77. package/dist/cpsat/rules/resolver.d.ts.map +1 -0
  78. package/dist/cpsat/rules/resolver.js +124 -0
  79. package/dist/cpsat/rules/resolver.js.map +1 -0
  80. package/dist/cpsat/rules/rules.types.d.ts +32 -0
  81. package/dist/cpsat/rules/rules.types.d.ts.map +1 -0
  82. package/dist/cpsat/rules/rules.types.js +2 -0
  83. package/dist/cpsat/rules/rules.types.js.map +1 -0
  84. package/dist/cpsat/rules/scoping.d.ts +129 -0
  85. package/dist/cpsat/rules/scoping.d.ts.map +1 -0
  86. package/dist/cpsat/rules/scoping.js +190 -0
  87. package/dist/cpsat/rules/scoping.js.map +1 -0
  88. package/dist/cpsat/rules/time-off.d.ts +78 -0
  89. package/dist/cpsat/rules/time-off.d.ts.map +1 -0
  90. package/dist/cpsat/rules/time-off.js +261 -0
  91. package/dist/cpsat/rules/time-off.js.map +1 -0
  92. package/dist/cpsat/rules.d.ts +5 -0
  93. package/dist/cpsat/rules.d.ts.map +1 -0
  94. package/dist/cpsat/rules.js +4 -0
  95. package/dist/cpsat/rules.js.map +1 -0
  96. package/dist/cpsat/semantic-time.d.ts +198 -0
  97. package/dist/cpsat/semantic-time.d.ts.map +1 -0
  98. package/dist/cpsat/semantic-time.js +222 -0
  99. package/dist/cpsat/semantic-time.js.map +1 -0
  100. package/dist/cpsat/types.d.ts +180 -0
  101. package/dist/cpsat/types.d.ts.map +1 -0
  102. package/dist/cpsat/types.js +2 -0
  103. package/dist/cpsat/types.js.map +1 -0
  104. package/dist/cpsat/utils.d.ts +47 -0
  105. package/dist/cpsat/utils.d.ts.map +1 -0
  106. package/dist/cpsat/utils.js +92 -0
  107. package/dist/cpsat/utils.js.map +1 -0
  108. package/dist/cpsat/validation-reporter.d.ts +54 -0
  109. package/dist/cpsat/validation-reporter.d.ts.map +1 -0
  110. package/dist/cpsat/validation-reporter.js +261 -0
  111. package/dist/cpsat/validation-reporter.js.map +1 -0
  112. package/dist/cpsat/validation.types.d.ts +141 -0
  113. package/dist/cpsat/validation.types.d.ts.map +1 -0
  114. package/dist/cpsat/validation.types.js +14 -0
  115. package/dist/cpsat/validation.types.js.map +1 -0
  116. package/dist/datetime.utils.d.ts +245 -0
  117. package/dist/datetime.utils.d.ts.map +1 -0
  118. package/dist/datetime.utils.js +372 -0
  119. package/dist/datetime.utils.js.map +1 -0
  120. package/dist/errors.d.ts +12 -0
  121. package/dist/errors.d.ts.map +1 -0
  122. package/dist/errors.js +17 -0
  123. package/dist/errors.js.map +1 -0
  124. package/dist/index.d.ts +112 -0
  125. package/dist/index.d.ts.map +1 -0
  126. package/dist/index.js +116 -0
  127. package/dist/index.js.map +1 -0
  128. package/dist/llms.d.ts +5 -0
  129. package/dist/llms.d.ts.map +1 -0
  130. package/dist/llms.js +8 -0
  131. package/dist/llms.js.map +1 -0
  132. package/dist/testing/index.d.ts +12 -0
  133. package/dist/testing/index.d.ts.map +1 -0
  134. package/dist/testing/index.js +11 -0
  135. package/dist/testing/index.js.map +1 -0
  136. package/dist/testing/solver-container.d.ts +49 -0
  137. package/dist/testing/solver-container.d.ts.map +1 -0
  138. package/dist/testing/solver-container.js +127 -0
  139. package/dist/testing/solver-container.js.map +1 -0
  140. package/dist/types.d.ts +155 -0
  141. package/dist/types.d.ts.map +1 -0
  142. package/dist/types.js +20 -0
  143. package/dist/types.js.map +1 -0
  144. package/dist/validation.d.ts +105 -0
  145. package/dist/validation.d.ts.map +1 -0
  146. package/dist/validation.js +130 -0
  147. package/dist/validation.js.map +1 -0
  148. package/llms.txt +2188 -0
  149. package/package.json +76 -0
  150. package/solver/Dockerfile +31 -0
  151. package/solver/README.md +23 -0
  152. package/solver/pyproject.toml +28 -0
  153. package/solver/src/solver/__init__.py +1 -0
  154. package/solver/src/solver/app.py +24 -0
  155. package/solver/src/solver/models.py +120 -0
  156. package/solver/src/solver/solver.py +359 -0
  157. package/solver/tests/test_solver.py +156 -0
  158. package/solver/uv.lock +661 -0
  159. package/src/client.schemas.ts +163 -0
  160. package/src/client.ts +67 -0
  161. package/src/client.types.ts +66 -0
  162. package/src/cpsat/model-builder.ts +858 -0
  163. package/src/cpsat/response.ts +130 -0
  164. package/src/cpsat/rules/assign-together.ts +96 -0
  165. package/src/cpsat/rules/employee-assignment-priority.ts +182 -0
  166. package/src/cpsat/rules/index.ts +12 -0
  167. package/src/cpsat/rules/location-preference.ts +68 -0
  168. package/src/cpsat/rules/max-consecutive-days.ts +98 -0
  169. package/src/cpsat/rules/max-hours-day.ts +187 -0
  170. package/src/cpsat/rules/max-hours-week.ts +197 -0
  171. package/src/cpsat/rules/max-shifts-day.ts +198 -0
  172. package/src/cpsat/rules/min-consecutive-days.ts +140 -0
  173. package/src/cpsat/rules/min-hours-day.ts +69 -0
  174. package/src/cpsat/rules/min-hours-week.ts +77 -0
  175. package/src/cpsat/rules/min-rest-between-shifts.ts +121 -0
  176. package/src/cpsat/rules/registry.ts +49 -0
  177. package/src/cpsat/rules/resolver.ts +181 -0
  178. package/src/cpsat/rules/rules.types.ts +41 -0
  179. package/src/cpsat/rules/scoping.ts +340 -0
  180. package/src/cpsat/rules/time-off.ts +336 -0
  181. package/src/cpsat/rules.ts +27 -0
  182. package/src/cpsat/semantic-time.ts +463 -0
  183. package/src/cpsat/types.ts +194 -0
  184. package/src/cpsat/utils.ts +105 -0
  185. package/src/cpsat/validation-reporter.ts +366 -0
  186. package/src/cpsat/validation.types.ts +185 -0
  187. package/src/datetime.utils.ts +426 -0
  188. package/src/errors.ts +17 -0
  189. package/src/index.ts +289 -0
  190. package/src/llms.ts +9 -0
  191. package/src/testing/index.ts +12 -0
  192. package/src/testing/solver-container.ts +172 -0
  193. package/src/types.ts +191 -0
  194. package/src/validation.ts +188 -0
package/llms.txt ADDED
@@ -0,0 +1,2188 @@
1
+ # dabke
2
+
3
+ > Scheduling library powered by constraint programming (CP-SAT)
4
+
5
+ Scheduling library powered by constraint programming (CP-SAT).
6
+
7
+ Define teams, shifts, coverage, and rules — dabke turns them
8
+ into an optimized schedule.
9
+
10
+ ## Core Concepts
11
+
12
+ **ModelBuilder**: Creates the constraint programming model from your team,
13
+ shift patterns, coverage requirements, and rules.
14
+
15
+ **Semantic Time**: Flexible time period definitions that can vary by day or date.
16
+ - `{ name: "morning", startTime: { hours: 8 }, endTime: { hours: 12 } }`
17
+ - The same semantic name can map to different times based on context
18
+ - Enables business-friendly scheduling: "Need 3 waiters during lunch_rush"
19
+
20
+ **Rules System**: Translate business requirements into scheduling constraints.
21
+ - 12 built-in rules: hours limits, time-off, rest periods, prioritization, etc.
22
+ - Scoping: Apply rules globally, per person, per role, or per time period
23
+ - Priority levels: MANDATORY (hard constraint) vs LOW/MEDIUM/HIGH (soft preferences)
24
+
25
+ ---
26
+
27
+ # CP-SAT Scheduling API
28
+
29
+ Core scheduling types for the CP-SAT solver.
30
+
31
+ ## Interfaces
32
+
33
+ ### TimeOfDay
34
+ Time of day representation (hours and minutes, with optional seconds/nanos).
35
+
36
+ Used for defining shift start/end times and semantic time boundaries.
37
+ Hours are in 24-hour format (0-23).
38
+
39
+ **Example:**
40
+ ```typescript
41
+ const morningStart: TimeOfDay = {
42
+ hours: 9,
43
+ minutes: 0
44
+ };
45
+
46
+ const afternoonEnd: TimeOfDay = {
47
+ hours: 17,
48
+ minutes: 30
49
+ };
50
+ ```
51
+
52
+ **Properties:**
53
+ - `hours: number`
54
+ - `minutes: number`
55
+ - `seconds?: number | undefined`
56
+ - `nanos?: number | undefined`
57
+
58
+ ### CalendarDate
59
+ Calendar date representation (year, month, day).
60
+
61
+ **Example:**
62
+ ```typescript
63
+ const christmas: CalendarDate = {
64
+ year: 2025,
65
+ month: 12,
66
+ day: 25
67
+ };
68
+ ```
69
+
70
+ **Properties:**
71
+ - `year: number`
72
+ - `month: number`
73
+ - `day: number`
74
+
75
+ ### TimeHorizon
76
+ Time horizon defining the start and end dates for scheduling.
77
+
78
+ Specifies the date range over which the schedule should be generated.
79
+ The range is inclusive of start date and exclusive of end date.
80
+
81
+ **Example:**
82
+ ```typescript
83
+ // One week schedule starting Monday, March 3, 2025
84
+ const horizon: TimeHorizon = {
85
+ start: new Date('2025-03-03'), // Monday
86
+ end: new Date('2025-03-10') // Following Monday (exclusive)
87
+ };
88
+ ```
89
+
90
+ **Properties:**
91
+ - `start: Date`
92
+ - `end: Date`
93
+
94
+ ### DateTimeComponents
95
+ **Properties:**
96
+ - `year?: number | undefined`
97
+ - `month?: number | undefined`
98
+ - `day?: number | undefined`
99
+ - `hours?: number | undefined`
100
+ - `minutes?: number | undefined`
101
+ - `seconds?: number | undefined`
102
+ - `nanos?: number | undefined`
103
+
104
+ ### DateTimeRange
105
+ Represents a time range with start and end DateTimes.
106
+ Used for checking overlaps and scheduling constraints.
107
+
108
+ **Properties:**
109
+ - `start: DateTimeWithUtcOffset | DateTimeWithTimeZone`
110
+ - `end: DateTimeWithUtcOffset | DateTimeWithTimeZone`
111
+
112
+ ### SolverClient
113
+ **Properties:**
114
+ - `solve: (request: SolverRequest, options?: { signal?: AbortSignal; }) => Promise<SolverResponse>`
115
+ - `health?: (() => Promise<void>) | undefined`
116
+
117
+ ### ModelBuilderConfig
118
+ Configuration for ModelBuilder.
119
+
120
+ **Example:**
121
+ Date range with day-of-week filtering (restaurant closed Mon/Tue)
122
+ ```typescript
123
+ const config: ModelBuilderConfig = {
124
+ employees: [...],
125
+ shiftPatterns: [...],
126
+ coverage: [...],
127
+ schedulingPeriod: {
128
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
129
+ daysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
130
+ },
131
+ };
132
+ ```
133
+
134
+ **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.
139
+ - `coverage: CoverageRequirement[]`
140
+ - `rules?: CompilationRule[] | undefined` - Pre-compiled rules; use this for custom rules that are not part of the registry.
141
+ - `ruleConfigs?: CpsatRuleConfigEntry[] | undefined` - Named rule configurations that will be compiled using the provided rule factories.
142
+ - `ruleFactories?: CpsatRuleFactories | undefined` - Rule factories to use when compiling ruleConfigs. Defaults to built-in CP-SAT rules.
143
+ - `reporter?: ValidationReporter | undefined` - Optional validation reporter for diagnostics.
144
+
145
+ ### CompilationResult
146
+ **Properties:**
147
+ - `request: { 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; }`
148
+ - `validation: ScheduleValidation`
149
+ - `canSolve: boolean`
150
+
151
+ ### CompilationRule
152
+ **Properties:**
153
+ - `compile: (builder: ModelBuilder) => void`
154
+ - `validate?: ((assignments: ResolvedShiftAssignment[], reporter: ValidationReporter, context: RuleValidationContext) => void) | undefined`
155
+
156
+ ### RuleValidationContext
157
+ Context provided to rules during post-solve validation.
158
+
159
+ **Properties:**
160
+ - `employees: SchedulingEmployee[]`
161
+ - `days: string[]`
162
+ - `shiftPatterns: ShiftPattern[]`
163
+
164
+ ### ShiftAssignment
165
+ A shift assignment extracted from the solver response.
166
+
167
+ **Properties:**
168
+ - `employeeId: string`
169
+ - `shiftPatternId: string`
170
+ - `day: string`
171
+
172
+ ### ResolvedShiftAssignment
173
+ A shift assignment with resolved times.
174
+
175
+ **Properties:**
176
+ - `employeeId: string`
177
+ - `day: string`
178
+ - `startTime: TimeOfDay`
179
+ - `endTime: TimeOfDay`
180
+
181
+ ### SolverResult
182
+ Parsed solver result with assignments and metadata.
183
+
184
+ **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`
189
+
190
+ ### CpsatRuleRegistry
191
+ **Properties:**
192
+ - `"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; }`
204
+
205
+ ### SemanticTimeDef
206
+ Base definition for a semantic time period.
207
+
208
+ **Properties:**
209
+ - `startTime: TimeOfDay`
210
+ - `endTime: TimeOfDay`
211
+
212
+ ### SemanticTimeVariant
213
+ Variant of a semantic time that applies to specific days or dates.
214
+
215
+ **Properties:**
216
+ - `days?: DayOfWeek[] | undefined` - Apply this variant only on these days of the week
217
+ - `dates?: string[] | undefined` - Apply this variant only on these specific dates (YYYY-MM-DD)
218
+
219
+ ### SemanticTimeContext
220
+ Result of defineSemanticTimes - provides type-safe coverage function.
221
+
222
+ **Properties:**
223
+ - `defs: { [P in S]: SemanticTimeEntry; }` - The semantic time definitions
224
+ - `coverage: (reqs: MixedCoverageRequirement<S>[]) => MixedCoverageRequirement<S>[]` - Create coverage requirements with type-safe semantic time names.
225
+ Accepts both semantic references and concrete one-off requirements.
226
+ - `resolve: (reqs: MixedCoverageRequirement<S>[], days: string[]) => CoverageRequirement[]` - Resolve all coverage requirements to concrete CoverageRequirement[]
227
+ for the given days in the scheduling horizon.
228
+
229
+ ### SchedulingEmployee
230
+ **Properties:**
231
+ - `id: string`
232
+ - `roleIds: string[]`
233
+ - `skillIds?: string[] | undefined`
234
+
235
+ ### ShiftPattern
236
+ A shift pattern defines WHEN people can work — the time slots available for assignment.
237
+
238
+ Shift patterns are templates that repeat across all scheduling days. The solver assigns
239
+ team members to these patterns based on coverage requirements and constraints.
240
+
241
+ **Example:**
242
+ // Simple venue: one shift type, anyone can work it
243
+ const patterns: ShiftPattern[] = [
244
+ { id: "day", startTime: { hours: 9 }, endTime: { hours: 17 } }
245
+ ];
246
+
247
+ **Example:**
248
+ // Restaurant: different shifts for different roles
249
+ const patterns: ShiftPattern[] = [
250
+ { id: "kitchen_morning", startTime: { hours: 6 }, endTime: { hours: 14 }, roleIds: ["chef", "prep_cook"] },
251
+ { id: "floor_lunch", startTime: { hours: 11 }, endTime: { hours: 15 }, roleIds: ["waiter", "host"] },
252
+ ];
253
+
254
+ **Properties:**
255
+ - `id: string` - Unique identifier for this shift pattern.
256
+ Used in assignments and rule configurations.
257
+ - `roleIds?: [string, ...string[]] | undefined` - Restricts who can be assigned to this shift based on their roles.
258
+
259
+ - If omitted: anyone can work this shift
260
+ - If provided: only team members whose roleIds overlap with this list can be assigned
261
+
262
+ Most venues have the same shifts for everyone and don't need this.
263
+ Use it when different roles have different schedules (e.g., kitchen staff starts
264
+ earlier than floor staff).
265
+ - `daysOfWeek?: DayOfWeek[] | undefined` - Restricts which days of the week this shift pattern can be used.
266
+
267
+ - If omitted: shift can be used on any day
268
+ - If provided: shift can only be assigned on the specified days
269
+
270
+ **Example:**
271
+ ```typescript
272
+ // Saturday-only short shift
273
+ { id: "saturday_shift", startTime: t(9), endTime: t(14), daysOfWeek: ["saturday"] }
274
+
275
+ // Weekday-only full shift
276
+ { id: "full_shift", startTime: t(9), endTime: t(18), daysOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"] }
277
+ ```
278
+ - `locationId?: string | undefined` - Physical location where this shift takes place.
279
+ 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)
282
+
283
+ ### TimeInterval
284
+ **Properties:**
285
+ - `day: string`
286
+ - `startTime: TimeOfDay`
287
+ - `endTime: TimeOfDay`
288
+
289
+ ### ModelBuilderOptions
290
+ **Properties:**
291
+ - `weekStartsOn?: DayOfWeek | undefined`
292
+ - `solverOptions?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined`
293
+ - `coverageBucketMinutes?: number | undefined` - Bucket size used when translating coverage requirements into time-indexed constraints.
294
+ Smaller buckets are more accurate but increase the number of constraints.
295
+ - `fairDistribution?: boolean | undefined` - Whether to enable fair distribution of shifts across team members.
296
+
297
+ When enabled (default), the solver minimizes the maximum number of shifts
298
+ any single person works, ensuring work is distributed evenly. Each person
299
+ works between floor(total/n) and ceil(total/n) shifts.
300
+
301
+ Disable this if you want other rules (like employee-assignment-priority)
302
+ to have full control over shift distribution.
303
+
304
+ ### ValidationReporter
305
+ **Properties:**
306
+ - `excludeFromCoverage: (exclusion: CoverageExclusion) => void`
307
+ - `reportCoverageError: (error: Omit<CoverageError, "type" | "id">) => void`
308
+ - `reportRuleError: (error: Omit<RuleError, "type" | "id">) => void`
309
+ - `reportSolverError: (reason: string) => void`
310
+ - `reportCoverageViolation: (violation: Omit<CoverageViolation, "type" | "id">) => void`
311
+ - `reportRuleViolation: (violation: Omit<RuleViolation, "type" | "id">) => void`
312
+ - `reportCoveragePassed: (passed: Omit<CoveragePassed, "type" | "id">) => void`
313
+ - `reportRulePassed: (passed: Omit<RulePassed, "type" | "id">) => void`
314
+ - `trackConstraint: (constraint: TrackedConstraint) => void`
315
+ - `hasErrors: () => boolean`
316
+ - `getValidation: () => ScheduleValidation`
317
+ - `getExclusions: () => CoverageExclusion[]`
318
+ - `analyzeSolution: (response: SolverResponse) => void`
319
+
320
+ ### TrackedConstraint
321
+ **Properties:**
322
+ - `id: string`
323
+ - `type: "coverage" | "rule"`
324
+ - `rule?: string | undefined`
325
+ - `description: string`
326
+ - `targetValue: number`
327
+ - `comparator: "<=" | ">="`
328
+ - `day?: string | undefined`
329
+ - `timeSlot?: string | undefined`
330
+ - `roleIds?: string[] | undefined`
331
+ - `skillIds?: readonly string[] | undefined`
332
+ - `context: ValidationContext`
333
+ - `groupKey?: GroupKey | undefined`
334
+
335
+ ### CoverageExclusion
336
+ Coverage exclusion - indicates a team member is unavailable for coverage during a time period.
337
+ Used during compile-time to determine coverage feasibility.
338
+
339
+ **Properties:**
340
+ - `employeeId: string`
341
+ - `day: string`
342
+ - `startTime?: TimeOfDay | undefined`
343
+ - `endTime?: TimeOfDay | undefined`
344
+
345
+ ### ScheduleValidation
346
+ **Properties:**
347
+ - `errors: readonly ScheduleError[]`
348
+ - `violations: readonly ScheduleViolation[]`
349
+ - `passed: readonly SchedulePassed[]`
350
+
351
+ ### CoverageError
352
+ **Properties:**
353
+ - `id: string`
354
+ - `type: "coverage"`
355
+ - `day: string`
356
+ - `timeSlots: readonly string[]`
357
+ - `roleIds?: string[] | undefined`
358
+ - `skillIds?: readonly string[] | undefined`
359
+ - `reason: string`
360
+ - `suggestions?: readonly string[] | undefined`
361
+ - `groupKey?: GroupKey | undefined`
362
+
363
+ ### CoverageViolation
364
+ **Properties:**
365
+ - `id: string`
366
+ - `type: "coverage"`
367
+ - `day: string`
368
+ - `timeSlots: readonly string[]`
369
+ - `roleIds?: string[] | undefined`
370
+ - `skillIds?: readonly string[] | undefined`
371
+ - `targetCount: number`
372
+ - `actualCount: number`
373
+ - `shortfall: number`
374
+ - `groupKey?: GroupKey | undefined`
375
+
376
+ ### CoveragePassed
377
+ **Properties:**
378
+ - `id: string`
379
+ - `type: "coverage"`
380
+ - `day: string`
381
+ - `timeSlots: readonly string[]`
382
+ - `roleIds?: string[] | undefined`
383
+ - `skillIds?: readonly string[] | undefined`
384
+ - `description: string`
385
+ - `groupKey?: GroupKey | undefined`
386
+
387
+ ### RuleError
388
+ **Properties:**
389
+ - `id: string`
390
+ - `type: "rule"`
391
+ - `rule: string`
392
+ - `reason: string`
393
+ - `context: ValidationContext`
394
+ - `suggestions?: readonly string[] | undefined`
395
+ - `groupKey?: GroupKey | undefined`
396
+
397
+ ### RuleViolation
398
+ **Properties:**
399
+ - `id: string`
400
+ - `type: "rule"`
401
+ - `rule: string`
402
+ - `reason: string`
403
+ - `context: ValidationContext`
404
+ - `shortfall?: number | undefined`
405
+ - `overflow?: number | undefined`
406
+ - `groupKey?: GroupKey | undefined`
407
+
408
+ ### RulePassed
409
+ **Properties:**
410
+ - `id: string`
411
+ - `type: "rule"`
412
+ - `rule: string`
413
+ - `description: string`
414
+ - `context: ValidationContext`
415
+ - `groupKey?: GroupKey | undefined`
416
+
417
+ ### SolverError
418
+ **Properties:**
419
+ - `id: string`
420
+ - `type: "solver"`
421
+ - `reason: string`
422
+
423
+ ### ValidationContext
424
+ Context shared across validation results for grouping/display.
425
+
426
+ **Properties:**
427
+ - `days?: string[] | undefined`
428
+ - `timeSlots?: string[] | undefined`
429
+ - `employeeIds?: string[] | undefined`
430
+
431
+ ### ValidationSummary
432
+ Summary of validation items grouped by their source instruction.
433
+ Use `summarizeValidation()` to create these from a ScheduleValidation.
434
+
435
+ **Properties:**
436
+ - `groupKey: string & { readonly [GroupKeyBrand]: never; }`
437
+ - `type: "coverage" | "rule"`
438
+ - `description: string`
439
+ - `days: readonly string[]`
440
+ - `status: "passed" | "partial" | "failed"`
441
+ - `passedCount: number`
442
+ - `violatedCount: number`
443
+ - `errorCount: number`
444
+
445
+ ### CoverageValidationResult
446
+ Result of coverage role validation.
447
+
448
+ **Properties:**
449
+ - `valid: boolean`
450
+ - `unknownRoles: string[]` - Role IDs used in coverage that don't match any team member
451
+ - `knownRoles: string[]` - Role IDs used in coverage that match team members
452
+
453
+ ### SkillValidationResult
454
+ Result of coverage skill validation.
455
+
456
+ **Properties:**
457
+ - `valid: boolean`
458
+ - `unknownSkills: string[]` - Skill IDs used in coverage that don't match any team member
459
+ - `knownSkills: string[]` - Skill IDs used in coverage that match team members
460
+
461
+ ### CoverageConfigValidationResult
462
+ Combined validation result for coverage requirements.
463
+
464
+ **Properties:**
465
+ - `valid: boolean`
466
+ - `roles: CoverageValidationResult`
467
+ - `skills: SkillValidationResult`
468
+ - `errors: string[]` - Human-readable error messages
469
+
470
+ ## Type Aliases
471
+
472
+ ### DayOfWeek
473
+ ```typescript
474
+ "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"
475
+ ```
476
+
477
+
478
+ ### DateTime
479
+ Date and time representation supporting both UTC offset and timezone-aware formats.
480
+
481
+ Can be specified either with a UTC offset (e.g., "-08:00") or with a timezone ID
482
+ (e.g., "America/Los_Angeles").
483
+
484
+ ```typescript
485
+ DateTimeWithUtcOffset | DateTimeWithTimeZone
486
+ ```
487
+
488
+
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
+ ```
523
+
524
+
525
+ ### SolverRequest
526
+ ```typescript
527
+ { 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
+ ```
529
+
530
+
531
+ ### SolverResponse
532
+ ```typescript
533
+ { 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
+ ```
535
+
536
+
537
+ ### SolverVariable
538
+ ```typescript
539
+ { 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
+ ```
541
+
542
+
543
+ ### SolverConstraint
544
+ ```typescript
545
+ { 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
+ ```
547
+
548
+
549
+ ### SolverTerm
550
+ ```typescript
551
+ { var: string; coeff: number; }
552
+ ```
553
+
554
+
555
+ ### SolverObjective
556
+ ```typescript
557
+ { sense: "minimize" | "maximize"; terms: { var: string; coeff: number; }[]; }
558
+ ```
559
+
560
+
561
+ ### SolverStatus
562
+ ```typescript
563
+ "OPTIMAL" | "FEASIBLE" | "INFEASIBLE" | "TIMEOUT" | "ERROR"
564
+ ```
565
+
566
+
567
+ ### SoftConstraintViolation
568
+ ```typescript
569
+ { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }
570
+ ```
571
+
572
+
573
+ ### FetcherLike
574
+ ```typescript
575
+ ((input: string | URL | Request, init?: RequestInit) => Promise<Response>) | { fetch: typeof fetch; }
576
+ ```
577
+
578
+
579
+ ### AssignTogetherConfig
580
+ ```typescript
581
+ { groupEmployeeIds: [string, string, ...string[]]; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; }
582
+ ```
583
+
584
+
585
+ ### EmployeeAssignmentPriorityConfig
586
+ ```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; }
588
+ ```
589
+
590
+
591
+ ### LocationPreferenceConfig
592
+ ```typescript
593
+ { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
594
+ ```
595
+
596
+
597
+ ### MaxConsecutiveDaysConfig
598
+ ```typescript
599
+ { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
600
+ ```
601
+
602
+
603
+ ### MaxHoursDayConfig
604
+ ```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; }
606
+ ```
607
+
608
+
609
+ ### MaxHoursWeekConfig
610
+ ```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; }
612
+ ```
613
+
614
+
615
+ ### MaxShiftsDayConfig
616
+ ```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; }
618
+ ```
619
+
620
+
621
+ ### MinConsecutiveDaysConfig
622
+ ```typescript
623
+ { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
624
+ ```
625
+
626
+
627
+ ### MinHoursDayConfig
628
+ ```typescript
629
+ { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
630
+ ```
631
+
632
+
633
+ ### MinHoursWeekConfig
634
+ ```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; }
636
+ ```
637
+
638
+
639
+ ### MinRestBetweenShiftsConfig
640
+ ```typescript
641
+ { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }
642
+ ```
643
+
644
+
645
+ ### TimeOffConfig
646
+ ```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; }
648
+ ```
649
+
650
+
651
+ ### CpsatRuleName
652
+ ```typescript
653
+ keyof CpsatRuleRegistry
654
+ ```
655
+
656
+
657
+ ### CpsatRuleConfigEntry
658
+ ```typescript
659
+ { name: K; config: CpsatRuleRegistry[K]; }
660
+ ```
661
+
662
+
663
+ ### CpsatRuleFactories
664
+ ```typescript
665
+ { [ruleName: string]: CreateCpsatRuleFunction<any>; }
666
+ ```
667
+
668
+
669
+ ### BuiltInCpsatRuleFactories
670
+ ```typescript
671
+ [Complex type: __type]
672
+ ```
673
+
674
+
675
+ ### CreateCpsatRuleFunction
676
+ ```typescript
677
+ (config: TConfig) => CompilationRule
678
+ ```
679
+
680
+
681
+ ### CpsatRuleRegistryFromFactories
682
+ ```typescript
683
+ { [K in keyof F]: InferCpsatRuleConfig<F[K]>; }
684
+ ```
685
+
686
+
687
+ ### SemanticTimeEntry
688
+ A semantic time can be a simple definition (applies every day)
689
+ or an array of variants with different times for different days/dates.
690
+
691
+ ```typescript
692
+ SemanticTimeDef | SemanticTimeVariant[]
693
+ ```
694
+
695
+
696
+ ### SemanticCoverageRequirement
697
+ Coverage requirement that references a semantic time by name.
698
+ Type-safe: S is constrained to known semantic time names.
699
+
700
+ This is a discriminated union enforcing at compile time that at least
701
+ one of `roleIds` or `skillIds` must be provided.
702
+
703
+ ```typescript
704
+ RoleBasedSemanticCoverageRequirement<S> | SkillBasedSemanticCoverageRequirement<S>
705
+ ```
706
+
707
+
708
+ ### ConcreteCoverageRequirement
709
+ Concrete coverage requirement with explicit day and times.
710
+ Used for one-off requirements that don't fit a semantic time.
711
+
712
+ This is a discriminated union enforcing at compile time that at least
713
+ one of `roleIds` or `skillIds` must be provided.
714
+
715
+ ```typescript
716
+ RoleBasedConcreteCoverageRequirement | SkillBasedConcreteCoverageRequirement
717
+ ```
718
+
719
+
720
+ ### MixedCoverageRequirement
721
+ Union type for coverage - either semantic (type-safe) or concrete.
722
+
723
+ ```typescript
724
+ ConcreteCoverageRequirement | SemanticCoverageRequirement<S>
725
+ ```
726
+
727
+
728
+ ### Employee
729
+ ```typescript
730
+ SchedulingEmployee
731
+ ```
732
+
733
+
734
+ ### CoverageRequirement
735
+ Defines staffing needs for a specific time period.
736
+
737
+ This is a discriminated union that enforces at compile time that at least
738
+ one of `roleIds` or `skillIds` must be provided:
739
+
740
+ - Role-based: `{ roleIds: ["waiter"], ... }` - anyone with ANY of these roles (OR logic)
741
+ - Role + skill: `{ roleIds: ["waiter"], skillIds: ["senior"], ... }` - role AND skills
742
+ - Skill-only: `{ skillIds: ["keyholder"], ... }` - any role with ALL skills (AND logic)
743
+
744
+ **Example:**
745
+ // Need 2 waiters during lunch (role-based)
746
+ { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 14 }, roleIds: ["waiter"], targetCount: 2, priority: "MANDATORY" }
747
+
748
+ **Example:**
749
+ // Need 1 manager OR supervisor during service (OR logic on roles)
750
+ { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 22 }, roleIds: ["manager", "supervisor"], targetCount: 1, priority: "MANDATORY" }
751
+
752
+ **Example:**
753
+ // Need 1 keyholder for opening (skill-only, any role)
754
+ { day: "2024-01-01", startTime: { hours: 6 }, endTime: { hours: 8 }, skillIds: ["keyholder"], targetCount: 1, priority: "MANDATORY" }
755
+
756
+ **Example:**
757
+ // Need 1 senior waiter for training shift (role + skill filter)
758
+ { day: "2024-01-01", startTime: { hours: 9 }, endTime: { hours: 17 }, roleIds: ["waiter"], skillIds: ["senior"], targetCount: 1, priority: "HIGH" }
759
+
760
+ ```typescript
761
+ RoleBasedCoverageRequirement | SkillBasedCoverageRequirement
762
+ ```
763
+
764
+
765
+ ### Priority
766
+ ```typescript
767
+ "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"
768
+ ```
769
+
770
+
771
+ ### ScheduleError
772
+ ```typescript
773
+ CoverageError | RuleError | SolverError
774
+ ```
775
+
776
+
777
+ ### ScheduleViolation
778
+ ```typescript
779
+ CoverageViolation | RuleViolation
780
+ ```
781
+
782
+
783
+ ### SchedulePassed
784
+ ```typescript
785
+ CoveragePassed | RulePassed
786
+ ```
787
+
788
+
789
+ ### GroupKey
790
+ Branded type for validation group keys.
791
+ Groups related validation items that originated from the same instruction.
792
+
793
+ ```typescript
794
+ string & { readonly [GroupKeyBrand]: never; }
795
+ ```
796
+
797
+
798
+ ### InferCpsatRuleConfig
799
+ ```typescript
800
+ T extends CreateCpsatRuleFunction<infer Config> ? Config : never
801
+ ```
802
+
803
+
804
+ ### Term
805
+ ```typescript
806
+ { var: string; coeff: number; }
807
+ ```
808
+
809
+
810
+ ## Functions
811
+
812
+ ### dateToCalendarDate
813
+ Converts a JavaScript Date to a CalendarDate
814
+
815
+ **Parameters:**
816
+ - `date: Date`
817
+
818
+ **Returns:** `CalendarDate`
819
+
820
+
821
+ ### dateTimeToDate
822
+ Converts a DateTime to a JavaScript Date
823
+ Internal helper function
824
+
825
+ **Parameters:**
826
+ - `dateTime: DateTimeWithUtcOffset | DateTimeWithTimeZone`
827
+
828
+ **Returns:** `Date`
829
+
830
+
831
+ ### compareDateTimes
832
+ Compares two DateTimes
833
+ Returns:
834
+ -1 if dateTime1 < dateTime2
835
+ 0 if dateTime1 = dateTime2
836
+ 1 if dateTime1 > dateTime2
837
+
838
+ **Parameters:**
839
+ - `dateTime1: DateTimeWithUtcOffset | DateTimeWithTimeZone`
840
+ - `dateTime2: DateTimeWithUtcOffset | DateTimeWithTimeZone`
841
+
842
+ **Returns:** `number`
843
+
844
+
845
+ ### toDayOfWeek
846
+ Helper to get the day of week name from a Date (local time)
847
+
848
+ **Parameters:**
849
+ - `date: Date`
850
+
851
+ **Returns:** `"monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"`
852
+
853
+
854
+ ### toDayOfWeekUTC
855
+ Helper to get the day of week name from a Date (UTC)
856
+ Use this when working with date strings like "2026-01-10" that are timezone-agnostic.
857
+
858
+ **Parameters:**
859
+ - `date: Date`
860
+
861
+ **Returns:** `"monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"`
862
+
863
+
864
+ ### formatDateString
865
+ Formats a date as YYYY-MM-DD string
866
+
867
+ **Parameters:**
868
+ - `date: Date`
869
+
870
+ **Returns:** `string`
871
+
872
+
873
+ ### generateDays
874
+ Generates an array of day strings (YYYY-MM-DD) from a time horizon.
875
+
876
+ **Example:**
877
+ ```typescript
878
+ const days = generateDays({
879
+ start: new Date('2025-01-01'),
880
+ end: new Date('2025-01-04')
881
+ });
882
+ // Returns: ["2025-01-01", "2025-01-02", "2025-01-03", "2025-01-04"]
883
+ ```
884
+
885
+ **Parameters:**
886
+ - `horizon: { start: Date; end: Date; }`
887
+
888
+ **Returns:** `string[]`
889
+
890
+
891
+ ### splitPeriodIntoDays
892
+ Splits a time period into consecutive day ranges.
893
+
894
+ Each range represents a single calendar day within the period from start to end.
895
+ This is useful for rules that need to apply constraints on a per-day basis,
896
+ such as maximum or minimum hours per day.
897
+
898
+ **Example:**
899
+ ```typescript
900
+ const ranges = splitPeriodIntoDays({
901
+ start: new Date('2025-01-01'),
902
+ end: new Date('2025-01-03')
903
+ });
904
+ // Returns:
905
+ // [
906
+ // [Date('2025-01-01'), Date('2025-01-02')],
907
+ // [Date('2025-01-02'), Date('2025-01-03')]
908
+ // ]
909
+ ```
910
+
911
+ **Parameters:**
912
+ - `{ start, end }: { start: Date; end: Date; }`
913
+
914
+ **Returns:** `[Date, Date][]`
915
+
916
+
917
+ ### splitPeriodIntoWeeks
918
+ Splits a time period into consecutive week ranges.
919
+
920
+ Each range represents a week period starting on the specified day of the week.
921
+ This is useful for rules that need to apply constraints on a per-week basis,
922
+ such as maximum or minimum hours per week.
923
+
924
+ The first range starts at the provided start date (not necessarily on weekStartsOn).
925
+ Subsequent ranges align to the weekStartsOn day. The last range's end date will be
926
+ the provided end date.
927
+
928
+ **Example:**
929
+ ```typescript
930
+ const ranges = splitPeriodIntoWeeks({
931
+ start: new Date('2025-01-01'), // Wednesday
932
+ end: new Date('2025-01-15'),
933
+ weekStartsOn: 'monday'
934
+ });
935
+ // Returns ranges starting from Jan 1 (Wed), then aligning to Mondays:
936
+ // [
937
+ // [Date('2025-01-01 Wed'), Date('2025-01-06 Mon')],
938
+ // [Date('2025-01-06 Mon'), Date('2025-01-13 Mon')],
939
+ // [Date('2025-01-13 Mon'), Date('2025-01-15 Wed')]
940
+ // ]
941
+ ```
942
+
943
+ **Parameters:**
944
+ - `{
945
+ start,
946
+ end,
947
+ weekStartsOn,
948
+ }: { start: Date; end: Date; weekStartsOn: DayOfWeek; }`
949
+
950
+ **Returns:** `[Date, Date][]`
951
+
952
+
953
+ ### dateTimeRangesOverlap
954
+ Checks if two DateTime ranges overlap in both date and time.
955
+ Ranges overlap if they share any moment in time.
956
+
957
+ Two ranges overlap if: range1.start < range2.end AND range2.start < range1.end
958
+
959
+ **Example:**
960
+ ```typescript
961
+ // Same day, overlapping times (9-17 overlaps with 12-20)
962
+ dateTimeRangesOverlap(
963
+ {
964
+ start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },
965
+ end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }
966
+ },
967
+ {
968
+ start: { year: 2025, month: 6, day: 1, hours: 12, minutes: 0 },
969
+ end: { year: 2025, month: 6, day: 1, hours: 20, minutes: 0 }
970
+ }
971
+ ); // true
972
+
973
+ // Different days - no overlap
974
+ dateTimeRangesOverlap(
975
+ {
976
+ start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },
977
+ end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }
978
+ },
979
+ {
980
+ start: { year: 2025, month: 6, day: 2, hours: 9, minutes: 0 },
981
+ end: { year: 2025, month: 6, day: 2, hours: 17, minutes: 0 }
982
+ }
983
+ ); // false
984
+
985
+ // Works naturally with Shift objects
986
+ dateTimeRangesOverlap(
987
+ { start: shift1.startDateTime, end: shift1.endDateTime },
988
+ { start: shift2.startDateTime, end: shift2.endDateTime }
989
+ );
990
+ ```
991
+
992
+ **Parameters:**
993
+ - `range1: DateTimeRange`
994
+ - `range2: DateTimeRange`
995
+
996
+ **Returns:** `boolean`
997
+
998
+
999
+ ### daysBetween
1000
+ Calculates the number of complete days between two dates
1001
+
1002
+ **Example:**
1003
+ ```typescript
1004
+ daysBetween(new Date('2025-01-01'), new Date('2025-01-05')); // 4
1005
+ ```
1006
+
1007
+ **Parameters:**
1008
+ - `start: Date`
1009
+ - `end: Date`
1010
+
1011
+ **Returns:** `number`
1012
+
1013
+
1014
+ ### resolveDaysFromPeriod
1015
+ Computes the list of day strings (YYYY-MM-DD) from a SchedulingPeriod.
1016
+
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).
1021
+
1022
+ **Example:**
1023
+ Date range with day-of-week filter
1024
+ ```typescript
1025
+ const days = resolveDaysFromPeriod({
1026
+ dateRange: { start: '2025-02-03', end: '2025-02-09' },
1027
+ daysOfWeek: ['wednesday', 'friday'],
1028
+ });
1029
+ // Returns: ['2025-02-05', '2025-02-07'] (Wed and Fri only)
1030
+ ```
1031
+
1032
+ **Example:**
1033
+ Date range without filter
1034
+ ```typescript
1035
+ const days = resolveDaysFromPeriod({
1036
+ dateRange: { start: '2025-02-03', end: '2025-02-05' },
1037
+ });
1038
+ // Returns: ['2025-02-03', '2025-02-04', '2025-02-05']
1039
+ ```
1040
+
1041
+ **Example:**
1042
+ Specific dates
1043
+ ```typescript
1044
+ const days = resolveDaysFromPeriod({
1045
+ specificDates: ['2025-02-07', '2025-02-03', '2025-02-10'],
1046
+ });
1047
+ // Returns: ['2025-02-03', '2025-02-07', '2025-02-10'] (sorted)
1048
+ ```
1049
+
1050
+ **Parameters:**
1051
+ - `period: { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }`
1052
+
1053
+ **Returns:** `string[]`
1054
+
1055
+
1056
+ ### parseSolverResponse
1057
+ Extracts shift assignments from solver response.
1058
+
1059
+ Parses variable names matching the pattern `assign:${employeeId}:${patternId}:${day}`
1060
+ and returns assignments where the variable value is 1 (true).
1061
+
1062
+ IDs are validated by ModelBuilder to not contain colons,
1063
+ ensuring unambiguous parsing.
1064
+
1065
+ **Example:**
1066
+ ```typescript
1067
+ const response = await client.solve(request);
1068
+ const result = parseSolverResponse(response);
1069
+
1070
+ if (result.status === "OPTIMAL" || result.status === "FEASIBLE") {
1071
+ for (const assignment of result.assignments) {
1072
+ console.log(`${assignment.employeeId} works ${assignment.shiftPatternId} on ${assignment.day}`);
1073
+ }
1074
+ }
1075
+ ```
1076
+
1077
+ **Parameters:**
1078
+ - `response: { 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; }`
1079
+
1080
+ **Returns:** `SolverResult`
1081
+
1082
+
1083
+ ### resolveAssignments
1084
+ Resolves shift assignments to concrete times using shift patterns.
1085
+
1086
+ **Example:**
1087
+ ```typescript
1088
+ const result = parseScheduleResult(response);
1089
+ const resolved = resolveAssignments(result.assignments, shiftPatterns);
1090
+
1091
+ for (const shift of resolved) {
1092
+ console.log(`${shift.employeeId} works ${shift.day} from ${shift.startTime.hours}:${shift.startTime.minutes}`);
1093
+ }
1094
+ ```
1095
+
1096
+ **Parameters:**
1097
+ - `assignments: ShiftAssignment[]`
1098
+ - `shiftPatterns: ShiftPattern[]`
1099
+
1100
+ **Returns:** `ResolvedShiftAssignment[]`
1101
+
1102
+
1103
+ ### createAssignTogetherRule
1104
+ Encourages or enforces that team members in the group work the same shift patterns on a day.
1105
+ For each pair of team members in the group, ensures they are assigned to the same shifts.
1106
+
1107
+ **Example:**
1108
+ ```ts
1109
+ const rule = createAssignTogetherRule({
1110
+ groupEmployeeIds: ["alice", "bob", "charlie"],
1111
+ priority: "HIGH",
1112
+ });
1113
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1114
+ ```
1115
+
1116
+ **Parameters:**
1117
+ - `config: { groupEmployeeIds: [string, string, ...string[]]; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; }`
1118
+
1119
+ **Returns:** `CompilationRule`
1120
+
1121
+
1122
+ ### createEmployeeAssignmentPriorityRule
1123
+ Adds objective weight to prefer or avoid assigning team members.
1124
+
1125
+ Supports entity scoping (people, roles, skills) and time scoping
1126
+ (date ranges, specific dates, days of week, recurring periods).
1127
+
1128
+ **Example:**
1129
+ Prefer specific team members
1130
+ ```ts
1131
+ createEmployeeAssignmentPriorityRule({
1132
+ employeeIds: ["alice", "bob"],
1133
+ preference: "high",
1134
+ });
1135
+ ```
1136
+
1137
+ **Example:**
1138
+ Avoid assigning students on weekdays
1139
+ ```ts
1140
+ createEmployeeAssignmentPriorityRule({
1141
+ roleIds: ["student"],
1142
+ dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
1143
+ preference: "low",
1144
+ });
1145
+ ```
1146
+
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
+ **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; }`
1159
+
1160
+ **Returns:** `CompilationRule`
1161
+
1162
+
1163
+ ### createLocationPreferenceRule
1164
+ Prefers assigning a person to shift patterns matching a specific location.
1165
+
1166
+ **Example:**
1167
+ ```ts
1168
+ const rule = createLocationPreferenceRule({
1169
+ locationId: "terrace",
1170
+ priority: "HIGH",
1171
+ employeeIds: ["alice"],
1172
+ });
1173
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1174
+ ```
1175
+
1176
+ **Parameters:**
1177
+ - `config: { locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1178
+
1179
+ **Returns:** `CompilationRule`
1180
+
1181
+
1182
+ ### createMaxConsecutiveDaysRule
1183
+ Limits how many consecutive days a person can be assigned.
1184
+
1185
+ **Example:**
1186
+ ```ts
1187
+ const rule = createMaxConsecutiveDaysRule({
1188
+ days: 5,
1189
+ priority: "MANDATORY",
1190
+ });
1191
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1192
+ ```
1193
+
1194
+ **Parameters:**
1195
+ - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1196
+
1197
+ **Returns:** `CompilationRule`
1198
+
1199
+
1200
+ ### createMaxHoursDayRule
1201
+ Limits how many hours a person can work in a single day.
1202
+
1203
+ Supports entity scoping (people, roles, skills) and time scoping
1204
+ (date ranges, specific dates, days of week, recurring periods).
1205
+
1206
+ **Example:**
1207
+ Limit everyone to 8 hours per day
1208
+ ```ts
1209
+ createMaxHoursDayRule({
1210
+ hours: 8,
1211
+ priority: "MANDATORY",
1212
+ });
1213
+ ```
1214
+
1215
+ **Example:**
1216
+ Students limited to 4 hours on weekdays during term
1217
+ ```ts
1218
+ createMaxHoursDayRule({
1219
+ roleIds: ["student"],
1220
+ hours: 4,
1221
+ dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
1222
+ priority: "MANDATORY",
1223
+ });
1224
+ ```
1225
+
1226
+ **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; }`
1228
+
1229
+ **Returns:** `CompilationRule`
1230
+
1231
+
1232
+ ### createMaxHoursWeekRule
1233
+ Caps total hours a person can work within each scheduling week.
1234
+
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
+ **Example:**
1240
+ Limit everyone to 40 hours per week
1241
+ ```ts
1242
+ createMaxHoursWeekRule({
1243
+ hours: 40,
1244
+ priority: "HIGH",
1245
+ });
1246
+ ```
1247
+
1248
+ **Example:**
1249
+ Students limited to 20 hours during term time
1250
+ ```ts
1251
+ createMaxHoursWeekRule({
1252
+ roleIds: ["student"],
1253
+ hours: 20,
1254
+ recurringPeriods: [
1255
+ { name: "fall-term", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },
1256
+ { name: "spring-term", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },
1257
+ ],
1258
+ priority: "MANDATORY",
1259
+ });
1260
+ ```
1261
+
1262
+ **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; }`
1264
+
1265
+ **Returns:** `CompilationRule`
1266
+
1267
+
1268
+ ### createMaxShiftsDayRule
1269
+ Limits how many shifts a person can work in a single day.
1270
+
1271
+ This rule controls the maximum number of distinct shift assignments per day,
1272
+ regardless of shift duration. For limiting total hours worked, use `max-hours-day`.
1273
+
1274
+ Supports entity scoping (people, roles, skills) and time scoping
1275
+ (date ranges, specific dates, days of week, recurring periods).
1276
+
1277
+ **Example:**
1278
+ Limit to one shift per day (common for most schedules)
1279
+ ```ts
1280
+ createMaxShiftsDayRule({
1281
+ shifts: 1,
1282
+ priority: "MANDATORY",
1283
+ });
1284
+ ```
1285
+
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
+ **Example:**
1297
+ Students can work 2 shifts on weekends only
1298
+ ```ts
1299
+ createMaxShiftsDayRule({
1300
+ roleIds: ["student"],
1301
+ shifts: 2,
1302
+ dayOfWeek: ["saturday", "sunday"],
1303
+ priority: "MANDATORY",
1304
+ });
1305
+ ```
1306
+
1307
+ **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; }`
1309
+
1310
+ **Returns:** `CompilationRule`
1311
+
1312
+
1313
+ ### createMinConsecutiveDaysRule
1314
+ Requires that once a person starts working, they continue for a minimum
1315
+ number of consecutive days.
1316
+
1317
+ **Example:**
1318
+ ```ts
1319
+ const rule = createMinConsecutiveDaysRule({
1320
+ days: 3,
1321
+ priority: "MANDATORY",
1322
+ });
1323
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1324
+ ```
1325
+
1326
+ **Parameters:**
1327
+ - `config: { days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1328
+
1329
+ **Returns:** `CompilationRule`
1330
+
1331
+
1332
+ ### createMinHoursDayRule
1333
+ Ensures a person works at least a minimum number of hours per day.
1334
+
1335
+ **Example:**
1336
+ ```ts
1337
+ const rule = createMinHoursDayRule({
1338
+ hours: 6,
1339
+ priority: "MANDATORY",
1340
+ });
1341
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1342
+ ```
1343
+
1344
+ **Parameters:**
1345
+ - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1346
+
1347
+ **Returns:** `CompilationRule`
1348
+
1349
+
1350
+ ### createMinHoursWeekRule
1351
+ Enforces a minimum total number of hours per scheduling week.
1352
+
1353
+ **Example:**
1354
+ ```ts
1355
+ const rule = createMinHoursWeekRule({
1356
+ hours: 30,
1357
+ priority: "HIGH",
1358
+ });
1359
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1360
+ ```
1361
+
1362
+ **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; }`
1364
+
1365
+ **Returns:** `CompilationRule`
1366
+
1367
+
1368
+ ### createMinRestBetweenShiftsRule
1369
+ Enforces a minimum rest period between any two shifts a person works.
1370
+
1371
+ **Example:**
1372
+ ```ts
1373
+ const rule = createMinRestBetweenShiftsRule({
1374
+ hours: 10,
1375
+ priority: "MANDATORY",
1376
+ });
1377
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1378
+ ```
1379
+
1380
+ **Parameters:**
1381
+ - `config: { hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1382
+
1383
+ **Returns:** `CompilationRule`
1384
+
1385
+
1386
+ ### createTimeOffRule
1387
+ Blocks or penalizes assignments during specified time periods.
1388
+
1389
+ Supports entity scoping (people, roles, skills) and time scoping
1390
+ (date ranges, specific dates, days of week, recurring periods).
1391
+ Optionally supports partial-day time-off with startTime/endTime.
1392
+
1393
+ **Example:**
1394
+ Full day vacation
1395
+ ```ts
1396
+ createTimeOffRule({
1397
+ employeeIds: ["alice"],
1398
+ dateRange: { start: "2024-02-01", end: "2024-02-05" },
1399
+ priority: "MANDATORY",
1400
+ });
1401
+ ```
1402
+
1403
+ **Example:**
1404
+ Every Wednesday afternoon off for students
1405
+ ```ts
1406
+ createTimeOffRule({
1407
+ roleIds: ["student"],
1408
+ dayOfWeek: ["wednesday"],
1409
+ startTime: { hours: 14, minutes: 0 },
1410
+ endTime: { hours: 23, minutes: 59 },
1411
+ priority: "MANDATORY",
1412
+ });
1413
+ ```
1414
+
1415
+ **Example:**
1416
+ Specific date, partial day
1417
+ ```ts
1418
+ createTimeOffRule({
1419
+ employeeIds: ["bob"],
1420
+ specificDates: ["2024-03-15"],
1421
+ startTime: { hours: 16, minutes: 0 },
1422
+ endTime: { hours: 23, minutes: 59 },
1423
+ priority: "MANDATORY",
1424
+ });
1425
+ ```
1426
+
1427
+ **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; }`
1429
+
1430
+ **Returns:** `CompilationRule`
1431
+
1432
+
1433
+ ### createCpsatRuleFactory
1434
+ Creates a rule factory map, preventing overriding built-in rules.
1435
+
1436
+ **Parameters:**
1437
+ - `factories: F`
1438
+
1439
+ **Returns:** `F`
1440
+
1441
+
1442
+ ### defineSemanticTimes
1443
+ Define semantic times with type-safe names.
1444
+
1445
+ Returns a context object that provides:
1446
+ - Type-safe coverage() function that only accepts defined semantic time names
1447
+ - resolve() function to expand semantic times to concrete requirements
1448
+
1449
+ **Example:**
1450
+ Basic usage
1451
+ ```typescript
1452
+ const times = defineSemanticTimes({
1453
+ opening: { startTime: { hours: 6 }, endTime: { hours: 8 } },
1454
+ lunch: { startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 } },
1455
+ closing: { startTime: { hours: 21 }, endTime: { hours: 23 } },
1456
+ });
1457
+
1458
+ const coverage = times.coverage([
1459
+ { semanticTime: "lunch", roleId: "server", targetCount: 3 },
1460
+ { semanticTime: "opening", roleId: "keyholder", targetCount: 1, priority: "MANDATORY" },
1461
+ // Type error: "dinner" is not a defined semantic time
1462
+ // { semanticTime: "dinner", roleId: "server", targetCount: 2 },
1463
+ ]);
1464
+ ```
1465
+
1466
+ **Example:**
1467
+ Variants for different days
1468
+ ```typescript
1469
+ const times = defineSemanticTimes({
1470
+ lunch: [
1471
+ { startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 }, days: ["monday", "tuesday", "wednesday", "thursday", "friday"] },
1472
+ { startTime: { hours: 12 }, endTime: { hours: 15 }, days: ["saturday", "sunday"] },
1473
+ ],
1474
+ });
1475
+ ```
1476
+
1477
+ **Example:**
1478
+ Mixed semantic and concrete coverage
1479
+ ```typescript
1480
+ const coverage = times.coverage([
1481
+ { semanticTime: "lunch", roleId: "server", targetCount: 3 },
1482
+ // One-off party - concrete time
1483
+ { day: "2026-01-14", startTime: { hours: 15 }, endTime: { hours: 20 }, roleId: "server", targetCount: 5 },
1484
+ ]);
1485
+ ```
1486
+
1487
+ **Parameters:**
1488
+ - `defs: T`
1489
+
1490
+ **Returns:** `SemanticTimeContext<keyof T & string>`
1491
+
1492
+
1493
+ ### isConcreteCoverage
1494
+ Type guard to check if a requirement is concrete (has explicit day/times).
1495
+
1496
+ **Parameters:**
1497
+ - `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`
1498
+
1499
+ **Returns:** `boolean`
1500
+
1501
+
1502
+ ### isSemanticCoverage
1503
+ Type guard to check if a requirement is semantic (references a named time).
1504
+
1505
+ **Parameters:**
1506
+ - `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`
1507
+
1508
+ **Returns:** `boolean`
1509
+
1510
+
1511
+ ### groupKey
1512
+ Creates a GroupKey from a description string.
1513
+ Use this to create keys that group related validation items together.
1514
+
1515
+ **Example:**
1516
+ ```typescript
1517
+ const key = groupKey("2x waiter during lunch");
1518
+ coverage.groupKey = key;
1519
+ ```
1520
+
1521
+ **Parameters:**
1522
+ - `description: string`
1523
+
1524
+ **Returns:** `string & { readonly [GroupKeyBrand]: never; }`
1525
+
1526
+
1527
+ ### summarizeValidation
1528
+ Aggregates validation items by their groupKey into summaries.
1529
+ This is a pure function that doesn't modify the input.
1530
+
1531
+ Items without a groupKey are grouped by their ID (ungrouped).
1532
+
1533
+ **Example:**
1534
+ ```typescript
1535
+ const validation = reporter.getValidation();
1536
+ const summaries = summarizeValidation(validation);
1537
+ // summaries[0] = {
1538
+ // groupKey: "2x waiter during lunch",
1539
+ // status: "passed",
1540
+ // passedCount: 180,
1541
+ // days: ["2026-02-02", "2026-02-03", ...]
1542
+ // }
1543
+ ```
1544
+
1545
+ **Parameters:**
1546
+ - `validation: ScheduleValidation`
1547
+
1548
+ **Returns:** `readonly ValidationSummary[]`
1549
+
1550
+
1551
+ ### validateCoverageRoles
1552
+ Validates that all roleIds used in coverage requirements match the team.
1553
+
1554
+ This catches a common LLM error where the model generates coverage requirements
1555
+ using role names that don't match any team member's roleIds. Without this validation,
1556
+ such mismatches would result in valid but semantically wrong schedules (e.g.,
1557
+ coverage requirements that no one can satisfy).
1558
+
1559
+ **Example:**
1560
+ ```typescript
1561
+ const employees = [
1562
+ { id: "alice", roleIds: ["cashier"] },
1563
+ { id: "bob", roleIds: ["stocker"] },
1564
+ ];
1565
+
1566
+ const coverage = [
1567
+ { roleId: "cashier", targetCount: 1, ... }, // OK
1568
+ { roleId: "worker", targetCount: 1, ... }, // Unknown role!
1569
+ ];
1570
+
1571
+ const result = validateCoverageRoles(coverage, employees);
1572
+ // result.valid = false
1573
+ // result.unknownRoles = ["worker"]
1574
+ // result.knownRoles = ["cashier"]
1575
+ ```
1576
+
1577
+ **Parameters:**
1578
+ - `coverage: CoverageRequirement[]`
1579
+ - `employees: SchedulingEmployee[]`
1580
+
1581
+ **Returns:** `CoverageValidationResult`
1582
+
1583
+
1584
+ ### validateCoverageSkills
1585
+ Validates that all skillIds used in coverage requirements match the team.
1586
+
1587
+ Similar to role validation, this catches LLM hallucinations where skill names
1588
+ in coverage don't match any team member's skillIds.
1589
+
1590
+ **Example:**
1591
+ ```typescript
1592
+ const employees = [
1593
+ { id: "alice", roleIds: ["server"], skillIds: ["keyholder"] },
1594
+ { id: "bob", roleIds: ["server"] },
1595
+ ];
1596
+
1597
+ const coverage = [
1598
+ { skillIds: ["keyholder"], targetCount: 1, ... }, // OK
1599
+ { skillIds: ["manager"], targetCount: 1, ... }, // Unknown skill!
1600
+ ];
1601
+
1602
+ const result = validateCoverageSkills(coverage, employees);
1603
+ // result.valid = false
1604
+ // result.unknownSkills = ["manager"]
1605
+ ```
1606
+
1607
+ **Parameters:**
1608
+ - `coverage: CoverageRequirement[]`
1609
+ - `employees: SchedulingEmployee[]`
1610
+
1611
+ **Returns:** `SkillValidationResult`
1612
+
1613
+
1614
+ ### validateCoverageConfig
1615
+ Validates coverage requirements against team roles and skills.
1616
+
1617
+ This is the primary validation function to call before building a scheduling model.
1618
+ It checks both roles and skills, returning a combined result with error messages.
1619
+
1620
+ **Example:**
1621
+ ```typescript
1622
+ const result = validateCoverageConfig(coverage, employees);
1623
+ if (!result.valid) {
1624
+ throw new Error(result.errors.join("; "));
1625
+ }
1626
+ ```
1627
+
1628
+ **Parameters:**
1629
+ - `coverage: CoverageRequirement[]`
1630
+ - `employees: SchedulingEmployee[]`
1631
+
1632
+ **Returns:** `CoverageConfigValidationResult`
1633
+
1634
+
1635
+ ### addMinutesToDate
1636
+ Adds a number of minutes to a base date and returns a DateTime.
1637
+ Treats the base date as a reference point (typically midnight of horizon start),
1638
+ and the minutes parameter as absolute minutes from that point.
1639
+
1640
+ **Example:**
1641
+ ```typescript
1642
+ // Add 90 minutes from midnight
1643
+ addMinutesToDate(new Date('2025-01-01'), 90);
1644
+ // Returns: { year: 2025, month: 1, day: 1, hours: 1, minutes: 30 }
1645
+
1646
+ // Add 1500 minutes (spans to next day)
1647
+ addMinutesToDate(new Date('2025-01-01'), 1500);
1648
+ // Returns: { year: 2025, month: 1, day: 2, hours: 1, minutes: 0 }
1649
+ ```
1650
+
1651
+ **Parameters:**
1652
+ - `baseDate: Date`
1653
+ - `minutes: number`
1654
+
1655
+ **Returns:** `DateTimeWithUtcOffset | DateTimeWithTimeZone`
1656
+
1657
+
1658
+ ### splitPoints
1659
+ Returns the points where a range should be split, filtered to within [start, end).
1660
+ Always includes range start. Sorted ascending.
1661
+
1662
+ **Parameters:**
1663
+ - `[start, end]: [number, number]`
1664
+ - `splitAt: number[]`
1665
+
1666
+ **Returns:** `number[]`
1667
+
1668
+
1669
+ ### parseDayString
1670
+ Parse a day string (YYYY-MM-DD) to a UTC Date.
1671
+ Used internally for day-of-week calculations and date comparisons.
1672
+
1673
+ **Parameters:**
1674
+ - `day: string`
1675
+
1676
+ **Returns:** `Date`
1677
+
1678
+
1679
+ ### timeOfDayToMinutes
1680
+ **Parameters:**
1681
+ - `time: TimeOfDay`
1682
+
1683
+ **Returns:** `number`
1684
+
1685
+
1686
+ ### normalizeEndMinutes
1687
+ **Parameters:**
1688
+ - `startMinutes: number`
1689
+ - `endMinutes: number`
1690
+
1691
+ **Returns:** `number`
1692
+
1693
+
1694
+ ### priorityToPenalty
1695
+ **Parameters:**
1696
+ - `priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"`
1697
+
1698
+ **Returns:** `number`
1699
+
1700
+
1701
+ ### splitIntoWeeks
1702
+ **Parameters:**
1703
+ - `days: string[]`
1704
+ - `weekStartsOn: "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"`
1705
+
1706
+ **Returns:** `string[][]`
1707
+
1708
+
1709
+ ## Classes
1710
+
1711
+ ### ORSchedulingError
1712
+ Error thrown when Google OR Tools scheduling API requests fail.
1713
+
1714
+ Contains the HTTP status code and raw response data from the API for debugging.
1715
+ Common causes include infeasible constraints, invalid requests, or API unavailability.
1716
+
1717
+
1718
+ ### HttpSolverClient
1719
+ Generic HTTP client for the solver service.
1720
+
1721
+
1722
+ ### ModelBuilder
1723
+ Compilation context that creates variables, constraints, and objectives
1724
+ and emits a `SolverRequest` for the Python CP-SAT solver service.
1725
+
1726
+
1727
+ ### ValidationReporterImpl
1728
+
1729
+ ## Constants
1730
+
1731
+ ### DayOfWeekSchema
1732
+ Zod schema for {@link DayOfWeek}.
1733
+ Useful for rule configs that need to accept a day-of-week string.
1734
+
1735
+ **Type:** `z.ZodUnion<readonly [z.ZodLiteral<"monday">, z.ZodLiteral<"tuesday">, z.ZodLiteral<"wednesday">, z.ZodLiteral<"thursday">, z.ZodLiteral<"friday">, z.ZodLiteral<"saturday">, z.ZodLiteral<"sunday">]>`
1736
+
1737
+
1738
+ ### SOLVER_STATUS
1739
+ **Type:** `{ readonly OPTIMAL: "OPTIMAL"; readonly FEASIBLE: "FEASIBLE"; readonly INFEASIBLE: "INFEASIBLE"; readonly TIMEOUT: "TIMEOUT"; readonly ERROR: "ERROR"; }`
1740
+
1741
+
1742
+ ### SolverRequestSchema
1743
+ **Type:** `[Complex type: ZodObject]`
1744
+
1745
+
1746
+ ### SolverResponseSchema
1747
+ **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
+
1749
+
1750
+ ### builtInCpsatRuleFactories
1751
+ **Type:** `[Complex type: __type]`
1752
+
1753
+
1754
+ ### OBJECTIVE_WEIGHTS
1755
+ Standard objective weights for the scheduling solver.
1756
+
1757
+ These weights define the relative importance of different objectives.
1758
+ Higher weights mean stronger preference. Rules can use these as reference
1759
+ points when adding their own penalties.
1760
+
1761
+ Weight hierarchy (highest to lowest priority):
1762
+ - SHIFT_ACTIVE (1000): Minimize number of active shift patterns
1763
+ - ASSIGNMENT_PREFERENCE (10): Per-assignment preference (e.g., prefer permanent staff)
1764
+ - FAIRNESS (5): Fair distribution of shifts across team members
1765
+ - ASSIGNMENT_BASE (1): Tiebreaker - minimize total assignments
1766
+
1767
+ **Example:**
1768
+ Using weights in a custom rule
1769
+ ```ts
1770
+ import { OBJECTIVE_WEIGHTS } from "feasible";
1771
+
1772
+ // Prefer senior staff with same weight as employee-assignment-priority
1773
+ b.addPenalty(assignment, -OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);
1774
+
1775
+ // Strong preference (2x normal)
1776
+ b.addPenalty(assignment, -2 * OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);
1777
+ ```
1778
+
1779
+ **Type:** `{ readonly SHIFT_ACTIVE: 1000; readonly ASSIGNMENT_PREFERENCE: 10; readonly FAIRNESS: 5; readonly ASSIGNMENT_BASE: 1; }`
1780
+
1781
+
1782
+ ### DAY_OF_WEEK_MAP
1783
+ **Type:** `{ sunday: number; monday: number; tuesday: number; wednesday: number; thursday: number; friday: number; saturday: number; }`
1784
+
1785
+
1786
+ ### SolverTermSchema
1787
+ **Type:** `z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>`
1788
+
1789
+
1790
+ ### BoolVariableSchema
1791
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"bool">; name: z.ZodString; }, z.core.$strip>`
1792
+
1793
+
1794
+ ### IntVariableSchema
1795
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"int">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>`
1796
+
1797
+
1798
+ ### IntervalVariableSchema
1799
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"interval">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>`
1800
+
1801
+
1802
+ ### SolverVariableSchema
1803
+ **Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<"bool">; name: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"int">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"interval">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>]>`
1804
+
1805
+
1806
+ ### LinearConstraintSchema
1807
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"linear">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<"<=">, z.ZodLiteral<">=">, z.ZodLiteral<"==">]>; rhs: z.ZodNumber; }, z.core.$strip>`
1808
+
1809
+
1810
+ ### SoftLinearConstraintSchema
1811
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"soft_linear">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<"<=">, z.ZodLiteral<">=">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>`
1812
+
1813
+
1814
+ ### ExactlyOneConstraintSchema
1815
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"exactly_one">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`
1816
+
1817
+
1818
+ ### AtMostOneConstraintSchema
1819
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"at_most_one">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`
1820
+
1821
+
1822
+ ### ImplicationConstraintSchema
1823
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"implication">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>`
1824
+
1825
+
1826
+ ### BoolOrConstraintSchema
1827
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"bool_or">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`
1828
+
1829
+
1830
+ ### BoolAndConstraintSchema
1831
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"bool_and">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`
1832
+
1833
+
1834
+ ### NoOverlapConstraintSchema
1835
+ **Type:** `z.ZodObject<{ type: z.ZodLiteral<"no_overlap">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>`
1836
+
1837
+
1838
+ ### SolverConstraintSchema
1839
+ **Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<"linear">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<"<=">, z.ZodLiteral<">=">, z.ZodLiteral<"==">]>; rhs: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"soft_linear">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<"<=">, z.ZodLiteral<">=">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"exactly_one">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"at_most_one">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"implication">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"bool_or">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"bool_and">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<"no_overlap">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>]>`
1840
+
1841
+
1842
+ ### SolverObjectiveSchema
1843
+ **Type:** `z.ZodObject<{ sense: z.ZodUnion<readonly [z.ZodLiteral<"minimize">, z.ZodLiteral<"maximize">]>; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; }, z.core.$strip>`
1844
+
1845
+
1846
+ ### SolverOptionsSchema
1847
+ **Type:** `z.ZodObject<{ timeLimitSeconds: z.ZodOptional<z.ZodNumber>; solutionLimit: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`
1848
+
1849
+
1850
+ ### SolverStatusSchema
1851
+ **Type:** `z.ZodEnum<{ OPTIMAL: "OPTIMAL"; FEASIBLE: "FEASIBLE"; INFEASIBLE: "INFEASIBLE"; TIMEOUT: "TIMEOUT"; ERROR: "ERROR"; }>`
1852
+
1853
+
1854
+ ### SolverStatisticsSchema
1855
+ **Type:** `z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`
1856
+
1857
+
1858
+ ### SoftConstraintViolationSchema
1859
+ **Type:** `z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>`
1860
+
1861
+
1862
+ ### MINUTES_PER_DAY
1863
+ **Type:** `number`
1864
+
1865
+
1866
+
1867
+ ## Built-In Rules Reference
1868
+
1869
+ Rules are constraints and preferences you can apply to schedules using `ctx.addRule(ruleName, config)`.
1870
+
1871
+ ### assign-together
1872
+
1873
+
1874
+
1875
+ Encourages or enforces that team members in the group work the same shift patterns on a day.
1876
+ For each pair of team members in the group, ensures they are assigned to the same shifts.
1877
+
1878
+ **Example:**
1879
+ ```ts
1880
+ const rule = createAssignTogetherRule({
1881
+ groupEmployeeIds: ["alice", "bob", "charlie"],
1882
+ priority: "HIGH",
1883
+ });
1884
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1885
+ ```
1886
+
1887
+ **Config:** `{ groupEmployeeIds: [string, string, ...string[]]; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; }`
1888
+
1889
+ ### employee-assignment-priority
1890
+
1891
+
1892
+
1893
+ Adds objective weight to prefer or avoid assigning team members.
1894
+
1895
+ Supports entity scoping (people, roles, skills) and time scoping
1896
+ (date ranges, specific dates, days of week, recurring periods).
1897
+
1898
+ **Example:**
1899
+ Prefer specific team members
1900
+ ```ts
1901
+ createEmployeeAssignmentPriorityRule({
1902
+ employeeIds: ["alice", "bob"],
1903
+ preference: "high",
1904
+ });
1905
+ ```
1906
+
1907
+ **Example:**
1908
+ Avoid assigning students on weekdays
1909
+ ```ts
1910
+ createEmployeeAssignmentPriorityRule({
1911
+ roleIds: ["student"],
1912
+ dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
1913
+ preference: "low",
1914
+ });
1915
+ ```
1916
+
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; }`
1928
+
1929
+ ### location-preference
1930
+
1931
+
1932
+
1933
+ Prefers assigning a person to shift patterns matching a specific location.
1934
+
1935
+ **Example:**
1936
+ ```ts
1937
+ const rule = createLocationPreferenceRule({
1938
+ locationId: "terrace",
1939
+ priority: "HIGH",
1940
+ employeeIds: ["alice"],
1941
+ });
1942
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1943
+ ```
1944
+
1945
+ **Config:** `{ locationId: string; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1946
+
1947
+ ### max-consecutive-days
1948
+
1949
+
1950
+
1951
+ Limits how many consecutive days a person can be assigned.
1952
+
1953
+ **Example:**
1954
+ ```ts
1955
+ const rule = createMaxConsecutiveDaysRule({
1956
+ days: 5,
1957
+ priority: "MANDATORY",
1958
+ });
1959
+ builder = new ModelBuilder({ ...config, rules: [rule] });
1960
+ ```
1961
+
1962
+ **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
1963
+
1964
+ ### max-hours-day
1965
+
1966
+
1967
+
1968
+ Limits how many hours a person can work in a single day.
1969
+
1970
+ Supports entity scoping (people, roles, skills) and time scoping
1971
+ (date ranges, specific dates, days of week, recurring periods).
1972
+
1973
+ **Example:**
1974
+ Limit everyone to 8 hours per day
1975
+ ```ts
1976
+ createMaxHoursDayRule({
1977
+ hours: 8,
1978
+ priority: "MANDATORY",
1979
+ });
1980
+ ```
1981
+
1982
+ **Example:**
1983
+ Students limited to 4 hours on weekdays during term
1984
+ ```ts
1985
+ createMaxHoursDayRule({
1986
+ roleIds: ["student"],
1987
+ hours: 4,
1988
+ dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
1989
+ priority: "MANDATORY",
1990
+ });
1991
+ ```
1992
+
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; }`
1994
+
1995
+ ### max-hours-week
1996
+
1997
+
1998
+
1999
+ Caps total hours a person can work within each scheduling week.
2000
+
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
+ **Example:**
2006
+ Limit everyone to 40 hours per week
2007
+ ```ts
2008
+ createMaxHoursWeekRule({
2009
+ hours: 40,
2010
+ priority: "HIGH",
2011
+ });
2012
+ ```
2013
+
2014
+ **Example:**
2015
+ Students limited to 20 hours during term time
2016
+ ```ts
2017
+ createMaxHoursWeekRule({
2018
+ roleIds: ["student"],
2019
+ hours: 20,
2020
+ recurringPeriods: [
2021
+ { name: "fall-term", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },
2022
+ { name: "spring-term", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },
2023
+ ],
2024
+ priority: "MANDATORY",
2025
+ });
2026
+ ```
2027
+
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; }`
2029
+
2030
+ ### max-shifts-day
2031
+
2032
+
2033
+
2034
+ Limits how many shifts a person can work in a single day.
2035
+
2036
+ This rule controls the maximum number of distinct shift assignments per day,
2037
+ regardless of shift duration. For limiting total hours worked, use `max-hours-day`.
2038
+
2039
+ Supports entity scoping (people, roles, skills) and time scoping
2040
+ (date ranges, specific dates, days of week, recurring periods).
2041
+
2042
+ **Example:**
2043
+ Limit to one shift per day (common for most schedules)
2044
+ ```ts
2045
+ createMaxShiftsDayRule({
2046
+ shifts: 1,
2047
+ priority: "MANDATORY",
2048
+ });
2049
+ ```
2050
+
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
+ **Example:**
2062
+ Students can work 2 shifts on weekends only
2063
+ ```ts
2064
+ createMaxShiftsDayRule({
2065
+ roleIds: ["student"],
2066
+ shifts: 2,
2067
+ dayOfWeek: ["saturday", "sunday"],
2068
+ priority: "MANDATORY",
2069
+ });
2070
+ ```
2071
+
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; }`
2073
+
2074
+ ### min-consecutive-days
2075
+
2076
+
2077
+
2078
+ Requires that once a person starts working, they continue for a minimum
2079
+ number of consecutive days.
2080
+
2081
+ **Example:**
2082
+ ```ts
2083
+ const rule = createMinConsecutiveDaysRule({
2084
+ days: 3,
2085
+ priority: "MANDATORY",
2086
+ });
2087
+ builder = new ModelBuilder({ ...config, rules: [rule] });
2088
+ ```
2089
+
2090
+ **Config:** `{ days: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2091
+
2092
+ ### min-hours-day
2093
+
2094
+
2095
+
2096
+ Ensures a person works at least a minimum number of hours per day.
2097
+
2098
+ **Example:**
2099
+ ```ts
2100
+ const rule = createMinHoursDayRule({
2101
+ hours: 6,
2102
+ priority: "MANDATORY",
2103
+ });
2104
+ builder = new ModelBuilder({ ...config, rules: [rule] });
2105
+ ```
2106
+
2107
+ **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2108
+
2109
+ ### min-hours-week
2110
+
2111
+
2112
+
2113
+ Enforces a minimum total number of hours per scheduling week.
2114
+
2115
+ **Example:**
2116
+ ```ts
2117
+ const rule = createMinHoursWeekRule({
2118
+ hours: 30,
2119
+ priority: "HIGH",
2120
+ });
2121
+ builder = new ModelBuilder({ ...config, rules: [rule] });
2122
+ ```
2123
+
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; }`
2125
+
2126
+ ### min-rest-between-shifts
2127
+
2128
+
2129
+
2130
+ Enforces a minimum rest period between any two shifts a person works.
2131
+
2132
+ **Example:**
2133
+ ```ts
2134
+ const rule = createMinRestBetweenShiftsRule({
2135
+ hours: 10,
2136
+ priority: "MANDATORY",
2137
+ });
2138
+ builder = new ModelBuilder({ ...config, rules: [rule] });
2139
+ ```
2140
+
2141
+ **Config:** `{ hours: number; priority: "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`
2142
+
2143
+ ### time-off
2144
+
2145
+
2146
+
2147
+ Blocks or penalizes assignments during specified time periods.
2148
+
2149
+ Supports entity scoping (people, roles, skills) and time scoping
2150
+ (date ranges, specific dates, days of week, recurring periods).
2151
+ Optionally supports partial-day time-off with startTime/endTime.
2152
+
2153
+ **Example:**
2154
+ Full day vacation
2155
+ ```ts
2156
+ createTimeOffRule({
2157
+ employeeIds: ["alice"],
2158
+ dateRange: { start: "2024-02-01", end: "2024-02-05" },
2159
+ priority: "MANDATORY",
2160
+ });
2161
+ ```
2162
+
2163
+ **Example:**
2164
+ Every Wednesday afternoon off for students
2165
+ ```ts
2166
+ createTimeOffRule({
2167
+ roleIds: ["student"],
2168
+ dayOfWeek: ["wednesday"],
2169
+ startTime: { hours: 14, minutes: 0 },
2170
+ endTime: { hours: 23, minutes: 59 },
2171
+ priority: "MANDATORY",
2172
+ });
2173
+ ```
2174
+
2175
+ **Example:**
2176
+ Specific date, partial day
2177
+ ```ts
2178
+ createTimeOffRule({
2179
+ employeeIds: ["bob"],
2180
+ specificDates: ["2024-03-15"],
2181
+ startTime: { hours: 16, minutes: 0 },
2182
+ endTime: { hours: 23, minutes: 59 },
2183
+ priority: "MANDATORY",
2184
+ });
2185
+ ```
2186
+
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; }`
2188
+