dabke 0.82.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +22 -24
  3. package/dist/client.d.ts +39 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.schemas.d.ts +118 -18
  7. package/dist/client.schemas.d.ts.map +1 -1
  8. package/dist/client.schemas.js +110 -6
  9. package/dist/client.schemas.js.map +1 -1
  10. package/dist/client.types.d.ts +63 -7
  11. package/dist/client.types.d.ts.map +1 -1
  12. package/dist/client.types.js.map +1 -1
  13. package/dist/cpsat/cost.d.ts +5 -2
  14. package/dist/cpsat/cost.d.ts.map +1 -1
  15. package/dist/cpsat/cost.js +13 -6
  16. package/dist/cpsat/cost.js.map +1 -1
  17. package/dist/cpsat/model-builder.d.ts +42 -61
  18. package/dist/cpsat/model-builder.d.ts.map +1 -1
  19. package/dist/cpsat/model-builder.js +451 -56
  20. package/dist/cpsat/model-builder.js.map +1 -1
  21. package/dist/cpsat/response.d.ts +19 -7
  22. package/dist/cpsat/response.d.ts.map +1 -1
  23. package/dist/cpsat/response.js +48 -9
  24. package/dist/cpsat/response.js.map +1 -1
  25. package/dist/cpsat/rule-descriptor.d.ts +250 -0
  26. package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
  27. package/dist/cpsat/rule-descriptor.js +51 -0
  28. package/dist/cpsat/rule-descriptor.js.map +1 -0
  29. package/dist/cpsat/rules/artifacts.d.ts +9 -0
  30. package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
  31. package/dist/cpsat/rules/artifacts.js +31 -0
  32. package/dist/cpsat/rules/artifacts.js.map +1 -0
  33. package/dist/cpsat/rules/assign-together.d.ts +18 -20
  34. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  35. package/dist/cpsat/rules/assign-together.js +90 -54
  36. package/dist/cpsat/rules/assign-together.js.map +1 -1
  37. package/dist/cpsat/rules/assigned-day.d.ts +12 -0
  38. package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
  39. package/dist/cpsat/rules/assigned-day.js +43 -0
  40. package/dist/cpsat/rules/assigned-day.js.map +1 -0
  41. package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
  42. package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
  43. package/dist/cpsat/rules/assignment-priority.js +47 -50
  44. package/dist/cpsat/rules/assignment-priority.js.map +1 -1
  45. package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
  46. package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
  47. package/dist/cpsat/rules/assignment-terms.js +19 -0
  48. package/dist/cpsat/rules/assignment-terms.js.map +1 -0
  49. package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
  50. package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
  51. package/dist/cpsat/rules/concurrent-intervals.js +57 -0
  52. package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
  53. package/dist/cpsat/rules/cost-utils.d.ts +1 -3
  54. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
  55. package/dist/cpsat/rules/cost-utils.js +0 -7
  56. package/dist/cpsat/rules/cost-utils.js.map +1 -1
  57. package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
  58. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  59. package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
  60. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  61. package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
  62. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  63. package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
  64. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  65. package/dist/cpsat/rules/index.d.ts +30 -21
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -21
  68. package/dist/cpsat/rules/index.js.map +1 -1
  69. package/dist/cpsat/rules/location-preference.d.ts +14 -21
  70. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  71. package/dist/cpsat/rules/location-preference.js +45 -45
  72. package/dist/cpsat/rules/location-preference.js.map +1 -1
  73. package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
  74. package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
  75. package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
  76. package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
  77. package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
  78. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  79. package/dist/cpsat/rules/max-consecutive-days.js +67 -66
  80. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  81. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
  82. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
  83. package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
  84. package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
  85. package/dist/cpsat/rules/max-days-week.d.ts +30 -0
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
  87. package/dist/cpsat/rules/max-days-week.js +83 -0
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -0
  89. package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
  90. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  91. package/dist/cpsat/rules/max-hours-day.js +56 -72
  92. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  93. package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
  94. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  95. package/dist/cpsat/rules/max-hours-week.js +60 -75
  96. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  97. package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
  98. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  99. package/dist/cpsat/rules/max-shifts-day.js +57 -73
  100. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  101. package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
  102. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  103. package/dist/cpsat/rules/min-consecutive-days.js +123 -94
  104. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  105. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
  106. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
  107. package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
  108. package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
  109. package/dist/cpsat/rules/min-days-week.d.ts +21 -0
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
  111. package/dist/cpsat/rules/min-days-week.js +95 -0
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -0
  113. package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
  114. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  115. package/dist/cpsat/rules/min-hours-day.js +72 -54
  116. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  117. package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
  118. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  119. package/dist/cpsat/rules/min-hours-week.js +75 -57
  120. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  121. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
  122. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  123. package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
  124. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  125. package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
  126. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  127. package/dist/cpsat/rules/minimize-cost.js +151 -163
  128. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  129. package/dist/cpsat/rules/must-assign.d.ts +12 -0
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
  131. package/dist/cpsat/rules/must-assign.js +54 -0
  132. package/dist/cpsat/rules/must-assign.js.map +1 -0
  133. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
  134. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  135. package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
  136. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  137. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
  138. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  139. package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
  140. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  141. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
  142. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  143. package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
  144. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  145. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
  146. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  147. package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
  148. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  149. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
  150. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  151. package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
  152. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  153. package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
  154. package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
  155. package/dist/cpsat/rules/pattern-eligibility.js +16 -0
  156. package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
  157. package/dist/cpsat/rules/pattern-time.d.ts +5 -0
  158. package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
  159. package/dist/cpsat/rules/pattern-time.js +20 -0
  160. package/dist/cpsat/rules/pattern-time.js.map +1 -0
  161. package/dist/cpsat/rules/registry.d.ts +217 -27
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -31
  164. package/dist/cpsat/rules/registry.js.map +1 -1
  165. package/dist/cpsat/rules/resolver.d.ts +9 -7
  166. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  167. package/dist/cpsat/rules/resolver.js +19 -18
  168. package/dist/cpsat/rules/resolver.js.map +1 -1
  169. package/dist/cpsat/rules/role-preference.d.ts +24 -0
  170. package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
  171. package/dist/cpsat/rules/role-preference.js +72 -0
  172. package/dist/cpsat/rules/role-preference.js.map +1 -0
  173. package/dist/cpsat/rules/rules.types.d.ts +93 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +25 -12
  176. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  177. package/dist/cpsat/rules/scope.types.js +14 -19
  178. package/dist/cpsat/rules/scope.types.js.map +1 -1
  179. package/dist/cpsat/rules/target-days-week.d.ts +31 -0
  180. package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
  181. package/dist/cpsat/rules/target-days-week.js +80 -0
  182. package/dist/cpsat/rules/target-days-week.js.map +1 -0
  183. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
  184. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
  185. package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
  186. package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
  187. package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
  188. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  189. package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
  190. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  191. package/dist/cpsat/rules/time-off.d.ts +21 -57
  192. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  193. package/dist/cpsat/rules/time-off.js +114 -127
  194. package/dist/cpsat/rules/time-off.js.map +1 -1
  195. package/dist/cpsat/rules/variables.d.ts +4 -0
  196. package/dist/cpsat/rules/variables.d.ts.map +1 -0
  197. package/dist/cpsat/rules/variables.js +10 -0
  198. package/dist/cpsat/rules/variables.js.map +1 -0
  199. package/dist/cpsat/rules.d.ts +20 -3
  200. package/dist/cpsat/rules.d.ts.map +1 -1
  201. package/dist/cpsat/rules.js +19 -2
  202. package/dist/cpsat/rules.js.map +1 -1
  203. package/dist/cpsat/semantic-time.d.ts +7 -6
  204. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  205. package/dist/cpsat/semantic-time.js +15 -28
  206. package/dist/cpsat/semantic-time.js.map +1 -1
  207. package/dist/cpsat/types.d.ts +47 -4
  208. package/dist/cpsat/types.d.ts.map +1 -1
  209. package/dist/cpsat/types.js +24 -1
  210. package/dist/cpsat/types.js.map +1 -1
  211. package/dist/cpsat/utils.d.ts +23 -6
  212. package/dist/cpsat/utils.d.ts.map +1 -1
  213. package/dist/cpsat/utils.js +76 -15
  214. package/dist/cpsat/utils.js.map +1 -1
  215. package/dist/cpsat/validation-reporter.d.ts +6 -2
  216. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  217. package/dist/cpsat/validation-reporter.js +122 -10
  218. package/dist/cpsat/validation-reporter.js.map +1 -1
  219. package/dist/cpsat/validation.types.d.ts +21 -10
  220. package/dist/cpsat/validation.types.d.ts.map +1 -1
  221. package/dist/datetime.utils.d.ts +7 -13
  222. package/dist/datetime.utils.d.ts.map +1 -1
  223. package/dist/datetime.utils.js +29 -36
  224. package/dist/datetime.utils.js.map +1 -1
  225. package/dist/index.d.ts +15 -9
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +11 -4
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +192 -0
  230. package/dist/schedule/cost.d.ts.map +1 -0
  231. package/dist/schedule/cost.js +151 -0
  232. package/dist/schedule/cost.js.map +1 -0
  233. package/dist/schedule/coverage.d.ts +114 -0
  234. package/dist/schedule/coverage.d.ts.map +1 -0
  235. package/dist/schedule/coverage.js +56 -0
  236. package/dist/schedule/coverage.js.map +1 -0
  237. package/dist/schedule/definition.d.ts +346 -0
  238. package/dist/schedule/definition.d.ts.map +1 -0
  239. package/dist/schedule/definition.js +668 -0
  240. package/dist/schedule/definition.js.map +1 -0
  241. package/dist/schedule/index.d.ts +67 -0
  242. package/dist/schedule/index.d.ts.map +1 -0
  243. package/dist/schedule/index.js +69 -0
  244. package/dist/schedule/index.js.map +1 -0
  245. package/dist/schedule/rules.d.ts +678 -0
  246. package/dist/schedule/rules.d.ts.map +1 -0
  247. package/dist/schedule/rules.js +554 -0
  248. package/dist/schedule/rules.js.map +1 -0
  249. package/dist/schedule/shift-patterns.d.ts +17 -0
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -0
  251. package/dist/schedule/shift-patterns.js +24 -0
  252. package/dist/schedule/shift-patterns.js.map +1 -0
  253. package/dist/schedule/time-periods.d.ts +79 -0
  254. package/dist/schedule/time-periods.d.ts.map +1 -0
  255. package/dist/schedule/time-periods.js +101 -0
  256. package/dist/schedule/time-periods.js.map +1 -0
  257. package/dist/types.d.ts +67 -3
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +64 -0
  260. package/dist/types.js.map +1 -1
  261. package/package.json +8 -14
  262. package/solver/src/solver/models.py +41 -4
  263. package/solver/src/solver/solver.py +546 -157
  264. package/solver/tests/test_solver.py +363 -7
  265. package/src/client.schemas.ts +127 -10
  266. package/src/client.ts +3 -0
  267. package/src/client.types.ts +73 -7
  268. package/src/cpsat/cost.ts +17 -7
  269. package/src/cpsat/model-builder.ts +614 -129
  270. package/src/cpsat/response.ts +57 -13
  271. package/src/cpsat/rule-descriptor.ts +311 -0
  272. package/src/cpsat/rules/artifacts.ts +56 -0
  273. package/src/cpsat/rules/assign-together.ts +104 -69
  274. package/src/cpsat/rules/assigned-day.ts +70 -0
  275. package/src/cpsat/rules/assignment-priority.ts +54 -58
  276. package/src/cpsat/rules/assignment-terms.ts +43 -0
  277. package/src/cpsat/rules/concurrent-intervals.ts +93 -0
  278. package/src/cpsat/rules/cost-utils.ts +1 -9
  279. package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
  280. package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
  281. package/src/cpsat/rules/index.ts +30 -21
  282. package/src/cpsat/rules/location-preference.ts +47 -52
  283. package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
  284. package/src/cpsat/rules/max-consecutive-days.ts +74 -91
  285. package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
  286. package/src/cpsat/rules/max-days-week.ts +117 -0
  287. package/src/cpsat/rules/max-hours-day.ts +64 -84
  288. package/src/cpsat/rules/max-hours-week.ts +74 -90
  289. package/src/cpsat/rules/max-shifts-day.ts +65 -85
  290. package/src/cpsat/rules/min-consecutive-days.ts +161 -127
  291. package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
  292. package/src/cpsat/rules/min-days-week.ts +122 -0
  293. package/src/cpsat/rules/min-hours-day.ts +79 -58
  294. package/src/cpsat/rules/min-hours-week.ts +87 -62
  295. package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
  296. package/src/cpsat/rules/minimize-cost.ts +175 -188
  297. package/src/cpsat/rules/must-assign.ts +71 -0
  298. package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
  299. package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
  300. package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
  301. package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
  302. package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
  303. package/src/cpsat/rules/pattern-eligibility.ts +26 -0
  304. package/src/cpsat/rules/pattern-time.ts +30 -0
  305. package/src/cpsat/rules/registry.ts +140 -56
  306. package/src/cpsat/rules/resolver.ts +58 -30
  307. package/src/cpsat/rules/role-preference.ts +92 -0
  308. package/src/cpsat/rules/rules.types.ts +92 -17
  309. package/src/cpsat/rules/scope.types.ts +32 -27
  310. package/src/cpsat/rules/target-days-week.ts +105 -0
  311. package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
  312. package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
  313. package/src/cpsat/rules/time-off.ts +154 -163
  314. package/src/cpsat/rules/variables.ts +11 -0
  315. package/src/cpsat/rules.ts +43 -17
  316. package/src/cpsat/semantic-time.ts +29 -43
  317. package/src/cpsat/types.ts +53 -4
  318. package/src/cpsat/utils.ts +92 -20
  319. package/src/cpsat/validation-reporter.ts +170 -19
  320. package/src/cpsat/validation.types.ts +22 -11
  321. package/src/datetime.utils.ts +45 -44
  322. package/src/index.ts +72 -15
  323. package/src/schedule/cost.ts +221 -0
  324. package/src/schedule/coverage.ts +164 -0
  325. package/src/schedule/definition.ts +1447 -0
  326. package/src/schedule/index.ts +132 -0
  327. package/src/schedule/rules.ts +1028 -0
  328. package/src/schedule/shift-patterns.ts +29 -0
  329. package/src/schedule/time-periods.ts +120 -0
  330. package/src/types.ts +114 -2
  331. package/dist/llms.d.ts +0 -2
  332. package/dist/llms.d.ts.map +0 -1
  333. package/dist/llms.js +0 -3
  334. package/dist/llms.js.map +0 -1
  335. package/dist/schedule.d.ts +0 -917
  336. package/dist/schedule.d.ts.map +0 -1
  337. package/dist/schedule.js +0 -1323
  338. package/dist/schedule.js.map +0 -1
  339. package/llms.txt +0 -758
  340. package/src/llms.ts +0 -3
  341. package/src/schedule.ts +0 -1960
