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
@@ -0,0 +1,212 @@
1
+ import * as z from "zod";
2
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
3
+ import { targetPeakPriorityToPenalty } from "../utils.js";
4
+ import {
5
+ boolVariable,
6
+ hardConstraint,
7
+ intVariable,
8
+ reportValidation,
9
+ skipValidation,
10
+ softConstraint,
11
+ } from "./artifacts.js";
12
+ import {
13
+ buildConcurrentAssignmentSegments,
14
+ collectConcurrentAssignmentIntervals,
15
+ resolveConcurrentWindow,
16
+ } from "./concurrent-intervals.js";
17
+ import {
18
+ entityScope,
19
+ parseEntityScope,
20
+ parseTimeScope,
21
+ resolveActiveDaysFromScope,
22
+ resolveMembersFromScope,
23
+ ruleGroup,
24
+ SoftPrioritySchema,
25
+ timeScope,
26
+ } from "./scope.types.js";
27
+
28
+ export const TargetPeakConcurrentAssignmentsSchema = z
29
+ .object({
30
+ assignments: z.number().int().min(0),
31
+ priority: SoftPrioritySchema.default("HIGH"),
32
+ })
33
+ .and(entityScope(["members", "roles", "skills"]))
34
+ .and(timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]));
35
+
36
+ /**
37
+ * Configuration for {@link targetPeakConcurrentAssignmentsRuleDescriptor}.
38
+ */
39
+ export type TargetPeakConcurrentAssignmentsConfig = z.infer<
40
+ typeof TargetPeakConcurrentAssignmentsSchema
41
+ >;
42
+
43
+ /** @internal */
44
+ export const TARGET_PEAK_CONCURRENT_ASSIGNMENTS_OBJECTIVE_STAGE_ID =
45
+ "__dabke_target_peak_concurrent_assignments__";
46
+
47
+ /**
48
+ * Low-level descriptor for the `target-peak-concurrent-assignments` rule.
49
+ *
50
+ * @remarks
51
+ * Softly targets the daily peak number of concurrent targeted assignments
52
+ * within the scoped day set. This is not a lower bound across the whole day.
53
+ * Use {@link import("../../schedule/coverage.js").cover} for whole-window
54
+ * minimum staffing, and pair this rule with
55
+ * {@link import("./max-concurrent-assignments.js").maxConcurrentAssignmentsRuleDescriptor}
56
+ * when the same value is also a hard capacity cap.
57
+ *
58
+ * @category Rules
59
+ */
60
+ export const targetPeakConcurrentAssignmentsRuleDescriptor = defineRuleDescriptor({
61
+ name: "target-peak-concurrent-assignments",
62
+ schema: TargetPeakConcurrentAssignmentsSchema,
63
+ compile(config, ctx) {
64
+ const { assignments, priority } = config;
65
+ if (assignments === 0) {
66
+ return { rule: "target-peak-concurrent-assignments", artifacts: [] };
67
+ }
68
+
69
+ const entityScopeValue = parseEntityScope(config);
70
+ const timeScopeValue = parseTimeScope(config);
71
+ const targetMembers = resolveMembersFromScope(entityScopeValue, [...ctx.members]);
72
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, [...ctx.days]);
73
+
74
+ if (activeDays.length === 0) {
75
+ return { rule: "target-peak-concurrent-assignments", artifacts: [] };
76
+ }
77
+
78
+ const group = ruleGroup(
79
+ `target-peak-concurrent-assignments:${assignments}`,
80
+ `Target peak ${assignments} concurrent assignment${assignments === 1 ? "" : "s"}`,
81
+ entityScopeValue,
82
+ timeScopeValue,
83
+ );
84
+
85
+ const artifacts = activeDays.flatMap((day) => {
86
+ const dayContext = { days: [day.iso], memberIds: targetMembers.map((member) => member.id) };
87
+ const intervals = collectConcurrentAssignmentIntervals(day, targetMembers, ctx.shiftPatterns);
88
+ const window = resolveConcurrentWindow(intervals);
89
+ const segments = buildConcurrentAssignmentSegments(intervals, window.start, window.end);
90
+ const dayKey = `${group.key}:${day.iso}`;
91
+ const shortfallVar = `${dayKey}:shortfall`;
92
+
93
+ if (segments.length === 0) {
94
+ return [
95
+ intVariable(shortfallVar, 0, assignments),
96
+ hardConstraint({
97
+ group,
98
+ description: `Fix peak-target shortfall to ${assignments} on ${day.iso} because no eligible assignments exist`,
99
+ context: dayContext,
100
+ validation: skipValidation(
101
+ "scaffolding",
102
+ "Peak-target shortfall is fixed when no eligible assignments can contribute to the daily peak.",
103
+ ),
104
+ terms: [{ var: shortfallVar, coeff: 1 }],
105
+ comparator: "==",
106
+ targetValue: assignments,
107
+ }),
108
+ softConstraint({
109
+ stage: TARGET_PEAK_CONCURRENT_ASSIGNMENTS_OBJECTIVE_STAGE_ID,
110
+ group,
111
+ description: `Target peak ${assignments} concurrent assignment${assignments === 1 ? "" : "s"} on ${day.iso}`,
112
+ context: dayContext,
113
+ validation: reportValidation(),
114
+ terms: [{ var: shortfallVar, coeff: 1 }],
115
+ comparator: "<=",
116
+ targetValue: 0,
117
+ penalty: targetPeakPriorityToPenalty(priority),
118
+ constraintId: `${dayKey}:shortfall`,
119
+ }),
120
+ ];
121
+ }
122
+
123
+ const witnessVars = segments.map((segment) => ({
124
+ segmentStart: segment.start,
125
+ name: `${dayKey}:${segment.start}:witness`,
126
+ }));
127
+
128
+ const helperArtifacts = [
129
+ intVariable(shortfallVar, 0, assignments),
130
+ ...witnessVars.map(({ name }) => boolVariable(name)),
131
+ hardConstraint({
132
+ group,
133
+ description: `Select exactly one peak segment witness on ${day.iso}`,
134
+ context: dayContext,
135
+ validation: skipValidation(
136
+ "scaffolding",
137
+ "Peak-target witness selection is an internal helper for day-level peak targeting.",
138
+ ),
139
+ terms: witnessVars.map(({ name }) => ({ var: name, coeff: 1 })),
140
+ comparator: "==",
141
+ targetValue: 1,
142
+ }),
143
+ ];
144
+
145
+ const linkageArtifacts = segments.map((segment) => {
146
+ const witnessVar = witnessVars.find(
147
+ (witness) => witness.segmentStart === segment.start,
148
+ )?.name;
149
+ if (!witnessVar) {
150
+ throw new Error(
151
+ `Missing peak-target witness variable for segment ${segment.start} on ${day.iso}`,
152
+ );
153
+ }
154
+
155
+ return hardConstraint({
156
+ group,
157
+ description: `Link peak-target shortfall to segment ${formatTimeRange(segment.start, segment.end)} on ${day.iso}`,
158
+ context: dayContext,
159
+ validation: skipValidation(
160
+ "scaffolding",
161
+ "Peak-target segment linkage is an internal helper that selects the best segment on a day.",
162
+ ),
163
+ terms: [
164
+ ...segment.varNames.map((varName) => ({ var: varName, coeff: 1 })),
165
+ { var: witnessVar, coeff: -assignments },
166
+ { var: shortfallVar, coeff: 1 },
167
+ ],
168
+ comparator: ">=",
169
+ targetValue: 0,
170
+ });
171
+ });
172
+
173
+ const shortfallArtifact = softConstraint({
174
+ stage: TARGET_PEAK_CONCURRENT_ASSIGNMENTS_OBJECTIVE_STAGE_ID,
175
+ group,
176
+ description: `Target peak ${assignments} concurrent assignment${assignments === 1 ? "" : "s"} on ${day.iso}`,
177
+ context: dayContext,
178
+ validation: reportValidation(),
179
+ terms: [{ var: shortfallVar, coeff: 1 }],
180
+ comparator: "<=",
181
+ targetValue: 0,
182
+ penalty: targetPeakPriorityToPenalty(priority),
183
+ constraintId: `${dayKey}:shortfall`,
184
+ });
185
+
186
+ const dayArtifacts: Array<
187
+ | (typeof helperArtifacts)[number]
188
+ | (typeof linkageArtifacts)[number]
189
+ | typeof shortfallArtifact
190
+ > = [];
191
+ dayArtifacts.push(...helperArtifacts, ...linkageArtifacts, shortfallArtifact);
192
+ return dayArtifacts;
193
+ });
194
+
195
+ return {
196
+ rule: "target-peak-concurrent-assignments",
197
+ artifacts,
198
+ };
199
+ },
200
+ });
201
+
202
+ function formatTimeRange(startMinutes: number, endMinutes: number): string {
203
+ return `${formatClockMinutes(startMinutes)}-${formatClockMinutes(endMinutes)}`;
204
+ }
205
+
206
+ function formatClockMinutes(totalMinutes: number): string {
207
+ const minutesPerDay = 24 * 60;
208
+ const normalized = ((totalMinutes % minutesPerDay) + minutesPerDay) % minutesPerDay;
209
+ const hours = Math.floor(normalized / 60);
210
+ const minutes = normalized % 60;
211
+ return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
212
+ }
@@ -1,17 +1,16 @@
1
1
  import * as z from "zod";
