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
@@ -0,0 +1,70 @@
1
+ import type { SchedulingDay } from "../../types.js";
2
+ import type { RuleArtifact } from "../rule-descriptor.js";
3
+ import { boolVariable, hardConstraint, skipValidation } from "./artifacts.js";
4
+ import { assignedDayVariableName } from "./variables.js";
5
+
6
+ export function buildAssignedDayIndicator(params: {
7
+ memberId: string;
8
+ day: SchedulingDay;
9
+ assignmentVars: readonly string[];
10
+ variableName?: string;
11
+ }): { assignedDayVar: string; artifacts: readonly RuleArtifact[] } {
12
+ const assignedDayVar =
13
+ params.variableName ?? assignedDayVariableName(params.memberId, params.day.iso);
14
+ const artifacts: RuleArtifact[] = [boolVariable(assignedDayVar)];
15
+
16
+ if (params.assignmentVars.length === 0) {
17
+ artifacts.push(
18
+ hardConstraint({
19
+ description: `${assignedDayVar} fixed to 0 on ${params.day.iso}`,
20
+ validation: skipValidation(
21
+ "scaffolding",
22
+ "No assignments exist for this member and day, so the derived assigned-day variable is fixed to zero.",
23
+ ),
24
+ context: { memberIds: [params.memberId], days: [params.day.iso] },
25
+ terms: [{ var: assignedDayVar, coeff: 1 }],
26
+ comparator: "==",
27
+ targetValue: 0,
28
+ }),
29
+ );
30
+ return { assignedDayVar, artifacts };
31
+ }
32
+
33
+ for (const assignVar of params.assignmentVars) {
34
+ artifacts.push(
35
+ hardConstraint({
36
+ description: `${assignedDayVar} covers ${assignVar}`,
37
+ validation: skipValidation(
38
+ "scaffolding",
39
+ "This derived assigned-day variable must turn on whenever one of the day's assignment variables is on.",
40
+ ),
41
+ context: { memberIds: [params.memberId], days: [params.day.iso] },
42
+ terms: [
43
+ { var: assignedDayVar, coeff: 1 },
44
+ { var: assignVar, coeff: -1 },
45
+ ],
46
+ comparator: ">=",
47
+ targetValue: 0,
48
+ }),
49
+ );
50
+ }
51
+
52
+ artifacts.push(
53
+ hardConstraint({
54
+ description: `${assignedDayVar} limited by assignments on ${params.day.iso}`,
55
+ validation: skipValidation(
56
+ "scaffolding",
57
+ "This derived assigned-day variable must stay off unless at least one assignment variable is on.",
58
+ ),
59
+ context: { memberIds: [params.memberId], days: [params.day.iso] },
60
+ terms: [
61
+ { var: assignedDayVar, coeff: 1 },
62
+ ...params.assignmentVars.map((varName) => ({ var: varName, coeff: -1 })),
63
+ ],
64
+ comparator: "<=",
65
+ targetValue: 0,
66
+ }),
67
+ );
68
+
69
+ return { assignedDayVar, artifacts };
70
+ }
@@ -1,80 +1,76 @@
1
1
  import * as z from "zod";
2
- import type { CompilationRule } from "../model-builder.js";
3
- import { OBJECTIVE_WEIGHTS } from "../utils.js";
2
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
3
+ import { PREFERENCE_WEIGHTS } from "../utils.js";
4
4
  import {
5
+ PrioritySchema,
5
6
  entityScope,
6
- timeScope,
7
7
  parseEntityScope,
8
8
  parseTimeScope,
9
- resolveMembersFromScope,
10
9
  resolveActiveDaysFromScope,
10
+ resolveMembersFromScope,
11
+ timeScope,
11
12
  } from "./scope.types.js";
13
+ import { skipValidation } from "./artifacts.js";
14
+ import { assignmentVar } from "./variables.js";
15
+ import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
12
16
 
13
- const AssignmentPrioritySchema = z
17
+ export const AssignmentPrioritySchema = z
14
18
  .object({
15
- preference: z.union([z.literal("high"), z.literal("low")]),
19
+ preference: z.union([z.literal("prefer"), z.literal("avoid")]),
20
+ priority: PrioritySchema.optional(),
16
21
  })
