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,25 +1,25 @@
1
1
  import * as z from "zod";
2
2
  import { DayOfWeekSchema } from "../../types.js";
3
- import type { CompilationRule, CostContribution } from "../model-builder.js";
4
- import type { ShiftPattern, SchedulingMember, Term } from "../types.js";
5
- import type { ShiftAssignment } from "../response.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
4
+ import type { CostArtifact } from "../rule-descriptor.js";
6
5
  import { COST_CATEGORY } from "../cost.js";
7
6
  import {
8
- timeOfDayToMinutes,
7
+ OBJECTIVE_WEIGHTS,
9
8
  normalizeEndMinutes,
10
9
  splitIntoWeeks,
10
+ timeOfDayToMinutes,
11
11
  unionMinutes,
12
- OBJECTIVE_WEIGHTS,
13
12
  } from "../utils.js";
14
13
  import {
15
14
  entityScope,
16
- timeScope,
17
15
  parseEntityScope,
18
16
  parseTimeScope,
19
- resolveMembersFromScope,
20
17
  resolveActiveDaysFromScope,
18
+ resolveMembersFromScope,
19
+ timeScope,
21
20
  } from "./scope.types.js";
22
- import { getHourlyRate, patternDurationMinutes } from "./cost-utils.js";
21
+ import { getHourlyRate } from "./cost-utils.js";
22
+ import { patternDurationMinutes } from "./pattern-time.js";
23
23
 
