dabke 0.82.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +22 -24
  3. package/dist/client.d.ts +39 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.schemas.d.ts +118 -18
  7. package/dist/client.schemas.d.ts.map +1 -1
  8. package/dist/client.schemas.js +110 -6
  9. package/dist/client.schemas.js.map +1 -1
  10. package/dist/client.types.d.ts +63 -7
  11. package/dist/client.types.d.ts.map +1 -1
  12. package/dist/client.types.js.map +1 -1
  13. package/dist/cpsat/cost.d.ts +5 -2
  14. package/dist/cpsat/cost.d.ts.map +1 -1
  15. package/dist/cpsat/cost.js +13 -6
  16. package/dist/cpsat/cost.js.map +1 -1
  17. package/dist/cpsat/model-builder.d.ts +42 -61
  18. package/dist/cpsat/model-builder.d.ts.map +1 -1
  19. package/dist/cpsat/model-builder.js +451 -56
  20. package/dist/cpsat/model-builder.js.map +1 -1
  21. package/dist/cpsat/response.d.ts +19 -7
  22. package/dist/cpsat/response.d.ts.map +1 -1
  23. package/dist/cpsat/response.js +48 -9
  24. package/dist/cpsat/response.js.map +1 -1
  25. package/dist/cpsat/rule-descriptor.d.ts +250 -0
  26. package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
  27. package/dist/cpsat/rule-descriptor.js +51 -0
  28. package/dist/cpsat/rule-descriptor.js.map +1 -0
  29. package/dist/cpsat/rules/artifacts.d.ts +9 -0
  30. package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
  31. package/dist/cpsat/rules/artifacts.js +31 -0
  32. package/dist/cpsat/rules/artifacts.js.map +1 -0
  33. package/dist/cpsat/rules/assign-together.d.ts +18 -20
  34. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  35. package/dist/cpsat/rules/assign-together.js +90 -54
  36. package/dist/cpsat/rules/assign-together.js.map +1 -1
  37. package/dist/cpsat/rules/assigned-day.d.ts +12 -0
  38. package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
  39. package/dist/cpsat/rules/assigned-day.js +43 -0
  40. package/dist/cpsat/rules/assigned-day.js.map +1 -0
  41. package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
  42. package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
  43. package/dist/cpsat/rules/assignment-priority.js +47 -50
  44. package/dist/cpsat/rules/assignment-priority.js.map +1 -1
  45. package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
  46. package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
  47. package/dist/cpsat/rules/assignment-terms.js +19 -0
  48. package/dist/cpsat/rules/assignment-terms.js.map +1 -0
  49. package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
  50. package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
  51. package/dist/cpsat/rules/concurrent-intervals.js +57 -0
  52. package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
  53. package/dist/cpsat/rules/cost-utils.d.ts +1 -3
  54. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
  55. package/dist/cpsat/rules/cost-utils.js +0 -7
  56. package/dist/cpsat/rules/cost-utils.js.map +1 -1
  57. package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
  58. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  59. package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
  60. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  61. package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
  62. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  63. package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
  64. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  65. package/dist/cpsat/rules/index.d.ts +30 -21
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -21
  68. package/dist/cpsat/rules/index.js.map +1 -1
  69. package/dist/cpsat/rules/location-preference.d.ts +14 -21
  70. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  71. package/dist/cpsat/rules/location-preference.js +45 -45
  72. package/dist/cpsat/rules/location-preference.js.map +1 -1
  73. package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
  74. package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
  75. package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
  76. package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
  77. package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
  78. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  79. package/dist/cpsat/rules/max-consecutive-days.js +67 -66
  80. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  81. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
  82. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
  83. package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
  84. package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
  85. package/dist/cpsat/rules/max-days-week.d.ts +30 -0
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
  87. package/dist/cpsat/rules/max-days-week.js +83 -0
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -0
  89. package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
  90. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  91. package/dist/cpsat/rules/max-hours-day.js +56 -72
  92. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  93. package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
  94. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  95. package/dist/cpsat/rules/max-hours-week.js +60 -75
  96. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  97. package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
  98. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  99. package/dist/cpsat/rules/max-shifts-day.js +57 -73
  100. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  101. package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
  102. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  103. package/dist/cpsat/rules/min-consecutive-days.js +123 -94
  104. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  105. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
  106. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
  107. package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
  108. package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
  109. package/dist/cpsat/rules/min-days-week.d.ts +21 -0
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
  111. package/dist/cpsat/rules/min-days-week.js +95 -0
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -0
  113. package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
  114. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  115. package/dist/cpsat/rules/min-hours-day.js +72 -54
  116. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  117. package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
  118. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  119. package/dist/cpsat/rules/min-hours-week.js +75 -57
  120. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  121. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
  122. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  123. package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
  124. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  125. package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
  126. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  127. package/dist/cpsat/rules/minimize-cost.js +151 -163
  128. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  129. package/dist/cpsat/rules/must-assign.d.ts +12 -0
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
  131. package/dist/cpsat/rules/must-assign.js +54 -0
  132. package/dist/cpsat/rules/must-assign.js.map +1 -0
  133. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
  134. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  135. package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
  136. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  137. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
  138. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  139. package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
  140. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  141. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
  142. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  143. package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
  144. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  145. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
  146. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  147. package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
  148. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  149. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
  150. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  151. package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
  152. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  153. package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
  154. package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
  155. package/dist/cpsat/rules/pattern-eligibility.js +16 -0
  156. package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
  157. package/dist/cpsat/rules/pattern-time.d.ts +5 -0
  158. package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
  159. package/dist/cpsat/rules/pattern-time.js +20 -0
  160. package/dist/cpsat/rules/pattern-time.js.map +1 -0
  161. package/dist/cpsat/rules/registry.d.ts +217 -27
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -31
  164. package/dist/cpsat/rules/registry.js.map +1 -1
  165. package/dist/cpsat/rules/resolver.d.ts +9 -7
  166. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  167. package/dist/cpsat/rules/resolver.js +19 -18
  168. package/dist/cpsat/rules/resolver.js.map +1 -1
  169. package/dist/cpsat/rules/role-preference.d.ts +24 -0
  170. package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
  171. package/dist/cpsat/rules/role-preference.js +72 -0
  172. package/dist/cpsat/rules/role-preference.js.map +1 -0
  173. package/dist/cpsat/rules/rules.types.d.ts +93 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +25 -12
  176. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  177. package/dist/cpsat/rules/scope.types.js +14 -19
  178. package/dist/cpsat/rules/scope.types.js.map +1 -1
  179. package/dist/cpsat/rules/target-days-week.d.ts +31 -0
  180. package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
  181. package/dist/cpsat/rules/target-days-week.js +80 -0
  182. package/dist/cpsat/rules/target-days-week.js.map +1 -0
  183. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
  184. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
  185. package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
  186. package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
  187. package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
  188. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  189. package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
  190. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  191. package/dist/cpsat/rules/time-off.d.ts +21 -57
  192. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  193. package/dist/cpsat/rules/time-off.js +114 -127
  194. package/dist/cpsat/rules/time-off.js.map +1 -1
  195. package/dist/cpsat/rules/variables.d.ts +4 -0
  196. package/dist/cpsat/rules/variables.d.ts.map +1 -0
  197. package/dist/cpsat/rules/variables.js +10 -0
  198. package/dist/cpsat/rules/variables.js.map +1 -0
  199. package/dist/cpsat/rules.d.ts +20 -3
  200. package/dist/cpsat/rules.d.ts.map +1 -1
  201. package/dist/cpsat/rules.js +19 -2
  202. package/dist/cpsat/rules.js.map +1 -1
  203. package/dist/cpsat/semantic-time.d.ts +7 -6
  204. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  205. package/dist/cpsat/semantic-time.js +15 -28
  206. package/dist/cpsat/semantic-time.js.map +1 -1
  207. package/dist/cpsat/types.d.ts +47 -4
  208. package/dist/cpsat/types.d.ts.map +1 -1
  209. package/dist/cpsat/types.js +24 -1
  210. package/dist/cpsat/types.js.map +1 -1
  211. package/dist/cpsat/utils.d.ts +23 -6
  212. package/dist/cpsat/utils.d.ts.map +1 -1
  213. package/dist/cpsat/utils.js +76 -15
  214. package/dist/cpsat/utils.js.map +1 -1
  215. package/dist/cpsat/validation-reporter.d.ts +6 -2
  216. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  217. package/dist/cpsat/validation-reporter.js +122 -10
  218. package/dist/cpsat/validation-reporter.js.map +1 -1
  219. package/dist/cpsat/validation.types.d.ts +21 -10
  220. package/dist/cpsat/validation.types.d.ts.map +1 -1
  221. package/dist/datetime.utils.d.ts +7 -13
  222. package/dist/datetime.utils.d.ts.map +1 -1
  223. package/dist/datetime.utils.js +29 -36
  224. package/dist/datetime.utils.js.map +1 -1
  225. package/dist/index.d.ts +15 -9
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +11 -4
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +192 -0
  230. package/dist/schedule/cost.d.ts.map +1 -0
  231. package/dist/schedule/cost.js +151 -0
  232. package/dist/schedule/cost.js.map +1 -0
  233. package/dist/schedule/coverage.d.ts +114 -0
  234. package/dist/schedule/coverage.d.ts.map +1 -0
  235. package/dist/schedule/coverage.js +56 -0
  236. package/dist/schedule/coverage.js.map +1 -0
  237. package/dist/schedule/definition.d.ts +346 -0
  238. package/dist/schedule/definition.d.ts.map +1 -0
  239. package/dist/schedule/definition.js +668 -0
  240. package/dist/schedule/definition.js.map +1 -0
  241. package/dist/schedule/index.d.ts +67 -0
  242. package/dist/schedule/index.d.ts.map +1 -0
  243. package/dist/schedule/index.js +69 -0
  244. package/dist/schedule/index.js.map +1 -0
  245. package/dist/schedule/rules.d.ts +678 -0
  246. package/dist/schedule/rules.d.ts.map +1 -0
  247. package/dist/schedule/rules.js +554 -0
  248. package/dist/schedule/rules.js.map +1 -0
  249. package/dist/schedule/shift-patterns.d.ts +17 -0
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -0
  251. package/dist/schedule/shift-patterns.js +24 -0
  252. package/dist/schedule/shift-patterns.js.map +1 -0
  253. package/dist/schedule/time-periods.d.ts +79 -0
  254. package/dist/schedule/time-periods.d.ts.map +1 -0
  255. package/dist/schedule/time-periods.js +101 -0
  256. package/dist/schedule/time-periods.js.map +1 -0
  257. package/dist/types.d.ts +67 -3
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +64 -0
  260. package/dist/types.js.map +1 -1
  261. package/package.json +8 -14
  262. package/solver/src/solver/models.py +41 -4
  263. package/solver/src/solver/solver.py +546 -157
  264. package/solver/tests/test_solver.py +363 -7
  265. package/src/client.schemas.ts +127 -10
  266. package/src/client.ts +3 -0
  267. package/src/client.types.ts +73 -7
  268. package/src/cpsat/cost.ts +17 -7
  269. package/src/cpsat/model-builder.ts +614 -129
  270. package/src/cpsat/response.ts +57 -13
  271. package/src/cpsat/rule-descriptor.ts +311 -0
  272. package/src/cpsat/rules/artifacts.ts +56 -0
  273. package/src/cpsat/rules/assign-together.ts +104 -69
  274. package/src/cpsat/rules/assigned-day.ts +70 -0
  275. package/src/cpsat/rules/assignment-priority.ts +54 -58
  276. package/src/cpsat/rules/assignment-terms.ts +43 -0
  277. package/src/cpsat/rules/concurrent-intervals.ts +93 -0
  278. package/src/cpsat/rules/cost-utils.ts +1 -9
  279. package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
  280. package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
  281. package/src/cpsat/rules/index.ts +30 -21
  282. package/src/cpsat/rules/location-preference.ts +47 -52
  283. package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
  284. package/src/cpsat/rules/max-consecutive-days.ts +74 -91
  285. package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
  286. package/src/cpsat/rules/max-days-week.ts +117 -0
  287. package/src/cpsat/rules/max-hours-day.ts +64 -84
  288. package/src/cpsat/rules/max-hours-week.ts +74 -90
  289. package/src/cpsat/rules/max-shifts-day.ts +65 -85
  290. package/src/cpsat/rules/min-consecutive-days.ts +161 -127
  291. package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
  292. package/src/cpsat/rules/min-days-week.ts +122 -0
  293. package/src/cpsat/rules/min-hours-day.ts +79 -58
  294. package/src/cpsat/rules/min-hours-week.ts +87 -62
  295. package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
  296. package/src/cpsat/rules/minimize-cost.ts +175 -188
  297. package/src/cpsat/rules/must-assign.ts +71 -0
  298. package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
  299. package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
  300. package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
  301. package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
  302. package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
  303. package/src/cpsat/rules/pattern-eligibility.ts +26 -0
  304. package/src/cpsat/rules/pattern-time.ts +30 -0
  305. package/src/cpsat/rules/registry.ts +140 -56
  306. package/src/cpsat/rules/resolver.ts +58 -30
  307. package/src/cpsat/rules/role-preference.ts +92 -0
  308. package/src/cpsat/rules/rules.types.ts +92 -17
  309. package/src/cpsat/rules/scope.types.ts +32 -27
  310. package/src/cpsat/rules/target-days-week.ts +105 -0
  311. package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
  312. package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
  313. package/src/cpsat/rules/time-off.ts +154 -163
  314. package/src/cpsat/rules/variables.ts +11 -0
  315. package/src/cpsat/rules.ts +43 -17
  316. package/src/cpsat/semantic-time.ts +29 -43
  317. package/src/cpsat/types.ts +53 -4
  318. package/src/cpsat/utils.ts +92 -20
  319. package/src/cpsat/validation-reporter.ts +170 -19
  320. package/src/cpsat/validation.types.ts +22 -11
  321. package/src/datetime.utils.ts +45 -44
  322. package/src/index.ts +72 -15
  323. package/src/schedule/cost.ts +221 -0
  324. package/src/schedule/coverage.ts +164 -0
  325. package/src/schedule/definition.ts +1447 -0
  326. package/src/schedule/index.ts +132 -0
  327. package/src/schedule/rules.ts +1028 -0
  328. package/src/schedule/shift-patterns.ts +29 -0
  329. package/src/schedule/time-periods.ts +120 -0
  330. package/src/types.ts +114 -2
  331. package/dist/llms.d.ts +0 -2
  332. package/dist/llms.d.ts.map +0 -1
  333. package/dist/llms.js +0 -3
  334. package/dist/llms.js.map +0 -1
  335. package/dist/schedule.d.ts +0 -917
  336. package/dist/schedule.d.ts.map +0 -1
  337. package/dist/schedule.js +0 -1323
  338. package/dist/schedule.js.map +0 -1
  339. package/llms.txt +0 -758
  340. package/src/llms.ts +0 -3
  341. package/src/schedule.ts +0 -1960
