dabke 0.81.1 → 0.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +45 -27
  3. package/dist/client.d.ts +20 -2
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +4 -1
  6. package/dist/client.js.map +1 -1
  7. package/dist/client.types.d.ts +9 -0
  8. package/dist/client.types.d.ts.map +1 -1
  9. package/dist/client.types.js +1 -0
  10. package/dist/client.types.js.map +1 -1
  11. package/dist/cpsat/model-builder.d.ts +9 -0
  12. package/dist/cpsat/model-builder.d.ts.map +1 -1
  13. package/dist/cpsat/model-builder.js +36 -34
  14. package/dist/cpsat/model-builder.js.map +1 -1
  15. package/dist/cpsat/response.d.ts +13 -1
  16. package/dist/cpsat/response.d.ts.map +1 -1
  17. package/dist/cpsat/response.js +4 -0
  18. package/dist/cpsat/response.js.map +1 -1
  19. package/dist/cpsat/rules/cost-utils.d.ts +11 -0
  20. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -0
  21. package/dist/cpsat/rules/cost-utils.js +24 -0
  22. package/dist/cpsat/rules/cost-utils.js.map +1 -0
  23. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  24. package/dist/cpsat/rules/day-cost-multiplier.js +3 -14
  25. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  26. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  27. package/dist/cpsat/rules/day-cost-surcharge.js +3 -7
  28. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  29. package/dist/cpsat/rules/index.d.ts +3 -0
  30. package/dist/cpsat/rules/index.d.ts.map +1 -1
  31. package/dist/cpsat/rules/index.js +3 -0
  32. package/dist/cpsat/rules/index.js.map +1 -1
  33. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  34. package/dist/cpsat/rules/max-consecutive-days.js +16 -2
  35. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  36. package/dist/cpsat/rules/max-days-week.d.ts +44 -0
  37. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
  38. package/dist/cpsat/rules/max-days-week.js +95 -0
  39. package/dist/cpsat/rules/max-days-week.js.map +1 -0
  40. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  41. package/dist/cpsat/rules/max-hours-day.js +15 -2
  42. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  43. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  44. package/dist/cpsat/rules/max-hours-week.js +16 -2
  45. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  46. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  47. package/dist/cpsat/rules/max-shifts-day.js +15 -2
  48. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  49. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  50. package/dist/cpsat/rules/min-consecutive-days.js +15 -2
  51. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  52. package/dist/cpsat/rules/min-days-week.d.ts +34 -0
  53. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
  54. package/dist/cpsat/rules/min-days-week.js +84 -0
  55. package/dist/cpsat/rules/min-days-week.js.map +1 -0
  56. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  57. package/dist/cpsat/rules/min-hours-day.js +15 -2
  58. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  59. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  60. package/dist/cpsat/rules/min-hours-week.js +16 -2
  61. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  62. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  63. package/dist/cpsat/rules/min-rest-between-shifts.js +72 -2
  64. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  65. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  66. package/dist/cpsat/rules/minimize-cost.js +2 -23
  67. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  68. package/dist/cpsat/rules/must-assign.d.ts +49 -0
  69. package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
  70. package/dist/cpsat/rules/must-assign.js +86 -0
  71. package/dist/cpsat/rules/must-assign.js.map +1 -0
  72. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  73. package/dist/cpsat/rules/overtime-daily-multiplier.js +1 -12
  74. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  75. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  76. package/dist/cpsat/rules/overtime-daily-surcharge.js +1 -5
  77. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  78. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +5 -1
  79. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  80. package/dist/cpsat/rules/overtime-tiered-multiplier.js +1 -12
  81. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  82. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  83. package/dist/cpsat/rules/overtime-weekly-multiplier.js +1 -12
  84. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  85. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  86. package/dist/cpsat/rules/overtime-weekly-surcharge.js +1 -5
  87. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  88. package/dist/cpsat/rules/registry.d.ts +28 -2
  89. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  90. package/dist/cpsat/rules/registry.js +4 -1
  91. package/dist/cpsat/rules/registry.js.map +1 -1
  92. package/dist/cpsat/rules/resolver.js +2 -2
  93. package/dist/cpsat/rules/resolver.js.map +1 -1
  94. package/dist/cpsat/rules/rules.types.d.ts +3 -0
  95. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  96. package/dist/cpsat/rules/scope.types.d.ts +18 -1
  97. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  98. package/dist/cpsat/rules/scope.types.js +59 -16
  99. package/dist/cpsat/rules/scope.types.js.map +1 -1
  100. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  101. package/dist/cpsat/rules/time-cost-surcharge.js +2 -1
  102. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  103. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  104. package/dist/cpsat/rules/time-off.js +6 -3
  105. package/dist/cpsat/rules/time-off.js.map +1 -1
  106. package/dist/cpsat/semantic-time.d.ts +44 -42
  107. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  108. package/dist/cpsat/semantic-time.js +64 -46
  109. package/dist/cpsat/semantic-time.js.map +1 -1
  110. package/dist/cpsat/types.d.ts +37 -27
  111. package/dist/cpsat/types.d.ts.map +1 -1
  112. package/dist/cpsat/utils.d.ts.map +1 -1
  113. package/dist/cpsat/utils.js +7 -12
  114. package/dist/cpsat/utils.js.map +1 -1
  115. package/dist/cpsat/validation-reporter.d.ts +10 -7
  116. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  117. package/dist/cpsat/validation-reporter.js +44 -72
  118. package/dist/cpsat/validation-reporter.js.map +1 -1
  119. package/dist/cpsat/validation.types.d.ts +54 -44
  120. package/dist/cpsat/validation.types.d.ts.map +1 -1
  121. package/dist/cpsat/validation.types.js +15 -10
  122. package/dist/cpsat/validation.types.js.map +1 -1
  123. package/dist/datetime.utils.d.ts +3 -203
  124. package/dist/datetime.utils.d.ts.map +1 -1
  125. package/dist/datetime.utils.js +1 -288
  126. package/dist/datetime.utils.js.map +1 -1
  127. package/dist/index.d.ts +14 -83
  128. package/dist/index.d.ts.map +1 -1
  129. package/dist/index.js +11 -83
  130. package/dist/index.js.map +1 -1
  131. package/dist/schedule/cost.d.ts +204 -0
  132. package/dist/schedule/cost.d.ts.map +1 -0
  133. package/dist/schedule/cost.js +187 -0
  134. package/dist/schedule/cost.js.map +1 -0
  135. package/dist/schedule/coverage.d.ts +85 -0
  136. package/dist/schedule/coverage.d.ts.map +1 -0
  137. package/dist/schedule/coverage.js +33 -0
  138. package/dist/schedule/coverage.js.map +1 -0
  139. package/dist/schedule/definition.d.ts +227 -0
  140. package/dist/schedule/definition.d.ts.map +1 -0
  141. package/dist/schedule/definition.js +659 -0
  142. package/dist/schedule/definition.js.map +1 -0
  143. package/dist/schedule/index.d.ts +67 -0
  144. package/dist/schedule/index.d.ts.map +1 -0
  145. package/dist/schedule/index.js +69 -0
  146. package/dist/schedule/index.js.map +1 -0
  147. package/dist/schedule/rules.d.ts +353 -0
  148. package/dist/schedule/rules.d.ts.map +1 -0
  149. package/dist/schedule/rules.js +352 -0
  150. package/dist/schedule/rules.js.map +1 -0
  151. package/dist/schedule/shift-patterns.d.ts +34 -0
  152. package/dist/schedule/shift-patterns.d.ts.map +1 -0
  153. package/dist/schedule/shift-patterns.js +41 -0
  154. package/dist/schedule/shift-patterns.js.map +1 -0
  155. package/dist/schedule/time-periods.d.ts +69 -0
  156. package/dist/schedule/time-periods.d.ts.map +1 -0
  157. package/dist/schedule/time-periods.js +91 -0
  158. package/dist/schedule/time-periods.js.map +1 -0
  159. package/dist/types.d.ts +14 -78
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/types.js.map +1 -1
  162. package/package.json +4 -9
  163. package/solver/src/solver/app.py +1 -1
  164. package/solver/src/solver/solver.py +7 -4
  165. package/src/client.ts +6 -8
  166. package/src/client.types.ts +9 -0
  167. package/src/cpsat/model-builder.ts +44 -35
  168. package/src/cpsat/response.ts +13 -1
  169. package/src/cpsat/rules/cost-utils.ts +25 -0
  170. package/src/cpsat/rules/day-cost-multiplier.ts +3 -14
  171. package/src/cpsat/rules/day-cost-surcharge.ts +3 -8
  172. package/src/cpsat/rules/index.ts +3 -0
  173. package/src/cpsat/rules/max-consecutive-days.ts +17 -0
  174. package/src/cpsat/rules/max-days-week.ts +143 -0
  175. package/src/cpsat/rules/max-hours-day.ts +21 -1
  176. package/src/cpsat/rules/max-hours-week.ts +22 -1
  177. package/src/cpsat/rules/max-shifts-day.ts +21 -1
  178. package/src/cpsat/rules/min-consecutive-days.ts +16 -1
  179. package/src/cpsat/rules/min-days-week.ts +120 -0
  180. package/src/cpsat/rules/min-hours-day.ts +16 -1
  181. package/src/cpsat/rules/min-hours-week.ts +17 -1
  182. package/src/cpsat/rules/min-rest-between-shifts.ts +92 -2
  183. package/src/cpsat/rules/minimize-cost.ts +2 -29
  184. package/src/cpsat/rules/must-assign.ts +108 -0
  185. package/src/cpsat/rules/overtime-daily-multiplier.ts +1 -12
  186. package/src/cpsat/rules/overtime-daily-surcharge.ts +1 -6
  187. package/src/cpsat/rules/overtime-tiered-multiplier.ts +6 -13
  188. package/src/cpsat/rules/overtime-weekly-multiplier.ts +1 -12
  189. package/src/cpsat/rules/overtime-weekly-surcharge.ts +1 -6
  190. package/src/cpsat/rules/registry.ts +8 -2
  191. package/src/cpsat/rules/resolver.ts +2 -2
  192. package/src/cpsat/rules/rules.types.ts +3 -0
  193. package/src/cpsat/rules/scope.types.ts +73 -20
  194. package/src/cpsat/rules/time-cost-surcharge.ts +2 -1
  195. package/src/cpsat/rules/time-off.ts +6 -2
  196. package/src/cpsat/semantic-time.ts +115 -91
  197. package/src/cpsat/types.ts +37 -27
  198. package/src/cpsat/utils.ts +8 -12
  199. package/src/cpsat/validation-reporter.ts +51 -82
  200. package/src/cpsat/validation.types.ts +72 -47
  201. package/src/datetime.utils.ts +3 -334
  202. package/src/index.ts +35 -107
  203. package/src/schedule/cost.ts +242 -0
  204. package/src/schedule/coverage.ts +135 -0
  205. package/src/schedule/definition.ts +958 -0
  206. package/src/schedule/index.ts +112 -0
  207. package/src/schedule/rules.ts +529 -0
  208. package/src/schedule/shift-patterns.ts +46 -0
  209. package/src/schedule/time-periods.ts +110 -0
  210. package/src/types.ts +14 -88
  211. package/dist/errors.d.ts +0 -12
  212. package/dist/errors.d.ts.map +0 -1
  213. package/dist/errors.js +0 -17
  214. package/dist/errors.js.map +0 -1
  215. package/dist/llms.d.ts +0 -2
  216. package/dist/llms.d.ts.map +0 -1
  217. package/dist/llms.js +0 -3
  218. package/dist/llms.js.map +0 -1
  219. package/dist/schedule.d.ts +0 -724
  220. package/dist/schedule.d.ts.map +0 -1
  221. package/dist/schedule.js +0 -899
  222. package/dist/schedule.js.map +0 -1
  223. package/dist/validation.d.ts +0 -105
  224. package/dist/validation.d.ts.map +0 -1
  225. package/dist/validation.js +0 -130
  226. package/dist/validation.js.map +0 -1
  227. package/llms.txt +0 -925
  228. package/src/errors.ts +0 -17
  229. package/src/llms.ts +0 -3
  230. package/src/schedule.ts +0 -1419
  231. package/src/validation.ts +0 -188