17
22
  .and(entityScope(["members", "roles", "skills"]))
18
23
  .and(timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]));
19
24
 
20
25
  /**
21
- * Configuration for {@link createAssignmentPriorityRule}.
22
- *
23
- * - `preference` (required): `"high"` to prefer assigning or `"low"` to avoid assigning
24
- *
25
- * Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
26
- * Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
26
+ * Configuration for {@link assignmentPriorityRuleDescriptor}.
27
27
  */
28
28
  export type AssignmentPriorityConfig = z.infer<typeof AssignmentPrioritySchema>;
29
29
 
30
30
  /**
31
- * Adds objective weight to prefer or avoid assigning team members.
32
- *
33
- * @param config - See {@link AssignmentPriorityConfig}
34
- * @example Prefer specific team members
35
- * ```ts
36
- * createAssignmentPriorityRule({
37
- * memberIds: ["alice", "bob"],
38
- * preference: "high",
39
- * });
40
- * ```
31
+ * Low-level descriptor for assignment priority rules.
41
32
  *
42
- * @example Avoid assigning students on weekdays
43
- * ```ts
44
- * createAssignmentPriorityRule({
45
- * roleIds: ["student"],
46
- * dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"],
47
- * preference: "low",
48
- * });
49
- * ```
33
+ * @category Rules
50
34
  */