@@ -0,0 +1,668 @@
1
+ /**
2
+ * Schedule definition, compilation, and solving.
3
+ *
4
+ * @module
5
+ */
6
+ import { defineSemanticTimes } from "../cpsat/semantic-time.js";
7
+ import { resolveDaysFromPeriod } from "../datetime.utils.js";
8
+ import { ModelBuilder } from "../cpsat/model-builder.js";
9
+ import { assertNoBuiltInCpsatRuleOverrides, assertValidCpsatRuleRegistry, builtInCpsatRuleRegistry, } from "../cpsat/rules/registry.js";
10
+ import { parseSolverResponse, resolveAssignments } from "../cpsat/response.js";
11
+ import { calculateScheduleCost } from "../cpsat/cost.js";
12
+ import { resolveAppliesTo } from "./rules.js";
13
+ // ============================================================================
14
+ // Schedule class
15
+ // ============================================================================
16
+ /**
17
+ * An immutable schedule definition.
18
+ *
19
+ * Created by {@link schedule}, composed via {@link Schedule.with},
20
+ * and solved via {@link Schedule.solve}.
21
+ *
22
+ * @category Schedule Definition
23
+ */
24
+ export class Schedule {
25
+ #config;
26
+ /** @internal */
27
+ constructor(config) {
28
+ this.#config = config;
29
+ }
30
+ /** @internal Returns a defensive copy of the config for merging. */
31
+ _getConfig() {
32
+ return {
33
+ ...this.#config,
34
+ roleIds: [...this.#config.roleIds],
35
+ skillIds: [...this.#config.skillIds],
36
+ times: { ...this.#config.times },
37
+ coverage: [...this.#config.coverage],
38
+ shiftPatterns: [...this.#config.shiftPatterns],
39
+ rules: [...this.#config.rules],
40
+ ruleRegistry: { ...this.#config.ruleRegistry },
41
+ members: [...this.#config.members],
42
+ };
43
+ }
44
+ // --------------------------------------------------------------------------
45
+ // Inspection
46
+ // --------------------------------------------------------------------------
47
+ /** Declared role IDs. */
48
+ get roleIds() {
49
+ return this.#config.roleIds;
50
+ }
51
+ /** Declared skill IDs. */
52
+ get skillIds() {
53
+ return this.#config.skillIds;
54
+ }
55
+ /** Names of declared semantic times. */
56
+ get timeNames() {
57
+ return Object.keys(this.#config.times);
58
+ }
59
+ /** Shift pattern IDs. */
60
+ get shiftPatternIds() {
61
+ return this.#config.shiftPatterns.map((sp) => sp.id);
62
+ }
63
+ /** Internal rule identifiers in kebab-case. */
64
+ get ruleNames() {
65
+ return this.#config.rules.map((r) => r._rule);
66
+ }
67
+ // --------------------------------------------------------------------------
68
+ // Composition
69
+ // --------------------------------------------------------------------------
70
+ /**
71
+ * Merges schedules or members onto this schedule, returning a new
72
+ * immutable `Schedule`. The original is untouched.
73
+ *
74
+ * Accepts any mix of `Schedule` instances and `SchedulingMember[]` arrays.
75
+ *
76
+ * Merge semantics (when merging schedules):
77
+ * - Roles: union (additive)
78
+ * - Skills: union (additive)
79
+ * - Times: additive; error on name collision
80
+ * - Coverage: additive
81
+ * - Shift patterns: additive; error on ID collision
82
+ * - Rules: additive
83
+ * - Members: additive; error on duplicate ID
84
+ *
85
+ * Validation runs eagerly: role/skill disjointness, coverage targets
86
+ * referencing declared roles/skills, member role references, etc.
87
+ */
88
+ with(...args) {
89
+ const merged = mergeConfig(this.#config, args);
90
+ return new Schedule(merged);
91
+ }
92
+ // --------------------------------------------------------------------------
93
+ // Solve / compile
94
+ // --------------------------------------------------------------------------
95
+ /**
96
+ * Compiles, validates, solves, and parses in one call.
97
+ *
98
+ * @param client - Solver client (e.g., `new HttpSolverClient(fetch, url)`)
99
+ * @param options - Date range and optional pinned assignments
100
+ */
101
+ async solve(client, options) {
102
+ const strategy = resolveSolveStrategy(options.strategy);
103
+ const compiled = this.compile(options);
104
+ if (!compiled.canSolve) {
105
+ return {
106
+ status: "infeasible",
107
+ assignments: [],
108
+ validation: compiled.validation,
109
+ };
110
+ }
111
+ const response = await client.solve(compiled.request);
112
+ return buildSolveResult(response, compiled, this.#config, strategy);
113
+ }
114
+ /**
115
+ * Diagnostic escape hatch. Compiles the schedule without solving.
116
+ *
117
+ * @param options - Date range and optional pinned assignments
118
+ */
119
+ compile(options) {
120
+ if (options.pinned && options.pinned.length > 0) {
121
+ throw new Error("Pinned assignments are not yet supported.");
122
+ }
123
+ const strategy = resolveSolveStrategy(options.strategy);
124
+ const modelConfig = resolveToModelConfig(this.#config, options, strategy);
125
+ const builder = new ModelBuilder(modelConfig);
126
+ const result = builder.compile();
127
+ return { ...result, builder };
128
+ }
129
+ }
130
+ export function schedule(config) {
131
+ const customRuleRegistry = config.ruleRegistry ?? {};
132
+ validateRuleRegistryExtension(customRuleRegistry);
133
+ const merged = buildMergedConfig(config, {
134
+ ...builtInCpsatRuleRegistry,
135
+ ...customRuleRegistry,
136
+ });
137
+ validateConfig(merged);
138
+ return new Schedule(merged);
139
+ }
140
+ /**
141
+ * Create a schedule definition with an explicit complete rule registry.
142
+ *
143
+ * @remarks
144
+ * Use this when you want full control over the active rule registry, including
145
+ * schedules that do not include the built-in rules.
146
+ *
147
+ * @category Schedule Definition
148
+ */
149
+ export function scheduleWithRuleRegistry(ruleRegistry, config) {
150
+ assertValidCpsatRuleRegistry(ruleRegistry);
151
+ assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
152
+ const merged = buildMergedConfig(config, ruleRegistry);
153
+ validateConfig(merged);
154
+ return new Schedule(merged);
155
+ }
156
+ export function partialSchedule(config) {
157
+ const customRuleRegistry = config.ruleRegistry ?? {};
158
+ validateRuleRegistryExtension(customRuleRegistry);
159
+ const merged = buildMergedConfig({
160
+ roleIds: [],
161
+ times: {},
162
+ coverage: [],
163
+ shiftPatterns: [],
164
+ ...config,
165
+ }, {
166
+ ...builtInCpsatRuleRegistry,
167
+ ...customRuleRegistry,
168
+ });
169
+ validateConfig(merged);
170
+ return new Schedule(merged);
171
+ }
172
+ // ============================================================================
173
+ // Internal: Build merged config from user input
174
+ // ============================================================================
175
+ function buildMergedConfig(config, ruleRegistry) {
176
+ return {
177
+ roleIds: [...config.roleIds],
178
+ skillIds: [...(config.skillIds ?? [])],
179
+ times: { ...config.times },
180
+ coverage: [...config.coverage],
181
+ shiftPatterns: [...config.shiftPatterns],
182
+ rules: [...(config.rules ?? [])],
183
+ ruleRegistry: { ...ruleRegistry },
184
+ members: [...(config.members ?? [])],
185
+ dayOfWeek: config.dayOfWeek,
186
+ weekStartsOn: config.weekStartsOn,
187
+ };
188
+ }
189
+ // ============================================================================
190
+ // Internal: Validate merged config
191
+ // ============================================================================
192
+ function validateRuleRegistryExtension(ruleRegistry) {
193
+ assertValidCpsatRuleRegistry(ruleRegistry);
194
+ assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
195
+ }
196
+ function validateConfig(config) {
197
+ const roles = new Set(config.roleIds);
198
+ const skills = new Set(config.skillIds);
199
+ assertValidCpsatRuleRegistry(config.ruleRegistry);
200
+ // Validate role/skill disjointness
201
+ for (const skill of skills) {
202
+ if (roles.has(skill)) {
203
+ throw new Error(`"${skill}" is declared as both a role and a skill. Roles and skills must be disjoint.`);
204
+ }
205
+ }
206
+ // Validate shift pattern role references
207
+ for (const sp of config.shiftPatterns) {
208
+ if (sp.roleIds) {
209
+ for (const role of sp.roleIds) {
210
+ if (!roles.has(role)) {
211
+ throw new Error(`Shift pattern "${sp.id}" references unknown role "${role}". ` +
212
+ `Declared roles: ${[...roles].join(", ")}`);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ // Validate coverage entries
218
+ for (const entry of config.coverage) {
219
+ validateCoverageEntry(entry, roles, skills);
220
+ }
221
+ // Validate member references
222
+ const memberIds = new Set();
223
+ for (const member of config.members) {
224
+ if (memberIds.has(member.id)) {
225
+ throw new Error(`Duplicate member ID "${member.id}".`);
226
+ }
227
+ memberIds.add(member.id);
228
+ if (roles.has(member.id)) {
229
+ throw new Error(`Member ID "${member.id}" collides with a declared role name.`);
230
+ }
231
+ if (skills.has(member.id)) {
232
+ throw new Error(`Member ID "${member.id}" collides with a declared skill name.`);
233
+ }
234
+ for (const role of member.roleIds) {
235
+ if (!roles.has(role)) {
236
+ throw new Error(`Member "${member.id}" references unknown role "${role}". ` +
237
+ `Declared roles: ${[...roles].join(", ")}`);
238
+ }
239
+ }
240
+ if (member.skillIds) {
241
+ for (const skill of member.skillIds) {
242
+ if (!skills.has(skill)) {
243
+ throw new Error(`Member "${member.id}" references unknown skill "${skill}". ` +
244
+ `Declared skills: ${[...skills].join(", ")}`);
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+ function validateCoverageEntry(entry, roles, skills) {
251
+ const targets = Array.isArray(entry.target) ? entry.target : [entry.target];
252
+ if (Array.isArray(entry.target)) {
253
+ for (const target of targets) {
254
+ if (!roles.has(target)) {
255
+ throw new Error(`Coverage for "${entry.timeName}" references "${target}" in a role OR group, ` +
256
+ `but it is not a declared role. Declared roles: ${[...roles].join(", ")}`);
257
+ }
258
+ }
259
+ }
260
+ else {
261
+ if (!roles.has(entry.target) && !skills.has(entry.target)) {
262
+ throw new Error(`Coverage for "${entry.timeName}" references unknown target "${entry.target}". ` +
263
+ `Declared roles: ${[...roles].join(", ")}. ` +
264
+ `Declared skills: ${[...skills].join(", ")}`);
265
+ }
266
+ }
267
+ if (entry.options.skillIds) {
268
+ for (const s of entry.options.skillIds) {
269
+ if (!skills.has(s)) {
270
+ throw new Error(`Coverage for "${entry.timeName}" uses skill filter "${s}" ` +
271
+ `which is not a declared skill. Declared skills: ${[...skills].join(", ")}`);
272
+ }
273
+ }
274
+ }
275
+ }
276
+ // ============================================================================
277
+ // Internal: Merge logic
278
+ // ============================================================================
279
+ function mergeConfig(base, args) {
280
+ const result = {
281
+ roleIds: [...base.roleIds],
282
+ skillIds: [...base.skillIds],
283
+ times: { ...base.times },
284
+ coverage: [...base.coverage],
285
+ shiftPatterns: [...base.shiftPatterns],
286
+ rules: [...base.rules],
287
+ ruleRegistry: { ...base.ruleRegistry },
288
+ members: [...base.members],
289
+ dayOfWeek: base.dayOfWeek,
290
+ weekStartsOn: base.weekStartsOn,
291
+ };
292
+ for (const arg of args) {
293
+ if (arg instanceof Schedule) {
294
+ mergeScheduleFragment(result, arg);
295
+ }
296
+ else if (Array.isArray(arg)) {
297
+ mergeMembers(result, arg);
298
+ }
299
+ else {
300
+ throw new Error(`Unexpected argument passed to .with(): expected Schedule or SchedulingMember[], got ${typeof arg}`);
301
+ }
302
+ }
303
+ // Validate the merged result
304
+ validateConfig(result);
305
+ return result;
306
+ }
307
+ function mergeScheduleFragment(result, s) {
308
+ const other = s._getConfig();
309
+ // dayOfWeek: error on conflict (semantics of union vs intersection are ambiguous)
310
+ if (other.dayOfWeek !== undefined) {
311
+ if (result.dayOfWeek !== undefined) {
312
+ const baseSet = new Set(result.dayOfWeek);
313
+ const same = result.dayOfWeek.length === other.dayOfWeek.length &&
314
+ other.dayOfWeek.every((d) => baseSet.has(d));
315
+ if (!same) {
316
+ throw new Error("Cannot merge schedules with different dayOfWeek filters. " +
317
+ `Base has [${result.dayOfWeek.join(", ")}], ` +
318
+ `incoming has [${other.dayOfWeek.join(", ")}].`);
319
+ }
320
+ }
321
+ else {
322
+ result.dayOfWeek = other.dayOfWeek;
323
+ }
324
+ }
325
+ // weekStartsOn: error on conflict (only one week boundary for weekly rules)
326
+ if (other.weekStartsOn !== undefined) {
327
+ if (result.weekStartsOn !== undefined && result.weekStartsOn !== other.weekStartsOn) {
328
+ throw new Error("Cannot merge schedules with different weekStartsOn values. " +
329
+ `Base has "${result.weekStartsOn}", incoming has "${other.weekStartsOn}".`);
330
+ }
331
+ result.weekStartsOn = other.weekStartsOn;
332
+ }
333
+ // Roles: union
334
+ for (const role of other.roleIds) {
335
+ if (!result.roleIds.includes(role)) {
336
+ result.roleIds.push(role);
337
+ }
338
+ }
339
+ // Skills: union
340
+ for (const skill of other.skillIds) {
341
+ if (!result.skillIds.includes(skill)) {
342
+ result.skillIds.push(skill);
343
+ }
344
+ }
345
+ // Times: additive, error on collision
346
+ for (const [name, entry] of Object.entries(other.times)) {
347
+ if (name in result.times) {
348
+ throw new Error(`Time name "${name}" already exists. Cannot merge schedules with colliding time names.`);
349
+ }
350
+ result.times[name] = entry;
351
+ }
352
+ // Coverage: additive
353
+ result.coverage.push(...other.coverage);
354
+ // Shift patterns: additive, error on ID collision
355
+ const existingIds = new Set(result.shiftPatterns.map((sp) => sp.id));
356
+ for (const sp of other.shiftPatterns) {
357
+ if (existingIds.has(sp.id)) {
358
+ throw new Error(`Shift pattern ID "${sp.id}" already exists. Cannot merge schedules with colliding shift pattern IDs.`);
359
+ }
360
+ result.shiftPatterns.push(sp);
361
+ existingIds.add(sp.id);
362
+ }
363
+ // Rules: additive
364
+ result.rules.push(...other.rules);
365
+ // Rule registry: merge, error on collision
366
+ for (const [name, descriptor] of Object.entries(other.ruleRegistry)) {
367
+ if (name in result.ruleRegistry && result.ruleRegistry[name] !== descriptor) {
368
+ throw new Error(`Rule registry entry "${name}" already registered. Cannot merge schedules with colliding rule registries.`);
369
+ }
370
+ result.ruleRegistry[name] = descriptor;
371
+ }
372
+ // Members: additive, error on duplicate ID
373
+ const existingMemberIds = new Set(result.members.map((m) => m.id));
374
+ for (const member of other.members) {
375
+ if (existingMemberIds.has(member.id)) {
376
+ throw new Error(`Duplicate member ID "${member.id}". Cannot merge schedules with colliding member IDs.`);
377
+ }
378
+ result.members.push(member);
379
+ existingMemberIds.add(member.id);
380
+ }
381
+ }
382
+ function mergeMembers(result, incoming) {
383
+ const existingIds = new Set(result.members.map((m) => m.id));
384
+ for (const member of incoming) {
385
+ if (existingIds.has(member.id)) {
386
+ throw new Error(`Duplicate member ID "${member.id}". Cannot merge members with colliding IDs.`);
387
+ }
388
+ result.members.push(member);
389
+ existingIds.add(member.id);
390
+ }
391
+ }
392
+ // ============================================================================
393
+ // Internal: Resolve to ModelBuilderConfig
394
+ // ============================================================================
395
+ function resolveToModelConfig(config, options, strategy) {
396
+ const roles = new Set(config.roleIds);
397
+ const skills = new Set(config.skillIds);
398
+ const memberIds = new Set(config.members.map((m) => m.id));
399
+ // Build semantic time context
400
+ const semanticTimes = defineSemanticTimes(config.times);
401
+ // Convert coverage entries to semantic coverage requirements
402
+ const coverageReqs = buildCoverageRequirements(config.coverage, roles, skills);
403
+ // Resolve scheduling period with dayOfWeek filter
404
+ const schedulingPeriod = {
405
+ dateRange: options.dateRange,
406
+ };
407
+ const resolvedPeriod = applyDaysFilter(schedulingPeriod, config.dayOfWeek);
408
+ const days = resolveDaysFromPeriod(resolvedPeriod);
409
+ // Resolve coverage
410
+ const resolvedCoverage = semanticTimes.resolve(coverageReqs, days);
411
+ // Resolve rules
412
+ const allRules = [...config.rules];
413
+ // Validate pay data when cost rules are present
414
+ const costRuleNames = new Set([
415
+ "minimize-cost",
416
+ "day-cost-multiplier",
417
+ "day-cost-surcharge",
418
+ "time-cost-surcharge",
419
+ "overtime-weekly-multiplier",
420
+ "overtime-weekly-surcharge",
421
+ "overtime-daily-multiplier",
422
+ "overtime-daily-surcharge",
423
+ "overtime-tiered-multiplier",
424
+ ]);
425
+ const hasCostRules = allRules.some((r) => costRuleNames.has(r._rule));
426
+ if (strategy.type === "optimize" && hasCostRules) {
427
+ const missingPay = config.members.filter((m) => !m.pay).map((m) => m.id);
428
+ if (missingPay.length > 0) {
429
+ throw new Error(`Cost rules require pay data on all members. Missing pay: ${missingPay.join(", ")}`);
430
+ }
431
+ }
432
+ // Sort rules so minimize-cost compiles before modifier rules
433
+ const sortedRules = sortCostRulesFirst(allRules);
434
+ const ruleConfigs = resolveRules(sortedRules, roles, skills, memberIds);
435
+ return {
436
+ members: config.members,
437
+ shiftPatterns: config.shiftPatterns,
438
+ schedulingPeriod: resolvedPeriod,
439
+ coverage: resolvedCoverage,
440
+ ruleConfigs,
441
+ ruleRegistry: config.ruleRegistry,
442
+ weekStartsOn: config.weekStartsOn,
443
+ solverOptions: buildSolverOptions(options),
444
+ strategy,
445
+ };
446
+ }
447
+ // ============================================================================
448
+ // Internal: Build SolveResult from solver response
449
+ // ============================================================================
450
+ function resolveSolveStrategy(strategy) {
451
+ if (strategy === undefined) {
452
+ return { type: "optimize" };
453
+ }
454
+ if (typeof strategy !== "object" || strategy === null || !("type" in strategy)) {
455
+ throw new Error("Solve strategy must be an object with a type field.");
456
+ }
457
+ const strategyType = strategy.type;
458
+ if (strategyType === "optimize" || strategyType === "feasibility-only") {
459
+ return { type: strategyType };
460
+ }
461
+ throw new Error(`Unknown solve strategy "${String(strategyType)}".`);
462
+ }
463
+ function buildSolverOptions(options) {
464
+ if (options.timeLimitSeconds === undefined) {
465
+ return undefined;
466
+ }
467
+ return { timeLimitSeconds: options.timeLimitSeconds };
468
+ }
469
+ function mapSolverStatus(solverStatus, strategy) {
470
+ switch (solverStatus) {
471
+ case "OPTIMAL":
472
+ return strategy.type === "feasibility-only" ? "feasible" : "optimal";
473
+ case "FEASIBLE":
474
+ return "feasible";
475
+ case "INFEASIBLE":
476
+ return "infeasible";
477
+ case "TIMEOUT":
478
+ case "ERROR":
479
+ return "no_solution";
480
+ default:
481
+ return "no_solution";
482
+ }
483
+ }
484
+ function buildSolveResult(response, compiled, config, strategy) {
485
+ const status = mapSolverStatus(response.status, strategy);
486
+ const hasFinalSolution = status === "optimal" || status === "feasible";
487
+ const parsed = hasFinalSolution
488
+ ? parseSolverResponse(response)
489
+ : {
490
+ status: response.status,
491
+ assignments: [],
492
+ statistics: response.statistics,
493
+ error: response.error,
494
+ };
495
+ compiled.builder.reporter.analyzeSolution(response, {
496
+ analyzeSoftConstraints: strategy.type === "optimize",
497
+ });
498
+ // Run post-solve validation when a solution exists
499
+ if (parsed.assignments.length > 0 && hasFinalSolution && strategy.type === "optimize") {
500
+ const resolved = resolveAssignments(parsed.assignments, compiled.builder.shiftPatterns);
501
+ compiled.builder.validateSolution(resolved);
502
+ }
503
+ const validation = compiled.builder.reporter.getValidation();
504
+ const result = {
505
+ status,
506
+ assignments: parsed.assignments,
507
+ validation,
508
+ };
509
+ // Compute cost breakdown when cost artifacts are present and a solution was found
510
+ if (parsed.assignments.length > 0 && hasFinalSolution && strategy.type === "optimize") {
511
+ const hasCostArtifacts = compiled.builder.rules.some((rule) => rule.artifacts.some((artifact) => artifact.kind === "cost"));
512
+ if (hasCostArtifacts) {
513
+ result.cost = calculateScheduleCost(parsed.assignments, {
514
+ members: config.members,
515
+ shiftPatterns: config.shiftPatterns,
516
+ rules: compiled.builder.rules,
517
+ days: compiled.builder.days,
518
+ weekStartsOn: compiled.builder.weekStartsOn,
519
+ });
520
+ }
521
+ }
522
+ return result;
523
+ }
524
+ // ============================================================================
525
+ // Internal: Coverage Translation
526
+ // ============================================================================
527
+ function buildCoverageRequirements(entries, roles, skills) {
528
+ return entries.map((entry) => {
529
+ // Variant form: produce a VariantCoverageRequirement
530
+ if (entry.variants) {
531
+ return buildVariantCoverageRequirement(entry, roles, skills);
532
+ }
533
+ // Simple form: produce a SemanticCoverageRequirement
534
+ const base = {
535
+ semanticTime: entry.timeName,
536
+ targetCount: entry.count,
537
+ };
538
+ if (entry.options.priority)
539
+ base.priority = entry.options.priority;
540
+ if (entry.options.dayOfWeek && entry.options.dayOfWeek.length > 0) {
541
+ base.dayOfWeek = entry.options.dayOfWeek;
542
+ }
543
+ if (entry.options.dates)
544
+ base.dates = entry.options.dates;
545
+ return buildSimpleCoverageTarget(entry, base, roles, skills);
546
+ });
547
+ }
548
+ /**
549
+ * Resolve the target (role/skill) for a simple coverage entry.
550
+ */
551
+ function buildSimpleCoverageTarget(entry, base, roles, skills) {
552
+ if (Array.isArray(entry.target)) {
553
+ return {
554
+ ...base,
555
+ roleIds: entry.target,
556
+ };
557
+ }
558
+ const singleTarget = entry.target;
559
+ if (roles.has(singleTarget)) {
560
+ if (entry.options.skillIds) {
561
+ return {
562
+ ...base,
563
+ roleIds: [singleTarget],
564
+ skillIds: entry.options.skillIds,
565
+ };
566
+ }
567
+ return {
568
+ ...base,
569
+ roleIds: [singleTarget],
570
+ };
571
+ }
572
+ if (skills.has(singleTarget)) {
573
+ return {
574
+ ...base,
575
+ skillIds: [singleTarget],
576
+ };
577
+ }
578
+ throw new Error(`Coverage target "${singleTarget}" is not a declared role or skill.`);
579
+ }
580
+ /**
581
+ * Build a VariantCoverageRequirement from a variant-form CoverageEntry.
582
+ */
583
+ function requireCoverageVariants(entry) {
584
+ if (!entry.variants || entry.variants.length === 0) {
585
+ throw new Error(`Coverage entry for "${entry.timeName}" must define at least one variant.`);
586
+ }
587
+ const first = entry.variants[0];
588
+ if (!first) {
589
+ throw new Error(`Coverage entry for "${entry.timeName}" must define at least one variant.`);
590
+ }
591
+ return [first, ...entry.variants.slice(1)];
592
+ }
593
+ function buildVariantCoverageRequirement(entry, roles, skills) {
594
+ const variants = requireCoverageVariants(entry);
595
+ const resolveTarget = () => {
596
+ if (Array.isArray(entry.target)) {
597
+ return { roleIds: entry.target };
598
+ }
599
+ const singleTarget = entry.target;
600
+ if (roles.has(singleTarget)) {
601
+ return { roleIds: [singleTarget] };
602
+ }
603
+ if (skills.has(singleTarget)) {
604
+ return { skillIds: [singleTarget] };
605
+ }
606
+ throw new Error(`Coverage target "${singleTarget}" is not a declared role or skill.`);
607
+ };
608
+ return {
609
+ semanticTime: entry.timeName,
610
+ variants,
611
+ ...resolveTarget(),
612
+ };
613
+ }
614
+ // ============================================================================
615
+ // Internal: Rule Translation
616
+ // ============================================================================
617
+ function resolveRules(rules, roles, skills, memberIds) {
618
+ const ctx = { roles, skills, memberIds };
619
+ return rules.map((rule) => {
620
+ // Rules with custom resolvers handle their own translation
621
+ if (rule._resolve) {
622
+ return rule._resolve(ctx);
623
+ }
624
+ // Default resolution: appliesTo → entity scope, dates → specificDates
625
+ const { _type, _rule, _resolve, appliesTo, dates, ...passthrough } = rule;
626
+ const entityScope = resolveAppliesTo(appliesTo, roles, skills, memberIds);
627
+ const resolvedDates = dates ? { specificDates: dates } : {};
628
+ return {
629
+ name: _rule,
630
+ ...passthrough,
631
+ ...entityScope,
632
+ ...resolvedDates,
633
+ };
634
+ });
635
+ }
636
+ // ============================================================================
637
+ // Internal: Cost Rule Ordering
638
+ // ============================================================================
639
+ /**
640
+ * Sorts rules so that `minimize-cost` compiles before cost modifier rules.
641
+ *
642
+ * The `minimize-cost` rule must be compiled first because modifier rules
643
+ * (multipliers, surcharges) reference cost variables it creates.
644
+ * Non-cost rules retain their original relative order.
645
+ */
646
+ function sortCostRulesFirst(rules) {
647
+ return rules.toSorted((a, b) => {
648
+ const aIsCostBase = a._rule === "minimize-cost" ? 0 : 1;
649
+ const bIsCostBase = b._rule === "minimize-cost" ? 0 : 1;
650
+ return aIsCostBase - bIsCostBase;
651
+ });
652
+ }
653
+ // ============================================================================
654
+ // Internal: Scheduling Period Helpers
655
+ // ============================================================================
656
+ function applyDaysFilter(schedulingPeriod, dayOfWeek) {
657
+ if (!dayOfWeek || dayOfWeek.length === 0) {
658
+ return schedulingPeriod;
659
+ }
660
+ const existingDays = schedulingPeriod.dayOfWeek;
661
+ if (!existingDays || existingDays.length === 0) {
662
+ return { ...schedulingPeriod, dayOfWeek: [...dayOfWeek] };
663
+ }
664
+ const existingSet = new Set(existingDays);
665
+ const intersected = dayOfWeek.filter((day) => existingSet.has(day));
666
+ return { ...schedulingPeriod, dayOfWeek: intersected };
667
+ }
668
+ //# sourceMappingURL=definition.js.map