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
@@ -1,5 +1,6 @@
1
1
  import * as z from "zod";
2
- import type { CompilationRule } from "../model-builder.js";
2
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
3
+ import type { RuleArtifact } from "../rule-descriptor.js";
3
4
  import { priorityToPenalty } from "../utils.js";
4
5
  import {
5
6
  PrioritySchema,
@@ -8,8 +9,18 @@ import {
8
9
  resolveMembersFromScope,
9
10
  ruleGroup,
10
11
  } from "./scope.types.js";
11
-
12
- const MinConsecutiveDaysSchema = z
12
+ import { assignmentVarsForDay } from "./assignment-terms.js";
13
+ import { assignedDayStartVariableName } from "./variables.js";
14
+ import {
15
+ boolVariable,
16
+ hardConstraint,
17
+ reportValidation,
18
+ skipValidation,
19
+ softConstraint,
20
+ } from "./artifacts.js";
21
+ import { buildAssignedDayIndicator } from "./assigned-day.js";
22
+
23
+ export const MinConsecutiveDaysSchema = z
13
24
  .object({
14
25
  days: z.number().min(0),
15
26
  priority: PrioritySchema,
@@ -17,143 +28,166 @@ const MinConsecutiveDaysSchema = z
17
28
  .and(entityScope(["members", "roles", "skills"]));
18
29
 
19
30
  /**
20
- * Configuration for {@link createMinConsecutiveDaysRule}.
21
- *
22
- * - `days` (required): minimum consecutive days required once a person starts working
23
- * - `priority` (required): how strictly the solver enforces this rule
24
- *
25
- * Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
31
+ * Configuration for {@link minConsecutiveDaysRuleDescriptor}.
26
32
  */
27
33
  export type MinConsecutiveDaysConfig = z.infer<typeof MinConsecutiveDaysSchema>;
28
34
 
29
35
  /**
30
- * Requires that once a person starts working, they continue for a minimum
31
- * number of consecutive days.
36
+ * Low-level descriptor for the `min-consecutive-days` rule.
32
37
  *
33
- * @param config - See {@link MinConsecutiveDaysConfig}
34
- * @example
35
- * ```ts
36
- * createMinConsecutiveDaysRule({ days: 3, priority: "MANDATORY" });
37
- * ```
38
+ * @remarks
39
+ * Once an assignment streak starts, this rule requires it to continue for at
40
+ * least the configured number of assigned days.
41
+ *
42
+ * @category Rules
38
43
  */
39
- export function createMinConsecutiveDaysRule(config: MinConsecutiveDaysConfig): CompilationRule {
40
- const parsed = MinConsecutiveDaysSchema.parse(config);
41
- const scope = parseEntityScope(parsed);
42
- const { days, priority } = parsed;
43
- const group = ruleGroup(`min-consecutive-days:${days}`, `Min ${days} consecutive days`, scope);
44
-
45
- return {
46
- compile(b) {
47
- if (days <= 1) return;
48
-
49
- const members = resolveMembersFromScope(scope, b.members);
50
-
51
- for (const emp of members) {
52
- const worksByDay: string[] = [];
53
-
54
- for (const day of b.days) {
55
- const worksVar = b.boolVar(`works_${emp.id}_${day}`);
56
- worksByDay.push(worksVar);
57
-
58
- const dayAssignments = b.shiftPatterns
59
- .filter((p) => b.canAssign(emp, p) && b.patternAvailableOnDay(p, day))
60
- .map((p) => b.assignment(emp.id, p.id, day));
61
-
62
- if (dayAssignments.length === 0) {
63
- b.addLinear([{ var: worksVar, coeff: 1 }], "==", 0);
64
- } else {
65
- for (const assignVar of dayAssignments) {
66
- b.addLinear(
67
- [
68
- { var: worksVar, coeff: 1 },
69
- { var: assignVar, coeff: -1 },
70
- ],
71
- ">=",
72
- 0,
73
- );
74
- }
75
- b.addLinear(
76
- [{ var: worksVar, coeff: 1 }, ...dayAssignments.map((v) => ({ var: v, coeff: -1 }))],
77
- "<=",
78
- 0,
79
- );
80
- }
81
- }
82
-
83
- for (let i = 0; i < b.days.length; i++) {
84
- const dayLabel = b.days[i];
85
- if (!dayLabel) continue;
86
-
87
- const worksToday = worksByDay[i];
88
- if (!worksToday) continue;
89
- const worksYesterday = i > 0 ? worksByDay[i - 1] : undefined;
90
- const startVar = b.boolVar(`work_start_${emp.id}_${dayLabel}`);
91
-
92
- b.addLinear(
93
- [
44
+ export const minConsecutiveDaysRuleDescriptor = defineRuleDescriptor({
45
+ name: "min-consecutive-days",
46
+ schema: MinConsecutiveDaysSchema,
47
+ compile(config, ctx) {
48
+ const scope = parseEntityScope(config);
49
+ const { days, priority } = config;
50
+ const group = ruleGroup(`min-consecutive-days:${days}`, `Min ${days} consecutive days`, scope);
51
+ if (days <= 1) {
52
+ return { rule: "min-consecutive-days", artifacts: [] };
53
+ }
54
+
55
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
56
+ const artifacts = members.flatMap((member) => {
57
+ const supportArtifacts = ctx.days.flatMap(
58
+ (day) =>
59
+ buildAssignedDayIndicator({
60
+ memberId: member.id,
61
+ day,
62
+ assignmentVars: assignmentVarsForDay(member, day, ctx.shiftPatterns),
63
+ variableName: `assigned_${member.id}_${day.iso}`,
64
+ }).artifacts,
65
+ );
66
+
67
+ const startArtifacts = ctx.days.flatMap((day, index) => {
68
+ const assignedToday = `assigned_${member.id}_${day.iso}`;
69
+ const assignedYesterday =
70
+ index > 0 ? `assigned_${member.id}_${ctx.days[index - 1]!.iso}` : undefined;
71
+ const startVar = assignedDayStartVariableName(member.id, day.iso);
72
+ const artifactsForDay: RuleArtifact[] = [boolVariable(startVar)];
73
+
74
+ artifactsForDay.push(
75
+ hardConstraint({
76
+ description: `${startVar} implies an assignment on ${day.iso}`,
77
+ validation: skipValidation(
78
+ "scaffolding",
79
+ "This helper variable only marks the start of an assignment streak when the member has an assignment that day.",
80
+ ),
81
+ context: { memberIds: [member.id], days: [day.iso] },
82
+ terms: [
94
83
  { var: startVar, coeff: 1 },
95
- { var: worksToday, coeff: -1 },
84
+ { var: assignedToday, coeff: -1 },
96
85
  ],
97
- "<=",
98
- 0,
99
- );
100
- if (worksYesterday) {
101
- b.addLinear(
102
- [
86
+ comparator: "<=",
87
+ targetValue: 0,
88
+ }),
89
+ );
90
+
91
+ if (assignedYesterday) {
92
+ artifactsForDay.push(
93
+ hardConstraint({
94
+ description: `${startVar} only when previous day is unassigned`,
95
+ validation: skipValidation(
96
+ "scaffolding",
97
+ "This helper variable must stay off when the previous day already belongs to the same assignment streak.",
98
+ ),
99
+ context: { memberIds: [member.id], days: [ctx.days[index - 1]!.iso, day.iso] },
100
+ terms: [
103
101
  { var: startVar, coeff: 1 },
104
- { var: worksYesterday, coeff: 1 },
102
+ { var: assignedYesterday, coeff: 1 },
105
103
  ],
106
- "<=",
107
- 1,
108
- );
109
- b.addLinear(
110
- [
104
+ comparator: "<=",
105
+ targetValue: 1,
106
+ }),
107
+ );
108
+ artifactsForDay.push(
109
+ hardConstraint({
110
+ description: `${startVar} activates on a new assignment streak`,
111
+ validation: skipValidation(
112
+ "scaffolding",
113
+ "This helper variable identifies new assignment streak boundaries for the consecutive-days window calculation.",
114
+ ),
115
+ context: { memberIds: [member.id], days: [ctx.days[index - 1]!.iso, day.iso] },
116
+ terms: [
111
117
  { var: startVar, coeff: 1 },
112
- { var: worksYesterday, coeff: 1 },
113
- { var: worksToday, coeff: -1 },
118
+ { var: assignedYesterday, coeff: 1 },
119
+ { var: assignedToday, coeff: -1 },
114
120
  ],
115
- ">=",
116
- 0,
117
- );
118
- } else {
119
- b.addLinear(
120
- [
121
+ comparator: ">=",
122
+ targetValue: 0,
123
+ }),
124
+ );
125
+ } else {
126
+ artifactsForDay.push(
127
+ hardConstraint({
128
+ description: `${startVar} matches first-day assignment state`,
129
+ validation: skipValidation(
130
+ "scaffolding",
131
+ "On the first day of the horizon, the helper variable matches whether a new streak starts that day.",
132
+ ),
133
+ context: { memberIds: [member.id], days: [day.iso] },
134
+ terms: [
121
135
  { var: startVar, coeff: 1 },
122
- { var: worksToday, coeff: -1 },
136
+ { var: assignedToday, coeff: -1 },
123
137
  ],
124
- ">=",
125
- 0,
126
- );
127
- }
128
-
129
- const window = worksByDay.slice(i, i + days).filter(Boolean) as string[];
130
- if (window.length === 0) continue;
131
-
132
- const terms = [
133
- ...window.map((v) => ({ var: v, coeff: 1 })),
134
- { var: startVar, coeff: -days },
135
- ];
136
-
137
- const constraintId = `min-consecutive-days:${emp.id}:${dayLabel}`;
138
-
139
- if (priority === "MANDATORY") {
140
- b.addLinear(terms, ">=", 0);
141
- } else {
142
- b.addSoftLinear(terms, ">=", 0, priorityToPenalty(priority), constraintId);
143
- b.reporter.trackConstraint({
144
- id: constraintId,
145
- type: "rule",
146
- rule: "min-consecutive-days",
147
- description: `${emp.id} min ${days} consecutive days from ${dayLabel}`,
148
- targetValue: 0,
149
138
  comparator: ">=",
150
- day: dayLabel,
151
- context: { memberIds: [emp.id], days: b.days.slice(i, i + days) },
152
- group,
153
- });
154
- }
139
+ targetValue: 0,
140
+ }),
141
+ );
155
142
  }
156
- }
157
- },
158
- };
159
- }
143
+
144
+ const windowDays = ctx.days.slice(index, index + days);
145
+ if (windowDays.length === 0) return artifactsForDay;
146
+
147
+ const description = `${member.id} min ${days} consecutive days from ${day.iso}`;
148
+ const context = { memberIds: [member.id], days: windowDays.map((entry) => entry.iso) };
149
+ const constraintId = `min-consecutive-days:${member.id}:${day.iso}`;
150
+ const terms = [
151
+ ...windowDays.map((windowDay) => ({
152
+ var: `assigned_${member.id}_${windowDay.iso}`,
153
+ coeff: 1,
154
+ })),
155
+ { var: startVar, coeff: -days },
156
+ ];
157
+
158
+ artifactsForDay.push(
159
+ priority === "MANDATORY"
160
+ ? hardConstraint({
161
+ group,
162
+ description,
163
+ context,
164
+ validation: reportValidation(constraintId),
165
+ terms,
166
+ comparator: ">=",
167
+ targetValue: 0,
168
+ })
169
+ : softConstraint({
170
+ group,
171
+ description,
172
+ context,
173
+ validation: reportValidation(),
174
+ terms,
175
+ comparator: ">=",
176
+ targetValue: 0,
177
+ penalty: priorityToPenalty(priority),
178
+ constraintId,
179
+ }),
180
+ );
181
+
182
+ return artifactsForDay;
183
+ });
184
+
185
+ return supportArtifacts.concat(startArtifacts);
186
+ });
187
+
188
+ return {
189
+ rule: "min-consecutive-days",
190
+ artifacts,
191
+ };
192
+ },
193
+ });
@@ -0,0 +1,144 @@
1
+ import * as z from "zod";
2
+ import { DayOfWeekSchema } from "../../types.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
4
+ import type { RuleCompileContext } from "../rule-descriptor.js";
5
+ import type { ValidationReporter } from "../validation-reporter.js";
6
+ import { groupWeekChunks, priorityToPenalty, splitIntoMonths } from "../utils.js";
7
+ import {
8
+ PrioritySchema,
9
+ entityScope,
10
+ parseEntityScope,
11
+ resolveMembersFromScope,
12
+ ruleGroup,
13
+ } from "./scope.types.js";
14
+ import { assignmentVarsForDay } from "./assignment-terms.js";
15
+ import { hasAnyAssignablePattern } from "./pattern-eligibility.js";
16
+ import { assignedDayVariableName } from "./variables.js";
17
+ import { hardConstraint, reportValidation, softConstraint } from "./artifacts.js";
18
+ import { buildAssignedDayIndicator } from "./assigned-day.js";
19
+
20
+ const PeriodSchema = z.discriminatedUnion("type", [
21
+ z.object({ type: z.literal("weeks"), value: z.number().int().min(1) }),
22
+ z.object({ type: z.literal("months"), value: z.number().int().min(1) }),
23
+ ]);
24
+
25
+ const MinDaysOfWeekPerPeriodBase = z.object({
26
+ days: z.number().int().min(0),
27
+ dayOfWeek: z.array(DayOfWeekSchema).nonempty(),
28
+ period: PeriodSchema,
29
+ priority: PrioritySchema,
30
+ });
31
+
32
+ export const MinDaysOfWeekPerPeriodSchema = MinDaysOfWeekPerPeriodBase.and(
33
+ entityScope(["members", "roles", "skills"]),
34
+ );
35
+
36
+ /**
37
+ * Configuration for {@link minDaysOfWeekPerPeriodRuleDescriptor}.
38
+ */
39
+ export type MinDaysOfWeekPerPeriodConfig = z.infer<typeof MinDaysOfWeekPerPeriodSchema>;
40
+
41
+ export const minDaysOfWeekPerPeriodRuleDescriptor = defineRuleDescriptor({
42
+ name: "min-days-of-week-per-period",
43
+ schema: MinDaysOfWeekPerPeriodSchema,
44
+ compile(config, ctx) {
45
+ const entityScopeValue = parseEntityScope(config);
46
+ const { days, dayOfWeek, period, priority } = config;
47
+ const dowLabel = dayOfWeek.toSorted().join(",");
48
+ const periodLabel = `${period.value}${period.type === "weeks" ? "w" : "m"}`;
49
+ const group = ruleGroup(
50
+ `min-days-of-week-per-period:${days}:${dowLabel}:${periodLabel}`,
51
+ `Min ${days} ${dowLabel} per ${period.value} ${period.type}`,
52
+ entityScopeValue,
53
+ );
54
+
55
+ const targetMembers = resolveMembersFromScope(entityScopeValue, [...ctx.members]);
56
+ const targetDows = new Set(dayOfWeek);
57
+ const chunks =
58
+ period.type === "weeks"
59
+ ? groupWeekChunks([...ctx.days], period.value, ctx.weekStartsOn)
60
+ : splitIntoMonths([...ctx.days], period.value);
61
+
62
+ const artifacts = targetMembers.flatMap((member) =>
63
+ chunks.flatMap((chunkDays) => {
64
+ const chunkStart = chunkDays[0];
65
+ if (!chunkStart) return [];
66
+
67
+ const matchingDays = chunkDays.filter((day) => targetDows.has(day.dayOfWeek));
68
+ if (matchingDays.length === 0) return [];
69
+
70
+ const supportArtifacts = matchingDays.flatMap(
71
+ (day) =>
72
+ buildAssignedDayIndicator({
73
+ memberId: member.id,
74
+ day,
75
+ assignmentVars: assignmentVarsForDay(member, day, ctx.shiftPatterns),
76
+ }).artifacts,
77
+ );
78
+
79
+ const assignedDayVars = matchingDays
80
+ .filter((day) => assignmentVarsForDay(member, day, ctx.shiftPatterns).length > 0)
81
+ .map((day) => assignedDayVariableName(member.id, day.iso));
82
+ if (assignedDayVars.length === 0) return supportArtifacts;
83
+
84
+ const description = `${member.id} min ${days} ${dowLabel} in period starting ${chunkStart.iso}`;
85
+ const context = { memberIds: [member.id], days: matchingDays.map((day) => day.iso) };
86
+ const constraintId = `min-days-of-week-per-period:${member.id}:${chunkStart.iso}`;
87
+ const terms = assignedDayVars.map((varName) => ({ var: varName, coeff: 1 }));
88
+
89
+ const finalArtifacts =
90
+ priority === "MANDATORY"
91
+ ? [
92
+ hardConstraint({
93
+ group,
94
+ description,
95
+ context,
96
+ validation: reportValidation(constraintId),
97
+ terms,
98
+ comparator: ">=",
99
+ targetValue: days,
100
+ }),
101
+ {
102
+ kind: "pre-solve-feedback" as const,
103
+ run(_preSolveContext: RuleCompileContext, reporter: ValidationReporter) {
104
+ const possibleDays = matchingDays.filter((day) =>
105
+ hasAnyAssignablePattern(member, day, ctx.shiftPatterns),
106
+ ).length;
107
+ if (possibleDays >= days) return;
108
+ reporter.reportRuleError({
109
+ rule: "min-days-of-week-per-period",
110
+ message: `${member.id} cannot reach ${days} ${dowLabel} assignments in the period starting ${chunkStart.iso}; only ${possibleDays} matching day${possibleDays === 1 ? " is" : "s are"} assignable.`,
111
+ context,
112
+ suggestions: [
113
+ `Reduce the ${dowLabel} minimum in this period`,
114
+ `Add more assignable ${dowLabel} shifts in the period starting ${chunkStart.iso}`,
115
+ ],
116
+ group,
117
+ });
118
+ },
119
+ },
120
+ ]
121
+ : [
122
+ softConstraint({
123
+ group,
124
+ description,
125
+ context,
126
+ validation: reportValidation(),
127
+ terms,
128
+ comparator: ">=",
129
+ targetValue: days,
130
+ penalty: priorityToPenalty(priority),
131
+ constraintId,
132
+ }),
133
+ ];
134
+
135
+ return supportArtifacts.concat(finalArtifacts);
136
+ }),
137
+ );
138
+
139
+ return {
140
+ rule: "min-days-of-week-per-period",
141
+ artifacts,
142
+ };
143
+ },
144
+ });
@@ -1,7 +1,8 @@
1
1
  import * as z from "zod";
2
2
  import { DayOfWeekSchema } from "../../types.js";
3
- import type { CompilationRule } from "../model-builder.js";
4
- import type { Term } from "../types.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
4
+ import type { RuleCompileContext } from "../rule-descriptor.js";
5
+ import type { ValidationReporter } from "../validation-reporter.js";
5
6
  import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
6
7
  import {
7
8
  PrioritySchema,
@@ -10,6 +11,11 @@ import {
10
11
  resolveMembersFromScope,
11
12
  ruleGroup,
12
13
  } from "./scope.types.js";
14
+ import { assignmentVarsForDay } from "./assignment-terms.js";
15
+ import { hasAnyAssignablePattern } from "./pattern-eligibility.js";
16
+ import { assignedDayVariableName } from "./variables.js";
17
+ import { hardConstraint, reportValidation, softConstraint } from "./artifacts.js";
18
+ import { buildAssignedDayIndicator } from "./assigned-day.js";
13
19
 
14
20
  const MinDaysWeekBase = z.object({
15
21
  days: z.number().int().min(0),
@@ -17,104 +23,100 @@ const MinDaysWeekBase = z.object({
17
23
  weekStartsOn: DayOfWeekSchema.optional(),
18
24
  });
19
25
 
20
- const MinDaysWeekSchema = MinDaysWeekBase.and(entityScope(["members", "roles", "skills"]));
26
+ export const MinDaysWeekSchema = MinDaysWeekBase.and(entityScope(["members", "roles", "skills"]));
21
27
 
22
28
  /**
23
- * Configuration for {@link createMinDaysWeekRule}.
24
- *
25
- * - `days` (required): minimum number of days required per scheduling week
26
- * - `priority` (required): how strictly the solver enforces this rule
27
- * - `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}
28
- *
29
- * Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
29
+ * Configuration for {@link minDaysWeekRuleDescriptor}.
30
30
  */
31
31
  export type MinDaysWeekConfig = z.infer<typeof MinDaysWeekSchema>;
32
32
 
33
- /**
34
- * Enforces a minimum number of days a person must work per scheduling week.
35
- *
36
- * @remarks
37
- * Creates a binary "works on day" variable for each member and day, then
38
- * constrains the weekly sum. This counts distinct days, regardless of how
39
- * many shifts are assigned on a single day.
40
- *
41
- * @param config - See {@link MinDaysWeekConfig}
42
- * @example
43
- * ```ts
44
- * createMinDaysWeekRule({ days: 3, priority: "HIGH" });
45
- * ```
46
- */
47
- export function createMinDaysWeekRule(config: MinDaysWeekConfig): CompilationRule {
48
- const parsed = MinDaysWeekSchema.parse(config);
49
- const scope = parseEntityScope(parsed);
50
- const { days, priority, weekStartsOn } = parsed;
51
- const group = ruleGroup(`min-days-week:${days}`, `Min ${days}d per week`, scope);
52
-
53
- return {
54
- compile(b) {
55
- if (days <= 0) return;
56
-
57
- const members = resolveMembersFromScope(scope, b.members);
58
- const weeks = splitIntoWeeks(b.days, weekStartsOn ?? b.weekStartsOn);
33
+ export const minDaysWeekRuleDescriptor = defineRuleDescriptor({
34
+ name: "min-days-week",
35
+ schema: MinDaysWeekSchema,
36
+ compile(config, ctx) {
37
+ const scope = parseEntityScope(config);
38
+ const { days, priority, weekStartsOn } = config;
39
+ const group = ruleGroup(`min-days-week:${days}`, `Min ${days}d per week`, scope);
40
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
41
+ const weeks = splitIntoWeeks([...ctx.days], weekStartsOn ?? ctx.weekStartsOn);
59
42
 
60
- for (const emp of members) {
61
- for (const weekDays of weeks) {
62
- const weekWorkVars: string[] = [];
43
+ const artifacts = members.flatMap((member) =>
44
+ weeks.flatMap((weekDays) => {
45
+ const weekStart = weekDays[0];
46
+ if (!weekStart) return [];
63
47
 
64
- for (const day of weekDays) {
65
- const dayAssignments = b.shiftPatterns
66
- .filter((p) => b.canAssign(emp, p) && b.patternAvailableOnDay(p, day))
67
- .map((p) => b.assignment(emp.id, p.id, day));
48
+ const supportArtifacts = weekDays.flatMap(
49
+ (day) =>
50
+ buildAssignedDayIndicator({
51
+ memberId: member.id,
52
+ day,
53
+ assignmentVars: assignmentVarsForDay(member, day, ctx.shiftPatterns),
54
+ }).artifacts,
55
+ );
68
56
 
69
- if (dayAssignments.length === 0) continue;
57
+ const assignedDayVars = weekDays
58
+ .filter((day) => assignmentVarsForDay(member, day, ctx.shiftPatterns).length > 0)
59
+ .map((day) => assignedDayVariableName(member.id, day.iso));
60
+ if (assignedDayVars.length === 0) return supportArtifacts;
70
61
 
71
- const worksVar = b.boolVar(`works_day_${emp.id}_${day}`);
72
- weekWorkVars.push(worksVar);
62
+ const description = `${member.id} min ${days}d in week starting ${weekStart.iso}`;
63
+ const context = { memberIds: [member.id], days: weekDays.map((day) => day.iso) };
64
+ const constraintId = `min-days-week:${member.id}:${weekStart.iso}`;
65
+ const terms = assignedDayVars.map((varName) => ({ var: varName, coeff: 1 }));
73
66
 
74
- // worksVar >= each assignment (if any assignment is 1, worksVar must be 1)
75
- for (const assignVar of dayAssignments) {
76
- b.addLinear(
77
- [
78
- { var: worksVar, coeff: 1 },
79
- { var: assignVar, coeff: -1 },
80
- ],
81
- ">=",
82
- 0,
83
- );
84
- }
67
+ const finalArtifacts =
68
+ priority === "MANDATORY"
69
+ ? [
70
+ hardConstraint({
71
+ group,
72
+ description,
73
+ context,
74
+ validation: reportValidation(constraintId),
75
+ terms,
76
+ comparator: ">=",
77
+ targetValue: days,
78
+ }),
79
+ {
80
+ kind: "pre-solve-feedback" as const,
81
+ run(_preSolveContext: RuleCompileContext, reporter: ValidationReporter) {
82
+ const possibleDays = weekDays.filter((day) =>
83
+ hasAnyAssignablePattern(member, day, ctx.shiftPatterns),
84
+ ).length;
85
+ if (possibleDays >= days) return;
86
+ reporter.reportRuleError({
87
+ rule: "min-days-week",
88
+ message: `${member.id} cannot reach ${days} assigned days in week starting ${weekStart.iso}; only ${possibleDays} day${possibleDays === 1 ? " is" : "s are"} assignable.`,
89
+ context,
90
+ suggestions: [
91
+ `Reduce the weekly day minimum for ${member.id}`,
92
+ `Add more assignable days in the week starting ${weekStart.iso}`,
93
+ ],
94
+ group,
95
+ });
96
+ },
97
+ },
98
+ ]
99
+ : [
100
+ softConstraint({
101
+ group,
102
+ description,
103
+ context,
104
+ validation: reportValidation(),
105
+ terms,
106
+ comparator: ">=",
107
+ targetValue: days,
108
+ penalty: priorityToPenalty(priority),
109
+ constraintId,
110
+ }),
111
+ ];
85
112
 
86
- // worksVar <= sum(assignments) (if no assignment is 1, worksVar must be 0)
87
- b.addLinear(
88
- [{ var: worksVar, coeff: 1 }, ...dayAssignments.map((v) => ({ var: v, coeff: -1 }))],
89
- "<=",
90
- 0,
91
- );
92
- }
113
+ return supportArtifacts.concat(finalArtifacts);
114
+ }),
115
+ );
93
116
 
94
- if (weekWorkVars.length === 0) continue;
95
-
96
- const weekLabel = weekDays[0]!;
97
- const constraintId = `min-days-week:${emp.id}:${weekLabel}`;
98
- const terms: Term[] = weekWorkVars.map((v) => ({ var: v, coeff: 1 }));
99
-
100
- if (priority === "MANDATORY") {
101
- b.addLinear(terms, ">=", days);
102
- } else {
103
- b.addSoftLinear(terms, ">=", days, priorityToPenalty(priority), constraintId);
104
- b.reporter.trackConstraint({
105
- id: constraintId,
106
- type: "rule",
107
- rule: "min-days-week",
108
- description: `${emp.id} min ${days}d in week starting ${weekLabel}`,
109
- targetValue: days,
110
- comparator: ">=",
111
- day: weekLabel,
112
- context: { memberIds: [emp.id], days: weekDays },
113
- group,
114
- });
115
- }
116
- }
117
- }
118
- },
119
- };
120
- }
117
+ return {
118
+ rule: "min-days-week",
119
+ artifacts,
120
+ };
121
+ },
122
+ });