dabke 0.83.0 → 0.84.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 (330) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +22 -27
  3. package/dist/client.d.ts +39 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.schemas.d.ts +118 -18
  7. package/dist/client.schemas.d.ts.map +1 -1
  8. package/dist/client.schemas.js +110 -6
  9. package/dist/client.schemas.js.map +1 -1
  10. package/dist/client.types.d.ts +63 -7
  11. package/dist/client.types.d.ts.map +1 -1
  12. package/dist/client.types.js.map +1 -1
  13. package/dist/cpsat/cost.d.ts +5 -2
  14. package/dist/cpsat/cost.d.ts.map +1 -1
  15. package/dist/cpsat/cost.js +13 -6
  16. package/dist/cpsat/cost.js.map +1 -1
  17. package/dist/cpsat/model-builder.d.ts +42 -61
  18. package/dist/cpsat/model-builder.d.ts.map +1 -1
  19. package/dist/cpsat/model-builder.js +451 -56
  20. package/dist/cpsat/model-builder.js.map +1 -1
  21. package/dist/cpsat/response.d.ts +19 -7
  22. package/dist/cpsat/response.d.ts.map +1 -1
  23. package/dist/cpsat/response.js +48 -9
  24. package/dist/cpsat/response.js.map +1 -1
  25. package/dist/cpsat/rule-descriptor.d.ts +250 -0
  26. package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
  27. package/dist/cpsat/rule-descriptor.js +51 -0
  28. package/dist/cpsat/rule-descriptor.js.map +1 -0
  29. package/dist/cpsat/rules/artifacts.d.ts +9 -0
  30. package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
  31. package/dist/cpsat/rules/artifacts.js +31 -0
  32. package/dist/cpsat/rules/artifacts.js.map +1 -0
  33. package/dist/cpsat/rules/assign-together.d.ts +18 -20
  34. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  35. package/dist/cpsat/rules/assign-together.js +90 -54
  36. package/dist/cpsat/rules/assign-together.js.map +1 -1
  37. package/dist/cpsat/rules/assigned-day.d.ts +12 -0
  38. package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
  39. package/dist/cpsat/rules/assigned-day.js +43 -0
  40. package/dist/cpsat/rules/assigned-day.js.map +1 -0
  41. package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
  42. package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
  43. package/dist/cpsat/rules/assignment-priority.js +47 -50
  44. package/dist/cpsat/rules/assignment-priority.js.map +1 -1
  45. package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
  46. package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
  47. package/dist/cpsat/rules/assignment-terms.js +19 -0
  48. package/dist/cpsat/rules/assignment-terms.js.map +1 -0
  49. package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
  50. package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
  51. package/dist/cpsat/rules/concurrent-intervals.js +57 -0
  52. package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
  53. package/dist/cpsat/rules/cost-utils.d.ts +1 -3
  54. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
  55. package/dist/cpsat/rules/cost-utils.js +0 -7
  56. package/dist/cpsat/rules/cost-utils.js.map +1 -1
  57. package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
  58. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  59. package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
  60. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  61. package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
  62. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  63. package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
  64. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  65. package/dist/cpsat/rules/index.d.ts +30 -24
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -24
  68. package/dist/cpsat/rules/index.js.map +1 -1
  69. package/dist/cpsat/rules/location-preference.d.ts +14 -21
  70. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  71. package/dist/cpsat/rules/location-preference.js +45 -45
  72. package/dist/cpsat/rules/location-preference.js.map +1 -1
  73. package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
  74. package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
  75. package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
  76. package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
  77. package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
  78. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  79. package/dist/cpsat/rules/max-consecutive-days.js +67 -66
  80. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  81. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
  82. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
  83. package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
  84. package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
  85. package/dist/cpsat/rules/max-days-week.d.ts +17 -31
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -1
  87. package/dist/cpsat/rules/max-days-week.js +69 -81
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -1
  89. package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
  90. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  91. package/dist/cpsat/rules/max-hours-day.js +56 -72
  92. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  93. package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
  94. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  95. package/dist/cpsat/rules/max-hours-week.js +60 -75
  96. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  97. package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
  98. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  99. package/dist/cpsat/rules/max-shifts-day.js +57 -73
  100. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  101. package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
  102. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  103. package/dist/cpsat/rules/min-consecutive-days.js +123 -94
  104. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  105. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
  106. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
  107. package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
  108. package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
  109. package/dist/cpsat/rules/min-days-week.d.ts +13 -26
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -1
  111. package/dist/cpsat/rules/min-days-week.js +85 -74
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -1
  113. package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
  114. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  115. package/dist/cpsat/rules/min-hours-day.js +72 -54
  116. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  117. package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
  118. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  119. package/dist/cpsat/rules/min-hours-week.js +75 -57
  120. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  121. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
  122. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  123. package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
  124. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  125. package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
  126. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  127. package/dist/cpsat/rules/minimize-cost.js +151 -163
  128. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  129. package/dist/cpsat/rules/must-assign.d.ts +5 -42
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -1
  131. package/dist/cpsat/rules/must-assign.js +43 -75
  132. package/dist/cpsat/rules/must-assign.js.map +1 -1
  133. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
  134. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  135. package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
  136. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  137. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
  138. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  139. package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
  140. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  141. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
  142. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  143. package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
  144. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  145. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
  146. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  147. package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
  148. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  149. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
  150. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  151. package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
  152. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  153. package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
  154. package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
  155. package/dist/cpsat/rules/pattern-eligibility.js +16 -0
  156. package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
  157. package/dist/cpsat/rules/pattern-time.d.ts +5 -0
  158. package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
  159. package/dist/cpsat/rules/pattern-time.js +20 -0
  160. package/dist/cpsat/rules/pattern-time.js.map +1 -0
  161. package/dist/cpsat/rules/registry.d.ts +217 -30
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -34
  164. package/dist/cpsat/rules/registry.js.map +1 -1
  165. package/dist/cpsat/rules/resolver.d.ts +9 -7
  166. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  167. package/dist/cpsat/rules/resolver.js +19 -18
  168. package/dist/cpsat/rules/resolver.js.map +1 -1
  169. package/dist/cpsat/rules/role-preference.d.ts +24 -0
  170. package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
  171. package/dist/cpsat/rules/role-preference.js +72 -0
  172. package/dist/cpsat/rules/role-preference.js.map +1 -0
  173. package/dist/cpsat/rules/rules.types.d.ts +90 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +24 -11
  176. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  177. package/dist/cpsat/rules/scope.types.js +14 -19
  178. package/dist/cpsat/rules/scope.types.js.map +1 -1
  179. package/dist/cpsat/rules/target-days-week.d.ts +31 -0
  180. package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
  181. package/dist/cpsat/rules/target-days-week.js +80 -0
  182. package/dist/cpsat/rules/target-days-week.js.map +1 -0
  183. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
  184. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
  185. package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
  186. package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
  187. package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
  188. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  189. package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
  190. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  191. package/dist/cpsat/rules/time-off.d.ts +21 -57
  192. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  193. package/dist/cpsat/rules/time-off.js +114 -127
  194. package/dist/cpsat/rules/time-off.js.map +1 -1
  195. package/dist/cpsat/rules/variables.d.ts +4 -0
  196. package/dist/cpsat/rules/variables.d.ts.map +1 -0
  197. package/dist/cpsat/rules/variables.js +10 -0
  198. package/dist/cpsat/rules/variables.js.map +1 -0
  199. package/dist/cpsat/rules.d.ts +20 -3
  200. package/dist/cpsat/rules.d.ts.map +1 -1
  201. package/dist/cpsat/rules.js +19 -2
  202. package/dist/cpsat/rules.js.map +1 -1
  203. package/dist/cpsat/semantic-time.d.ts +7 -6
  204. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  205. package/dist/cpsat/semantic-time.js +15 -28
  206. package/dist/cpsat/semantic-time.js.map +1 -1
  207. package/dist/cpsat/types.d.ts +47 -4
  208. package/dist/cpsat/types.d.ts.map +1 -1
  209. package/dist/cpsat/types.js +24 -1
  210. package/dist/cpsat/types.js.map +1 -1
  211. package/dist/cpsat/utils.d.ts +23 -6
  212. package/dist/cpsat/utils.d.ts.map +1 -1
  213. package/dist/cpsat/utils.js +76 -15
  214. package/dist/cpsat/utils.js.map +1 -1
  215. package/dist/cpsat/validation-reporter.d.ts +6 -2
  216. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  217. package/dist/cpsat/validation-reporter.js +122 -10
  218. package/dist/cpsat/validation-reporter.js.map +1 -1
  219. package/dist/cpsat/validation.types.d.ts +21 -10
  220. package/dist/cpsat/validation.types.d.ts.map +1 -1
  221. package/dist/datetime.utils.d.ts +7 -13
  222. package/dist/datetime.utils.d.ts.map +1 -1
  223. package/dist/datetime.utils.js +29 -36
  224. package/dist/datetime.utils.js.map +1 -1
  225. package/dist/index.d.ts +12 -8
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +8 -3
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +48 -60
  230. package/dist/schedule/cost.d.ts.map +1 -1
  231. package/dist/schedule/cost.js +15 -51
  232. package/dist/schedule/cost.js.map +1 -1
  233. package/dist/schedule/coverage.d.ts +57 -28
  234. package/dist/schedule/coverage.d.ts.map +1 -1
  235. package/dist/schedule/coverage.js +24 -1
  236. package/dist/schedule/coverage.js.map +1 -1
  237. package/dist/schedule/definition.d.ts +138 -19
  238. package/dist/schedule/definition.d.ts.map +1 -1
  239. package/dist/schedule/definition.js +99 -90
  240. package/dist/schedule/definition.js.map +1 -1
  241. package/dist/schedule/index.d.ts +4 -4
  242. package/dist/schedule/index.d.ts.map +1 -1
  243. package/dist/schedule/index.js +2 -2
  244. package/dist/schedule/index.js.map +1 -1
  245. package/dist/schedule/rules.d.ts +438 -113
  246. package/dist/schedule/rules.d.ts.map +1 -1
  247. package/dist/schedule/rules.js +310 -108
  248. package/dist/schedule/rules.js.map +1 -1
  249. package/dist/schedule/shift-patterns.d.ts +0 -17
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -1
  251. package/dist/schedule/shift-patterns.js +0 -17
  252. package/dist/schedule/shift-patterns.js.map +1 -1
  253. package/dist/schedule/time-periods.d.ts +37 -27
  254. package/dist/schedule/time-periods.d.ts.map +1 -1
  255. package/dist/schedule/time-periods.js +37 -27
  256. package/dist/schedule/time-periods.js.map +1 -1
  257. package/dist/types.d.ts +67 -3
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +64 -0
  260. package/dist/types.js.map +1 -1
  261. package/package.json +8 -9
  262. package/solver/src/solver/models.py +41 -4
  263. package/solver/src/solver/solver.py +546 -157
  264. package/solver/tests/test_solver.py +363 -7
  265. package/src/client.schemas.ts +127 -10
  266. package/src/client.ts +3 -0
  267. package/src/client.types.ts +73 -7
  268. package/src/cpsat/cost.ts +17 -7
  269. package/src/cpsat/model-builder.ts +614 -129
  270. package/src/cpsat/response.ts +57 -13
  271. package/src/cpsat/rule-descriptor.ts +311 -0
  272. package/src/cpsat/rules/artifacts.ts +56 -0
  273. package/src/cpsat/rules/assign-together.ts +104 -69
  274. package/src/cpsat/rules/assigned-day.ts +70 -0
  275. package/src/cpsat/rules/assignment-priority.ts +54 -58
  276. package/src/cpsat/rules/assignment-terms.ts +43 -0
  277. package/src/cpsat/rules/concurrent-intervals.ts +93 -0
  278. package/src/cpsat/rules/cost-utils.ts +1 -9
  279. package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
  280. package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
  281. package/src/cpsat/rules/index.ts +30 -24
  282. package/src/cpsat/rules/location-preference.ts +47 -52
  283. package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
  284. package/src/cpsat/rules/max-consecutive-days.ts +74 -91
  285. package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
  286. package/src/cpsat/rules/max-days-week.ts +89 -115
  287. package/src/cpsat/rules/max-hours-day.ts +64 -84
  288. package/src/cpsat/rules/max-hours-week.ts +74 -90
  289. package/src/cpsat/rules/max-shifts-day.ts +65 -85
  290. package/src/cpsat/rules/min-consecutive-days.ts +161 -127
  291. package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
  292. package/src/cpsat/rules/min-days-week.ts +93 -91
  293. package/src/cpsat/rules/min-hours-day.ts +79 -58
  294. package/src/cpsat/rules/min-hours-week.ts +87 -62
  295. package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
  296. package/src/cpsat/rules/minimize-cost.ts +175 -188
  297. package/src/cpsat/rules/must-assign.ts +44 -81
  298. package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
  299. package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
  300. package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
  301. package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
  302. package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
  303. package/src/cpsat/rules/pattern-eligibility.ts +26 -0
  304. package/src/cpsat/rules/pattern-time.ts +30 -0
  305. package/src/cpsat/rules/registry.ts +140 -62
  306. package/src/cpsat/rules/resolver.ts +58 -30
  307. package/src/cpsat/rules/role-preference.ts +92 -0
  308. package/src/cpsat/rules/rules.types.ts +89 -17
  309. package/src/cpsat/rules/scope.types.ts +31 -26
  310. package/src/cpsat/rules/target-days-week.ts +105 -0
  311. package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
  312. package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
  313. package/src/cpsat/rules/time-off.ts +154 -163
  314. package/src/cpsat/rules/variables.ts +11 -0
  315. package/src/cpsat/rules.ts +43 -17
  316. package/src/cpsat/semantic-time.ts +29 -43
  317. package/src/cpsat/types.ts +53 -4
  318. package/src/cpsat/utils.ts +92 -20
  319. package/src/cpsat/validation-reporter.ts +170 -19
  320. package/src/cpsat/validation.types.ts +22 -11
  321. package/src/datetime.utils.ts +45 -44
  322. package/src/index.ts +64 -12
  323. package/src/schedule/cost.ts +40 -61
  324. package/src/schedule/coverage.ts +57 -28
  325. package/src/schedule/definition.ts +565 -76
  326. package/src/schedule/index.ts +23 -3
  327. package/src/schedule/rules.ts +631 -132
  328. package/src/schedule/shift-patterns.ts +0 -17
  329. package/src/schedule/time-periods.ts +37 -27
  330. package/src/types.ts +114 -2