2
- import type { CompilationRule, CostContribution } from "../model-builder.js";
3
- import type { ShiftPattern, SchedulingMember } from "../types.js";
4
2
  import type { TimeOfDay } 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
- import { timeOfDayToMinutes, normalizeEndMinutes, MINUTES_PER_DAY } from "../utils.js";
6
+ import { MINUTES_PER_DAY, normalizeEndMinutes, timeOfDayToMinutes } from "../utils.js";
8
7
  import {
9
8
  entityScope,
10
- timeScope,
11
9
  parseEntityScope,
12
10
  parseTimeScope,
13
- resolveMembersFromScope,
14
11
  resolveActiveDaysFromScope,
12
+ resolveMembersFromScope,
13
+ timeScope,
15
14
  } from "./scope.types.js";
16
15
 
17
16
  const TimeOfDaySchema = z.object({
@@ -19,7 +18,7 @@ const TimeOfDaySchema = z.object({
19
18
  minutes: z.number().int().min(0).max(59),
20
19
  });
21
20
 
22
- const TimeCostSurchargeSchema = z
21
+ export const TimeCostSurchargeSchema = z
23
22
  .object({
24
23
  amountPerHour: z.number().min(0),
25
24
  window: z.object({
@@ -30,14 +29,112 @@ const TimeCostSurchargeSchema = z
30
29
  .and(entityScope(["members", "roles", "skills"]))
31
30
  .and(timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]));
32
31
 
33
- /** Configuration for {@link createTimeCostSurchargeRule}. */
32
+ /** Configuration for {@link timeCostSurchargeRuleDescriptor}. */
34
33
  export type TimeCostSurchargeConfig = z.infer<typeof TimeCostSurchargeSchema>;
35
34
 
36
- /**
37
- * Computes the overlap in minutes between a shift and a time-of-day window.
38
- *
39
- * Handles overnight windows (e.g., 22:00-06:00) and overnight shifts.
40
- */
35
+ export const timeCostSurchargeRuleDescriptor = defineRuleDescriptor({
36
+ name: "time-cost-surcharge",
37
+ schema: TimeCostSurchargeSchema,
38
+ compile(config) {
39
+ const { amountPerHour, window } = config;
40
+ const entityScopeValue = parseEntityScope(config);
41
+ const timeScopeValue = parseTimeScope(config);
42
+
43
+ const validation = {
44
+ strategy: "skip" as const,
45
+ category: "no-meaningful-feedback" as const,
46
+ rationale:
47
+ amountPerHour <= 0
48
+ ? "A zero time surcharge does not change optimization or cost accounting."
49
+ : "Time cost surcharges only affect optimization when minimize-cost is active.",
50
+ };
51
+
52
+ const costArtifact: CostArtifact = {
53
+ kind: "cost",
54
+ validation,
55
+ compileObjective(builder) {
56
+ if (!builder.costContext?.active || amountPerHour <= 0) return;
57
+
58
+ const targetMembers = resolveMembersFromScope(entityScopeValue, builder.members);
59
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, builder.days);
60
+ if (targetMembers.length === 0 || activeDays.length === 0) return;
61
+
62
+ const { normalizationFactor } = builder.costContext;
63
+ for (const member of targetMembers) {
64
+ for (const pattern of builder.shiftPatterns) {
65
+ if (!builder.canAssign(member, pattern)) continue;
66
+ const overlapMinutes = computeOverlapMinutes(
67
+ pattern.startTime,
68
+ pattern.endTime,
69
+ window.from,
70
+ window.until,
71
+ );
72
+ if (overlapMinutes <= 0) continue;
73
+ const surcharge = (amountPerHour * overlapMinutes) / 60;
74
+ const normalizedPenalty = surcharge / normalizationFactor;
75
+
76
+ for (const day of activeDays) {
77
+ if (!builder.patternAvailableOnDay(pattern, day)) continue;
78
+ builder.addPenalty(
79
+ builder.assignment(member.id, pattern.id, day),
80
+ Math.max(1, normalizedPenalty),
81
+ );
82
+ }
83
+ }
84
+ }
85
+ },
86
+ calculateCost(assignments, costContext) {
87
+ if (amountPerHour <= 0) return { entries: [] };
88
+
89
+ const patternMap = new Map(
90
+ costContext.shiftPatterns.map((pattern) => [pattern.id, pattern]),
91
+ );
92
+ const activeDays = new Set(
93
+ resolveActiveDaysFromScope(timeScopeValue, [...costContext.days]).map((day) => day.iso),
94
+ );
95
+ const targetMemberIds = new Set(
96
+ resolveMembersFromScope(entityScopeValue, [...costContext.members]).map(
97
+ (member) => member.id,
98
+ ),
99
+ );
100
+
101
+ const entries = [] as Array<{
102
+ memberId: string;
103
+ day: string;
104
+ category: string;
105
+ amount: number;
106
+ }>;
107
+ for (const assignment of assignments) {
108
+ if (!activeDays.has(assignment.day) || !targetMemberIds.has(assignment.memberId))
109
+ continue;
110
+ const pattern = patternMap.get(assignment.shiftPatternId);
111
+ if (!pattern) continue;
112
+ const overlapMinutes = computeOverlapMinutes(
113
+ pattern.startTime,
114
+ pattern.endTime,
115
+ window.from,
116
+ window.until,
117
+ );
118
+ if (overlapMinutes <= 0) continue;
119
+ entries.push({
120
+ memberId: assignment.memberId,
121
+ day: assignment.day,
122
+ category: COST_CATEGORY.PREMIUM,
123
+ amount: (amountPerHour * overlapMinutes) / 60,
124
+ });
125
+ }
126
+
127
+ return { entries };
128
+ },
129
+ };
130
+
131
+ return {
132
+ rule: "time-cost-surcharge",
133
+ artifacts: [costArtifact],
134
+ };
135
+ },
136
+ });
137
+
41
138
  function computeOverlapMinutes(
42
139
  shiftStart: TimeOfDay,
43
140
  shiftEnd: TimeOfDay,
@@ -49,24 +146,15 @@ function computeOverlapMinutes(
49
146
  const wFrom = timeOfDayToMinutes(windowFrom);
50
147
  const wUntil = timeOfDayToMinutes(windowUntil);
51
148
 
52
- // Expand window: if overnight (e.g., 22:00-06:00), wUntil < wFrom
53
- // Represent as two intervals or one extended interval
54
- let totalOverlap = 0;
55
-
56
149
  if (wFrom < wUntil) {
57
- // Normal window (e.g., 14:00-18:00)
58
- totalOverlap = rangeOverlap(sStart, sEnd, wFrom, wUntil);
59
- } else {
60
- // Overnight window (e.g., 22:00-06:00)
61
- // Split into [wFrom, MINUTES_PER_DAY) and [0, wUntil)
62
- // But shift may also span overnight, so use extended ranges
63
- totalOverlap += rangeOverlap(sStart, sEnd, wFrom, MINUTES_PER_DAY);
64
- totalOverlap += rangeOverlap(sStart, sEnd, MINUTES_PER_DAY, MINUTES_PER_DAY + wUntil);
65
- // Also check if shift overlaps [0, wUntil) in the same day
66
- totalOverlap += rangeOverlap(sStart, sEnd, 0, wUntil);
150
+ return rangeOverlap(sStart, sEnd, wFrom, wUntil);
67
151
  }
68
152
 
69
- return totalOverlap;
153
+ return (
154
+ rangeOverlap(sStart, sEnd, wFrom, MINUTES_PER_DAY) +
155
+ rangeOverlap(sStart, sEnd, MINUTES_PER_DAY, MINUTES_PER_DAY + wUntil) +
156
+ rangeOverlap(sStart, sEnd, 0, wUntil)
157
+ );
70
158
  }
71
159
 
72
160
  function rangeOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): number {
@@ -74,97 +162,3 @@ function rangeOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number
74
162
  const end = Math.min(aEnd, bEnd);
75
163
  return Math.max(0, end - start);
76
164
  }
77
-
78
- /**
79
- * Creates a time-of-day surcharge rule (e.g., night differential).
80
- *
81
- * Adds a flat surcharge per hour for the portion of a shift that overlaps
82
- * a time-of-day window. Independent of the member's base rate.
83
- *
84
- * When `minimizeCost()` is not present, no solver terms are emitted,
85
- * but the `cost()` method still contributes to post-solve calculation.
86
- */
87
- export function createTimeCostSurchargeRule(config: TimeCostSurchargeConfig): CompilationRule {
88
- const parsed = TimeCostSurchargeSchema.parse(config);
89
- const { amountPerHour, window } = parsed;
90
- const entityScopeValue = parseEntityScope(parsed);
91
- const timeScopeValue = parseTimeScope(parsed);
92
-
93
- return {
94
- compile(b) {
95
- if (!b.costContext?.active) return;
96
- if (amountPerHour <= 0) return;
97
-
98
- const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
99
- const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
100
-
101
- if (targetMembers.length === 0 || activeDays.length === 0) return;
102
-
103
- const { normalizationFactor } = b.costContext;
104
-
105
- for (const emp of targetMembers) {
106
- for (const pattern of b.shiftPatterns) {
107
- if (!b.canAssign(emp, pattern)) continue;
108
-
109
- const overlapMinutes = computeOverlapMinutes(
110
- pattern.startTime,
111
- pattern.endTime,
112
- window.from,
113
- window.until,
114
- );
115
- if (overlapMinutes <= 0) continue;
116
-
117
- const surcharge = (amountPerHour * overlapMinutes) / 60;
118
- const normalizedPenalty = surcharge / normalizationFactor;
119
-
120
- for (const day of activeDays) {
121
- if (!b.patternAvailableOnDay(pattern, day)) continue;
122
- b.addPenalty(b.assignment(emp.id, pattern.id, day), Math.max(1, normalizedPenalty));
123
- }
124
- }
125
- }
126
- },
127
-
128
- cost(
129
- assignments: ShiftAssignment[],
130
- members: ReadonlyArray<SchedulingMember>,
131
- shiftPatterns: ReadonlyArray<ShiftPattern>,
132
- ): CostContribution {
133
- if (amountPerHour <= 0) return { entries: [] };
134
-
135
- const patternMap = new Map(shiftPatterns.map((p) => [p.id, p]));
136
- const allDays = [...new Set(assignments.map((a) => a.day))].toSorted();
137
- const activeDays = new Set(resolveActiveDaysFromScope(timeScopeValue, allDays));
138
- const targetEmpIds = new Set(
139
- resolveMembersFromScope(entityScopeValue, [...members]).map((e) => e.id),
140
- );
141
-
142
- const entries: CostContribution["entries"] = [];
143
-
144
- for (const a of assignments) {
145
- if (!activeDays.has(a.day)) continue;
146
- if (!targetEmpIds.has(a.memberId)) continue;
147
- const pattern = patternMap.get(a.shiftPatternId);
148
- if (!pattern) continue;
149
-
150
- const overlapMinutes = computeOverlapMinutes(
151
- pattern.startTime,
152
- pattern.endTime,
153
- window.from,
154
- window.until,
155
- );
156
- if (overlapMinutes <= 0) continue;
157
-
158
- const amount = (amountPerHour * overlapMinutes) / 60;
159
- entries.push({
160
- memberId: a.memberId,
161
- day: a.day,
162
- category: COST_CATEGORY.PREMIUM,
163
- amount,
164
- });
165
- }
166
-
167
- return { entries };
168
- },
169
- };
170
- }