51
- export function createAssignmentPriorityRule(config: AssignmentPriorityConfig): CompilationRule {
52
- const parsed = AssignmentPrioritySchema.parse(config);
53
- const { preference } = parsed;
54
- const entityScopeValue = parseEntityScope(parsed);
55
- const timeScopeValue = parseTimeScope(parsed);
56
-
57
- return {
58
- compile(b) {
59
- const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
60
- const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
61
-
62
- if (targetMembers.length === 0 || activeDays.length === 0) return;
35
+ export const assignmentPriorityRuleDescriptor = defineRuleDescriptor({
36
+ name: "assignment-priority",
37
+ schema: AssignmentPrioritySchema,
38
+ compile(config, ctx) {
39
+ const { preference } = config;
40
+ const priority = config.priority ?? "MANDATORY";
41
+ const entityScopeValue = parseEntityScope(config);
42
+ const timeScopeValue = parseTimeScope(config);
43
+ const targetMembers = resolveMembersFromScope(entityScopeValue, [...ctx.members]);
44
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, [...ctx.days]);
45
+ const baseWeight = PREFERENCE_WEIGHTS[priority as keyof typeof PREFERENCE_WEIGHTS] ?? 0;
46
+ if (targetMembers.length === 0 || activeDays.length === 0 || baseWeight === 0) {
47
+ return { rule: "assignment-priority", artifacts: [] };
48
+ }
63
49
 
64
- const weight =
65
- preference === "high"
66
- ? -OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE
67
- : OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE;
50
+ const weight = preference === "prefer" ? -baseWeight : baseWeight;
51
+ const terms = targetMembers.flatMap((member) =>
52
+ ctx.shiftPatterns.flatMap((pattern) => {
53
+ if (!canAssignMemberToPattern(member, pattern)) return [];
54
+ return activeDays
55
+ .filter((day) => isPatternAvailableOnDay(pattern, day))
56
+ .map((day) => ({ var: assignmentVar(member.id, pattern.id, day.iso), coeff: weight }));
57
+ }),
58
+ );
68
59
 
69
- for (const member of targetMembers) {
70
- for (const pattern of b.shiftPatterns) {
71
- if (!b.canAssign(member, pattern)) continue;
72
- for (const day of activeDays) {
73
- if (!b.patternAvailableOnDay(pattern, day)) continue;
74
- b.addPenalty(b.assignment(member.id, pattern.id, day), weight);
75
- }
76
- }
77
- }
78
- },
79
- };
80
- }
60
+ return {
61
+ rule: "assignment-priority",
62
+ artifacts: [
63
+ {
64
+ kind: "objective",
65
+ terms,
66
+ validation: skipValidation(
67
+ "no-meaningful-feedback",
68
+ terms.length === 0
69
+ ? "Assignment priority produced no objective terms for the selected scope."
70
+ : "Preference rules influence the objective but do not emit validation feedback.",
71
+ ),
72
+ },
73
+ ],
74
+ };
75
+ },
76
+ });
@@ -0,0 +1,43 @@
1
+ import type { SchedulingDay } from "../../types.js";
2
+ import type { SchedulingMember, ShiftPattern, Term } from "../types.js";
3
+ import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
4
+ import { assignmentVar } from "./variables.js";
5
+
6
+ export function assignmentVarsForDay(
7
+ member: SchedulingMember,
8
+ day: SchedulingDay,
9
+ shiftPatterns: readonly ShiftPattern[],
10
+ ): string[] {
11
+ return shiftPatterns
12
+ .filter(
13
+ (pattern) =>
14
+ canAssignMemberToPattern(member, pattern) && isPatternAvailableOnDay(pattern, day),
15
+ )
16
+ .map((pattern) => assignmentVar(member.id, pattern.id, day.iso));
17
+ }
18
+
19
+ export function assignmentTermsForDay(
20
+ member: SchedulingMember,
21
+ day: SchedulingDay,
22
+ shiftPatterns: readonly ShiftPattern[],
23
+ coeffForPattern: (pattern: ShiftPattern) => number,
24
+ ): Term[] {
25
+ return shiftPatterns
26
+ .filter(
27
+ (pattern) =>
28
+ canAssignMemberToPattern(member, pattern) && isPatternAvailableOnDay(pattern, day),
29
+ )
30
+ .map((pattern) => ({
31
+ var: assignmentVar(member.id, pattern.id, day.iso),
32
+ coeff: coeffForPattern(pattern),
33
+ }));
34
+ }
35
+
36
+ export function assignmentTermsForDays(
37
+ member: SchedulingMember,
38
+ days: readonly SchedulingDay[],
39
+ shiftPatterns: readonly ShiftPattern[],
40
+ coeffForPattern: (pattern: ShiftPattern) => number,
41
+ ): Term[] {
42
+ return days.flatMap((day) => assignmentTermsForDay(member, day, shiftPatterns, coeffForPattern));
43
+ }
@@ -0,0 +1,93 @@
1
+ import type { SchedulingDay } from "../../types.js";
2
+ import type { SchedulingMember, ShiftPattern } from "../types.js";
3
+ import { MINUTES_PER_DAY, normalizeEndMinutes, timeOfDayToMinutes } from "../utils.js";
4
+ import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
5
+ import { assignmentVar } from "./variables.js";
6
+
7
+ export interface ConcurrentAssignmentInterval {
8
+ readonly start: number;
9
+ readonly end: number;
10
+ readonly varName: string;
11
+ }
12
+
13
+ export interface ConcurrentAssignmentSegment {
14
+ readonly start: number;
15
+ readonly end: number;
16
+ readonly varNames: readonly string[];
17
+ }
18
+
19
+ export function collectConcurrentAssignmentIntervals(
20
+ day: SchedulingDay,
21
+ targetMembers: readonly SchedulingMember[],
22
+ shiftPatterns: readonly ShiftPattern[],
23
+ ): ConcurrentAssignmentInterval[] {
24
+ const intervals: ConcurrentAssignmentInterval[] = [];
25
+
26
+ for (const pattern of shiftPatterns) {
27
+ if (!isPatternAvailableOnDay(pattern, day)) continue;
28
+
29
+ const start = timeOfDayToMinutes(pattern.startTime);
30
+ const end = normalizeEndMinutes(start, timeOfDayToMinutes(pattern.endTime));
31
+
32
+ for (const member of targetMembers) {
33
+ if (!canAssignMemberToPattern(member, pattern)) continue;
34
+ intervals.push({
35
+ start,
36
+ end,
37
+ varName: assignmentVar(member.id, pattern.id, day.iso),
38
+ });
39
+ }
40
+ }
41
+
42
+ return intervals;
43
+ }
44
+
45
+ export function resolveConcurrentWindow(
46
+ intervals: readonly ConcurrentAssignmentInterval[],
47
+ startMinutes?: number,
48
+ endMinutes?: number,
49
+ ): { start: number; end: number } {
50
+ const start = startMinutes ?? 0;
51
+ if (endMinutes !== undefined) {
52
+ return { start, end: endMinutes };
53
+ }
54
+
55
+ const latestEnd = intervals.reduce((maxEnd, interval) => Math.max(maxEnd, interval.end), 0);
56
+ return { start, end: Math.max(MINUTES_PER_DAY, latestEnd) };
57
+ }
58
+
59
+ export function buildConcurrentAssignmentSegments(
60
+ intervals: readonly ConcurrentAssignmentInterval[],
61
+ windowStart: number,
62
+ windowEnd: number,
63
+ ): ConcurrentAssignmentSegment[] {
64
+ const boundaries = new Set<number>([windowStart, windowEnd]);
65
+
66
+ for (const interval of intervals) {
67
+ const start = Math.max(interval.start, windowStart);
68
+ const end = Math.min(interval.end, windowEnd);
69
+ if (start >= end) continue;
70
+
71
+ boundaries.add(start);
72
+ boundaries.add(end);
73
+ }
74
+
75
+ const sortedBoundaries = [...boundaries].toSorted((left, right) => left - right);
76
+ const segments: ConcurrentAssignmentSegment[] = [];
77
+
78
+ for (let index = 0; index < sortedBoundaries.length - 1; index++) {
79
+ const start = sortedBoundaries[index];
80
+ const end = sortedBoundaries[index + 1];
81
+ if (start === undefined || end === undefined || start >= end) continue;
82
+
83
+ const varNames = intervals
84
+ .filter((interval) => interval.start < end && interval.end > start)
85
+ .map((interval) => interval.varName);
86
+
87
+ if (varNames.length === 0) continue;
88
+
89
+ segments.push({ start, end, varNames });
90
+ }
91
+
92
+ return segments;
93
+ }
@@ -1,5 +1,4 @@
1
- import type { SchedulingMember, ShiftPattern } from "../types.js";
2
- import { timeOfDayToMinutes, normalizeEndMinutes } from "../utils.js";
1
+ import type { SchedulingMember } from "../types.js";
3
2
 