@@ -5,19 +5,24 @@ import {
5
5
  type ParsedEntityScope,
6
6
  type ParsedTimeScope,
7
7
  } from "./scope.types.js";
8
- import { builtInCpsatRuleFactories } from "./registry.js";
8
+ import {
9
+ assertNoBuiltInCpsatRuleOverrides,
10
+ assertValidCpsatRuleRegistry,
11
+ builtInCpsatRuleRegistry,
12
+ } from "./registry.js";
9
13
  import type {
14
+ AnyCpsatRuleConfigEntry,
15
+ BuiltInCpsatRuleRegistry,
10
16
  CpsatRuleConfigEntry,
11
- CpsatRuleFactories,
12
- CpsatRuleName,
17
+ CpsatRuleConfigEntryFor,
13
18
  CpsatRuleRegistry,
14
19
  } from "./rules.types.js";
15
- import type { CompilationRule } from "../model-builder.js";
20
+ import type { CompiledRule, RuleCompileContext } from "../rule-descriptor.js";
21
+ import { compileRuleDescriptor } from "../rule-descriptor.js";
16
22
 
17
23
  type InternalEntry = {
18
24
  index: number;
19
- name: CpsatRuleName;
20
- config: CpsatRuleRegistry[CpsatRuleName];
25
+ entry: AnyCpsatRuleConfigEntry;
21
26
  entityScope: ParsedEntityScope;
22
27
  timeScope: ParsedTimeScope;
23
28
  };
