dabke 0.82.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 (341) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +22 -24
  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 -21
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -21
  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 +30 -0
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
  87. package/dist/cpsat/rules/max-days-week.js +83 -0
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -0
  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 +21 -0
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
  111. package/dist/cpsat/rules/min-days-week.js +95 -0
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -0
  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 +12 -0
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
  131. package/dist/cpsat/rules/must-assign.js +54 -0
  132. package/dist/cpsat/rules/must-assign.js.map +1 -0
  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 -27
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -31
  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 +93 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +25 -12
  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 +15 -9
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +11 -4
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +192 -0
  230. package/dist/schedule/cost.d.ts.map +1 -0
  231. package/dist/schedule/cost.js +151 -0
  232. package/dist/schedule/cost.js.map +1 -0
  233. package/dist/schedule/coverage.d.ts +114 -0
  234. package/dist/schedule/coverage.d.ts.map +1 -0
  235. package/dist/schedule/coverage.js +56 -0
  236. package/dist/schedule/coverage.js.map +1 -0
  237. package/dist/schedule/definition.d.ts +346 -0
  238. package/dist/schedule/definition.d.ts.map +1 -0
  239. package/dist/schedule/definition.js +668 -0
  240. package/dist/schedule/definition.js.map +1 -0
  241. package/dist/schedule/index.d.ts +67 -0
  242. package/dist/schedule/index.d.ts.map +1 -0
  243. package/dist/schedule/index.js +69 -0
  244. package/dist/schedule/index.js.map +1 -0
  245. package/dist/schedule/rules.d.ts +678 -0
  246. package/dist/schedule/rules.d.ts.map +1 -0
  247. package/dist/schedule/rules.js +554 -0
  248. package/dist/schedule/rules.js.map +1 -0
  249. package/dist/schedule/shift-patterns.d.ts +17 -0
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -0
  251. package/dist/schedule/shift-patterns.js +24 -0
  252. package/dist/schedule/shift-patterns.js.map +1 -0
  253. package/dist/schedule/time-periods.d.ts +79 -0
  254. package/dist/schedule/time-periods.d.ts.map +1 -0
  255. package/dist/schedule/time-periods.js +101 -0
  256. package/dist/schedule/time-periods.js.map +1 -0
  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 -14
  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 -21
  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 +117 -0
  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 +122 -0
  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 +71 -0
  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 -56
  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 +92 -17
  309. package/src/cpsat/rules/scope.types.ts +32 -27
  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 +72 -15
  323. package/src/schedule/cost.ts +221 -0
  324. package/src/schedule/coverage.ts +164 -0
  325. package/src/schedule/definition.ts +1447 -0
  326. package/src/schedule/index.ts +132 -0
  327. package/src/schedule/rules.ts +1028 -0
  328. package/src/schedule/shift-patterns.ts +29 -0
  329. package/src/schedule/time-periods.ts +120 -0
  330. package/src/types.ts +114 -2
  331. package/dist/llms.d.ts +0 -2
  332. package/dist/llms.d.ts.map +0 -1
  333. package/dist/llms.js +0 -3
  334. package/dist/llms.js.map +0 -1
  335. package/dist/schedule.d.ts +0 -917
  336. package/dist/schedule.d.ts.map +0 -1
  337. package/dist/schedule.js +0 -1323
  338. package/dist/schedule.js.map +0 -1
  339. package/llms.txt +0 -758
  340. package/src/llms.ts +0 -3
  341. package/src/schedule.ts +0 -1960
@@ -1,8 +1,10 @@
1
1
  import * as z from "zod";
2
- import type { CompilationRule, RuleValidationContext } from "../model-builder.js";
2
+ import { schedulingDay, type DateString, type SchedulingDay } from "../../types.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
4
+ import type { RuleArtifact, RuleCompileContext } from "../rule-descriptor.js";
5
+ import type { ValidationReporter } from "../validation-reporter.js";
3
6
  import type { ResolvedShiftAssignment } from "../response.js";
4
7
  import { normalizeEndMinutes, priorityToPenalty, timeOfDayToMinutes } from "../utils.js";