package/llms.txt DELETED
@@ -1,925 +0,0 @@
1
- # dabke
2
-
3
- > Scheduling library powered by constraint programming (CP-SAT)
4
-
5
- Define teams, shifts, coverage, and rules declaratively. dabke compiles
6
- them into a constraint model and solves for an optimized schedule.
7
-
8
- ## Core Concepts
9
-
10
- **Schedule Definition**: The primary API. Small, composable functions
11
- (`time`, `cover`, `shift`, rule functions) produce a
12
- complete scheduling configuration via `defineSchedule`. Each concept
13
- is a single function call with full type safety.
14
-
15
- **Times vs Shift Patterns**: These are two distinct concepts.
16
- `times` are named time windows used to define and reference recurring
17
- periods: service hours, delivery windows, peak periods, weekly events
18
- like a fire drill. Times may overlap (e.g., "dinner" 18:00-22:00 and
19
- "happy_hour" 17:30-18:30). Coverage and rules reference these names.
20
- `shiftPatterns` define WHEN people CAN work (available time slots).
21
- The solver assigns people to shift patterns whose hours overlap with
22
- times to satisfy coverage. Not every shift pattern needs a
23
- corresponding time; create times only for periods you need to
24
- reference.
25
-
26
- **Rules**: Business requirements expressed as scheduling constraints.
27
- - Built-in rules: hours limits, time-off, rest periods, preferences, cost optimization
28
- - Scoping: apply rules globally, per person, per role, per skill, or per time period
29
- - Priority: `MANDATORY` (hard constraint) vs `LOW`/`MEDIUM`/`HIGH` (soft preferences)
30
-
31
- **Solving**: `ScheduleDefinition.createSchedulerConfig` merges the
32
- static definition with runtime data (members, scheduling period).
33
- `ModelBuilder` compiles the config into a solver request;
34
- `HttpSolverClient` sends it to the CP-SAT solver.
35
-
36
- **Example:**
37
- Define a schedule
38
- ```typescript
39
- import {
40
- defineSchedule, t, time, cover, shift,
41
- maxHoursPerWeek, minRestBetweenShifts, timeOff,
42
- weekdays, weekend,
43
- } from "dabke";
44
-
45
- const schedule = defineSchedule({
46
- roles: ["nurse", "doctor"],
47
- skills: ["charge_nurse"],
48
-
49
- times: {
50
- morning_round: time({ startTime: t(7), endTime: t(9) }),
51
- day_ward: time({ startTime: t(7), endTime: t(15) }),
52
- night_ward: time({ startTime: t(23), endTime: t(7) }),
53
- },
54
-
55
- coverage: [
56
- cover("morning_round", "doctor", 1),
57
- cover("day_ward", "nurse", 3, { dayOfWeek: weekdays }),
58
- cover("day_ward", "nurse", 2, { dayOfWeek: weekend }),
59
- cover("night_ward", "nurse", 2),
60
- cover("night_ward", "charge_nurse", 1),
61
- ],
62
-
63
- shiftPatterns: [
64
- shift("day", t(7), t(15)),
65
- shift("night", t(23), t(7)),
66
- ],
67
-
68
- rules: [
69
- maxHoursPerWeek(40),
70
- minRestBetweenShifts(11),
71
- timeOff({ appliesTo: "alice", dayOfWeek: weekend }),
72
- ],
73
- });
74
- ```
75
-
76
- **Example:**
77
- Solve a schedule
78
- ```typescript
79
- import { ModelBuilder, HttpSolverClient, parseSolverResponse } from "dabke";
80
-
81
- const config = schedule.createSchedulerConfig({
82
- schedulingPeriod: {
83
- dateRange: { start: "2026-02-09", end: "2026-02-15" },
84
- },
85
- members: [
86
- { id: "alice", roles: ["nurse"], skills: ["charge_nurse"] },
87
- { id: "bob", roles: ["nurse"] },
88
- { id: "carol", roles: ["doctor"] },
89
- ],
90
- });
91
-
92
- const builder = new ModelBuilder(config);
93
- const { request, canSolve, validation } = builder.compile();
94
- if (canSolve) {
95
- const client = new HttpSolverClient(fetch, "http://localhost:8080");
96
- const response = await client.solve(request);
97
- const result = parseSolverResponse(response);
98
- }
99
- ```
100
-
101
- ---
102
-
103
- ## Schedule Definition
104
-
105
- ### `defineSchedule`
106
-
107
- Defines a complete schedule configuration.
108
-
109
- Validates the static config at call time (role/skill disjointness, coverage
110
- targets, shift pattern roles). Returns a `ScheduleDefinition` whose
111
- `createSchedulerConfig` method validates runtime data (member IDs,
112
- `appliesTo` resolution) and produces a `ModelBuilderConfig`.
113
-
114
- **Example:**
115
- ```typescript
116
- import { defineSchedule, t, time, cover, shift, maxHoursPerDay } from "dabke";
117
-
118
- export default defineSchedule({
119
- roles: ["agent", "supervisor"],
120
- times: { peak: time({ startTime: t(9), endTime: t(17) }) },
121
- coverage: [cover("peak", "agent", 4)],
122
- shiftPatterns: [shift("day", t(9), t(17))],
123
- rules: [maxHoursPerDay(8)],
124
- });
125
- ```
126
-
127
- ```ts
128
- defineSchedule(config: ScheduleConfig<R, S, T>): ScheduleDefinition
129
- ```
130
-
131
- ### `ScheduleDefinition`
132
-
133
- Result of `defineSchedule`.
134
-
135
- **Properties:**
136
- - `createSchedulerConfig: ModelBuilderConfig` — Produce a `ModelBuilderConfig` for the solver.
137
- - `roles: readonly string[]` — Declared role names.
138
- - `skills: readonly string[]` — Declared skill names.
139
- - `timeNames: readonly string[]` — Names of declared semantic times.
140
- - `shiftPatternIds: readonly string[]` — Shift pattern IDs.
141
- - `ruleNames: readonly string[]` — Internal rule identifiers in kebab-case (e.g., "max-hours-day", "time-off").
142
-
143
- ### `RuntimeArgs`
144
-
145
- Runtime arguments passed to `ScheduleDefinition.createSchedulerConfig`.
146
-
147
- Separates data known at runtime (team roster, date range, ad-hoc rules)
148
- from the static schedule definition. Runtime rules are merged after the
149
- definition's own rules and undergo the same `appliesTo` resolution.
150
-
151
- **Properties:**
152
- - `schedulingPeriod: SchedulingPeriod` — The scheduling period (date range + optional filters).
153
- - `members: SchedulingMember[]` — Team members available for this scheduling run.
154
- - `runtimeRules?: RuleEntry[]` — Ad-hoc rules injected at runtime (e.g., vacation, holiday closures).
155
-
156
- ---
157
-
158
- ## Time Periods
159
-
160
- ### `t`
161
-
162
- Creates a `TimeOfDay` value.
163
-
164
- **Example:**
165
- Hours only
166
- ```ts
167
- t(9) // { hours: 9, minutes: 0 }
168
- ```
169
-
170
- **Example:**
171
- Hours and minutes
172
- ```ts
173
- t(17, 30) // { hours: 17, minutes: 30 }
174
- ```
175
-
176
- **Parameters:**
177
- - `hours: number` — Hour component (0-23)
178
- - `minutes: number` — Minute component (0-59)
179
-
180
- **Returns:** `TimeOfDay`
181
-
182
- ### `time`
183
-
184
- Defines a named time window.
185
-
186
- A semantic time is any recurring period you need to reference:
187
- service hours, delivery windows, peak periods, weekly events. Times
188
- may overlap (e.g., "dinner" 18:00-22:00 and "happy_hour"
189
- 17:30-18:30, or "lunch" 12:00-14:00 with "peak_lunch"
190
- 13:00-13:30). Coverage and rules reference these names; each
191
- generates independent constraints.
192
-
193
- Every argument is a `SemanticTimeVariant` with `startTime`/`endTime`
194
- and optional `dayOfWeek`/`dates` scoping. An entry without scoping is the
195
- default (applies when no scoped entry matches). At most one default is
196
- allowed. If no default, the time only exists on the scoped days.
197
-
198
- Resolution precedence: `dates` > `dayOfWeek` > default.
199
-
200
- **Example:**
201
- Every day
202
- ```typescript
203
- day_shift: time({ startTime: t(7), endTime: t(15) }),
204
- ```
205
-
206
- **Example:**
207
- Default with weekend variant
208
- ```typescript
209
- peak_hours: time(
210
- { startTime: t(9), endTime: t(17) },
211
- { startTime: t(10), endTime: t(15), dayOfWeek: weekend },
212
- ),
213
- ```
214
-
215
- **Example:**
216
- No default (specific days only)
217
- ```typescript
218
- happy_hour: time(
219
- { startTime: t(16), endTime: t(18), dayOfWeek: ["monday", "tuesday"] },
220
- { startTime: t(17), endTime: t(19), dayOfWeek: ["friday"] },
221
- ),
222
- ```
223
-
224
- ```ts
225
- time(entries: [SemanticTimeVariant, ...SemanticTimeVariant[]]): SemanticTimeEntry
226
- ```
227
-
228
- ### `weekdays`
229
-
230
- Monday through Friday.
231
-
232
- ```typescript
233
- readonly DayOfWeek[]
234
- ```
235
-
236
- ### `weekend`
237
-
238
- Saturday and Sunday.
239
-
240
- ```typescript
241
- readonly DayOfWeek[]
242
- ```
243
-
244
- ---
245
-
246
- ## Coverage
247
-
248
- ### `cover`
249
-
250
- Defines a staffing requirement for a semantic time period.
251
-
252
- Two call forms are supported:
253
-
254
- **Simple form** `cover(time, target, count, opts?)` creates a single
255
- constraint. Use `dayOfWeek`/`dates` in `opts` to restrict which days
256
- it applies to.
257
-
258
- **Variant form** `cover(time, target, ...variants)` accepts one or more
259
- `CoverageVariant` entries with day-specific counts. For each
260
- scheduling day, exactly one variant is selected using the same
261
- precedence as `time`: `dates` > `dayOfWeek` > default (unscoped).
262
- At most one variant may be unscoped (the default). Days with no matching
263
- variant produce no coverage. See `CoverageVariant` for the entry
264
- shape.
265
-
266
- **Target resolution.** The `target` parameter is resolved against declared
267
- `roles` and `skills`:
268
-
269
- - Single string: matched against roles first, then skills.
270
- - Array of strings: OR logic (any of the listed roles).
271
- - With `skills` option (simple form only): role AND skill(s) filter.
272
-
273
- **Example:**
274
- Basic role coverage
275
- ```ts
276
- cover("day_shift", "nurse", 3)
277
- ```
278
-
279
- **Example:**
280
- OR logic (any of the listed roles)
281
- ```ts
282
- cover("day_shift", ["manager", "team_lead"], 1)
283
- ```
284
-
285
- **Example:**
286
- Skill-based coverage
287
- ```ts
288
- cover("night_shift", "keyholder", 1)
289
- ```
290
-
291
- **Example:**
292
- Role with skill filter (role AND skill)
293
- ```ts
294
- cover("day_shift", "nurse", 1, { skills: ["charge_nurse"] })
295
- ```
296
-
297
- **Example:**
298
- Day-of-week scoping (simple form)
299
- ```ts
300
- cover("peak_hours", "cashier", 3, { dayOfWeek: weekdays }),
301
- cover("peak_hours", "cashier", 5, { dayOfWeek: weekend }),
302
- ```
303
-
304
- **Example:**
305
- Default with date override (variant form)
306
- ```ts
307
- cover("peak_hours", "agent",
308
- { count: 4 },
309
- { count: 2, dates: ["2025-12-24"] },
310
- )
311
- ```
312
-
313
- **Example:**
314
- Weekday vs weekend with holiday override (variant form)
315
- ```ts
316
- cover("peak_hours", "agent",
317
- { count: 3, dayOfWeek: weekdays },
318
- { count: 5, dayOfWeek: weekend },
319
- { count: 8, dates: ["2025-12-31"] },
320
- )
321
- ```
322
-
323
- **Parameters:**
324
- - `timeName: T` — Name of a declared semantic time
325
- - `target: R | [R, ...R[]]` — Role name, skill name, or array of role names (OR)
326
- - `count: number` — Number of people needed (simple form)
327
- - `opts?: CoverageOptions` — See `CoverageOptions` (simple form)
328
-
329
- **Returns:** `CoverageEntry<T, R>`
330
-
331
- ### `CoverageOptions`
332
-
333
- Options for a `cover` call.
334
-
335
- Day/date scoping controls which days this coverage entry applies to.
336
- An entry without `dayOfWeek` or `dates` applies every day in the
337
- scheduling period.
338
-
339
- **Properties:**
340
- - `skills?: [string, ...string[]]` — Additional skill filter (AND logic with the target role).
341
- - `dayOfWeek?: readonly DayOfWeek[]` — Restrict to specific days of the week.
342
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
343
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
344
-
345
- ### `CoverageVariant`
346
-
347
- A day-specific count within a variant `cover` call.
348
-
349
- Each variant specifies a count and optional day/date scope. During
350
- resolution, the most specific matching variant wins for each day
351
- (`dates` > `dayOfWeek` > default), mirroring `SemanticTimeVariant`.
352
- At most one variant may be unscoped (the default).
353
-
354
- **Example:**
355
- ```typescript
356
- // Default: 4 agents. Christmas Eve: 2.
357
- cover("peak_hours", "agent",
358
- { count: 4 },
359
- { count: 2, dates: ["2025-12-24"] },
360
- )
361
- ```
362
-
363
- **Properties:**
364
- - `count: number` — Number of people needed.
365
- - `dayOfWeek?: readonly DayOfWeek[]` — Restrict this variant to specific days of the week.
366
- - `dates?: string[]` — Restrict this variant to specific dates (YYYY-MM-DD).
367
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
368
-
369
- ---
370
-
371
- ## Shift Patterns
372
-
373
- ### `shift`
374
-
375
- Creates a `ShiftPattern` (time slot template).
376
-
377
- Shift patterns define when people can work: the concrete time slots
378
- the solver may assign members to. Each pattern repeats across all
379
- scheduling days unless filtered by `dayOfWeek` or `roles`.
380
-
381
- **Example:**
382
- ```typescript
383
- shift("early", t(6), t(14)),
384
- shift("day", t(9), t(17)),
385
- shift("night", t(22), t(6), { roles: ["nurse", "doctor"] }),
386
- ```
387
-
388
- ```ts
389
- shift(id: string, startTime: TimeOfDay, endTime: TimeOfDay, opts?: Pick<ShiftPattern, "roles" | "dayOfWeek" | "locationId">): ShiftPattern
390
- ```
391
-
392
- ---
393
-
394
- ## Rules
395
-
396
- ### `RuleOptions`
397
-
398
- Scoping options shared by most rule functions.
399
-
400
- Each rule function returns an opaque `RuleEntry` for the `rules`
401
- array. Most accept a `RuleOptions` parameter for scoping and priority.
402
-
403
- **Entity scoping.** `appliesTo` targets a role name, skill name, or
404
- member ID. It is resolved against declared roles first, then skills,
405
- then runtime member IDs. The namespaces are guaranteed disjoint by
406
- validation. Unscoped rules apply to all members.
407
-
408
- **Time scoping.** `dayOfWeek`, `dateRange`, `dates`, and
409
- `recurringPeriods` narrow when the rule is active. Unscoped rules
410
- apply to every day in the scheduling period.
411
-
412
- **Priority.** Defaults to `MANDATORY` (hard constraint the solver
413
- must satisfy). Use `LOW`, `MEDIUM`, or `HIGH` for soft preferences
414
- the solver may violate when necessary.
415
-
416
- **Properties:**
417
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
418
- - `dayOfWeek?: readonly DayOfWeek[]` — Restrict to specific days of the week.
419
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
420
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
421
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
422
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
423
-
424
- ### `EntityOnlyRuleOptions`
425
-
426
- Options for rules that support entity scoping only (no time scoping).
427
-
428
- Used by rules whose semantics are inherently per-day or per-week
429
- (e.g., `minHoursPerDay`, `maxConsecutiveDays`) and cannot
430
- be meaningfully restricted to a date range or day of week.
431
-
432
- **Properties:**
433
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
434
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
435
-
436
- ### `TimeOffOptions`
437
-
438
- Options for `timeOff`.
439
-
440
- At least one time scoping field is required (`dayOfWeek`, `dateRange`,
441
- `dates`, or `recurringPeriods`). Use `from`/`until` to block only part
442
- of a day.
443
-
444
- **Properties:**
445
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
446
- - `from?: TimeOfDay` — Off from this time until end of day.
447
- - `until?: TimeOfDay` — Off from start of day until this time.
448
- - `dayOfWeek?: readonly DayOfWeek[]` — Restrict to specific days of the week.
449
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
450
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
451
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
452
- - `priority?: Priority` — Defaults to `"MANDATORY"`.
453
-
454
- ### `CostRuleOptions`
455
-
456
- Options for cost rules.
457
-
458
- Cost rules are objective terms, not constraints. The `priority` field from
459
- `RuleOptions` does not apply.
460
-
461
- **Properties:**
462
- - `appliesTo?: string | string[]` — Who this rule applies to (role name, skill name, or member ID).
463
- - `dayOfWeek?: DayOfWeek[]` — Restrict to specific days of the week.
464
- - `dateRange?: { start: string; end: string }` — Restrict to a date range.
465
- - `dates?: string[]` — Restrict to specific dates (YYYY-MM-DD).
466
- - `recurringPeriods?: [RecurringPeriod, ...RecurringPeriod[]]` — Restrict to recurring calendar periods.
467
-
468
- ### `maxHoursPerDay`
469
-
470
- Limits how many hours a person can work in a single day.
471
-
472
- **Example:**
473
- Global limit
474
- ```ts
475
- maxHoursPerDay(10)
476
- ```
477
-
478
- **Example:**
479
- Scoped to a role
480
- ```ts
481
- maxHoursPerDay(6, { appliesTo: "student" })
482
- ```
483
-
484
- ```ts
485
- maxHoursPerDay(hours: number, opts?: RuleOptions): RuleEntry
486
- ```
487
-
488
- ### `maxHoursPerWeek`
489
-
490
- Caps total hours a person can work within each scheduling week.
491
-
492
- **Example:**
493
- Global cap
494
- ```ts
495
- maxHoursPerWeek(48)
496
- ```
497
-
498
- **Example:**
499
- Part-time cap for a skill group
500
- ```ts
501
- maxHoursPerWeek(20, { appliesTo: "part_time" })
502
- ```
503
-
504
- ```ts
505
- maxHoursPerWeek(hours: number, opts?: RuleOptions): RuleEntry
506
- ```
507
-
508
- ### `minHoursPerDay`
509
-
510
- Ensures a person works at least a minimum number of hours per day when assigned.
511
-
512
- **Example:**
513
- ```ts
514
- minHoursPerDay(4)
515
- ```
516
-
517
- ```ts
518
- minHoursPerDay(hours: number, opts?: EntityOnlyRuleOptions): RuleEntry
519
- ```
520
-
521
- ### `minHoursPerWeek`
522
-
523
- Enforces a minimum total number of hours per scheduling week.
524
-
525
- **Example:**
526
- Guaranteed minimum for full-time members
527
- ```ts
528
- minHoursPerWeek(30, { appliesTo: "full_time" })
529
- ```
530
-
531
- ```ts
532
- minHoursPerWeek(hours: number, opts?: EntityOnlyRuleOptions): RuleEntry
533
- ```
534
-
535
- ### `maxShiftsPerDay`
536
-
537
- Limits how many shifts a person can work in a single day.
538
-
539
- **Example:**
540
- One shift per day
541
- ```ts
542
- maxShiftsPerDay(1)
543
- ```
544
-
545
- ```ts
546
- maxShiftsPerDay(shifts: number, opts?: RuleOptions): RuleEntry
547
- ```
548
-
549
- ### `maxConsecutiveDays`
550
-
551
- Limits how many consecutive days a person can be assigned.
552
-
553
- **Example:**
554
- Five-day work week limit
555
- ```ts
556
- maxConsecutiveDays(5)
557
- ```
558
-
559
- ```ts
560
- maxConsecutiveDays(days: number, opts?: EntityOnlyRuleOptions): RuleEntry
561
- ```
562
-
563
- ### `minConsecutiveDays`
564
-
565
- Requires a minimum stretch of consecutive working days once assigned.
566
-
567
- **Example:**
568
- ```ts
569
- minConsecutiveDays(2)
570
- ```
571
-
572
- ```ts
573
- minConsecutiveDays(days: number, opts?: EntityOnlyRuleOptions): RuleEntry
574
- ```
575
-
576
- ### `minRestBetweenShifts`
577
-
578
- Enforces a minimum rest period between any two shifts a person works.
579
-
580
- **Example:**
581
- EU Working Time Directive (11 hours)
582
- ```ts
583
- minRestBetweenShifts(11)
584
- ```
585
-
586
- ```ts
587
- minRestBetweenShifts(hours: number, opts?: EntityOnlyRuleOptions): RuleEntry
588
- ```
589
-
590
- ### `preference`
591
-
592
- Adds objective weight to prefer or avoid assigning team members.
593
-
594
- **Example:**
595
- Prefer assigning full-time staff
596
- ```ts
597
- preference("high", { appliesTo: "full_time" })
598
- ```
599
-
600
- **Example:**
601
- Avoid assigning a specific member on weekends
602
- ```ts
603
- preference("low", { appliesTo: "alice", dayOfWeek: weekend })
604
- ```
605
-
606
- **Parameters:**
607
- - `level: "high" | "low"` — `"high"` to prefer assigning, `"low"` to avoid
608
- - `opts?: Omit<RuleOptions, "priority">` — Entity and time scoping (no priority; preference is the priority mechanism)
609
-
610
- **Returns:** `RuleEntry`
611
-
612
- ### `preferLocation`
613
-
614
- Prefers assigning a person to shift patterns at a specific location.
615
-
616
- **Example:**
617
- ```ts
618
- preferLocation("north_wing", { appliesTo: "alice" })
619
- ```
620
-
621
- ```ts
622
- preferLocation(locationId: string, opts?: EntityOnlyRuleOptions): RuleEntry
623
- ```
624
-
625
- ### `timeOff`
626
-
627
- Blocks or penalizes assignments during specified time periods.
628
-
629
- At least one time scoping field is required (`dayOfWeek`, `dateRange`,
630
- `dates`, or `recurringPeriods`).
631
-
632
- Use `from` for "off from this time until end of day" and `until` for
633
- "off from start of day until this time."
634
-
635
- **Example:**
636
- ```typescript
637
- timeOff({ appliesTo: "mauro", dayOfWeek: weekend }),
638
- timeOff({ appliesTo: "student", dayOfWeek: ["wednesday"], from: t(14) }),
639
- timeOff({ appliesTo: "alice", dateRange: { start: "2024-02-01", end: "2024-02-05" } }),
640
- ```
641
-
642
- ```ts
643
- timeOff(opts: TimeOffOptions): RuleEntry
644
- ```
645
-
646
- ### `assignTogether`
647
-
648
- Encourages or enforces that team members work the same shifts on a day.
649
-
650
- **Example:**
651
- ```typescript
652
- assignTogether(["alice", "bob"], { priority: "HIGH" }),
653
- ```
654
-
655
- ```ts
656
- assignTogether(members: [string, string, ...string[]], opts?: AssignTogetherOptions): RuleEntry
657
- ```
658
-
659
- ### `minimizeCost`
660
-
661
- Tells the solver to minimize total labor cost.
662
-
663
- Without this rule, cost modifiers only affect post-solve calculation.
664
- When present, the solver actively prefers cheaper assignments.
665
-
666
- For hourly members, penalizes each assignment proportionally to cost.
667
- For salaried members, adds a fixed weekly salary cost when they have
668
- any assignment that week (zero marginal cost up to contracted hours).
669
-
670
- **Example:**
671
- ```ts
672
- minimizeCost()
673
- ```
674
-
675
- ```ts
676
- minimizeCost(opts?: CostRuleOptions): RuleEntry
677
- ```
678
-
679
- ### `dayMultiplier`
680
-
681
- Multiplies the base rate for assignments on specified days.
682
-
683
- The base cost (1x) is already counted by `minimizeCost`;
684
- this rule adds only the extra portion above 1x.
685
-
686
- **Example:**
687
- Weekend multiplier
688
- ```typescript
689
- dayMultiplier(1.5, { dayOfWeek: weekend })
690
- ```
691
-
692
- ```ts
693
- dayMultiplier(factor: number, opts?: CostRuleOptions): RuleEntry
694
- ```
695
-
696
- ### `daySurcharge`
697
-
698
- Adds a flat extra amount per hour for assignments on specified days.
699
-
700
- The surcharge is independent of the member's base rate.
701
-
702
- **Example:**
703
- Weekend surcharge
704
- ```typescript
705
- daySurcharge(500, { dayOfWeek: weekend })
706
- ```
707
-
708
- ```ts
709
- daySurcharge(amountPerHour: number, opts?: CostRuleOptions): RuleEntry
710
- ```
711
-
712
- ### `timeSurcharge`
713
-
714
- Adds a flat surcharge per hour for the portion of a shift that overlaps a time-of-day window.
715
-
716
- The window supports overnight spans (e.g., 22:00-06:00). The surcharge
717
- is independent of the member's base rate.
718
-
719
- **Example:**
720
- Night differential
721
- ```typescript
722
- timeSurcharge(200, { from: t(22), until: t(6) })
723
- ```
724
-
725
- **Parameters:**
726
- - `amountPerHour: number` — Flat surcharge per hour in smallest currency unit
727
- - `window: { from: TimeOfDay; until: TimeOfDay }` — Time-of-day window
728
- - `opts?: CostRuleOptions` — Entity and time scoping
729
-
730
- **Returns:** `RuleEntry`
731
-
732
- ### `overtimeMultiplier`
733
-
734
- Applies a multiplier to hours beyond a weekly threshold.
735
-
736
- Only the extra portion above 1x is added (the base cost is already
737
- counted by `minimizeCost`).
738
-
739
- **Example:**
740
- ```typescript
741
- overtimeMultiplier({ after: 40, factor: 1.5 })
742
- ```
743
-
744
- ```ts
745
- overtimeMultiplier(opts: { after: number; factor: number } & CostRuleOptions): RuleEntry
746
- ```
747
-
748
- ### `overtimeSurcharge`
749
-
750
- Adds a flat surcharge per hour beyond a weekly threshold.
751
-
752
- The surcharge is independent of the member's base rate.
753
-
754
- **Example:**
755
- ```typescript
756
- overtimeSurcharge({ after: 40, amount: 1000 })
757
- ```
758
-
759
- ```ts
760
- overtimeSurcharge(opts: { after: number; amount: number } & CostRuleOptions): RuleEntry
761
- ```
762
-
763
- ### `dailyOvertimeMultiplier`
764
-
765
- Applies a multiplier to hours beyond a daily threshold.
766
-
767
- Only the extra portion above 1x is added (the base cost is already
768
- counted by `minimizeCost`).
769
-
770
- **Example:**
771
- ```typescript
772
- dailyOvertimeMultiplier({ after: 8, factor: 1.5 })
773
- ```
774
-
775
- ```ts
776
- dailyOvertimeMultiplier(opts: { after: number; factor: number } & CostRuleOptions): RuleEntry
777
- ```
778
-
779
- ### `dailyOvertimeSurcharge`
780
-
781
- Adds a flat surcharge per hour beyond a daily threshold.
782
-
783
- The surcharge is independent of the member's base rate.
784
-
785
- **Example:**
786
- ```typescript
787
- dailyOvertimeSurcharge({ after: 8, amount: 500 })
788
- ```
789
-
790
- ```ts
791
- dailyOvertimeSurcharge(opts: { after: number; amount: number } & CostRuleOptions): RuleEntry
792
- ```
793
-
794
- ### `tieredOvertimeMultiplier`
795
-
796
- Applies multiple overtime thresholds with increasing multipliers.
797
-
798
- Each tier applies only to the hours between its threshold and the next.
799
- Tiers must be sorted by threshold ascending.
800
-
801
- **Example:**
802
- ```typescript
803
- // Hours 0-40: base rate
804
- // Hours 40-48: 1.5x
805
- // Hours 48+: 2.0x
806
- tieredOvertimeMultiplier([
807
- { after: 40, factor: 1.5 },
808
- { after: 48, factor: 2.0 },
809
- ])
810
- ```
811
-
812
- ```ts
813
- tieredOvertimeMultiplier(tiers: [OvertimeTier, ...OvertimeTier[]], opts?: CostRuleOptions): RuleEntry
814
- ```
815
-
816
- ---
817
-
818
- ## Supporting Types
819
-
820
- ### `TimeOfDay`
821
-
822
- Time of day representation (hours and minutes, with optional seconds/nanos).
823
-
824
- Used for defining shift start/end times and semantic time boundaries.
825
- Hours are in 24-hour format (0-23).
826
-
827
- **Example:**
828
- ```typescript
829
- { hours: 9, minutes: 0 }
830
- { hours: 17, minutes: 30 }
831
- ```
832
-
833
- **Properties:**
834
- - `hours: number`
835
- - `minutes: number`
836
- - `seconds?: number`
837
- - `nanos?: number`
838
-
839
- ### `DayOfWeek`
840
-
841
- Day of the week identifier.
842
-
843
- ```typescript
844
- "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"
845
- ```
846
-
847
- ### `SchedulingPeriod`
848
-
849
- Defines a scheduling period as a date range with optional filters.
850
-
851
- The `dateRange` specifies the overall scheduling window. Use `dayOfWeek`
852
- and/or `dates` to narrow which days within the range are included.
853
- Filters compose: a day must pass all specified filters to be included.
854
-
855
- **Example:**
856
- All days in a week
857
- ```typescript
858
- const period: SchedulingPeriod = {
859
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
860
- };
861
- ```
862
-
863
- **Example:**
864
- Only specific days of the week (closed Mon/Tue)
865
- ```typescript
866
- const period: SchedulingPeriod = {
867
- dateRange: { start: '2025-02-03', end: '2025-02-09' },
868
- dayOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
869
- };
870
- ```
871
-
872
- **Properties:**
873
- - `dateRange: { start: string; end: string }` — The overall scheduling window (start and end are inclusive).
874
- Dates should be in YYYY-MM-DD format.
875
- - `dayOfWeek?: DayOfWeek[]` — Include only these days of the week.
876
- If omitted, all days of the week are included.
877
- - `dates?: string[]` — Include only these specific dates (YYYY-MM-DD) within the range.
878
- If omitted, all dates in the range are included (subject to dayOfWeek filter).
879
-
880
- ### `SchedulingMember`
881
-
882
- A team member available for scheduling.
883
-
884
- Members are assigned to shift patterns by the solver based on
885
- coverage requirements, rules, and constraints.
886
-
887
- **Properties:**
888
- - `id: string` — Unique identifier for this member. Must not contain colons.
889
- - `roles: string[]` — Roles this member can fill (e.g. "nurse", "doctor").
890
- - `skills?: string[]` — Skills this member has (e.g. "charge_nurse", "forklift").
891
- - `pay?: HourlyPay | SalariedPay` — Base pay. Required when cost rules are used.
892
-
893
- ### `HourlyPay`
894
-
895
- Pay per hour in the caller's smallest currency unit (e.g., pence, cents).
896
-
897
- **Properties:**
898
- - `hourlyRate: number` — Pay per hour in smallest currency unit.
899
-
900
- ### `SalariedPay`
901
-
902
- Annual salary with contracted weekly hours.
903
-
904
- The solver treats salaried members as having a fixed weekly cost
905
- (`annual / 52`) that is incurred once they work any shift in a week.
906
- Additional shifts within the same week have zero marginal cost.
907
-
908
- Note: overtime multiplier rules apply only to hourly members.
909
- Overtime surcharge rules apply to all members regardless of pay type.
910
-
911
- **Properties:**
912
- - `annual: number` — Annual salary in smallest currency unit.
913
- - `hoursPerWeek: number` — Contracted hours per week. Reserved for future overtime support.
914
-
915
- ### `Priority`
916
-
917
- How strictly the solver enforces a rule.
918
-
919
- - `"LOW"`, `"MEDIUM"`, `"HIGH"`: soft constraints with increasing penalty for violations
920
- - `"MANDATORY"`: hard constraint; the solver will not produce a solution that violates it
921
-
922
- ```typescript
923
- "LOW" | "MEDIUM" | "HIGH" | "MANDATORY"
924
- ```
925
-