dabke 0.83.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +22 -27
  3. package/dist/client.d.ts +39 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.schemas.d.ts +118 -18
  7. package/dist/client.schemas.d.ts.map +1 -1
  8. package/dist/client.schemas.js +110 -6
  9. package/dist/client.schemas.js.map +1 -1
  10. package/dist/client.types.d.ts +63 -7
  11. package/dist/client.types.d.ts.map +1 -1
  12. package/dist/client.types.js.map +1 -1
  13. package/dist/cpsat/cost.d.ts +5 -2
  14. package/dist/cpsat/cost.d.ts.map +1 -1
  15. package/dist/cpsat/cost.js +13 -6
  16. package/dist/cpsat/cost.js.map +1 -1
  17. package/dist/cpsat/model-builder.d.ts +42 -61
  18. package/dist/cpsat/model-builder.d.ts.map +1 -1
  19. package/dist/cpsat/model-builder.js +451 -56
  20. package/dist/cpsat/model-builder.js.map +1 -1
  21. package/dist/cpsat/response.d.ts +19 -7
  22. package/dist/cpsat/response.d.ts.map +1 -1
  23. package/dist/cpsat/response.js +48 -9
  24. package/dist/cpsat/response.js.map +1 -1
  25. package/dist/cpsat/rule-descriptor.d.ts +250 -0
  26. package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
  27. package/dist/cpsat/rule-descriptor.js +51 -0
  28. package/dist/cpsat/rule-descriptor.js.map +1 -0
  29. package/dist/cpsat/rules/artifacts.d.ts +9 -0
  30. package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
  31. package/dist/cpsat/rules/artifacts.js +31 -0
  32. package/dist/cpsat/rules/artifacts.js.map +1 -0
  33. package/dist/cpsat/rules/assign-together.d.ts +18 -20
  34. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  35. package/dist/cpsat/rules/assign-together.js +90 -54
  36. package/dist/cpsat/rules/assign-together.js.map +1 -1
  37. package/dist/cpsat/rules/assigned-day.d.ts +12 -0
  38. package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
  39. package/dist/cpsat/rules/assigned-day.js +43 -0
  40. package/dist/cpsat/rules/assigned-day.js.map +1 -0
  41. package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
  42. package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
  43. package/dist/cpsat/rules/assignment-priority.js +47 -50
  44. package/dist/cpsat/rules/assignment-priority.js.map +1 -1
  45. package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
  46. package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
  47. package/dist/cpsat/rules/assignment-terms.js +19 -0
  48. package/dist/cpsat/rules/assignment-terms.js.map +1 -0
  49. package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
  50. package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
  51. package/dist/cpsat/rules/concurrent-intervals.js +57 -0
  52. package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
  53. package/dist/cpsat/rules/cost-utils.d.ts +1 -3
  54. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
  55. package/dist/cpsat/rules/cost-utils.js +0 -7
  56. package/dist/cpsat/rules/cost-utils.js.map +1 -1
  57. package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
  58. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  59. package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
  60. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  61. package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
  62. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  63. package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
  64. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  65. package/dist/cpsat/rules/index.d.ts +30 -24
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -24
  68. package/dist/cpsat/rules/index.js.map +1 -1
  69. package/dist/cpsat/rules/location-preference.d.ts +14 -21
  70. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  71. package/dist/cpsat/rules/location-preference.js +45 -45
  72. package/dist/cpsat/rules/location-preference.js.map +1 -1
  73. package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
  74. package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
  75. package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
  76. package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
  77. package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
  78. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  79. package/dist/cpsat/rules/max-consecutive-days.js +67 -66
  80. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  81. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
  82. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
  83. package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
  84. package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
  85. package/dist/cpsat/rules/max-days-week.d.ts +17 -31
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -1
  87. package/dist/cpsat/rules/max-days-week.js +69 -81
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -1
  89. package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
  90. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  91. package/dist/cpsat/rules/max-hours-day.js +56 -72
  92. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  93. package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
  94. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  95. package/dist/cpsat/rules/max-hours-week.js +60 -75
  96. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  97. package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
  98. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  99. package/dist/cpsat/rules/max-shifts-day.js +57 -73
  100. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  101. package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
  102. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  103. package/dist/cpsat/rules/min-consecutive-days.js +123 -94
  104. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  105. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
  106. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
  107. package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
  108. package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
  109. package/dist/cpsat/rules/min-days-week.d.ts +13 -26
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -1
  111. package/dist/cpsat/rules/min-days-week.js +85 -74
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -1
  113. package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
  114. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  115. package/dist/cpsat/rules/min-hours-day.js +72 -54
  116. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  117. package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
  118. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  119. package/dist/cpsat/rules/min-hours-week.js +75 -57
  120. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  121. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
  122. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  123. package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
  124. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  125. package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
  126. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  127. package/dist/cpsat/rules/minimize-cost.js +151 -163
  128. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  129. package/dist/cpsat/rules/must-assign.d.ts +5 -42
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -1
  131. package/dist/cpsat/rules/must-assign.js +43 -75
  132. package/dist/cpsat/rules/must-assign.js.map +1 -1
  133. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
  134. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  135. package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
  136. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  137. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
  138. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  139. package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
  140. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  141. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
  142. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  143. package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
  144. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  145. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
  146. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  147. package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
  148. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  149. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
  150. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  151. package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
  152. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  153. package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
  154. package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
  155. package/dist/cpsat/rules/pattern-eligibility.js +16 -0
  156. package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
  157. package/dist/cpsat/rules/pattern-time.d.ts +5 -0
  158. package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
  159. package/dist/cpsat/rules/pattern-time.js +20 -0
  160. package/dist/cpsat/rules/pattern-time.js.map +1 -0
  161. package/dist/cpsat/rules/registry.d.ts +217 -30
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -34
  164. package/dist/cpsat/rules/registry.js.map +1 -1
  165. package/dist/cpsat/rules/resolver.d.ts +9 -7
  166. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  167. package/dist/cpsat/rules/resolver.js +19 -18
  168. package/dist/cpsat/rules/resolver.js.map +1 -1
  169. package/dist/cpsat/rules/role-preference.d.ts +24 -0
  170. package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
  171. package/dist/cpsat/rules/role-preference.js +72 -0
  172. package/dist/cpsat/rules/role-preference.js.map +1 -0
  173. package/dist/cpsat/rules/rules.types.d.ts +90 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +24 -11
  176. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  177. package/dist/cpsat/rules/scope.types.js +14 -19
  178. package/dist/cpsat/rules/scope.types.js.map +1 -1
  179. package/dist/cpsat/rules/target-days-week.d.ts +31 -0
  180. package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
  181. package/dist/cpsat/rules/target-days-week.js +80 -0
  182. package/dist/cpsat/rules/target-days-week.js.map +1 -0
  183. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
  184. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
  185. package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
  186. package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
  187. package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
  188. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  189. package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
  190. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  191. package/dist/cpsat/rules/time-off.d.ts +21 -57
  192. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  193. package/dist/cpsat/rules/time-off.js +114 -127
  194. package/dist/cpsat/rules/time-off.js.map +1 -1
  195. package/dist/cpsat/rules/variables.d.ts +4 -0
  196. package/dist/cpsat/rules/variables.d.ts.map +1 -0
  197. package/dist/cpsat/rules/variables.js +10 -0
  198. package/dist/cpsat/rules/variables.js.map +1 -0
  199. package/dist/cpsat/rules.d.ts +20 -3
  200. package/dist/cpsat/rules.d.ts.map +1 -1
  201. package/dist/cpsat/rules.js +19 -2
  202. package/dist/cpsat/rules.js.map +1 -1
  203. package/dist/cpsat/semantic-time.d.ts +7 -6
  204. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  205. package/dist/cpsat/semantic-time.js +15 -28
  206. package/dist/cpsat/semantic-time.js.map +1 -1
  207. package/dist/cpsat/types.d.ts +47 -4
  208. package/dist/cpsat/types.d.ts.map +1 -1
  209. package/dist/cpsat/types.js +24 -1
  210. package/dist/cpsat/types.js.map +1 -1
  211. package/dist/cpsat/utils.d.ts +23 -6
  212. package/dist/cpsat/utils.d.ts.map +1 -1
  213. package/dist/cpsat/utils.js +76 -15
  214. package/dist/cpsat/utils.js.map +1 -1
  215. package/dist/cpsat/validation-reporter.d.ts +6 -2
  216. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  217. package/dist/cpsat/validation-reporter.js +122 -10
  218. package/dist/cpsat/validation-reporter.js.map +1 -1
  219. package/dist/cpsat/validation.types.d.ts +21 -10
  220. package/dist/cpsat/validation.types.d.ts.map +1 -1
  221. package/dist/datetime.utils.d.ts +7 -13
  222. package/dist/datetime.utils.d.ts.map +1 -1
  223. package/dist/datetime.utils.js +29 -36
  224. package/dist/datetime.utils.js.map +1 -1
  225. package/dist/index.d.ts +12 -8
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +8 -3
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +48 -60
  230. package/dist/schedule/cost.d.ts.map +1 -1
  231. package/dist/schedule/cost.js +15 -51
  232. package/dist/schedule/cost.js.map +1 -1
  233. package/dist/schedule/coverage.d.ts +57 -28
  234. package/dist/schedule/coverage.d.ts.map +1 -1
  235. package/dist/schedule/coverage.js +24 -1
  236. package/dist/schedule/coverage.js.map +1 -1
  237. package/dist/schedule/definition.d.ts +138 -19
  238. package/dist/schedule/definition.d.ts.map +1 -1
  239. package/dist/schedule/definition.js +99 -90
  240. package/dist/schedule/definition.js.map +1 -1
  241. package/dist/schedule/index.d.ts +4 -4
  242. package/dist/schedule/index.d.ts.map +1 -1
  243. package/dist/schedule/index.js +2 -2
  244. package/dist/schedule/index.js.map +1 -1
  245. package/dist/schedule/rules.d.ts +438 -113
  246. package/dist/schedule/rules.d.ts.map +1 -1
  247. package/dist/schedule/rules.js +310 -108
  248. package/dist/schedule/rules.js.map +1 -1
  249. package/dist/schedule/shift-patterns.d.ts +0 -17
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -1
  251. package/dist/schedule/shift-patterns.js +0 -17
  252. package/dist/schedule/shift-patterns.js.map +1 -1
  253. package/dist/schedule/time-periods.d.ts +37 -27
  254. package/dist/schedule/time-periods.d.ts.map +1 -1
  255. package/dist/schedule/time-periods.js +37 -27
  256. package/dist/schedule/time-periods.js.map +1 -1
  257. package/dist/types.d.ts +67 -3
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +64 -0
  260. package/dist/types.js.map +1 -1
  261. package/package.json +8 -9
  262. package/solver/src/solver/models.py +41 -4
  263. package/solver/src/solver/solver.py +546 -157
  264. package/solver/tests/test_solver.py +363 -7
  265. package/src/client.schemas.ts +127 -10
  266. package/src/client.ts +3 -0
  267. package/src/client.types.ts +73 -7
  268. package/src/cpsat/cost.ts +17 -7
  269. package/src/cpsat/model-builder.ts +614 -129
  270. package/src/cpsat/response.ts +57 -13
  271. package/src/cpsat/rule-descriptor.ts +311 -0
  272. package/src/cpsat/rules/artifacts.ts +56 -0
  273. package/src/cpsat/rules/assign-together.ts +104 -69
  274. package/src/cpsat/rules/assigned-day.ts +70 -0
  275. package/src/cpsat/rules/assignment-priority.ts +54 -58
  276. package/src/cpsat/rules/assignment-terms.ts +43 -0
  277. package/src/cpsat/rules/concurrent-intervals.ts +93 -0
  278. package/src/cpsat/rules/cost-utils.ts +1 -9
  279. package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
  280. package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
  281. package/src/cpsat/rules/index.ts +30 -24
  282. package/src/cpsat/rules/location-preference.ts +47 -52
  283. package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
  284. package/src/cpsat/rules/max-consecutive-days.ts +74 -91
  285. package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
  286. package/src/cpsat/rules/max-days-week.ts +89 -115
  287. package/src/cpsat/rules/max-hours-day.ts +64 -84
  288. package/src/cpsat/rules/max-hours-week.ts +74 -90
  289. package/src/cpsat/rules/max-shifts-day.ts +65 -85
  290. package/src/cpsat/rules/min-consecutive-days.ts +161 -127
  291. package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
  292. package/src/cpsat/rules/min-days-week.ts +93 -91
  293. package/src/cpsat/rules/min-hours-day.ts +79 -58
  294. package/src/cpsat/rules/min-hours-week.ts +87 -62
  295. package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
  296. package/src/cpsat/rules/minimize-cost.ts +175 -188
  297. package/src/cpsat/rules/must-assign.ts +44 -81
  298. package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
  299. package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
  300. package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
  301. package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
  302. package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
  303. package/src/cpsat/rules/pattern-eligibility.ts +26 -0
  304. package/src/cpsat/rules/pattern-time.ts +30 -0
  305. package/src/cpsat/rules/registry.ts +140 -62
  306. package/src/cpsat/rules/resolver.ts +58 -30
  307. package/src/cpsat/rules/role-preference.ts +92 -0
  308. package/src/cpsat/rules/rules.types.ts +89 -17
  309. package/src/cpsat/rules/scope.types.ts +31 -26
  310. package/src/cpsat/rules/target-days-week.ts +105 -0
  311. package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
  312. package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
  313. package/src/cpsat/rules/time-off.ts +154 -163
  314. package/src/cpsat/rules/variables.ts +11 -0
  315. package/src/cpsat/rules.ts +43 -17
  316. package/src/cpsat/semantic-time.ts +29 -43
  317. package/src/cpsat/types.ts +53 -4
  318. package/src/cpsat/utils.ts +92 -20
  319. package/src/cpsat/validation-reporter.ts +170 -19
  320. package/src/cpsat/validation.types.ts +22 -11
  321. package/src/datetime.utils.ts +45 -44
  322. package/src/index.ts +64 -12
  323. package/src/schedule/cost.ts +40 -61
  324. package/src/schedule/coverage.ts +57 -28
  325. package/src/schedule/definition.ts +565 -76
  326. package/src/schedule/index.ts +23 -3
  327. package/src/schedule/rules.ts +631 -132
  328. package/src/schedule/shift-patterns.ts +0 -17
  329. package/src/schedule/time-periods.ts +37 -27
  330. package/src/types.ts +114 -2