5
- import type { ValidationReporter } from "../validation-reporter.js";
6
8
  import {
7
9
  PrioritySchema,
8
10
  entityScope,
@@ -10,8 +12,11 @@ import {
10
12
  resolveMembersFromScope,
11
13
  ruleGroup,
12
14
  } from "./scope.types.js";
15
+ import { assignmentVar } from "./variables.js";
16
+ import { hardConstraint, reportValidation, softConstraint } from "./artifacts.js";
17
+ import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
13
18
 
14
- const MinRestBetweenShiftsSchema = z
19
+ export const MinRestBetweenShiftsSchema = z
15
20
  .object({
16
21
  hours: z.number().min(0),
17
22
  priority: PrioritySchema,
@@ -19,197 +24,205 @@ const MinRestBetweenShiftsSchema = z
19
24
  .and(entityScope(["members", "roles", "skills"]));
20
25
 
21
26
  /**
22
- * Configuration for {@link createMinRestBetweenShiftsRule}.
23
- *
24
- * - `hours` (required): minimum rest hours required between consecutive shifts
25
- * - `priority` (required): how strictly the solver enforces this rule
26
- *
27
- * Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
27
+ * Configuration for {@link minRestBetweenShiftsRuleDescriptor}.
28
28
  */
29
29
  export type MinRestBetweenShiftsConfig = z.infer<typeof MinRestBetweenShiftsSchema>;
30
30
 
31
31
  /**
32
- * Enforces a minimum rest period between any two shifts a person works.
32
+ * Low-level descriptor for the `min-rest-between-shifts` rule.
33
33
  *
34
- * @param config - See {@link MinRestBetweenShiftsConfig}
35
- * @example
36
- * ```ts
37
- * createMinRestBetweenShiftsRule({ hours: 10, priority: "MANDATORY" });
38
- * ```
34
+ * @category Rules
39
35
  */
40
- export function createMinRestBetweenShiftsRule(
41
- config: MinRestBetweenShiftsConfig,
42
- ): CompilationRule {
43
- const parsed = MinRestBetweenShiftsSchema.parse(config);
44
- const scope = parseEntityScope(parsed);
45
- const { hours, priority } = parsed;
46
- const minMinutes = hours * 60;
47
- const group = ruleGroup(
48
- `min-rest-between-shifts:${hours}`,
49
- `Min ${hours}h rest between shifts`,
50
- scope,
51
- );
36
+ export const minRestBetweenShiftsRuleDescriptor = defineRuleDescriptor({
37
+ name: "min-rest-between-shifts",
38
+ schema: MinRestBetweenShiftsSchema,
39
+ compile(config, ctx) {
40
+ const scope = parseEntityScope(config);
41
+ const { hours, priority } = config;
42
+ const minMinutes = hours * 60;
43
+ const group = ruleGroup(
44
+ `min-rest-between-shifts:${hours}`,
45
+ `Min ${hours}h rest between shifts`,
46
+ scope,
47
+ );
48
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
49
+
50
+ const artifacts = members.flatMap((member) => {
51
+ const memberArtifacts: RuleArtifact[] = [];
52
+
53
+ for (let dayIndex = 0; dayIndex < ctx.days.length; dayIndex++) {
54
+ const day1 = ctx.days[dayIndex];
55
+ if (!day1) continue;
56
+
57
+ const checkDays = [day1];
58
+ const nextDay = ctx.days[dayIndex + 1];
59
+ if (nextDay) checkDays.push(nextDay);
60
+
61
+ for (const pattern1 of ctx.shiftPatterns) {
62
+ if (
63
+ !canAssignMemberToPattern(member, pattern1) ||
64
+ !isPatternAvailableOnDay(pattern1, day1)
65
+ ) {
66
+ continue;
67
+ }
68
+ const end1 = patternEndMinutes(pattern1);
69
+
70
+ for (const day2 of checkDays) {
71
+ for (const pattern2 of ctx.shiftPatterns) {
72
+ if (
73
+ !canAssignMemberToPattern(member, pattern2) ||
74
+ !isPatternAvailableOnDay(pattern2, day2) ||
75
+ (day1.iso === day2.iso && pattern1.id === pattern2.id)
76
+ ) {
77
+ continue;
78
+ }
52
79
 
53
- return {
54
- compile(b) {
55
- const members = resolveMembersFromScope(scope, b.members);
56
-
57
- for (const emp of members) {
58
- for (let i = 0; i < b.days.length; i++) {
59
- const day1 = b.days[i];
60
- if (!day1) continue;
61
-
62
- const checkDays: string[] = [day1];
63
- const nextDay = b.days[i + 1];
64
- if (nextDay) checkDays.push(nextDay);
65
-
66
- for (const pattern1 of b.shiftPatterns) {
67
- if (!b.canAssign(emp, pattern1)) continue;
68
- if (!b.patternAvailableOnDay(pattern1, day1)) continue;
69
- const end1 = b.endMinutes(pattern1, day1);
70
-
71
- for (const day2 of checkDays) {
72
- if (!day2) continue;
73
- for (const pattern2 of b.shiftPatterns) {
74
- if (!b.canAssign(emp, pattern2)) continue;
75
- if (!b.patternAvailableOnDay(pattern2, day2)) continue;
76
- if (day1 === day2 && pattern1.id === pattern2.id) continue;
77
-
78
- const start2 = b.startMinutes(pattern2, day2);
79
- const gap = start2 - end1;
80
-
81
- if (gap >= 0 && gap < minMinutes) {
82
- const var1 = b.assignment(emp.id, pattern1.id, day1);
83
- const var2 = b.assignment(emp.id, pattern2.id, day2);
84
-
85
- if (priority === "MANDATORY") {
86
- b.addLinear(
87
- [
88
- { var: var1, coeff: 1 },
89
- { var: var2, coeff: 1 },
90
- ],
91
- "<=",
92
- 1,
93
- );
94
- } else {
95
- const conflictVar = b.boolVar(
96
- `rest_conflict_${emp.id}_${pattern1.id}_${day1}_${pattern2.id}_${day2}`,
97
- );
98
- b.addLinear(
99
- [
100
- { var: var1, coeff: 1 },
101
- { var: var2, coeff: 1 },
102
- { var: conflictVar, coeff: -1 },
103
- ],
104
- "<=",
105
- 1,
106
- );
107
- b.addLinear(
108
- [
109
- { var: conflictVar, coeff: 1 },
110
- { var: var1, coeff: -1 },
111
- ],
112
- "<=",
113
- 0,
114
- );
115
- b.addLinear(
116
- [
117
- { var: conflictVar, coeff: 1 },
118
- { var: var2, coeff: -1 },
119
- ],
120
- "<=",
121
- 0,
122
- );
123
- b.addPenalty(conflictVar, priorityToPenalty(priority));
124
- }
125
- }
80
+ const start2 = dayGapMinutes(day1, day2) + timeOfDayToMinutes(pattern2.startTime);
81
+ const gap = start2 - end1;
82
+ if (gap < 0 || gap >= minMinutes) continue;
83
+
84
+ const description = `${member.id} needs ${hours}h rest between ${pattern1.id} on ${day1.iso} and ${pattern2.id} on ${day2.iso}`;
85
+ const context = { memberIds: [member.id], days: [day1.iso, day2.iso] };
86
+ const constraintId = `min-rest-between-shifts:${member.id}:${pattern1.id}:${day1.iso}:${pattern2.id}:${day2.iso}`;
87
+ const terms = [
88
+ { var: assignmentVar(member.id, pattern1.id, day1.iso), coeff: 1 },
89
+ { var: assignmentVar(member.id, pattern2.id, day2.iso), coeff: 1 },
90
+ ];
91
+
92
+ if (priority === "MANDATORY") {
93
+ memberArtifacts.push(
94
+ hardConstraint({
95
+ group,
96
+ description,
97
+ context,
98
+ validation: reportValidation(constraintId),
99
+ terms,
100
+ comparator: "<=",
101
+ targetValue: 1,
102
+ }),
103
+ );
104
+ } else {
105
+ memberArtifacts.push(
106
+ softConstraint({
107
+ group,
108
+ description,
109
+ context,
110
+ validation: reportValidation(),
111
+ terms,
112
+ comparator: "<=",
113
+ targetValue: 1,
114
+ penalty: priorityToPenalty(priority),
115
+ constraintId,
116
+ }),
117
+ );
126
118
  }
127
119
  }
128
120
  }
129
121
  }
130
122
  }
131
- },
132
-
133
- validate(
134
- assignments: ResolvedShiftAssignment[],
135
- reporter: ValidationReporter,
136
- context: RuleValidationContext,
137
- ) {
138
- if (priority === "MANDATORY") return;
139
-
140
- const members = resolveMembersFromScope(scope, context.members);
141
- if (members.length === 0) return;
142
-
143
- const memberIds = new Set(members.map((m) => m.id));
144
-
145
- // Group assignments by member, sort by day then start time
146
- const byMember = new Map<string, ResolvedShiftAssignment[]>();
147
- for (const a of assignments) {
148
- if (!memberIds.has(a.memberId)) continue;
149
- const list = byMember.get(a.memberId) ?? [];
150
- list.push(a);
151
- byMember.set(a.memberId, list);
152
- }
153
123
 
154
- for (const [memberId, memberAssignments] of byMember) {
155
- const sorted = memberAssignments.toSorted((a, b) => {
156
- if (a.day !== b.day) return a.day < b.day ? -1 : 1;
157
- return timeOfDayToMinutes(a.startTime) - timeOfDayToMinutes(b.startTime);
158
- });
159
-
160
- let violated = false;
161
- for (let i = 0; i < sorted.length - 1; i++) {
162
- const current = sorted[i]!;
163
- const next = sorted[i + 1]!;
164
-
165
- const currentEndMin = timeOfDayToMinutes(current.endTime);
166
- const normalizedEnd = normalizeEndMinutes(
167
- timeOfDayToMinutes(current.startTime),
168
- currentEndMin,
169
- );
170
- const nextStartMin = timeOfDayToMinutes(next.startTime);
171
-
172
- // Calculate gap considering day boundaries
173
- let gap: number;
174
- if (current.day === next.day) {
175
- gap = nextStartMin - normalizedEnd;
176
- } else {
177
- // Different days: account for the day boundary
178
- const dayDiff = daysBetween(current.day, next.day);
179
- gap = dayDiff * 24 * 60 - normalizedEnd + nextStartMin;
180
- }
124
+ return memberArtifacts;
125
+ });
126
+
127
+ const validatorArtifacts =
128
+ priority === "MANDATORY"
129
+ ? []
130
+ : [
131
+ {
132
+ kind: "post-solve-feedback" as const,
133
+ run(
134
+ assignments: readonly ResolvedShiftAssignment[],
135
+ reporter: ValidationReporter,
136
+ validationContext: RuleCompileContext,
137
+ ) {
138
+ const validatorMembers = resolveMembersFromScope(scope, [
139
+ ...validationContext.members,
140
+ ]);
141
+ if (validatorMembers.length === 0) return;
142
+
143
+ const dayLookup = new Map<SchedulingDay["iso"], SchedulingDay>(
144
+ validationContext.days.map((day: SchedulingDay) => [day.iso, day] as const),
145
+ );
146
+ const memberIds = new Set(validatorMembers.map((member) => member.id));
147
+ const byMember = new Map<string, ResolvedShiftAssignment[]>();
148
+
149
+ for (const assignment of assignments) {
150
+ if (!memberIds.has(assignment.memberId)) continue;
151
+ const list = byMember.get(assignment.memberId) ?? [];
152
+ list.push(assignment);
153
+ byMember.set(assignment.memberId, list);
154
+ }
181
155
 
182
- if (gap >= 0 && gap < minMinutes) {
183
- reporter.reportRuleViolation({
184
- rule: "min-rest-between-shifts",
185
- message: `${memberId} has ${Math.round((gap / 60) * 10) / 10}h rest between shifts on ${current.day} and ${next.day}, need ${hours}h`,
186
- context: { memberIds: [memberId], days: [current.day, next.day] },
187
- shortfall: minMinutes - gap,
188
- group,
189
- });
190
- violated = true;
191
- }
192
- }
156
+ for (const [memberId, memberAssignments] of byMember) {
157
+ const sorted = [...memberAssignments].toSorted((left, right) => {
158
+ if (left.day !== right.day) return left.day < right.day ? -1 : 1;
159
+ return timeOfDayToMinutes(left.startTime) - timeOfDayToMinutes(right.startTime);
160
+ });
161
+
162
+ let violated = false;
163
+ for (let index = 0; index < sorted.length - 1; index++) {
164
+ const current = sorted[index]!;
165
+ const next = sorted[index + 1]!;
166
+ const currentEnd = normalizeEndMinutes(
167
+ timeOfDayToMinutes(current.startTime),
168
+ timeOfDayToMinutes(current.endTime),
169
+ );
170
+ const nextStart = timeOfDayToMinutes(next.startTime);
171
+ const gap =
172
+ current.day === next.day
173
+ ? nextStart - currentEnd
174
+ : daysBetween(current.day, next.day, dayLookup) * 24 * 60 -
175
+ currentEnd +
176
+ nextStart;
177
+
178
+ if (gap < 0 || gap >= minMinutes) continue;
179
+ violated = true;
180
+ }
193
181
 
194
- if (!violated && sorted.length > 1) {
195
- reporter.reportRulePassed({
196
- rule: "min-rest-between-shifts",
197
- message: `${memberId} has ${hours}h+ rest between all shifts`,
198
- context: {
199
- memberIds: [memberId],
200
- days: [...new Set(sorted.map((a) => a.day))],
182
+ if (!violated && sorted.length > 1) {
183
+ reporter.reportRulePassed({
184
+ rule: "min-rest-between-shifts",
185
+ message: `${memberId} has ${hours}h+ rest between all shifts`,
186
+ context: {
187
+ memberIds: [memberId],
188
+ days: [...new Set(sorted.map((assignment) => assignment.day))],
189
+ },
190
+ group,
191
+ });
192
+ }
193
+ }
194
+ },
201
195
  },
202
- group,
203
- });
204
- }
205
- }
206
- },
207
- };
196
+ ];
197
+
198
+ return {
199
+ rule: "min-rest-between-shifts",
200
+ artifacts: [...artifacts, ...validatorArtifacts],
201
+ };
202
+ },
203
+ });
204
+
205
+ function patternEndMinutes(pattern: {
206
+ startTime: { hours: number; minutes: number };
207
+ endTime: { hours: number; minutes: number };
208
+ }): number {
209
+ return normalizeEndMinutes(
210
+ timeOfDayToMinutes(pattern.startTime),
211
+ timeOfDayToMinutes(pattern.endTime),
212
+ );
213
+ }
214
+
215
+ function dayGapMinutes(day1: SchedulingDay, day2: SchedulingDay): number {
216
+ return (day2.epochDay - day1.epochDay) * 24 * 60;
208
217
  }
209
218
 
210
- /** Compute day difference from YYYY-MM-DD strings. */
211
- function daysBetween(day1: string, day2: string): number {
212
- const d1 = new Date(`${day1}T00:00:00Z`);
213
- const d2 = new Date(`${day2}T00:00:00Z`);
214
- return Math.round((d2.getTime() - d1.getTime()) / (24 * 60 * 60 * 1000));
219
+ function daysBetween(
220
+ day1: DateString,
221
+ day2: DateString,
222
+ lookup: ReadonlyMap<DateString, SchedulingDay>,
223
+ ): number {
224
+ const left = lookup.get(day1);
225
+ const right = lookup.get(day2);
226
+ if (left && right) return right.epochDay - left.epochDay;
227
+ return schedulingDay(day2).epochDay - schedulingDay(day1).epochDay;
215
228
  }