4
3
  /** Returns the hourly rate for a member, or undefined if not hourly. */
5
4
  export function getHourlyRate(member: SchedulingMember): number | undefined {
@@ -16,10 +15,3 @@ export function getSalariedPay(
16
15
  if ("annual" in member.pay) return member.pay;
17
16
  return undefined;
18
17
  }
19
-
20
- /** Computes shift duration in minutes from a pattern. */
21
- export function patternDurationMinutes(pattern: ShiftPattern): number {
22
- const start = timeOfDayToMinutes(pattern.startTime);
23
- const end = normalizeEndMinutes(start, timeOfDayToMinutes(pattern.endTime));
24
- return end - start;
25
- }
@@ -1,113 +1,133 @@
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
- import type { ShiftAssignment } from "../response.js";
2
+ import { defineRuleDescriptor } from "../rule-descriptor.js";
3
+ import type { CostArtifact } from "../rule-descriptor.js";
5
4
  import { COST_CATEGORY } from "../cost.js";
6
5
  import {
7
6
  entityScope,
8
- timeScope,
9
7
  parseEntityScope,
10
8
  parseTimeScope,
11
- resolveMembersFromScope,
12
9
  resolveActiveDaysFromScope,
10
+ resolveMembersFromScope,
11
+ timeScope,
13
12
  } from "./scope.types.js";
14
- import { getHourlyRate, patternDurationMinutes } from "./cost-utils.js";
13
+ import { getHourlyRate } from "./cost-utils.js";
14
+ import { patternDurationMinutes } from "./pattern-time.js";
15
15
 
16
- const DayCostMultiplierSchema = z
16
+ export const DayCostMultiplierSchema = z
17
17
  .object({
18
18
  factor: z.number().min(1),
19
19
  })
20
20
  .and(entityScope(["members", "roles", "skills"]))
21
21
  .and(timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]));