@@ -1,223 +1,210 @@
1
1
  import type * as z from "zod";
2
- import type { DayOfWeek } from "../../types.js";
3
- import type { CompilationRule, CostContribution } from "../model-builder.js";
4
- import type { ShiftPattern, SchedulingMember } from "../types.js";
5
- import type { ShiftAssignment } from "../response.js";
2
+ import type { DateString, DayOfWeek } from "../../types.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 { OBJECTIVE_WEIGHTS, splitIntoWeeks } 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
- import { getHourlyRate, getSalariedPay, patternDurationMinutes } from "./cost-utils.js";
15
+ import { getHourlyRate, getSalariedPay } from "./cost-utils.js";
16
+ import { patternDurationMinutes } from "./pattern-time.js";
17
17
 
18
18
  const MinimizeCostSchema = entityScope(["members", "roles", "skills"]).and(
19
19
  timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]),
20
20
  );
21
21
 
22
- /** Configuration for {@link createMinimizeCostRule}. */
22
+ /** Configuration for {@link minimizeCostRuleDescriptor}. */
23
23
  export type MinimizeCostConfig = z.infer<typeof MinimizeCostSchema>;
24
24
 
25
- /**
26
- * Creates the minimize-cost rule.
27
- *
28
- * **Hourly members:** adds a penalty proportional to
29
- * `hourlyRate * shiftDurationMinutes / 60` for each assignment.
30
- *
31
- * **Salaried members:** zero marginal cost up to their contracted hours.
32
- * A boolean variable tracks whether the member has any assignment in a
33
- * given week. When active, their full weekly salary (`annual / 52`) is
34
- * added as a penalty. This causes the solver to load work onto salaried
35
- * members (who are "free" once activated) before hourly workers.
36
- *
37
- * Computes a normalization factor and stores it on the model builder's
38
- * `costContext` so modifier rules can use the same scale.
39
- */
40
- export function createMinimizeCostRule(config: MinimizeCostConfig): CompilationRule {
41
- const parsed = MinimizeCostSchema.parse(config);
42
- const entityScopeValue = parseEntityScope(parsed);
43
- const timeScopeValue = parseTimeScope(parsed);
44
-
45
- let resolvedWeekStartsOn: DayOfWeek = "monday";
46
-
47
- return {
48
- compile(b) {
49
- resolvedWeekStartsOn = b.weekStartsOn;
50
-
51
- const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
52
- const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
53
-
54
- if (targetMembers.length === 0 || activeDays.length === 0) return;
55
-
56
- // Compute normalization factor: max raw cost of any single assignment
57
- // (hourly) or weekly salary (salaried)
58
- let maxRawCost = 0;
59
- for (const member of targetMembers) {
60
- const rate = getHourlyRate(member);
61
- if (rate !== undefined) {
62
- for (const pattern of b.shiftPatterns) {
63
- if (!b.canAssign(member, pattern)) continue;
64
- const duration = patternDurationMinutes(pattern);
65
- const rawCost = (rate * duration) / 60;
66
- if (rawCost > maxRawCost) maxRawCost = rawCost;
67
- }
68
- }
69
- const salaried = getSalariedPay(member);
70
- if (salaried !== undefined) {
71
- const weeklyCost = salaried.annual / 52;
72
- if (weeklyCost > maxRawCost) maxRawCost = weeklyCost;
73
- }
74
- }
75
-
76
- if (maxRawCost === 0) return;
77
-
78
- const normalizationFactor = maxRawCost / OBJECTIVE_WEIGHTS.COST;
79
-
80
- b.costContext = {
81
- normalizationFactor,
82
- active: true,
83
- };
84
-
85
- // Add penalty terms for hourly members
86
- for (const member of targetMembers) {
87
- const rate = getHourlyRate(member);
88
- if (rate !== undefined) {
89
- for (const pattern of b.shiftPatterns) {
90
- if (!b.canAssign(member, pattern)) continue;
91
- for (const day of activeDays) {
92
- if (!b.patternAvailableOnDay(pattern, day)) continue;
25
+ export const minimizeCostRuleDescriptor = defineRuleDescriptor({
26
+ name: "minimize-cost",
27
+ schema: MinimizeCostSchema,
28
+ compile(config) {
29
+ const entityScopeValue = parseEntityScope(config);
30
+ const timeScopeValue = parseTimeScope(config);
31
+ let resolvedWeekStartsOn: DayOfWeek = "monday";
32
+
33
+ const costArtifact: CostArtifact = {
34
+ kind: "cost",
35
+ validation: {
36
+ strategy: "skip",
37
+ category: "no-meaningful-feedback",
38
+ rationale:
39
+ "Cost optimization influences the objective and contributes post-solve accounting, not validation feedback.",
40
+ },
41
+ compileObjective(builder) {
42
+ resolvedWeekStartsOn = builder.weekStartsOn;
43
+
44
+ const targetMembers = resolveMembersFromScope(entityScopeValue, builder.members);
45
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, builder.days);
46
+ if (targetMembers.length === 0 || activeDays.length === 0) return;
47
+
48
+ let maxRawCost = 0;
49
+ for (const member of targetMembers) {
50
+ const rate = getHourlyRate(member);
51
+ if (rate !== undefined) {
52
+ for (const pattern of builder.shiftPatterns) {
53
+ if (!builder.canAssign(member, pattern)) continue;
93
54
  const duration = patternDurationMinutes(pattern);
94
55
  const rawCost = (rate * duration) / 60;
95
- const normalizedPenalty = rawCost / normalizationFactor;
96
- b.addPenalty(
97
- b.assignment(member.id, pattern.id, day),
98
- Math.max(1, normalizedPenalty),
99
- );
56
+ if (rawCost > maxRawCost) maxRawCost = rawCost;
100
57
  }
101
58
  }
102
- continue;
59
+ const salaried = getSalariedPay(member);
60
+ if (salaried !== undefined) {
61
+ const weeklyCost = salaried.annual / 52;
62
+ if (weeklyCost > maxRawCost) maxRawCost = weeklyCost;
63
+ }
103
64
  }
104
65
 
105
- // Salaried members: weekly fixed cost when active
106
- const salaried = getSalariedPay(member);
107
- if (salaried === undefined) continue;
108
-
109
- const weeklyCost = salaried.annual / 52;
110
- const normalizedWeeklyCost = weeklyCost / normalizationFactor;
111
-
112
- const weeks = splitIntoWeeks(activeDays, b.weekStartsOn);
113
-
114
- for (const [weekIdx, weekDays] of weeks.entries()) {
115
- // Collect all assignment vars for this member this week
116
- const weekAssignmentVars: string[] = [];
117
- for (const day of weekDays) {
118
- for (const pattern of b.shiftPatterns) {
119
- if (!b.canAssign(member, pattern)) continue;
120
- if (!b.patternAvailableOnDay(pattern, day)) continue;
121
- weekAssignmentVars.push(b.assignment(member.id, pattern.id, day));
66
+ if (maxRawCost === 0) return;
67
+
68
+ const normalizationFactor = maxRawCost / OBJECTIVE_WEIGHTS.COST;
69
+ builder.costContext = {
70
+ normalizationFactor,
71
+ active: true,
72
+ };
73
+
74
+ for (const member of targetMembers) {
75
+ const rate = getHourlyRate(member);
76
+ if (rate !== undefined) {
77
+ for (const pattern of builder.shiftPatterns) {
78
+ if (!builder.canAssign(member, pattern)) continue;
79
+ for (const day of activeDays) {
80
+ if (!builder.patternAvailableOnDay(pattern, day)) continue;
81
+ const duration = patternDurationMinutes(pattern);
82
+ const rawCost = (rate * duration) / 60;
83
+ const normalizedPenalty = rawCost / normalizationFactor;
84
+ builder.addPenalty(
85
+ builder.assignment(member.id, pattern.id, day),
86
+ Math.max(1, normalizedPenalty),
87
+ );
88
+ }
122
89
  }
90
+ continue;
123
91
  }
124
92
 
125
- if (weekAssignmentVars.length === 0) continue;
126
-
127
- // Bool var: true if this salaried member works any shift this week
128
- const activeVar = b.boolVar(`active:cost:${member.id}:w${weekIdx}`);
129
-
130
- // If any assignment is true, activeVar must be true
131
- // activeVar OR NOT(assignment_1) OR NOT(assignment_2) OR ...
132
- // This is equivalent to: for each assignment, assignment => activeVar
133
- for (const assignVar of weekAssignmentVars) {
134
- b.addImplication(assignVar, activeVar);
135
- }
93
+ const salaried = getSalariedPay(member);
94
+ if (salaried === undefined) continue;
136
95
 
137
- b.addPenalty(activeVar, Math.max(1, Math.round(normalizedWeeklyCost)));
138
- }
139
- }
140
- },
141
-
142
- cost(
143
- assignments: ShiftAssignment[],
144
- members: ReadonlyArray<SchedulingMember>,
145
- shiftPatterns: ReadonlyArray<ShiftPattern>,
146
- ): CostContribution {
147
- const memberMap = new Map(members.map((m) => [m.id, m]));
148
- const patternMap = new Map(shiftPatterns.map((p) => [p.id, p]));
149
-
150
- const allDays = [...new Set(assignments.map((a) => a.day))].toSorted();
151
- const activeDays = new Set(resolveActiveDaysFromScope(timeScopeValue, allDays));
152
- const targetMemberIds = new Set(
153
- resolveMembersFromScope(entityScopeValue, [...members]).map((m) => m.id),
154
- );
155
-
156
- const entries: CostContribution["entries"] = [];
157
-
158
- // Track which salaried members are active per week for weekly cost
159
- const salariedWeekActive = new Map<string, Set<string>>();
160
-
161
- for (const a of assignments) {
162
- if (!activeDays.has(a.day)) continue;
163
- if (!targetMemberIds.has(a.memberId)) continue;
164
- const member = memberMap.get(a.memberId);
165
- if (!member) continue;
166
-
167
- const pattern = patternMap.get(a.shiftPatternId);
168
- if (!pattern) continue;
169
- const duration = patternDurationMinutes(pattern);
170
-
171
- const rate = getHourlyRate(member);
172
- if (rate !== undefined) {
173
- entries.push({
174
- memberId: a.memberId,
175
- day: a.day,
176
- category: COST_CATEGORY.BASE,
177
- amount: (rate * duration) / 60,
178
- });
179
- continue;
180
- }
96
+ const weeklyCost = salaried.annual / 52;
97
+ const normalizedWeeklyCost = weeklyCost / normalizationFactor;
98
+ const weeks = splitIntoWeeks(activeDays, builder.weekStartsOn);
99
+
100
+ for (const [weekIndex, weekDays] of weeks.entries()) {
101
+ const weekAssignmentVars: string[] = [];
102
+ for (const day of weekDays) {
103
+ for (const pattern of builder.shiftPatterns) {
104
+ if (!builder.canAssign(member, pattern)) continue;
105
+ if (!builder.patternAvailableOnDay(pattern, day)) continue;
106
+ weekAssignmentVars.push(builder.assignment(member.id, pattern.id, day));
107
+ }
108
+ }
181
109
 
182
- // Salaried: track weeks with assignments
183
- const salaried = getSalariedPay(member);
184
- if (salaried === undefined) continue;
110
+ if (weekAssignmentVars.length === 0) continue;
111
+
112
+ const activeVar = builder.boolVar(`active:cost:${member.id}:w${weekIndex}`);
113
+ for (const assignVar of weekAssignmentVars) {
114
+ builder.addLinear(
115
+ [
116
+ { var: activeVar, coeff: 1 },
117
+ { var: assignVar, coeff: -1 },
118
+ ],
119
+ ">=",
120
+ 0,
121
+ );
122
+ }
185
123
 
186
- let days = salariedWeekActive.get(a.memberId);
187
- if (!days) {
188
- days = new Set();
189
- salariedWeekActive.set(a.memberId, days);
124
+ builder.addPenalty(activeVar, Math.max(1, Math.round(normalizedWeeklyCost)));
125
+ }
190
126
  }
191
- days.add(a.day);
192
- }
193
-
194
- // Compute salaried costs: weekly salary distributed across active days
195
- for (const [memberId, activeDaysSet] of salariedWeekActive) {
196
- const member = memberMap.get(memberId);
197
- if (!member) continue;
198
- const salaried = getSalariedPay(member);
199
- if (!salaried) continue;
200
-
201
- const activeList = [...activeDaysSet].toSorted();
202
- const weeks = splitIntoWeeks(activeList, resolvedWeekStartsOn);
203
-
204
- for (const weekDays of weeks) {
205
- if (weekDays.length === 0) continue;
206
- const weeklyCost = salaried.annual / 52;
207
- // Distribute evenly across days worked that week
208
- const perDay = weeklyCost / weekDays.length;
209
- for (const day of weekDays) {
127
+ },
128
+ calculateCost(assignments, costContext) {
129
+ const memberMap = new Map(costContext.members.map((member) => [member.id, member]));
130
+ const patternMap = new Map(
131
+ costContext.shiftPatterns.map((pattern) => [pattern.id, pattern]),
132
+ );
133
+ const activeDays = new Set(
134
+ resolveActiveDaysFromScope(timeScopeValue, [...costContext.days]).map((day) => day.iso),
135
+ );
136
+ const targetMemberIds = new Set(
137
+ resolveMembersFromScope(entityScopeValue, [...costContext.members]).map(
138
+ (member) => member.id,
139
+ ),
140
+ );
141
+
142
+ const entries: Array<{ memberId: string; day: string; category: string; amount: number }> =
143
+ [];
144
+ const salariedWeekActive = new Map<string, Set<DateString>>();
145
+
146
+ for (const assignment of assignments) {
147
+ if (!activeDays.has(assignment.day) || !targetMemberIds.has(assignment.memberId))
148
+ continue;
149
+ const member = memberMap.get(assignment.memberId);
150
+ if (!member) continue;
151
+ const pattern = patternMap.get(assignment.shiftPatternId);
152
+ if (!pattern) continue;
153
+ const duration = patternDurationMinutes(pattern);
154
+
155
+ const rate = getHourlyRate(member);
156
+ if (rate !== undefined) {
210
157
  entries.push({
211
- memberId,
212
- day,
158
+ memberId: assignment.memberId,
159
+ day: assignment.day,
213
160
  category: COST_CATEGORY.BASE,
214
- amount: perDay,
161
+ amount: (rate * duration) / 60,
215
162
  });
163
+ continue;
216
164
  }
165
+
166
+ const salaried = getSalariedPay(member);
167
+ if (salaried === undefined) continue;
168
+ let memberDays = salariedWeekActive.get(assignment.memberId);
169
+ if (!memberDays) {
170
+ memberDays = new Set();
171
+ salariedWeekActive.set(assignment.memberId, memberDays);
172
+ }
173
+ memberDays.add(assignment.day);
217
174
  }
218
- }
219
175
 
220
- return { entries };
221
- },
222
- };
223
- }
176
+ for (const [memberId, activeDaysSet] of salariedWeekActive) {
177
+ const member = memberMap.get(memberId);
178
+ if (!member) continue;
179
+ const salaried = getSalariedPay(member);
180
+ if (!salaried) continue;
181
+
182
+ const activeDateSet = new Set(activeDaysSet);
183
+ const activeList = costContext.days.filter((day) => activeDateSet.has(day.iso));
184
+ const weeks = splitIntoWeeks(activeList, resolvedWeekStartsOn);
185
+
186
+ for (const weekDays of weeks) {
187
+ if (weekDays.length === 0) continue;
188
+ const weeklyCost = salaried.annual / 52;
189
+ const perDay = weeklyCost / weekDays.length;
190
+ for (const day of weekDays) {
191
+ entries.push({
192
+ memberId,
193
+ day: day.iso,
194
+ category: COST_CATEGORY.BASE,
195
+ amount: perDay,
196
+ });
197
+ }
198
+ }
199
+ }
200
+
201
+ return { entries };
202
+ },
203
+ };
204
+
205
+ return {
206
+ rule: "minimize-cost",
207
+ artifacts: [costArtifact],
208
+ };
209
+ },
210
+ });
@@ -1,7 +1,6 @@
1
1
  import * as z from "zod";
2
2
  import { DayOfWeekSchema } from "../../types.js";
3
- import type { CompilationRule } from "../model-builder.js";
4
- import type { Term } from "../types.js";
3
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
5
4
  import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
6
5
  import {
7
6
  entityScope,
@@ -9,100 +8,64 @@ import {
9
8
  resolveMembersFromScope,
10
9
  ruleGroup,
11
10
  } from "./scope.types.js";
11
+ import { assignmentTermsForDays } from "./assignment-terms.js";
12
+ import { reportValidation, softConstraint } from "./artifacts.js";
12
13
 
13
14
  /** Internally always HIGH. Not user-configurable. */
14
15
  const MUST_ASSIGN_PENALTY = priorityToPenalty("HIGH");
15
16
 
16
- const MustAssignSchema = z
17
+ export const MustAssignSchema = z
17
18
  .object({
18
19
  weekStartsOn: DayOfWeekSchema.optional(),
19
20
  })
20
21
  .and(entityScope(["members", "roles", "skills"]));
21
22
 
22
23
  /**
23
- * Configuration for {@link createMustAssignRule}.
24
- *
25
- * - `weekStartsOn` (optional): which day starts the week; defaults to
26
- * {@link ModelBuilder.weekStartsOn}
27
- *
28
- * Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
24
+ * Configuration for {@link mustAssignRuleDescriptor}.
29
25
  */
30
26
  export type MustAssignConfig = z.infer<typeof MustAssignSchema>;
31
27
 
32
- /**
33
- * Guarantees that targeted members appear on the schedule.
34
- *
35
- * @remarks
36
- * Use this for staffing obligations: salaried employees who are paid
37
- * regardless of whether they work, or contracted staff who must be
38
- * rostered. The solver ensures each targeted member has at least one
39
- * assignment per scheduling week.
40
- *
41
- * Always compiles as a soft constraint (HIGH priority internally) so the
42
- * schedule still generates when a member genuinely cannot be placed
43
- * (e.g., full week of absences, no compatible shift patterns). Violations
44
- * surface as validation warnings with distinct messaging from
45
- * `min-days-week`.
46
- *
47
- * Unlike {@link createMinDaysWeekRule}, this rule communicates a staffing
48
- * obligation rather than a scheduling preference. Validation messages
49
- * reflect this: "Alice was not assigned (staffing obligation)" rather
50
- * than "Alice worked 0 days, minimum was 1."
51
- *
52
- * @param config - See {@link MustAssignConfig}
53
- *
54
- * @example Ensure a salaried manager is always rostered
55
- * ```ts
56
- * createMustAssignRule({ memberIds: ["diana"] });
57
- * ```
58
- *
59
- * @example Ensure all supervisors appear on the rota
60
- * ```ts
61
- * createMustAssignRule({ roleIds: ["supervisor"] });
62
- * ```
63
- */
64
- export function createMustAssignRule(config: MustAssignConfig): CompilationRule {
65
- const parsed = MustAssignSchema.parse(config);
66
- const scope = parseEntityScope(parsed);
67
- const { weekStartsOn } = parsed;
68
- const group = ruleGroup("must-assign", "Must assign", scope);
69
-
70
- return {
71
- compile(b) {
72
- const members = resolveMembersFromScope(scope, b.members);
73
- const weeks = splitIntoWeeks(b.days, weekStartsOn ?? b.weekStartsOn);
28
+ export const mustAssignRuleDescriptor = defineRuleDescriptor({
29
+ name: "must-assign",
30
+ schema: MustAssignSchema,
31
+ compile(config, ctx) {
32
+ const scope = parseEntityScope(config);
33
+ const { weekStartsOn } = config;
34
+ const group = ruleGroup("must-assign", "Must assign", scope);
35
+ const members = resolveMembersFromScope(scope, [...ctx.members]);
36
+ const weeks = splitIntoWeeks([...ctx.days], weekStartsOn ?? ctx.weekStartsOn);
74
37
 
75
- for (const emp of members) {
76
- for (const weekDays of weeks) {
77
- const terms: Term[] = [];
38
+ const artifacts = members.flatMap((member) =>
39
+ weeks.flatMap((weekDays) => {
40
+ const weekStart = weekDays[0];
41
+ if (!weekStart) return [];
78
42
 
79
- for (const day of weekDays) {
80
- for (const pattern of b.shiftPatterns) {
81
- if (!b.canAssign(emp, pattern)) continue;
82
- if (!b.patternAvailableOnDay(pattern, day)) continue;
83
- terms.push({ var: b.assignment(emp.id, pattern.id, day), coeff: 1 });
84
- }
85
- }
43
+ const terms = assignmentTermsForDays(member, weekDays, ctx.shiftPatterns, () => 1);
44
+ if (terms.length === 0) return [];
86
45
 
87
- if (terms.length === 0) continue;
46
+ const description = `${member.id} not assigned in week starting ${weekStart.iso} (staffing obligation)`;
47
+ const context = { memberIds: [member.id], days: weekDays.map((day) => day.iso) };
48
+ const constraintId = `must-assign:${member.id}:${weekStart.iso}`;
88
49
 
89
- const weekLabel = weekDays[0]!;
90
- const constraintId = `must-assign:${emp.id}:${weekLabel}`;
91
-
92
- b.addSoftLinear(terms, ">=", 1, MUST_ASSIGN_PENALTY, constraintId);
93
- b.reporter.trackConstraint({
94
- id: constraintId,
95
- type: "rule",
96
- rule: "must-assign",
97
- description: `${emp.id} not assigned in week starting ${weekLabel} (staffing obligation)`,
98
- targetValue: 1,
99
- comparator: ">=",
100
- day: weekLabel,
101
- context: { memberIds: [emp.id], days: weekDays },
50
+ return [
51
+ softConstraint({
102
52
  group,
103
- });
104
- }
105
- }
106
- },
107
- };
108
- }
53
+ description,
54
+ context,
55
+ validation: reportValidation(),
56
+ terms,
57
+ comparator: ">=",
58
+ targetValue: 1,
59
+ penalty: MUST_ASSIGN_PENALTY,
60
+ constraintId,
61
+ }),
62
+ ];
63
+ }),
64
+ );
65
+
66
+ return {
67
+ rule: "must-assign",
68
+ artifacts,
69
+ };
70
+ },
71
+ });