24
24
  const OvertimeTierSchema = z.object({
25
25
  after: z.number().min(0),
@@ -33,7 +33,7 @@ const OvertimeTierSchema = z.object({
33
33
  */
34
34
  export type OvertimeTier = z.infer<typeof OvertimeTierSchema>;
35
35
 
36
- const OvertimeTieredMultiplierSchema = z
36
+ export const OvertimeTieredMultiplierSchema = z
37
37
  .object({
38
38
  tiers: z.array(OvertimeTierSchema).nonempty(),
39
39
  weekStartsOn: DayOfWeekSchema.optional(),
@@ -41,276 +41,257 @@ const OvertimeTieredMultiplierSchema = z
41
41
  .and(entityScope(["members", "roles", "skills"]))
42
42
  .and(timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]));
43
43
 
44
- /** Configuration for {@link createOvertimeTieredMultiplierRule}. */
44
+ /** Configuration for {@link overtimeTieredMultiplierRuleDescriptor}. */
45
45
  export type OvertimeTieredMultiplierConfig = z.infer<typeof OvertimeTieredMultiplierSchema>;
46
46
 
47
- /**
48
- * Creates a multi-threshold weekly overtime multiplier rule.
49
- *
50
- * Each tier applies only to the hours between its threshold and the next.
51
- * Tiers are sorted by threshold ascending internally.
52
- *
53
- * @example
54
- * ```typescript
55
- * // Hours 0-40: base rate
56
- * // Hours 40-48: 1.5x
57
- * // Hours 48+: 2.0x
58
- * tieredOvertimeMultiplier([
59
- * { after: 40, factor: 1.5 },
60
- * { after: 48, factor: 2.0 },
61
- * ])
62
- * ```
63
- */
64
- export function createOvertimeTieredMultiplierRule(
65
- config: OvertimeTieredMultiplierConfig,
66
- ): CompilationRule {
67
- const parsed = OvertimeTieredMultiplierSchema.parse(config);
68
- const { weekStartsOn } = parsed;
69
- const entityScopeValue = parseEntityScope(parsed);
70
- const timeScopeValue = parseTimeScope(parsed);
71
-
72
- // Sort tiers by threshold ascending
73
- const tiers = parsed.tiers.toSorted((a, b) => a.after - b.after);
74
-
75
- // Filter out tiers with factor <= 1 (no extra cost)
76
- const effectiveTiers = tiers.filter((t) => t.factor > 1);
77
- let resolvedWeekStartsOn = weekStartsOn ?? "monday";
78
-
79
- return {
80
- compile(b) {
81
- resolvedWeekStartsOn = weekStartsOn ?? b.weekStartsOn;
82
- if (effectiveTiers.length === 0) return;
83
-
84
- const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
85
- const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
86
-
87
- if (targetMembers.length === 0 || activeDays.length === 0) return;
88
-
89
- const weeks = splitIntoWeeks(activeDays, weekStartsOn ?? b.weekStartsOn);
90
-
91
- const hasCostContext = b.costContext?.active === true;
92
-
93
- for (const emp of targetMembers) {
94
- const rate = getHourlyRate(emp);
95
- if (rate === undefined) continue;
96
-
97
- for (const [weekIdx, weekDays] of weeks.entries()) {
98
- // Build total minutes expression and compute per-member week max
99
- // using time-range union per day (accounts for no-overlap constraints)
100
- let empWeekMaxMinutes = 0;
101
- const terms: Term[] = [];
102
- for (const day of weekDays) {
103
- const dayRanges: Array<{ start: number; end: number }> = [];
104
- for (const pattern of b.shiftPatterns) {
105
- if (!b.canAssign(emp, pattern)) continue;
106
- if (!b.patternAvailableOnDay(pattern, day)) continue;
107
- const start = timeOfDayToMinutes(pattern.startTime);
108
- dayRanges.push({
109
- start,
110
- end: normalizeEndMinutes(start, timeOfDayToMinutes(pattern.endTime)),
111
- });
112
- terms.push({
113
- var: b.assignment(emp.id, pattern.id, day),
114
- coeff: patternDurationMinutes(pattern),
115
- });
116
- }
117
- empWeekMaxMinutes += unionMinutes(dayRanges);
118
- }
119
-
120
- if (terms.length === 0) continue;
47
+ export const overtimeTieredMultiplierRuleDescriptor = defineRuleDescriptor({
48
+ name: "overtime-tiered-multiplier",
49
+ schema: OvertimeTieredMultiplierSchema,
50
+ compile(config) {
51
+ const { weekStartsOn } = config;
52
+ const entityScopeValue = parseEntityScope(config);
53
+ const timeScopeValue = parseTimeScope(config);
54
+ const tiers = config.tiers.toSorted((left, right) => left.after - right.after);
55
+ const effectiveTiers = tiers.filter((tier) => tier.factor > 1);
56
+ let resolvedWeekStartsOn = weekStartsOn ?? "monday";
57
+
58
+ const costArtifact: CostArtifact = {
59
+ kind: "cost",
60
+ validation: {
61
+ strategy: "skip",
62
+ category: "no-meaningful-feedback",
63
+ rationale:
64
+ "Overtime cost artifacts influence optimization and post-solve accounting, not validation feedback.",
65
+ },
66
+ compileObjective(builder) {
67
+ resolvedWeekStartsOn = weekStartsOn ?? builder.weekStartsOn;
68
+ if (effectiveTiers.length === 0) return;
69
+
70
+ const targetMembers = resolveMembersFromScope(entityScopeValue, builder.members);
71
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, builder.days);
72
+ if (targetMembers.length === 0 || activeDays.length === 0) return;
73
+
74
+ const weeks = splitIntoWeeks(activeDays, weekStartsOn ?? builder.weekStartsOn);
75
+ const hasCostContext = builder.costContext?.active === true;
76
+
77
+ for (const member of targetMembers) {
78
+ const rate = getHourlyRate(member);
79
+ if (rate === undefined) continue;
121
80
 
122
- // Create a tierVar for each tier: max(0, totalMinutes - threshold)
123
- const tierVars: string[] = [];
124
- for (const [tierIdx, tier] of effectiveTiers.entries()) {
125
- const thresholdMinutes = tier.after * 60;
126
- const maxOvertime = Math.max(0, empWeekMaxMinutes - thresholdMinutes);
127
- if (maxOvertime === 0) {
128
- // Tiers are sorted ascending, so all subsequent tiers are also unreachable.
129
- tierVars.push("");
130
- continue;
81
+ for (const [weekIndex, weekDays] of weeks.entries()) {
82
+ let memberWeekMaxMinutes = 0;
83
+ const terms = [] as Array<{ var: string; coeff: number }>;
84
+ for (const day of weekDays) {
85
+ const dayRanges: Array<{ start: number; end: number }> = [];
86
+ for (const pattern of builder.shiftPatterns) {
87
+ if (
88
+ !builder.canAssign(member, pattern) ||
89
+ !builder.patternAvailableOnDay(pattern, day)
90
+ ) {
91
+ continue;
92
+ }
93
+ const start = timeOfDayToMinutes(pattern.startTime);
94
+ dayRanges.push({
95
+ start,
96
+ end: normalizeEndMinutes(start, timeOfDayToMinutes(pattern.endTime)),
97
+ });
98
+ terms.push({
99
+ var: builder.assignment(member.id, pattern.id, day),
100
+ coeff: patternDurationMinutes(pattern),
101
+ });
102
+ }
103
+ memberWeekMaxMinutes += unionMinutes(dayRanges);
131
104
  }
132
105
 
133
- const tierVar = b.intVar(
134
- `overtime:tiered:${emp.id}:w${weekIdx}:t${tierIdx}`,
135
- 0,
136
- maxOvertime,
137
- );
138
- tierVars.push(tierVar);
139
-
140
- // tierVar >= totalMinutes - threshold
141
- b.addLinear(
142
- [{ var: tierVar, coeff: 1 }, ...terms.map((t) => ({ var: t.var, coeff: -t.coeff }))],
143
- ">=",
144
- -thresholdMinutes,
145
- );
146
- }
147
-
148
- // Compute per-tier-only hours and add penalties.
149
- // tierOnly_i = tierVar_i - tierVar_{i+1} for non-last tiers.
150
- // For the last tier, tierOnly = tierVar_last.
151
- for (let i = 0; i < effectiveTiers.length; i++) {
152
- const tierVar = tierVars[i];
153
- if (!tierVar) continue; // Tier unreachable
106
+ if (terms.length === 0) continue;
154
107
 
155
- const tier = effectiveTiers[i]!;
156
- const extraFactor = tier.factor - 1;
157
-
158
- const nextTierVar = i + 1 < effectiveTiers.length ? tierVars[i + 1] : undefined;
108
+ const tierVars: string[] = [];
109
+ for (const [tierIndex, tier] of effectiveTiers.entries()) {
110
+ const thresholdMinutes = tier.after * 60;
111
+ const maxOvertime = Math.max(0, memberWeekMaxMinutes - thresholdMinutes);
112
+ if (maxOvertime === 0) {
113
+ tierVars.push("");
114
+ continue;
115
+ }
159
116
 
160
- if (nextTierVar) {
161
- // Create tierOnly_i = tierVar_i - tierVar_{i+1}
162
- const maxTierOnly = (effectiveTiers[i + 1]!.after - tier.after) * 60;
163
- const tierOnlyVar = b.intVar(
164
- `overtime:tiered-only:${emp.id}:w${weekIdx}:t${i}`,
117
+ const tierVar = builder.intVar(
118
+ `overtime:tiered:${member.id}:w${weekIndex}:t${tierIndex}`,
165
119
  0,
166
- Math.max(0, maxTierOnly),
120
+ maxOvertime,
167
121
  );
168
-
169
- // tierOnlyVar == tierVar - nextTierVar
170
- b.addLinear(
122
+ tierVars.push(tierVar);
123
+ builder.addLinear(
171
124
  [
172
- { var: tierOnlyVar, coeff: 1 },
173
- { var: tierVar, coeff: -1 },
174
- { var: nextTierVar, coeff: 1 },
125
+ { var: tierVar, coeff: 1 },
126
+ ...terms.map((term) => ({ var: term.var, coeff: -term.coeff })),
175
127
  ],
176
- "==",
177
- 0,
128
+ ">=",
129
+ -thresholdMinutes,
178
130
  );
131
+ }
179
132
 
180
- if (rate > 0) {
181
- const totalTierCost = (rate * extraFactor * Math.max(1, maxTierOnly)) / 60;
182
- if (hasCostContext) {
183
- const normalizedMax = totalTierCost / b.costContext!.normalizationFactor;
184
- b.addPenalty(
185
- tierOnlyVar,
186
- Math.max(1, Math.round(normalizedMax / Math.max(1, maxTierOnly))),
187
- );
133
+ for (let index = 0; index < effectiveTiers.length; index++) {
134
+ const tierVar = tierVars[index];
135
+ if (!tierVar) continue;
136
+
137
+ const tier = effectiveTiers[index]!;
138
+ const extraFactor = tier.factor - 1;
139
+ const nextTierVar =
140
+ index + 1 < effectiveTiers.length ? tierVars[index + 1] : undefined;
141
+
142
+ if (nextTierVar) {
143
+ const maxTierOnly = (effectiveTiers[index + 1]!.after - tier.after) * 60;
144
+ const tierOnlyVar = builder.intVar(
145
+ `overtime:tiered-only:${member.id}:w${weekIndex}:t${index}`,
146
+ 0,
147
+ Math.max(0, maxTierOnly),
148
+ );
149
+ builder.addLinear(
150
+ [
151
+ { var: tierOnlyVar, coeff: 1 },
152
+ { var: tierVar, coeff: -1 },
153
+ { var: nextTierVar, coeff: 1 },
154
+ ],
155
+ "==",
156
+ 0,
157
+ );
158
+
159
+ if (rate > 0) {
160
+ const totalTierCost = (rate * extraFactor * Math.max(1, maxTierOnly)) / 60;
161
+ if (hasCostContext) {
162
+ const normalizedMax = totalTierCost / builder.costContext!.normalizationFactor;
163
+ builder.addPenalty(
164
+ tierOnlyVar,
165
+ Math.max(1, Math.round(normalizedMax / Math.max(1, maxTierOnly))),
166
+ );
167
+ } else {
168
+ builder.addPenalty(
169
+ tierOnlyVar,
170
+ Math.max(1, Math.round(OBJECTIVE_WEIGHTS.COST / Math.max(1, maxTierOnly))),
171
+ );
172
+ }
188
173
  } else {
189
- b.addPenalty(
190
- tierOnlyVar,
191
- Math.max(1, Math.round(OBJECTIVE_WEIGHTS.COST / Math.max(1, maxTierOnly))),
192
- );
174
+ builder.addPenalty(tierOnlyVar, 1);
193
175
  }
194
176
  } else {
195
- b.addPenalty(tierOnlyVar, 1);
196
- }
197
- } else {
198
- // Last tier: penalty directly on tierVar
199
- const maxOvertimeLast = Math.max(1, empWeekMaxMinutes - tier.after * 60);
200
- if (rate > 0) {
201
- const totalTierCost = (rate * extraFactor * maxOvertimeLast) / 60;
202
- if (hasCostContext) {
203
- const normalizedMax = totalTierCost / b.costContext!.normalizationFactor;
204
- b.addPenalty(tierVar, Math.max(1, Math.round(normalizedMax / maxOvertimeLast)));
177
+ const maxOvertimeLast = Math.max(1, memberWeekMaxMinutes - tier.after * 60);
178
+ if (rate > 0) {
179
+ const totalTierCost = (rate * extraFactor * maxOvertimeLast) / 60;
180
+ if (hasCostContext) {
181
+ const normalizedMax = totalTierCost / builder.costContext!.normalizationFactor;
182
+ builder.addPenalty(
183
+ tierVar,
184
+ Math.max(1, Math.round(normalizedMax / maxOvertimeLast)),
185
+ );
186
+ } else {
187
+ builder.addPenalty(
188
+ tierVar,
189
+ Math.max(1, Math.round(OBJECTIVE_WEIGHTS.COST / maxOvertimeLast)),
190
+ );
191
+ }
205
192
  } else {
206
- b.addPenalty(
207
- tierVar,
208
- Math.max(1, Math.round(OBJECTIVE_WEIGHTS.COST / maxOvertimeLast)),
209
- );
193
+ builder.addPenalty(tierVar, 1);
210
194
  }
211
- } else {
212
- b.addPenalty(tierVar, 1);
213
195
  }
214
196
  }
215
197
  }
216
198
  }
217
- }
218
- },
219
-
220
- cost(
221
- assignments: ShiftAssignment[],
222
- members: ReadonlyArray<SchedulingMember>,
223
- shiftPatterns: ReadonlyArray<ShiftPattern>,
224
- ): CostContribution {
225
- if (effectiveTiers.length === 0) return { entries: [] };
226
-
227
- const memberMap = new Map(members.map((e) => [e.id, e]));
228
- const patternMap = new Map(shiftPatterns.map((p) => [p.id, p]));
229
-
230
- const allDays = [...new Set(assignments.map((a) => a.day))].toSorted();
231
- const activeDays = new Set(resolveActiveDaysFromScope(timeScopeValue, allDays));
232
- const targetEmpIds = new Set(
233
- resolveMembersFromScope(entityScopeValue, [...memberMap.values()]).map((e) => e.id),
234
- );
235
-
236
- const activeDaysList = allDays.filter((d) => activeDays.has(d));
237
- const weeks = splitIntoWeeks(activeDaysList, resolvedWeekStartsOn);
238
-
239
- const entries: CostContribution["entries"] = [];
240
-
241
- for (const weekDays of weeks) {
242
- const weekDaySet = new Set(weekDays);
243
-
244
- const empWeekData = new Map<
245
- string,
246
- { totalMinutes: number; dayMinutes: Map<string, number> }
247
- >();
248
-
249
- for (const a of assignments) {
250
- if (!weekDaySet.has(a.day)) continue;
251
- if (!activeDays.has(a.day)) continue;
252
- if (!targetEmpIds.has(a.memberId)) continue;
253
-
254
- const pattern = patternMap.get(a.shiftPatternId);
255
- if (!pattern) continue;
256
- const duration = patternDurationMinutes(pattern);
257
-
258
- let data = empWeekData.get(a.memberId);
259
- if (!data) {
260
- data = { totalMinutes: 0, dayMinutes: new Map() };
261
- empWeekData.set(a.memberId, data);
262
- }
263
- data.totalMinutes += duration;
264
- data.dayMinutes.set(a.day, (data.dayMinutes.get(a.day) ?? 0) + duration);
265
- }
266
-
267
- for (const [empId, data] of empWeekData) {
268
- const emp = memberMap.get(empId);
269
- if (!emp) continue;
270
- const rate = getHourlyRate(emp);
271
- if (rate === undefined) continue;
272
-
273
- // Compute total overtime cost across all tiers
274
- let totalOvertimeCost = 0;
275
-
276
- for (let i = 0; i < effectiveTiers.length; i++) {
277
- const tier = effectiveTiers[i]!;
278
- const thresholdMinutes = tier.after * 60;
279
- const extraFactor = tier.factor - 1;
280
-
281
- const overtimeAboveThreshold = Math.max(0, data.totalMinutes - thresholdMinutes);
282
- if (overtimeAboveThreshold <= 0) continue;
283
-
284
- // Minutes in this tier only (capped by next tier's threshold)
285
- const nextThreshold = effectiveTiers[i + 1]
286
- ? effectiveTiers[i + 1]!.after * 60
287
- : Infinity;
288
- const tierMinutes = Math.min(
289
- overtimeAboveThreshold,
290
- nextThreshold === Infinity
291
- ? overtimeAboveThreshold
292
- : nextThreshold - thresholdMinutes,
199
+ },
200
+ calculateCost(assignments, costContext) {
201
+ if (effectiveTiers.length === 0) return { entries: [] };
202
+
203
+ const memberMap = new Map(costContext.members.map((member) => [member.id, member]));
204
+ const patternMap = new Map(
205
+ costContext.shiftPatterns.map((pattern) => [pattern.id, pattern]),
206
+ );
207
+ const activeDays = new Set(
208
+ resolveActiveDaysFromScope(timeScopeValue, [...costContext.days]).map((day) => day.iso),
209
+ );
210
+ const targetMemberIds = new Set(
211
+ resolveMembersFromScope(entityScopeValue, [...costContext.members]).map(
212
+ (member) => member.id,
213
+ ),
214
+ );
215
+ const activeDaysList = costContext.days.filter((day) => activeDays.has(day.iso));
216
+ const weeks = splitIntoWeeks(activeDaysList, resolvedWeekStartsOn);
217
+
218
+ const entries = [] as Array<{
219
+ memberId: string;
220
+ day: string;
221
+ category: string;
222
+ amount: number;
223
+ }>;
224
+ for (const weekDays of weeks) {
225
+ const weekDaySet = new Set(weekDays.map((day) => day.iso));
226
+ const memberWeekData = new Map<
227
+ string,
228
+ { totalMinutes: number; dayMinutes: Map<string, number> }
229
+ >();
230
+
231
+ for (const assignment of assignments) {
232
+ if (!weekDaySet.has(assignment.day) || !activeDays.has(assignment.day)) continue;
233
+ if (!targetMemberIds.has(assignment.memberId)) continue;
234
+ const pattern = patternMap.get(assignment.shiftPatternId);
235
+ if (!pattern) continue;
236
+ const duration = patternDurationMinutes(pattern);
237
+ let data = memberWeekData.get(assignment.memberId);
238
+ if (!data) {
239
+ data = { totalMinutes: 0, dayMinutes: new Map() };
240
+ memberWeekData.set(assignment.memberId, data);
241
+ }
242
+ data.totalMinutes += duration;
243
+ data.dayMinutes.set(
244
+ assignment.day,
245
+ (data.dayMinutes.get(assignment.day) ?? 0) + duration,
293
246
  );
294
-
295
- totalOvertimeCost += (rate * extraFactor * tierMinutes) / 60;
296
247
  }
297
248
 
298
- if (totalOvertimeCost <= 0) continue;
249
+ for (const [memberId, data] of memberWeekData) {
250
+ const member = memberMap.get(memberId);
251
+ if (!member) continue;
252
+ const rate = getHourlyRate(member);
253
+ if (rate === undefined) continue;
254
+
255
+ let totalOvertimeCost = 0;
256
+ for (let index = 0; index < effectiveTiers.length; index++) {
257
+ const tier = effectiveTiers[index]!;
258
+ const thresholdMinutes = tier.after * 60;
259
+ const extraFactor = tier.factor - 1;
260
+ const overtimeAboveThreshold = Math.max(0, data.totalMinutes - thresholdMinutes);
261
+ if (overtimeAboveThreshold <= 0) continue;
262
+
263
+ const nextThreshold = effectiveTiers[index + 1]
264
+ ? effectiveTiers[index + 1]!.after * 60
265
+ : Infinity;
266
+ const tierMinutes = Math.min(
267
+ overtimeAboveThreshold,
268
+ nextThreshold === Infinity
269
+ ? overtimeAboveThreshold
270
+ : nextThreshold - thresholdMinutes,
271
+ );
272
+ totalOvertimeCost += (rate * extraFactor * tierMinutes) / 60;
273
+ }
299
274
 
300
- // Distribute proportionally across days
301
- for (const [day, dayMinutes] of data.dayMinutes) {
302
- const proportion = dayMinutes / data.totalMinutes;
303
- entries.push({
304
- memberId: empId,
305
- day,
306
- category: COST_CATEGORY.OVERTIME,
307
- amount: totalOvertimeCost * proportion,
308
- });
275
+ if (totalOvertimeCost <= 0) continue;
276
+ for (const [day, dayMinutes] of data.dayMinutes) {
277
+ const proportion = dayMinutes / data.totalMinutes;
278
+ entries.push({
279
+ memberId,
280
+ day,
281
+ category: COST_CATEGORY.OVERTIME,
282
+ amount: totalOvertimeCost * proportion,
283
+ });
284
+ }
309
285
  }
310
286
  }
311
- }
312
287
 
313
- return { entries };
314
- },
315
- };
316
- }
288
+ return { entries };
289
+ },
290
+ };
291
+
292
+ return {
293
+ rule: "overtime-tiered-multiplier",
294
+ artifacts: [costArtifact],
295
+ };
296
+ },
297
+ });