@@ -1,4 +1,4 @@
1
- import type { TimeOfDay, DayOfWeek } from "../types.js";
1
+ import type { DateString, TimeOfDay, DayOfWeek } from "../types.js";
2
2
  import type { SolverRequest, SolverTerm } from "../client.types.js";
3
3
  import type { ValidationGroup } from "./validation.types.js";
4
4
 
@@ -15,11 +15,12 @@ export interface HourlyPay {
15
15
  /**
16
16
  * Annual salary with contracted weekly hours.
17
17
  *
18
+ * @remarks
18
19
  * The solver treats salaried members as having a fixed weekly cost
19
20
  * (`annual / 52`) that is incurred once they work any shift in a week.
20
21
  * Additional shifts within the same week have zero marginal cost.
21
22
  *
22
- * Note: overtime multiplier rules apply only to hourly members.
23
+ * Overtime multiplier rules apply only to hourly members.
23
24
  * Overtime surcharge rules apply to all members regardless of pay type.
24
25
  *
25
26
  * @category Supporting Types
@@ -34,12 +35,37 @@ export interface SalariedPay {
34
35
  /**
35
36
  * How strictly the solver enforces a rule.
36
37
  *
38
+ * @remarks
39
+ * Priority is part of the schedule design, not a cosmetic option. Use it to
40
+ * preserve the distinction between hard requirements and softer preferences
41
+ * from the requirements. In practice, words like "must", "at least", "needs", and
42
+ * "cannot" usually map to `"MANDATORY"`, while words like "ideally",
43
+ * "prefer", "mainly", and "where possible" usually map to a soft priority.
44
+ * The same coverage shape or rule scope can be modeled with different
45
+ * priorities when the business language is subtler than a pure hard limit.
46
+ *
37
47
  * - `"LOW"`, `"MEDIUM"`, `"HIGH"`: soft constraints with increasing penalty for violations
38
48
  * - `"MANDATORY"`: hard constraint; the solver will not produce a solution that violates it
39
49
  *
40
50
  * @category Supporting Types
41
51
  */
42
- export type Priority = "LOW" | "MEDIUM" | "HIGH" | "MANDATORY";
52
+ export const PRIORITY_VALUES = ["LOW", "MEDIUM", "HIGH", "MANDATORY"] as const;
53
+
54
+ /**
55
+ * The soft-only priority values.
56
+ *
57
+ * @category Supporting Types
58
+ */
59
+ export const SOFT_PRIORITY_VALUES = ["LOW", "MEDIUM", "HIGH"] as const;
60
+
61
+ export type Priority = (typeof PRIORITY_VALUES)[number];
62
+
63
+ /**
64
+ * Soft-only priority values.
65
+ *
66
+ * @category Supporting Types
67
+ */
68
+ export type SoftPriority = (typeof SOFT_PRIORITY_VALUES)[number];
43
69
 
44
70
  /**
45
71
  * A team member available for scheduling.
@@ -131,7 +157,7 @@ export interface ShiftPattern {
131
157
  }
132
158
 
133
159
  export interface TimeInterval {
134
- day: string;
160
+ day: DateString;
135
161
  startTime: TimeOfDay;
136
162
  endTime: TimeOfDay;
137
163
  }
@@ -215,12 +241,35 @@ interface SkillBasedCoverageRequirement extends CoverageRequirementBase {
215
241
  */
216
242
  export type CoverageRequirement = RoleBasedCoverageRequirement | SkillBasedCoverageRequirement;
217
243
 
244
+ /**
245
+ * Compile-time solve profile used by model builders.
246
+ *
247
+ * @category Model Builder
248
+ */
249
+ export type ModelSolveStrategy =
250
+ | {
251
+ /** Build the normal optimized solver request. */
252
+ readonly type: "optimize";
253
+ }
254
+ | {
255
+ /** Build a hard-feasibility request that ignores objectives and soft constraints. */
256
+ readonly type: "feasibility-only";
257
+ };
258
+
218
259
  /** Optional settings for the model builder. */
219
260
  export interface ModelBuilderOptions {
220
261
  /** Which day starts the week for weekly rules. Defaults to "monday". */
221
262
  weekStartsOn?: DayOfWeek;
222
263
  /** Solver-level options (time limit, solution limit). */
223
264
  solverOptions?: SolverRequest["options"];
265
+ /** Compile-time solve profile. Defaults to optimized solving. */
266
+ strategy?: ModelSolveStrategy;
267
+ /**
268
+ * Internal objective stage order for staged solver requests.
269
+ *
270
+ * @internal
271
+ */
272
+ objectiveStageOrder?: readonly string[];
224
273
  /**
225
274
  * Bucket size used when translating coverage requirements into time-indexed constraints.
226
275
  * Smaller buckets are more accurate but increase the number of constraints.
@@ -1,6 +1,5 @@
1
- import { DAY_OF_WEEK_MAP } from "../datetime.utils.js";
2
- import type { DayOfWeek, TimeOfDay } from "../types.js";
3
- import type { Priority } from "./types.js";
1
+ import { DAY_OF_WEEK_INDEX, type DayOfWeek, type SchedulingDay, type TimeOfDay } from "../types.js";
2
+ import type { Priority, SoftPriority } from "./types.js";
4
3
 
5
4
  export const MINUTES_PER_DAY = 24 * 60;
6
5
 
@@ -43,12 +42,17 @@ export const OBJECTIVE_WEIGHTS = {
43
42
  } as const;
44
43
 
45
44
  /**
46
- * Parse a day string (YYYY-MM-DD) to a UTC Date.
47
- * Used internally for day-of-week calculations and date comparisons.
45
+ * Maps {@link Priority} levels to penalty weights for preference rules.
46
+ *
47
+ * Used by `assignment-priority`, `location-preference`, and `role-preference`
48
+ * to scale the strength of their soft constraints.
48
49
  */
49
- export function parseDayString(day: string): Date {
50
- return new Date(`${day}T00:00:00Z`);
51
- }
50
+ export const PREFERENCE_WEIGHTS: Record<Priority, number> = {
51
+ LOW: OBJECTIVE_WEIGHTS.FAIRNESS,
52
+ MEDIUM: OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE,
53
+ HIGH: OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE * 2.5,
54
+ MANDATORY: OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE * 5,
55
+ };
52
56
 
53
57
  export function timeOfDayToMinutes(time: TimeOfDay): number {
54
58
  return time.hours * 60 + (time.minutes ?? 0);
@@ -70,6 +74,16 @@ export function priorityToPenalty(priority: Priority): number {
70
74
  return PRIORITY_PENALTIES[priority];
71
75
  }
72
76
 
77
+ const TARGET_PEAK_PRIORITY_PENALTIES = {
78
+ LOW: OBJECTIVE_WEIGHTS.COST,
79
+ MEDIUM: OBJECTIVE_WEIGHTS.SHIFT_ACTIVE,
80
+ HIGH: OBJECTIVE_WEIGHTS.SHIFT_ACTIVE * 2.5,
81
+ } as const satisfies Record<SoftPriority, number>;
82
+
83
+ export function targetPeakPriorityToPenalty(priority: SoftPriority): number {
84
+ return TARGET_PEAK_PRIORITY_PENALTIES[priority];
85
+ }
86
+
73
87
  /**
74
88
  * Computes the total duration of the union of time ranges.
75
89
  *
@@ -80,8 +94,10 @@ export function priorityToPenalty(priority: Priority): number {
80
94
  export function unionMinutes(ranges: ReadonlyArray<{ start: number; end: number }>): number {
81
95
  if (ranges.length === 0) return 0;
82
96
  const sorted = ranges.toSorted((a, b) => a.start - b.start);
97
+ const first = sorted[0];
98
+ if (!first) return 0;
83
99
  let total = 0;
84
- let currentEnd = sorted[0]!.start;
100
+ let currentEnd = first.start;
85
101
  for (const r of sorted) {
86
102
  const effectiveStart = Math.max(r.start, currentEnd);
87
103
  if (effectiveStart < r.end) {
@@ -92,28 +108,84 @@ export function unionMinutes(ranges: ReadonlyArray<{ start: number; end: number
92
108
  return total;
93
109
  }
94
110
 
95
- export function splitIntoWeeks(days: string[], weekStartsOn: DayOfWeek): string[][] {
111
+ /**
112
+ * Splits scheduling days into non-overlapping chunks of N calendar months.
113
+ *
114
+ * Days are grouped by their {@link SchedulingDay.yearMonth}, then consecutive
115
+ * months are merged into chunks of the requested size.
116
+ */
117
+ export function splitIntoMonths(days: SchedulingDay[], monthsPerChunk: number): SchedulingDay[][] {
118
+ if (days.length === 0) return [];
119
+
120
+ const monthGroups = new Map<string, SchedulingDay[]>();
121
+ for (const day of days) {
122
+ let group = monthGroups.get(day.yearMonth);
123
+ if (!group) {
124
+ group = [];
125
+ monthGroups.set(day.yearMonth, group);
126
+ }
127
+ group.push(day);
128
+ }
129
+
130
+ const sortedKeys = [...monthGroups.keys()].toSorted();
131
+ const chunks: SchedulingDay[][] = [];
132
+
133
+ for (let i = 0; i < sortedKeys.length; i += monthsPerChunk) {
134
+ const chunkKeys = sortedKeys.slice(i, i + monthsPerChunk);
135
+ const chunk = chunkKeys.flatMap((key) => monthGroups.get(key)!);
136
+ if (chunk.length > 0) {
137
+ chunks.push(chunk);
138
+ }
139
+ }
140
+
141
+ return chunks;
142
+ }
143
+
144
+ /**
145
+ * Groups days into chunks of N weeks, aligned to weekStartsOn.
146
+ *
147
+ * Uses {@link splitIntoWeeks} internally, then merges consecutive weeks
148
+ * into larger chunks.
149
+ */
150
+ export function groupWeekChunks(
151
+ days: SchedulingDay[],
152
+ weeksPerChunk: number,
153
+ weekStartsOn: DayOfWeek,
154
+ ): SchedulingDay[][] {
155
+ const weeks = splitIntoWeeks(days, weekStartsOn);
156
+ const chunks: SchedulingDay[][] = [];
157
+
158
+ for (let i = 0; i < weeks.length; i += weeksPerChunk) {
159
+ const chunk = weeks.slice(i, i + weeksPerChunk).flat();
160
+ if (chunk.length > 0) {
161
+ chunks.push(chunk);
162
+ }
163
+ }
164
+
165
+ return chunks;
166
+ }
167
+
168
+ export function splitIntoWeeks(days: SchedulingDay[], weekStartsOn: DayOfWeek): SchedulingDay[][] {
96
169
  if (days.length === 0) return [];
97
170
 
98
- const weekStartIndex = DAY_OF_WEEK_MAP[weekStartsOn];
99
- const result: string[][] = [];
100
- let currentWeek: string[] = [];
101
- let currentWeekStart: Date | null = null;
171
+ const weekStartIndex = DAY_OF_WEEK_INDEX[weekStartsOn];
172
+ const result: SchedulingDay[][] = [];
173
+ let currentWeek: SchedulingDay[] = [];
174
+ let currentWeekStartEpoch: number | null = null;
102
175
 
103
176
  for (const day of days) {
104
- const date = parseDayString(day);
105
- const isWeekStartDay = date.getUTCDay() === weekStartIndex;
177
+ const isWeekStartDay = DAY_OF_WEEK_INDEX[day.dayOfWeek] === weekStartIndex;
106
178
  const isNewWeek =
107
- isWeekStartDay && currentWeekStart !== null && date.getTime() !== currentWeekStart.getTime();
179
+ isWeekStartDay && currentWeekStartEpoch !== null && day.epochDay !== currentWeekStartEpoch;
108
180
 
109
181
  if (isNewWeek) {
110
182
  result.push(currentWeek);
111
183
  currentWeek = [];
112
- currentWeekStart = null;
184
+ currentWeekStartEpoch = null;
113
185
  }
114
186
 
115
- if (currentWeekStart === null) {
116
- currentWeekStart = date;
187
+ if (currentWeekStartEpoch === null) {
188
+ currentWeekStartEpoch = day.epochDay;
117
189
  }
118
190
  currentWeek.push(day);
119
191
  }
@@ -1,4 +1,5 @@
1
1
  import type { SolverResponse } from "../client.types.js";
2
+ import type { DateString } from "../types.js";
2
3
  import {
3
4
  type ScheduleError,
4
5
  type ScheduleViolation,
@@ -24,7 +25,7 @@ export interface ValidationReporter {
24
25
  reportRuleError(error: Omit<RuleError, "type" | "id">): void;
25
26
  reportSolverError(message: string): void;
26
27
 
27
- // Violations (soft constraint issues)
28
+ // Violations (non-fatal feedback)
28
29
  reportCoverageViolation(violation: Omit<CoverageViolation, "type" | "id">): void;
29
30
  reportRuleViolation(violation: Omit<RuleViolation, "type" | "id">): void;
30
31
 
@@ -41,9 +42,17 @@ export interface ValidationReporter {
41
42
  getExclusions(): CoverageExclusion[];
42
43
 
43
44
  // Post-solve analysis
44
- analyzeSolution(response: SolverResponse): void;
45
+ analyzeSolution(response: SolverResponse, options?: AnalyzeSolutionOptions): void;
45
46
  }
46
47
 
48
+ export interface AnalyzeSolutionOptions {
49
+ /** Whether solver-reported soft constraint diagnostics should update validation. */
50
+ analyzeSoftConstraints?: boolean;
51
+ }
52
+
53
+ const MISSING_SOFT_CONSTRAINT_VIOLATIONS_ERROR =
54
+ "Solver response missing softConstraintViolations for tracked soft constraints.";
55
+
47
56
  /**
48
57
  * Generates a deterministic ID for a coverage-based validation item.
49
58
  * Format: {category}:coverage:{day}:{timeSlots}:{roles}:{skills}
@@ -91,19 +100,28 @@ export class ValidationReporterImpl implements ValidationReporter {
91
100
  #passed: SchedulePassed[] = [];
92
101
  #trackedConstraints = new Map<string, TrackedConstraint>();
93
102
  #exclusions: CoverageExclusion[] = [];
103
+ #validationIdCounts = new Map<string, number>();
94
104
  #solverErrorCount = 0;
95
105
 
106
+ #nextValidationId(baseId: string): string {
107
+ const count = (this.#validationIdCounts.get(baseId) ?? 0) + 1;
108
+ this.#validationIdCounts.set(baseId, count);
109
+ return count === 1 ? baseId : `${baseId}:${count}`;
110
+ }
111
+
96
112
  excludeFromCoverage(exclusion: CoverageExclusion): void {
97
113
  this.#exclusions.push(exclusion);
98
114
  }
99
115
 
100
116
  reportCoverageError(error: Omit<CoverageError, "type" | "id">): void {
101
- const id = coverageId("error", error.day, error.timeSlots, error.roleIds, error.skillIds);
117
+ const id = this.#nextValidationId(
118
+ coverageId("error", error.day, error.timeSlots, error.roleIds, error.skillIds),
119
+ );
102
120
  this.#errors.push({ id, type: "coverage", ...error });
103
121
  }
104
122
 
105
123
  reportRuleError(error: Omit<RuleError, "type" | "id">): void {
106
- const id = ruleId("error", error.rule, error.context);
124
+ const id = this.#nextValidationId(ruleId("error", error.rule, error.context));
107
125
  this.#errors.push({ id, type: "rule", ...error });
108
126
  }
109
127
 
@@ -114,28 +132,32 @@ export class ValidationReporterImpl implements ValidationReporter {
114
132
  }
115
133
 
116
134
  reportCoverageViolation(violation: Omit<CoverageViolation, "type" | "id">): void {
117
- const id = coverageId(
118
- "violation",
119
- violation.day,
120
- violation.timeSlots,
121
- violation.roleIds,
122
- violation.skillIds,
135
+ const id = this.#nextValidationId(
136
+ coverageId(
137
+ "violation",
138
+ violation.day,
139
+ violation.timeSlots,
140
+ violation.roleIds,
141
+ violation.skillIds,
142
+ ),
123
143
  );
124
144
  this.#violations.push({ id, type: "coverage", ...violation });
125
145
  }
126
146
 
127
147
  reportRuleViolation(violation: Omit<RuleViolation, "type" | "id">): void {
128
- const id = ruleId("violation", violation.rule, violation.context);
148
+ const id = this.#nextValidationId(ruleId("violation", violation.rule, violation.context));
129
149
  this.#violations.push({ id, type: "rule", ...violation });
130
150
  }
131
151
 
132
152
  reportCoveragePassed(passed: Omit<CoveragePassed, "type" | "id">): void {
133
- const id = coverageId("passed", passed.day, passed.timeSlots, passed.roleIds, passed.skillIds);
153
+ const id = this.#nextValidationId(
154
+ coverageId("passed", passed.day, passed.timeSlots, passed.roleIds, passed.skillIds),
155
+ );
134
156
  this.#passed.push({ id, type: "coverage", ...passed });
135
157
  }
136
158
 
137
159
  reportRulePassed(passed: Omit<RulePassed, "type" | "id">): void {
138
- const id = ruleId("passed", passed.rule, passed.context);
160
+ const id = this.#nextValidationId(ruleId("passed", passed.rule, passed.context));
139
161
  this.#passed.push({ id, type: "rule", ...passed });
140
162
  }
141
163
 
@@ -163,10 +185,16 @@ export class ValidationReporterImpl implements ValidationReporter {
163
185
  return [...this.#trackedConstraints.values()];
164
186
  }
165
187
 
166
- analyzeSolution(response: SolverResponse): void {
188
+ analyzeSolution(response: SolverResponse, options?: AnalyzeSolutionOptions): void {
167
189
  if (response.status !== "OPTIMAL" && response.status !== "FEASIBLE") {
168
190
  if (response.status === "INFEASIBLE") {
191
+ this.#reportHardConstraintConflicts(
192
+ (response.hardConstraintConflicts ?? []).map((conflict) => conflict.constraintId),
193
+ );
169
194
  this.reportSolverError(response.solutionInfo ?? "Schedule is infeasible");
195
+ if (response.error) {
196
+ this.reportSolverError(response.error);
197
+ }
170
198
  } else if (response.status === "TIMEOUT") {
171
199
  this.reportSolverError("Solver timed out");
172
200
  } else if (response.error) {
@@ -175,16 +203,33 @@ export class ValidationReporterImpl implements ValidationReporter {
175
203
  return;
176
204
  }
177
205
 
178
- const solverViolations = response.softViolations ?? [];
206
+ const analyzeSoftConstraints = options?.analyzeSoftConstraints ?? true;
207
+
208
+ const hasTrackedSoftConstraints = [...this.#trackedConstraints.values()].some(
209
+ (constraint) => constraint.source === "soft",
210
+ );
211
+
212
+ if (
213
+ analyzeSoftConstraints &&
214
+ hasTrackedSoftConstraints &&
215
+ response.softConstraintViolations === undefined
216
+ ) {
217
+ throw new Error(MISSING_SOFT_CONSTRAINT_VIOLATIONS_ERROR);
218
+ }
219
+
220
+ const solverViolations = analyzeSoftConstraints
221
+ ? (response.softConstraintViolations ?? [])
222
+ : [];
179
223
 
180
224
  for (const violation of solverViolations) {
181
225
  const tracked = this.#trackedConstraints.get(violation.constraintId);
182
226
 
183
227
  if (tracked?.type === "coverage") {
228
+ if (!tracked.day) continue;
184
229
  const roles = tracked.roleIds?.join(", ") ?? "staff";
185
230
  const slot = tracked.timeSlot ?? "all day";
186
231
  this.reportCoverageViolation({
187
- day: tracked.day ?? "",
232
+ day: tracked.day,
188
233
  timeSlots: tracked.timeSlot ? [tracked.timeSlot] : [],
189
234
  roleIds: tracked.roleIds,
190
235
  skillIds: tracked.skillIds,
@@ -216,11 +261,13 @@ export class ValidationReporterImpl implements ValidationReporter {
216
261
  // Mark tracked coverage constraints as passed if not violated
217
262
  const violatedIds = new Set(solverViolations.map((v) => v.constraintId));
218
263
  for (const tracked of this.#trackedConstraints.values()) {
264
+ if (!analyzeSoftConstraints && tracked.source === "soft") continue;
219
265
  if (violatedIds.has(tracked.id)) continue;
220
266
 
221
267
  if (tracked.type === "coverage") {
268
+ if (!tracked.day) continue;
222
269
  this.reportCoveragePassed({
223
- day: tracked.day ?? "",
270
+ day: tracked.day,
224
271
  timeSlots: tracked.timeSlot ? [tracked.timeSlot] : [],
225
272
  roleIds: tracked.roleIds,
226
273
  skillIds: tracked.skillIds,
@@ -230,6 +277,110 @@ export class ValidationReporterImpl implements ValidationReporter {
230
277
  }
231
278
  }
232
279
  }
280
+
281
+ #reportHardConstraintConflicts(constraintIds: readonly string[]): void {
282
+ if (constraintIds.length === 0) {
283
+ return;
284
+ }
285
+
286
+ type CoverageGroup = {
287
+ day: DateString;
288
+ timeSlots: Set<string>;
289
+ roleIds?: string[];
290
+ skillIds?: readonly string[];
291
+ group?: TrackedConstraint["group"];
292
+ descriptions: string[];
293
+ };
294
+ type RuleGroup = {
295
+ rule: string;
296
+ days: Set<DateString>;
297
+ memberIds: Set<string>;
298
+ group?: TrackedConstraint["group"];
299
+ descriptions: string[];
300
+ };
301
+
302
+ const coverageGroups = new Map<string, CoverageGroup>();
303
+ const ruleGroups = new Map<string, RuleGroup>();
304
+
305
+ for (const constraintId of constraintIds) {
306
+ const tracked = this.#trackedConstraints.get(constraintId);
307
+ if (!tracked) continue;
308
+
309
+ if (tracked.type === "coverage") {
310
+ if (!tracked.day) continue;
311
+ const key = [
312
+ tracked.group?.key ?? tracked.id,
313
+ tracked.day,
314
+ tracked.roleIds?.join(",") ?? "_",
315
+ tracked.skillIds?.join(",") ?? "_",
316
+ ].join(":");
317
+ const existing = coverageGroups.get(key);
318
+ if (existing) {
319
+ if (tracked.timeSlot) existing.timeSlots.add(tracked.timeSlot);
320
+ existing.descriptions.push(tracked.description);
321
+ continue;
322
+ }
323
+ coverageGroups.set(key, {
324
+ day: tracked.day,
325
+ timeSlots: new Set(tracked.timeSlot ? [tracked.timeSlot] : []),
326
+ roleIds: tracked.roleIds,
327
+ skillIds: tracked.skillIds,
328
+ group: tracked.group,
329
+ descriptions: [tracked.description],
330
+ });
331
+ continue;
332
+ }
333
+
334
+ const key = tracked.group?.key ?? tracked.id;
335
+ const existing = ruleGroups.get(key);
336
+ if (existing) {
337
+ for (const day of tracked.context.days ?? []) existing.days.add(day);
338
+ for (const memberId of tracked.context.memberIds ?? []) existing.memberIds.add(memberId);
339
+ existing.descriptions.push(tracked.description);
340
+ continue;
341
+ }
342
+ ruleGroups.set(key, {
343
+ rule: tracked.rule ?? "unknown",
344
+ days: new Set(tracked.context.days ?? []),
345
+ memberIds: new Set(tracked.context.memberIds ?? []),
346
+ group: tracked.group,
347
+ descriptions: [tracked.description],
348
+ });
349
+ }
350
+
351
+ for (const coverage of coverageGroups.values()) {
352
+ const label = coverage.group?.title ?? coverage.descriptions[0] ?? "Coverage requirement";
353
+ this.reportCoverageError({
354
+ day: coverage.day,
355
+ timeSlots: [...coverage.timeSlots].toSorted(),
356
+ roleIds: coverage.roleIds,
357
+ skillIds: coverage.skillIds,
358
+ message: `${label} is part of a sufficient infeasible constraint set.`,
359
+ suggestions: [
360
+ "Relax this mandatory coverage requirement",
361
+ "Relax conflicting mandatory rules or add eligible team members",
362
+ ],
363
+ group: coverage.group,
364
+ });
365
+ }
366
+
367
+ for (const rule of ruleGroups.values()) {
368
+ const label = rule.group?.title ?? rule.descriptions[0] ?? rule.rule;
369
+ this.reportRuleError({
370
+ rule: rule.rule,
371
+ message: `${label} is part of a sufficient infeasible constraint set.`,
372
+ context: {
373
+ days: [...rule.days].toSorted(),
374
+ memberIds: [...rule.memberIds].toSorted(),
375
+ },
376
+ suggestions: [
377
+ "Relax this mandatory rule",
378
+ "Relax conflicting mandatory coverage requirements or add eligible team members",
379
+ ],
380
+ group: rule.group,
381
+ });
382
+ }
383
+ }
233
384
  }
234
385
 
235
386
  // =============================================================================
@@ -265,7 +416,7 @@ export function summarizeValidation(validation: ScheduleValidation): readonly Va
265
416
  {
266
417
  type: "coverage" | "rule";
267
418
  title: string;
268
- days: Set<string>;
419
+ days: Set<DateString>;
269
420
  passedCount: number;
270
421
  violatedCount: number;
271
422
  errorCount: number;
@@ -280,7 +431,7 @@ export function summarizeValidation(validation: ScheduleValidation): readonly Va
280
431
  groups.set(key, {
281
432
  type: item.type,
282
433
  title,
283
- days: new Set(),
434
+ days: new Set<DateString>(),
284
435
  passedCount: 0,
285
436
  violatedCount: 0,
286
437
  errorCount: 0,
@@ -1,10 +1,10 @@
1
- import type { TimeOfDay } from "../types.js";
1
+ import type { DateString, TimeOfDay } from "../types.js";
2
2
 
3
3
  /**
4
4
  * Context shared across validation results for grouping/display.
5
5
  */
6
6
  export interface ValidationContext {
7
- days?: string[];
7
+ days?: DateString[];
8
8
  timeSlots?: string[];
9
9
  memberIds?: string[];
10
10
  }
@@ -60,7 +60,7 @@ export function assertSafeKeySegments(values: readonly string[], label: string):
60
60
  export interface CoverageError {
61
61
  readonly id: string;
62
62
  readonly type: "coverage";
63
- readonly day: string;
63
+ readonly day: DateString;
64
64
  readonly timeSlots: readonly string[];
65
65
  readonly roleIds?: string[];
66
66
  readonly skillIds?: readonly string[];
@@ -88,13 +88,13 @@ export interface SolverError {
88
88
  export type ScheduleError = CoverageError | RuleError | SolverError;
89
89
 
90
90
  // =============================================================================
91
- // Violations - soft constraints not met, but schedule generated
91
+ // Violations - non-fatal unmet requirements or preferences
92
92
  // =============================================================================
93
93
 
94
94
  export interface CoverageViolation {
95
95
  readonly id: string;
96
96
  readonly type: "coverage";
97
- readonly day: string;
97
+ readonly day: DateString;
98
98
  readonly timeSlots: readonly string[];
99
99
  readonly roleIds?: string[];
100
100
  readonly skillIds?: readonly string[];
@@ -125,7 +125,7 @@ export type ScheduleViolation = CoverageViolation | RuleViolation;
125
125
  export interface CoveragePassed {
126
126
  readonly id: string;
127
127
  readonly type: "coverage";
128
- readonly day: string;
128
+ readonly day: DateString;
129
129
  readonly timeSlots: readonly string[];
130
130
  readonly roleIds?: string[];
131
131
  readonly skillIds?: readonly string[];
@@ -148,7 +148,17 @@ export type SchedulePassed = CoveragePassed | RulePassed;
148
148
  // Complete validation result
149
149
  // =============================================================================
150
150
 
151
- /** @category Validation */
151
+ /**
152
+ * Public validation feedback collected across pre-solve, solver-stage, and
153
+ * post-solve analysis.
154
+ *
155
+ * @remarks
156
+ * This is not a direct mirror of the raw solver response. Solver transport
157
+ * fields such as `softConstraintViolations` are translated into these
158
+ * phase-agnostic `errors`, `violations`, and `passed` items.
159
+ *
160
+ * @category Validation
161
+ */
152
162
  export interface ScheduleValidation {
153
163
  readonly errors: readonly ScheduleError[];
154
164
  readonly violations: readonly ScheduleViolation[];
@@ -171,7 +181,7 @@ export interface ValidationSummary {
171
181
  readonly type: "coverage" | "rule";
172
182
  /** Human-readable title for this group (e.g., "3x nurse during day_ward"). */
173
183
  readonly title: string;
174
- readonly days: readonly string[];
184
+ readonly days: readonly DateString[];
175
185
  readonly status: "passed" | "partial" | "failed";
176
186
  readonly passedCount: number;
177
187
  readonly violatedCount: number;
@@ -185,12 +195,13 @@ export interface ValidationSummary {
185
195
  export interface TrackedConstraint {
186
196
  readonly id: string;
187
197
  readonly type: "coverage" | "rule";
198
+ readonly source?: "hard" | "soft";
188
199
  readonly rule?: string;
189
200
  /** Per-constraint description used to generate violation messages. */
190
201
  readonly description: string;
191
202
  readonly targetValue: number;
192
- readonly comparator: "<=" | ">=";
193
- readonly day?: string;
203
+ readonly comparator: "<=" | ">=" | "==";
204
+ readonly day?: DateString;
194
205
  readonly timeSlot?: string;
195
206
  readonly roleIds?: string[];
196
207
  readonly skillIds?: readonly string[];
@@ -204,7 +215,7 @@ export interface TrackedConstraint {
204
215
  */
205
216
  export interface CoverageExclusion {
206
217
  memberId: string;
207
- day: string;
218
+ day: DateString;
208
219
  startTime?: TimeOfDay;
209
220
  endTime?: TimeOfDay;
210
221
  }