@@ -25,9 +30,9 @@ type InternalEntry = {
25
30
  /**
26
31
  * Extract the config portion from a flat rule entry (everything except `name`).
27
32
  */
28
- function entryConfig(entry: CpsatRuleConfigEntry): CpsatRuleRegistry[CpsatRuleName] {
33
+ function entryConfig(entry: AnyCpsatRuleConfigEntry): Record<string, unknown> {
29
34
  const { name: _name, ...config } = entry;
30
- return config as CpsatRuleRegistry[CpsatRuleName];
35
+ return config;
31
36
  }
32
37
 
33
38
  /**
@@ -97,7 +102,9 @@ function timeScopeKey(time: ParsedTimeScope): string {
97
102
  }
98
103
 
99
104
  /**
100
- * Rules that don't use standard scoping and should pass through unchanged.
105
+ * Built-in rules that don't use standard scoping and should pass through unchanged.
106
+ * Custom rules also pass through unchanged so they can receive their declared
107
+ * scope fields directly.
101
108
  */
102
109
  const NON_SCOPED_RULES = new Set<string>(["assign-together"]);
103
110
 
@@ -105,22 +112,22 @@ const NON_SCOPED_RULES = new Set<string>(["assign-together"]);
105
112
  * Resolves overlapping scopes so that more specific scopes override broader ones.
106
113
  *
107
114
  * Resolution rules:
108
- * 1. Rules in NON_SCOPED_RULES pass through unchanged (they handle their own logic)
109
- * 2. Rules with different time scopes coexist (different time windows don't compete)
115
+ * 1. Custom rules and built-in rules in NON_SCOPED_RULES pass through unchanged
116
+ * 2. Built-in rules with different time scopes coexist (different time windows don't compete)
110
117
  * 3. Within the same time scope, rules are sorted by specificity (person > role > skill > global)
111
118
  * and then by insertion order (later wins for same specificity, i.e., last insert overrides)
112
119
  * 4. More specific rules claim their IDs first; less specific rules get the remainder
113
120
  * 5. All scope types (person, role, skill, global) are expanded to explicit IDs
114
121
  */
115
122
  export function resolveRuleScopes(
116
- entries: CpsatRuleConfigEntry[],
123
+ entries: readonly AnyCpsatRuleConfigEntry[],
117
124
  members: SchedulingMember[],
118
- ): CpsatRuleConfigEntry[] {
119
- const resolved: CpsatRuleConfigEntry[] = [];
120
- const scopedEntries: Array<{ entry: CpsatRuleConfigEntry; index: number }> = [];
125
+ ): AnyCpsatRuleConfigEntry[] {
126
+ const resolved: AnyCpsatRuleConfigEntry[] = [];
127
+ const scopedEntries: Array<{ entry: AnyCpsatRuleConfigEntry; index: number }> = [];
121
128
 
122
129
  entries.forEach((entry, index) => {
123
- if (NON_SCOPED_RULES.has(entry.name)) {
130
+ if (NON_SCOPED_RULES.has(entry.name) || !(entry.name in builtInCpsatRuleRegistry)) {
124
131
  resolved.push(entry);
125
132
  } else {
126
133
  scopedEntries.push({ entry, index });
@@ -141,8 +148,7 @@ export function resolveRuleScopes(
141
148
  const list = grouped.get(groupKey) ?? [];
142
149
  list.push({
143
150
  index,
144
- name: entry.name,
145
- config,
151
+ entry,
146
152
  entityScope: entity,
147
153
  timeScope: time,
148
154
  });
@@ -159,8 +165,8 @@ export function resolveRuleScopes(
159
165
 
160
166
  const assignedMembers = new Set<string>();
161
167
 
162
- for (const entry of sorted) {
163
- const targetIds = getMemberIdsForScope(entry.entityScope, members);
168
+ for (const item of sorted) {
169
+ const targetIds = getMemberIdsForScope(item.entityScope, members);
164
170
  const remaining = subtractIds(targetIds, assignedMembers);
165
171
  if (remaining.length === 0) continue;
166
172
 
@@ -171,12 +177,12 @@ export function resolveRuleScopes(
171
177
  roleIds: _roleIds,
172
178
  skillIds: _skillIds,
173
179
  ...configWithoutEntityScope
174
- } = entry.config as Record<string, unknown>;
180
+ } = entryConfig(item.entry);
175
181
  resolved.push({
176
- name: entry.name,
182
+ name: item.entry.name,
177
183
  ...configWithoutEntityScope,
178
184
  memberIds: remaining,
179
- } as CpsatRuleConfigEntry);
185
+ });
180
186
  }
181
187
  }
182
188
 
@@ -184,22 +190,44 @@ export function resolveRuleScopes(
184
190
  }
185
191
 
186
192
  /**
187
- * Builds CompilationRule instances from named configs, applying scoping resolution.
193
+ * Builds compiled rules from named configs, applying scoping resolution.
188
194
  */
189
195
  export function buildCpsatRules(
190
- entries: CpsatRuleConfigEntry[],
196
+ entries: readonly CpsatRuleConfigEntry[],
197
+ members: SchedulingMember[],
198
+ compileContext: RuleCompileContext,
199
+ ruleRegistry?: BuiltInCpsatRuleRegistry,
200
+ ): CompiledRule[];
201
+ export function buildCpsatRules<Registry extends CpsatRuleRegistry>(
202
+ entries: readonly CpsatRuleConfigEntryFor<Registry>[],
203
+ members: SchedulingMember[],
204
+ compileContext: RuleCompileContext,
205
+ ruleRegistry: Registry,
206
+ ): CompiledRule[];
207
+ export function buildCpsatRules(
208
+ entries: readonly AnyCpsatRuleConfigEntry[],
191
209
  members: SchedulingMember[],
192
- factories: CpsatRuleFactories = builtInCpsatRuleFactories,
193
- ): CompilationRule[] {
210
+ compileContext: RuleCompileContext,
211
+ ruleRegistry: CpsatRuleRegistry,
212
+ ): CompiledRule[];
213
+ export function buildCpsatRules(
214
+ entries: readonly AnyCpsatRuleConfigEntry[],
215
+ members: SchedulingMember[],
216
+ compileContext: RuleCompileContext,
217
+ ruleRegistry: CpsatRuleRegistry = builtInCpsatRuleRegistry,
218
+ ): CompiledRule[] {
194
219
  if (entries.length === 0) return [];
195
220
 
221
+ assertValidCpsatRuleRegistry(ruleRegistry);
222
+ assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
223
+
196
224
  const resolved = resolveRuleScopes(entries, members);
197
225
 
198
226
  return resolved.map((entry) => {
199
- const factory = factories[entry.name];
200
- if (!factory) {
227
+ const descriptor = ruleRegistry[entry.name];
228
+ if (!descriptor) {
201
229
  throw new Error(`Unknown CP-SAT rule "${entry.name}"`);
202
230
  }
203
- return factory(entryConfig(entry));
231
+ return compileRuleDescriptor(descriptor, entryConfig(entry), compileContext);
204
232
  });
205
233
  }
@@ -0,0 +1,92 @@
1
+ import * as z from "zod";
2
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
3
+ import type { RuleArtifact } from "../rule-descriptor.js";
4
+ import { PREFERENCE_WEIGHTS } from "../utils.js";
5
+ import {
6
+ PrioritySchema,
7
+ entityScope,
8
+ parseEntityScope,
9
+ resolveMembersFromScope,
10
+ } from "./scope.types.js";
11
+ import { assignmentVar } from "./variables.js";
12
+ import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
13
+
14
+ export const RolePreferenceSchema = z
15
+ .object({
16
+ roleId: z.string(),
17
+ priority: PrioritySchema,
18
+ })
19
+ .and(entityScope(["members", "roles", "skills"]));
20
+
21
+ /**
22
+ * Configuration for {@link rolePreferenceRuleDescriptor}.
23
+ */
24
+ export type RolePreferenceConfig = z.infer<typeof RolePreferenceSchema>;
25
+
26
+ /**
27
+ * Low-level descriptor for role preference rules.
28
+ *
29
+ * @category Rules
30
+ */
31
+ export const rolePreferenceRuleDescriptor = defineRuleDescriptor({
32
+ name: "role-preference",
33
+ schema: RolePreferenceSchema,
34
+ compile(config, ctx) {
35
+ const scope = parseEntityScope(config);
36
+ const { roleId } = config;
37
+ const weight = PREFERENCE_WEIGHTS[config.priority as keyof typeof PREFERENCE_WEIGHTS] ?? 0;
38
+ if (weight === 0) {
39
+ return { rule: "role-preference", artifacts: [] };
40
+ }
41
+
42
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
43
+ if (members.length === 0) {
44
+ return { rule: "role-preference", artifacts: [] };
45
+ }
46
+
47
+ const membersWithRole = members.filter((member) => member.roleIds.includes(roleId));
48
+ const artifacts: RuleArtifact[] = [];
49
+
50
+ if (membersWithRole.length === 0) {
51
+ artifacts.push({
52
+ kind: "pre-solve-feedback",
53
+ run(_precheckContext, reporter) {
54
+ const ids = members.map((member) => member.id).join(", ");
55
+ reporter.reportRuleError({
56
+ rule: "role-preference",
57
+ message:
58
+ `None of the targeted members (${ids}) have role "${roleId}". ` +
59
+ `Add "${roleId}" to their roleIds, or remove this preferRole rule.`,
60
+ context: { memberIds: members.map((member) => member.id) },
61
+ });
62
+ },
63
+ });
64
+ return { rule: "role-preference", artifacts };
65
+ }
66
+
67
+ const terms = membersWithRole.flatMap((member) =>
68
+ ctx.shiftPatterns.flatMap((pattern) => {
69
+ if (!canAssignMemberToPattern(member, pattern)) return [];
70
+ if (pattern.roleIds?.includes(roleId)) return [];
71
+ return ctx.days
72
+ .filter((day) => isPatternAvailableOnDay(pattern, day))
73
+ .map((day) => ({ var: assignmentVar(member.id, pattern.id, day.iso), coeff: weight }));
74
+ }),
75
+ );
76
+
77
+ artifacts.push({
78
+ kind: "objective",
79
+ terms,
80
+ validation: {
81
+ strategy: "skip",
82
+ category: "no-meaningful-feedback",
83
+ rationale:
84
+ terms.length === 0
85
+ ? `Role preference for "${roleId}" produced no objective terms.`
86
+ : "Preference rules influence the objective but do not emit validation feedback.",
87
+ },
88
+ });
89
+
90
+ return { rule: "role-preference", artifacts };
91
+ },
92
+ });
@@ -1,12 +1,23 @@
1
- import type { CompilationRule } from "../model-builder.js";
1
+ import type { RuleDescriptor } from "../rule-descriptor.js";
2
2
 
3
- export type CreateCpsatRuleFunction<TConfig = any> = (config: TConfig) => CompilationRule;
4
-
5
- // Registry of built-in rule names to their config types
6
- export interface CpsatRuleRegistry {
3
+ /**
4
+ * Maps built-in CP-SAT rule names to their validated config types.
5
+ *
6
+ * @remarks
7
+ * Use this when you need the exact config shape for a known built-in rule.
8
+ * For registry-driven APIs, prefer `CpsatRuleRegistry`, `CpsatRuleConfigByName`,
9
+ * and `CpsatRuleConfigEntryFor`.
10
+ *
11
+ * @category Rules
12
+ */
13
+ export interface BuiltInCpsatRuleConfigRegistry {
7
14
  "assign-together": import("./assign-together.js").AssignTogetherConfig;
8
15
  "assignment-priority": import("./assignment-priority.js").AssignmentPriorityConfig;
9
16
  "location-preference": import("./location-preference.js").LocationPreferenceConfig;
17
+ "role-preference": import("./role-preference.js").RolePreferenceConfig;
18
+ "max-concurrent-assignments": import("./max-concurrent-assignments.js").MaxConcurrentAssignmentsConfig;
19
+ "target-peak-concurrent-assignments": import("./target-peak-concurrent-assignments.js").TargetPeakConcurrentAssignmentsConfig;
20
+ "target-days-week": import("./target-days-week.js").TargetDaysWeekConfig;
10
21
  "max-consecutive-days": import("./max-consecutive-days.js").MaxConsecutiveDaysConfig;
11
22
  "max-days-week": import("./max-days-week.js").MaxDaysWeekConfig;
12
23
  "max-hours-day": import("./max-hours-day.js").MaxHoursDayConfig;
@@ -28,25 +39,85 @@ export interface CpsatRuleRegistry {
28
39
  "overtime-daily-multiplier": import("./overtime-daily-multiplier.js").OvertimeDailyMultiplierConfig;
29
40
  "overtime-daily-surcharge": import("./overtime-daily-surcharge.js").OvertimeDailySurchargeConfig;
30
41
  "overtime-tiered-multiplier": import("./overtime-tiered-multiplier.js").OvertimeTieredMultiplierConfig;
42
+ "max-days-of-week-per-period": import("./max-days-of-week-per-period.js").MaxDaysOfWeekPerPeriodConfig;
43
+ "min-days-of-week-per-period": import("./min-days-of-week-per-period.js").MinDaysOfWeekPerPeriodConfig;
31
44
  }
32
45
 
33
- export type CpsatRuleName = keyof CpsatRuleRegistry;
46
+ /**
47
+ * The union of all built-in CP-SAT rule names.
48
+ *
49
+ * @category Rules
50
+ */
51
+ export type CpsatRuleName = keyof BuiltInCpsatRuleConfigRegistry;
34
52
 
35
- export type CpsatRuleFactories = {
36
- [ruleName: string]: CreateCpsatRuleFunction<any>;
37
- };
53
+ /**
54
+ * A registry of CP-SAT rule descriptors keyed by rule name.
55
+ *
56
+ * @remarks
57
+ * The registry key must exactly match each descriptor's `name` field.
58
+ * Use `createCpsatRuleRegistry` to validate that invariant when building
59
+ * custom registries.
60
+ *
61
+ * @category Rules
62
+ */
63
+ export type CpsatRuleRegistry = Record<string, RuleDescriptor<string, unknown>>;
38
64
 
39
- export type BuiltInCpsatRuleFactories = {
40
- [K in CpsatRuleName]: CreateCpsatRuleFunction<CpsatRuleRegistry[K]>;
65
+ /**
66
+ * The built-in CP-SAT rule registry shape.
67
+ *
68
+ * @category Rules
69
+ */
70
+ export type BuiltInCpsatRuleRegistry = {
71
+ [K in CpsatRuleName]: RuleDescriptor<K, BuiltInCpsatRuleConfigRegistry[K]>;
41
72
  };
42
73
 
74
+ /**
75
+ * Extracts the config type from a rule descriptor.
76
+ *
77
+ * @category Rules
78
+ */
43
79
  export type InferCpsatRuleConfig<T> =
44
- T extends CreateCpsatRuleFunction<infer Config> ? Config : never;
80
+ T extends RuleDescriptor<string, infer Config> ? Config : never;
45
81
 
46
- export type CpsatRuleRegistryFromFactories<F extends CpsatRuleFactories> = {
47
- [K in keyof F]: InferCpsatRuleConfig<F[K]>;
82
+ /**
83
+ * Maps a rule registry to a name-to-config lookup type.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * type ConfigByName = CpsatRuleConfigByName<typeof builtInCpsatRuleRegistry>;
88
+ * // ^? { "max-hours-day": MaxHoursDayConfig; ... }
89
+ * ```
90
+ *
91
+ * @category Rules
92
+ */
93
+ export type CpsatRuleConfigByName<Registry extends CpsatRuleRegistry> = {
94
+ [K in keyof Registry & string]: InferCpsatRuleConfig<Registry[K]>;
48
95
  };
49
96
 
97
+ /**
98
+ * Builds the discriminated rule-entry union for a rule registry.
99
+ *
100
+ * @remarks
101
+ * This preserves the pairing between each `name` value and the config fields
102
+ * allowed for that rule.
103
+ *
104
+ * @category Rules
105
+ */
106
+ export type CpsatRuleConfigEntryFor<Registry extends CpsatRuleRegistry> = {
107
+ [K in keyof Registry & string]: { name: K } & InferCpsatRuleConfig<Registry[K]>;
108
+ }[keyof Registry & string];
109
+
110
+ /**
111
+ * A normalized rule entry with an unconstrained string rule name.
112
+ *
113
+ * @remarks
114
+ * This is used internally at points where the specific registry is no longer
115
+ * known at the type level.
116
+ *
117
+ * @category Rules
118
+ */
119
+ export type AnyCpsatRuleConfigEntry = { name: string } & Record<string, unknown>;
120
+
50
121
  /**
51
122
  * A named rule configuration entry.
52
123
  *
@@ -62,7 +133,8 @@ export type CpsatRuleRegistryFromFactories<F extends CpsatRuleFactories> = {
62
133
  * { name: "time-off", memberIds: ["alice"], dayOfWeek: ["monday"], priority: "MANDATORY" },
63
134
  * ];
64
135
  * ```
136
+ *
137
+ * @category Rules
65
138
  */
66
- export type CpsatRuleConfigEntry = {
67
- [K in CpsatRuleName]: { name: K } & CpsatRuleRegistry[K];
68
- }[CpsatRuleName];
139
+ export type CpsatRuleConfigEntry<Registry extends CpsatRuleRegistry = BuiltInCpsatRuleRegistry> =
140
+ CpsatRuleConfigEntryFor<Registry>;
@@ -34,10 +34,13 @@
34
34
  */
35
35
 
36
36
  import * as z from "zod";
37
- import { DayOfWeekSchema, type DayOfWeek } from "../../types.js";
38
- import { toDayOfWeekUTC } from "../../datetime.utils.js";
39
- import type { SchedulingMember } from "../types.js";
40
- import { parseDayString } from "../utils.js";
37
+ import {
38
+ DayOfWeekSchema,
39
+ type DateString,
40
+ type DayOfWeek,
41
+ type SchedulingDay,
42
+ } from "../../types.js";
43
+ import { PRIORITY_VALUES, SOFT_PRIORITY_VALUES, type SchedulingMember } from "../types.js";
41
44
  import { assertSafeKeySegments, type ValidationGroup } from "../validation.types.js";
42
45
 
43
46
  // ============================================================================
@@ -50,9 +53,12 @@ import { assertSafeKeySegments, type ValidationGroup } from "../validation.types
50
53
  * Rules that use priority should include this in their schema so the
51
54
  * default is co-located with the rule system, not injected externally.
52
55
  */
53
- export const PrioritySchema = z
54
- .union([z.literal("LOW"), z.literal("MEDIUM"), z.literal("HIGH"), z.literal("MANDATORY")])
55
- .default("MANDATORY");
56
+ export const PrioritySchema = z.enum(PRIORITY_VALUES).default("MANDATORY");
57
+
58
+ /**
59
+ * Zod schema for soft-only priorities.
60
+ */
61
+ export const SoftPrioritySchema = z.enum(SOFT_PRIORITY_VALUES);
56
62
 
57
63
  // ============================================================================
58
64
  // Scope Keys
@@ -104,8 +110,8 @@ export interface RecurringPeriod {
104
110
  * The field shape when a time scope variant is active (selected).
105
111
  */
106
112
  type ActiveTimeFields = {
107
- dateRange: { dateRange: { start: string; end: string } };
108
- specificDates: { specificDates: NonEmptyArray<string> };
113
+ dateRange: { dateRange: { start: DateString; end: DateString } };
114
+ specificDates: { specificDates: NonEmptyArray<DateString> };
109
115
  dayOfWeek: { dayOfWeek: NonEmptyArray<DayOfWeek> };
110
116
  recurring: { recurringPeriods: NonEmptyArray<RecurringPeriod> };
111
117
  };
@@ -407,7 +413,7 @@ export type ParsedEntityScope =
407
413
  export type ParsedTimeScope =
408
414
  | { type: "none" }
409
415
  | { type: "dateRange"; start: string; end: string }
410
- | { type: "specificDates"; dates: string[] }
416
+ | { type: "specificDates"; dates: DateString[] }
411
417
  | { type: "dayOfWeek"; days: DayOfWeek[] }
412
418
  | { type: "recurring"; periods: RecurringPeriod[] };
413
419
 
@@ -421,8 +427,8 @@ export interface EntityScopeInput {
421
427
 
422
428
  /** Input shape accepted by {@link parseTimeScope}. */
423
429
  export interface TimeScopeInput {
424
- dateRange?: { start: string; end: string };
425
- specificDates?: string[];
430
+ dateRange?: { start: DateString; end: DateString };
431
+ specificDates?: DateString[];
426
432
  dayOfWeek?: DayOfWeek[];
427
433
  recurringPeriods?: RecurringPeriod[];
428
434
  [key: string]: unknown;
@@ -488,28 +494,27 @@ export function resolveMembersFromScope(
488
494
  /**
489
495
  * Resolves which days a rule applies to based on time scope.
490
496
  */
491
- export function resolveActiveDaysFromScope(scope: ParsedTimeScope, allDays: string[]): string[] {
497
+ export function resolveActiveDaysFromScope(
498
+ scope: ParsedTimeScope,
499
+ allDays: SchedulingDay[],
500
+ ): SchedulingDay[] {
492
501
  switch (scope.type) {
493
502
  case "none":
494
503
  return allDays;
495
504
  case "dateRange":
496
- return allDays.filter((day) => day >= scope.start && day <= scope.end);
497
- case "specificDates":
498
- return allDays.filter((day) => scope.dates.includes(day));
505
+ return allDays.filter((day) => day.iso >= scope.start && day.iso <= scope.end);
506
+ case "specificDates": {
507
+ const dateSet = new Set(scope.dates);
508
+ return allDays.filter((day) => dateSet.has(day.iso));
509
+ }
499
510
  case "dayOfWeek": {
500
511
  const targetDays = new Set(scope.days);
501
- return allDays.filter((day) => {
502
- const date = parseDayString(day);
503
- return targetDays.has(toDayOfWeekUTC(date));
504
- });
512
+ return allDays.filter((day) => targetDays.has(day.dayOfWeek));
505
513
  }
506
514
  case "recurring":
507
- return allDays.filter((day) => {
508
- const date = parseDayString(day);
509
- const month = date.getUTCMonth() + 1;
510
- const dayOfMonth = date.getUTCDate();
511
- return scope.periods.some((period) => isDateInRecurringPeriod(month, dayOfMonth, period));
512
- });
515
+ return allDays.filter((day) =>
516
+ scope.periods.some((period) => isDateInRecurringPeriod(day.month, day.dayOfMonth, period)),
517
+ );
513
518
  }
514
519
  }
515
520
 
@@ -0,0 +1,105 @@
1
+ import * as z from "zod";
2
+ import { DayOfWeekSchema } from "../../types.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
4
+ import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
5
+ import {
6
+ entityScope,
7
+ parseEntityScope,
8
+ resolveMembersFromScope,
9
+ ruleGroup,
10
+ SoftPrioritySchema,
11
+ } from "./scope.types.js";
12
+ import { assignmentVarsForDay } from "./assignment-terms.js";
13
+ import { assignedDayVariableName } from "./variables.js";
14
+ import { reportValidation, softConstraint } from "./artifacts.js";
15
+ import { buildAssignedDayIndicator } from "./assigned-day.js";
16
+
17
+ const TargetDaysWeekBase = z.object({
18
+ days: z.number().int().min(0),
19
+ priority: SoftPrioritySchema.default("HIGH"),
20
+ weekStartsOn: DayOfWeekSchema.optional(),
21
+ });
22
+
23
+ export const TargetDaysWeekSchema = TargetDaysWeekBase.and(
24
+ entityScope(["members", "roles", "skills"]),
25
+ );
26
+
27
+ /**
28
+ * Configuration for {@link targetDaysWeekRuleDescriptor}.
29
+ */
30
+ export type TargetDaysWeekConfig = z.infer<typeof TargetDaysWeekSchema>;
31
+
32
+ /**
33
+ * Low-level descriptor for the `target-days-week` rule.
34
+ *
35
+ * @remarks
36
+ * Softly targets assigned days per scheduling week. Deviations in either
37
+ * direction are penalized, so working fewer than the target or more than the
38
+ * target both count as misses. Use this for stated weekly patterns like
39
+ * "works a 4-day week" when that pattern should remain flexible.
40
+ *
41
+ * @category Rules
42
+ */
43
+ export const targetDaysWeekRuleDescriptor = defineRuleDescriptor({
44
+ name: "target-days-week",
45
+ schema: TargetDaysWeekSchema,
46
+ compile(config, ctx) {
47
+ const scope = parseEntityScope(config);
48
+ const { days, priority, weekStartsOn } = config;
49
+ const group = ruleGroup(`target-days-week:${days}`, `Target ${days}d per week`, scope);
50
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
51
+ const weeks = splitIntoWeeks([...ctx.days], weekStartsOn ?? ctx.weekStartsOn);
52
+
53
+ const artifacts = members.flatMap((member) =>
54
+ weeks.flatMap((weekDays) => {
55
+ const weekStart = weekDays[0];
56
+ if (!weekStart) return [];
57
+
58
+ const supportArtifacts = weekDays.flatMap(
59
+ (day) =>
60
+ buildAssignedDayIndicator({
61
+ memberId: member.id,
62
+ day,
63
+ assignmentVars: assignmentVarsForDay(member, day, ctx.shiftPatterns),
64
+ }).artifacts,
65
+ );
66
+
67
+ const assignedDayVars = weekDays.map((day) => assignedDayVariableName(member.id, day.iso));
68
+
69
+ const context = { memberIds: [member.id], days: weekDays.map((day) => day.iso) };
70
+ const terms = assignedDayVars.map((varName) => ({ var: varName, coeff: 1 }));
71
+ const penalty = priorityToPenalty(priority);
72
+
73
+ return supportArtifacts.concat([
74
+ softConstraint({
75
+ group,
76
+ description: `${member.id} target ${days}d per week starting ${weekStart.iso} (under)`,
77
+ context,
78
+ validation: reportValidation(),
79
+ terms,
80
+ comparator: ">=",
81
+ targetValue: days,
82
+ penalty,
83
+ constraintId: `target-days-week:under:${member.id}:${weekStart.iso}`,
84
+ }),
85
+ softConstraint({
86
+ group,
87
+ description: `${member.id} target ${days}d per week starting ${weekStart.iso} (over)`,
88
+ context,
89
+ validation: reportValidation(),
90
+ terms,
91
+ comparator: "<=",
92
+ targetValue: days,
93
+ penalty,
94
+ constraintId: `target-days-week:over:${member.id}:${weekStart.iso}`,
95
+ }),
96
+ ]);
97
+ }),
98
+ );
99
+
100
+ return {
101
+ rule: "target-days-week",
102
+ artifacts,
103
+ };
104
+ },
105
+ });