22
22
 
23
- /** Configuration for {@link createDayCostMultiplierRule}. */
23
+ /** Configuration for {@link dayCostMultiplierRuleDescriptor}. */
24
24
  export type DayCostMultiplierConfig = z.infer<typeof DayCostMultiplierSchema>;
25
25
 
26
26
  /**
27
- * Creates a day-based rate multiplier rule.
27
+ * Low-level descriptor for the `day-cost-multiplier` rule.
28
28
  *
29
- * Multiplies the base rate for assignments on matching days. The extra cost
30
- * above 1x is added as a penalty term (the base 1x is handled by `minimizeCost()`).
29
+ * @remarks
30
+ * This artifact always contributes to post-solve cost calculation. It only adds
31
+ * solver objective terms when `minimize-cost` is active.
31
32
  *
32
- * When `minimizeCost()` is not present, no solver terms are emitted,
33
- * but the `cost()` method still contributes to post-solve calculation.
33
+ * @category Rules
34
34
  */
35
- export function createDayCostMultiplierRule(config: DayCostMultiplierConfig): CompilationRule {
36
- const parsed = DayCostMultiplierSchema.parse(config);
37
- const { factor } = parsed;
38
- const entityScopeValue = parseEntityScope(parsed);
39
- const timeScopeValue = parseTimeScope(parsed);
35
+ export const dayCostMultiplierRuleDescriptor = defineRuleDescriptor({
36
+ name: "day-cost-multiplier",
37
+ schema: DayCostMultiplierSchema,
38
+ compile(config) {
39
+ const { factor } = config;
40
+ const entityScopeValue = parseEntityScope(config);
41
+ const timeScopeValue = parseTimeScope(config);
40
42
 
41
- return {
42
- compile(b) {
43
- if (!b.costContext?.active) return;
44
- if (factor <= 1) return;
43
+ const validation = {
44
+ strategy: "skip" as const,
45
+ category: "no-meaningful-feedback" as const,
46
+ rationale:
47
+ factor <= 1
48
+ ? "A 1x multiplier does not change optimization or cost accounting."
49
+ : "Day cost multipliers only affect optimization when minimize-cost is active.",
50
+ };
45
51
 
46
- const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
47
- const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
52
+ const costArtifact: CostArtifact = {
53
+ kind: "cost",
54
+ validation,
55
+ compileObjective(builder) {
56
+ if (!builder.costContext?.active || factor <= 1) return;
48
57
 
49
- if (targetMembers.length === 0 || activeDays.length === 0) return;
58
+ const targetMembers = resolveMembersFromScope(entityScopeValue, builder.members);
59
+ const activeDays = resolveActiveDaysFromScope(timeScopeValue, builder.days);
60
+ if (targetMembers.length === 0 || activeDays.length === 0) return;
50
61
 
51
- const { normalizationFactor } = b.costContext;
52
- const extraFactor = factor - 1;
62
+ const { normalizationFactor } = builder.costContext;
63
+ const extraFactor = factor - 1;
53
64
 
54
- for (const emp of targetMembers) {
55
- const rate = getHourlyRate(emp);
56
- if (rate === undefined) continue;
57
-
58
- for (const pattern of b.shiftPatterns) {
59
- if (!b.canAssign(emp, pattern)) continue;
60
- for (const day of activeDays) {
61
- if (!b.patternAvailableOnDay(pattern, day)) continue;
62
- const duration = patternDurationMinutes(pattern);
63
- const extraCost = (rate * extraFactor * duration) / 60;
64
- const normalizedPenalty = extraCost / normalizationFactor;
65
- b.addPenalty(b.assignment(emp.id, pattern.id, day), Math.max(1, normalizedPenalty));
65
+ for (const member of targetMembers) {
66
+ const rate = getHourlyRate(member);
67
+ if (rate === undefined) continue;
68
+ for (const pattern of builder.shiftPatterns) {
69
+ if (!builder.canAssign(member, pattern)) continue;
70
+ for (const day of activeDays) {
71
+ if (!builder.patternAvailableOnDay(pattern, day)) continue;
72
+ const duration = patternDurationMinutes(pattern);
73
+ const extraCost = (rate * extraFactor * duration) / 60;
74
+ const normalizedPenalty = extraCost / normalizationFactor;
75
+ builder.addPenalty(
76
+ builder.assignment(member.id, pattern.id, day),
77
+ Math.max(1, normalizedPenalty),
78
+ );
79
+ }
66
80
  }
67
81
  }
68
- }
69
- },
70
-
71
- cost(
72
- assignments: ShiftAssignment[],
73
- members: ReadonlyArray<SchedulingMember>,
74
- shiftPatterns: ReadonlyArray<ShiftPattern>,
75
- ): CostContribution {
76
- if (factor <= 1) return { entries: [] };
82
+ },
83
+ calculateCost(assignments, costContext) {
84
+ if (factor <= 1) return { entries: [] };
77
85
 
78
- const empMap = new Map(members.map((e) => [e.id, e]));
79
- const patternMap = new Map(shiftPatterns.map((p) => [p.id, p]));
86
+ const memberMap = new Map(costContext.members.map((member) => [member.id, member]));
87
+ const patternMap = new Map(
88
+ costContext.shiftPatterns.map((pattern) => [pattern.id, pattern]),
89
+ );
90
+ const activeDays = new Set(
91
+ resolveActiveDaysFromScope(timeScopeValue, [...costContext.days]).map((day) => day.iso),
92
+ );
93
+ const targetMemberIds = new Set(
94
+ resolveMembersFromScope(entityScopeValue, [...costContext.members]).map(
95
+ (member) => member.id,
96
+ ),
97
+ );
98
+ const extraFactor = factor - 1;
80
99
 
81
- // Resolve which days this rule applies to from assignments
82
- const allDays = [...new Set(assignments.map((a) => a.day))].toSorted();
83
- const activeDays = new Set(resolveActiveDaysFromScope(timeScopeValue, allDays));
84
- const targetEmpIds = new Set(
85
- resolveMembersFromScope(entityScopeValue, [...empMap.values()]).map((e) => e.id),
86
- );
87
-
88
- const entries: CostContribution["entries"] = [];
89
- const extraFactor = factor - 1;
100
+ const entries = [] as Array<{
101
+ memberId: string;
102
+ day: string;
103
+ category: string;
104
+ amount: number;
105
+ }>;
106
+ for (const assignment of assignments) {
107
+ if (!activeDays.has(assignment.day) || !targetMemberIds.has(assignment.memberId))
108
+ continue;
109
+ const member = memberMap.get(assignment.memberId);
110
+ if (!member) continue;
111
+ const rate = getHourlyRate(member);
112
+ if (rate === undefined) continue;
113
+ const pattern = patternMap.get(assignment.shiftPatternId);
114
+ if (!pattern) continue;
115
+ const duration = patternDurationMinutes(pattern);
116
+ entries.push({
117
+ memberId: assignment.memberId,
118
+ day: assignment.day,
119
+ category: COST_CATEGORY.PREMIUM,
120
+ amount: (rate * extraFactor * duration) / 60,
121
+ });
122
+ }
90
123
 
91
- for (const a of assignments) {
92
- if (!activeDays.has(a.day)) continue;
93
- if (!targetEmpIds.has(a.memberId)) continue;
94
- const emp = empMap.get(a.memberId);
95
- if (!emp) continue;
96
- const rate = getHourlyRate(emp);
97
- if (rate === undefined) continue;
98
- const pattern = patternMap.get(a.shiftPatternId);
99
- if (!pattern) continue;
100
- const duration = patternDurationMinutes(pattern);
101
- const amount = (rate * extraFactor * duration) / 60;
102
- entries.push({
103
- memberId: a.memberId,
104
- day: a.day,
105
- category: COST_CATEGORY.PREMIUM,
106
- amount,
107
- });
108
- }
124
+ return { entries };
125
+ },
126
+ };
109
127
 
110
- return { entries };
111
- },
112
- };
113
- }
128
+ return {
129
+ rule: "day-cost-multiplier",
130
+ artifacts: [costArtifact],
131
+ };
132
+ },
133
+ });