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
package/dist/schedule.js DELETED
@@ -1,1323 +0,0 @@
1
- /**
2
- * High-level schedule definition API.
3
- *
4
- * Small, composable factory functions that produce a complete scheduling
5
- * configuration. Designed for LLM code generation: each concept is a single
6
- * function call with per-call type safety.
7
- *
8
- * @example
9
- * ```typescript
10
- * import {
11
- * schedule, t, time, cover, shift,
12
- * maxHoursPerDay, maxHoursPerWeek, minRestBetweenShifts,
13
- * weekdays, weekend,
14
- * } from "dabke";
15
- *
16
- * const venue = schedule({
17
- * roleIds: ["cashier", "floor_lead", "stocker"],
18
- * skillIds: ["keyholder"],
19
- *
20
- * times: {
21
- * opening: time({ startTime: t(8), endTime: t(10) }),
22
- * peak_hours: time(
23
- * { startTime: t(11), endTime: t(14) },
24
- * { startTime: t(10), endTime: t(15), dayOfWeek: weekend },
25
- * ),
26
- * closing: time({ startTime: t(20), endTime: t(22) }),
27
- * },
28
- *
29
- * coverage: [
30
- * cover("opening", "keyholder", 1),
31
- * cover("peak_hours", "cashier", 3, { dayOfWeek: weekdays }),
32
- * cover("peak_hours", "cashier", 5, { dayOfWeek: weekend }),
33
- * cover("closing", "floor_lead", 1),
34
- * ],
35
- *
36
- * shiftPatterns: [
37
- * shift("morning", t(8), t(14)),
38
- * shift("afternoon", t(14), t(22)),
39
- * ],
40
- *
41
- * rules: [
42
- * maxHoursPerDay(10),
43
- * maxHoursPerWeek(48),
44
- * minRestBetweenShifts(10),
45
- * ],
46
- * });
47
- *
48
- * const result = await venue
49
- * .with([
50
- * { id: "alice", roleIds: ["cashier"], skillIds: ["keyholder"] },
51
- * ])
52
- * .solve(client, { dateRange: { start: "2025-03-03", end: "2025-03-09" } });
53
- * ```
54
- *
55
- * @module
56
- */
57
- import { defineSemanticTimes } from "./cpsat/semantic-time.js";
58
- import { resolveDaysFromPeriod } from "./datetime.utils.js";
59
- import { ModelBuilder } from "./cpsat/model-builder.js";
60
- import { builtInCpsatRuleFactories } from "./cpsat/rules/registry.js";
61
- import { parseSolverResponse, resolveAssignments } from "./cpsat/response.js";
62
- import { calculateScheduleCost } from "./cpsat/cost.js";
63
- // ============================================================================
64
- // Primitives
65
- // ============================================================================
66
- /**
67
- * Creates a {@link TimeOfDay} value.
68
- *
69
- * @param hours - Hour component (0-23)
70
- * @param minutes - Minute component (0-59)
71
- *
72
- * @example Hours only
73
- * ```ts
74
- * t(9) // { hours: 9, minutes: 0 }
75
- * ```
76
- *
77
- * @example Hours and minutes
78
- * ```ts
79
- * t(17, 30) // { hours: 17, minutes: 30 }
80
- * ```
81
- *
82
- * @category Time Periods
83
- */
84
- export function t(hours, minutes = 0) {
85
- return { hours, minutes };
86
- }
87
- /**
88
- * Monday through Friday.
89
- *
90
- * @category Time Periods
91
- */
92
- export const weekdays = [
93
- "monday",
94
- "tuesday",
95
- "wednesday",
96
- "thursday",
97
- "friday",
98
- ];
99
- /**
100
- * Saturday and Sunday.
101
- *
102
- * @category Time Periods
103
- */
104
- export const weekend = ["saturday", "sunday"];
105
- // ============================================================================
106
- // Semantic Times
107
- // ============================================================================
108
- /**
109
- * Define a named semantic time period.
110
- *
111
- * @remarks
112
- * Each entry has `startTime`/`endTime` and optional `dayOfWeek` or `dates`
113
- * scoping. Entries without scoping are the default.
114
- *
115
- * @example
116
- * ```typescript
117
- * times: {
118
- * // Simple: same times every day
119
- * lunch: time({ startTime: t(12), endTime: t(15) }),
120
- *
121
- * // Variants: different times on weekends
122
- * dinner: time(
123
- * { startTime: t(17), endTime: t(21) },
124
- * { startTime: t(18), endTime: t(22), dayOfWeek: weekend },
125
- * ),
126
- *
127
- * // Point-in-time window (keyholder at opening)
128
- * opening: time({ startTime: t(8, 30), endTime: t(9) }),
129
- * }
130
- * ```
131
- *
132
- * @privateRemarks
133
- * Resolution precedence: `dates` > `dayOfWeek` > default.
134
- *
135
- * @category Time Periods
136
- */
137
- export function time(...entries) {
138
- // Validate: at most one default (no dayOfWeek and no dates)
139
- const defaults = entries.filter((e) => !e.dayOfWeek && !e.dates);
140
- if (defaults.length > 1) {
141
- throw new Error("time() accepts at most one default entry (without dayOfWeek or dates). " +
142
- `Found ${defaults.length} default entries.`);
143
- }
144
- // Single entry without scoping: simple SemanticTimeDef
145
- if (entries.length === 1 && !entries[0].dayOfWeek && !entries[0].dates) {
146
- return {
147
- startTime: entries[0].startTime,
148
- endTime: entries[0].endTime,
149
- };
150
- }
151
- // Multiple entries or scoped entries: shallow-copy to decouple from caller
152
- return entries.map((entry) => Object.assign({}, entry));
153
- }
154
- export function cover(timeName, target, countOrFirstVariant, ...rest) {
155
- if (typeof countOrFirstVariant === "number") {
156
- // Simple form: cover(time, target, count, opts?)
157
- return {
158
- _type: "coverage",
159
- timeName,
160
- target,
161
- count: countOrFirstVariant,
162
- options: rest[0] ?? {},
163
- };
164
- }
165
- // Variant form: cover(time, target, ...variants)
166
- const variants = [countOrFirstVariant, ...rest];
167
- const defaults = variants.filter((v) => !v.dayOfWeek && !v.dates);
168
- if (defaults.length > 1) {
169
- throw new Error("cover() accepts at most one default variant (without dayOfWeek or dates). " +
170
- `Found ${defaults.length} default variants.`);
171
- }
172
- return {
173
- _type: "coverage",
174
- timeName,
175
- target,
176
- count: 0,
177
- options: {},
178
- variants,
179
- };
180
- }
181
- // ============================================================================
182
- // Shift Patterns
183
- // ============================================================================
184
- /**
185
- * Define a shift pattern: a time slot available for employee assignment.
186
- *
187
- * @remarks
188
- * Each pattern repeats daily unless filtered by `dayOfWeek`.
189
- *
190
- * @example
191
- * ```typescript
192
- * shiftPatterns: [
193
- * shift("morning", t(11, 30), t(15)),
194
- * shift("evening", t(17), t(22)),
195
- *
196
- * // Role-restricted shift
197
- * shift("kitchen", t(6), t(14), { roleIds: ["chef", "prep_cook"] }),
198
- *
199
- * // Day-restricted shift
200
- * shift("saturday_short", t(9), t(14), { dayOfWeek: ["saturday"] }),
201
- *
202
- * // Location-specific shift
203
- * shift("terrace_lunch", t(12), t(16), { locationId: "terrace" }),
204
- * ]
205
- * ```
206
- *
207
- * @category Shift Patterns
208
- */
209
- export function shift(id, startTime, endTime, opts) {
210
- const pattern = { id, startTime, endTime };
211
- if (opts?.roleIds)
212
- pattern.roleIds = opts.roleIds;
213
- if (opts?.dayOfWeek)
214
- pattern.dayOfWeek = opts.dayOfWeek;
215
- if (opts?.locationId)
216
- pattern.locationId = opts.locationId;
217
- return pattern;
218
- }
219
- /**
220
- * Creates a rule entry for use in {@link ScheduleConfig.rules}.
221
- *
222
- * Built-in rules use the helpers (`maxHoursPerDay`, `timeOff`, etc.).
223
- * Custom rules can use `defineRule` to create entries that plug into the
224
- * same resolution and compilation pipeline.
225
- *
226
- * @param name - Rule name. Must match a key in the rule factory registry.
227
- * @param fields - Rule-specific configuration fields.
228
- * @param resolve - Optional custom resolver. When omitted, the default
229
- * resolution applies: `appliesTo` is mapped to `roleIds`/`skillIds`/`memberIds`,
230
- * `dates` is renamed to `specificDates`, and all other fields pass through.
231
- *
232
- * @category Rules
233
- */
234
- export function defineRule(name, fields, resolve) {
235
- const { _type: _, _rule: __, ...safeFields } = fields;
236
- const entry = { _type: "rule", _rule: name, ...safeFields };
237
- if (resolve) {
238
- Object.defineProperty(entry, "_resolve", { value: resolve, enumerable: false });
239
- }
240
- return entry;
241
- }
242
- function makeRule(rule, fields) {
243
- return defineRule(rule, fields);
244
- }
245
- /**
246
- * Limits hours per day.
247
- *
248
- * @example
249
- * ```typescript
250
- * maxHoursPerDay(10)
251
- * maxHoursPerDay(4, { appliesTo: "student", dayOfWeek: weekdays })
252
- * ```
253
- *
254
- * @category Rules
255
- */
256
- export function maxHoursPerDay(hours, opts) {
257
- return makeRule("max-hours-day", { hours, ...opts });
258
- }
259
- /**
260
- * Limits hours per scheduling week.
261
- *
262
- * @example
263
- * ```typescript
264
- * maxHoursPerWeek(48)
265
- * maxHoursPerWeek(20, { appliesTo: "student" })
266
- * ```
267
- *
268
- * @category Rules
269
- */
270
- export function maxHoursPerWeek(hours, opts) {
271
- return makeRule("max-hours-week", { hours, ...opts });
272
- }
273
- /**
274
- * Minimum hours when assigned on a day.
275
- *
276
- * @example
277
- * ```typescript
278
- * minHoursPerDay(4)
279
- * ```
280
- *
281
- * @category Rules
282
- */
283
- export function minHoursPerDay(hours, opts) {
284
- return makeRule("min-hours-day", { hours, ...opts });
285
- }
286
- /**
287
- * Minimum hours per scheduling week.
288
- *
289
- * @example
290
- * ```typescript
291
- * minHoursPerWeek(20, { priority: "HIGH" })
292
- * ```
293
- *
294
- * @category Rules
295
- */
296
- export function minHoursPerWeek(hours, opts) {
297
- return makeRule("min-hours-week", { hours, ...opts });
298
- }
299
- /**
300
- * Maximum distinct shifts per day.
301
- *
302
- * @example
303
- * ```typescript
304
- * maxShiftsPerDay(1)
305
- * maxShiftsPerDay(2, { appliesTo: "student", dayOfWeek: weekend })
306
- * ```
307
- *
308
- * @category Rules
309
- */
310
- export function maxShiftsPerDay(shifts, opts) {
311
- return makeRule("max-shifts-day", { shifts, ...opts });
312
- }
313
- /**
314
- * Maximum consecutive working days.
315
- *
316
- * @example
317
- * ```typescript
318
- * maxConsecutiveDays(5)
319
- * ```
320
- *
321
- * @category Rules
322
- */
323
- export function maxConsecutiveDays(days, opts) {
324
- return makeRule("max-consecutive-days", { days, ...opts });
325
- }
326
- /**
327
- * Once working, continue for at least this many consecutive days.
328
- *
329
- * @example
330
- * ```typescript
331
- * minConsecutiveDays(2, { priority: "HIGH" })
332
- * ```
333
- *
334
- * @category Rules
335
- */
336
- export function minConsecutiveDays(days, opts) {
337
- return makeRule("min-consecutive-days", { days, ...opts });
338
- }
339
- /**
340
- * Minimum rest hours between shifts.
341
- *
342
- * @example
343
- * ```typescript
344
- * minRestBetweenShifts(10)
345
- * ```
346
- *
347
- * @category Rules
348
- */
349
- export function minRestBetweenShifts(hours, opts) {
350
- return makeRule("min-rest-between-shifts", { hours, ...opts });
351
- }
352
- /**
353
- * Prefer (`"high"`) or avoid (`"low"`) assigning. Requires `appliesTo`.
354
- *
355
- * @example
356
- * ```typescript
357
- * preference("high", { appliesTo: "waiter" })
358
- * preference("low", { appliesTo: "student", dayOfWeek: weekdays })
359
- * ```
360
- *
361
- * @category Rules
362
- */
363
- export function preference(level, opts) {
364
- return makeRule("assignment-priority", { preference: level, ...opts });
365
- }
366
- /**
367
- * Prefer assigning to shifts at a specific location. Requires `appliesTo`.
368
- *
369
- * @example
370
- * ```typescript
371
- * preferLocation("terrace", { appliesTo: "alice" })
372
- * ```
373
- *
374
- * @category Rules
375
- */
376
- export function preferLocation(locationId, opts) {
377
- return makeRule("location-preference", { locationId, ...opts });
378
- }
379
- /**
380
- * Tells the solver to minimize total labor cost.
381
- *
382
- * @remarks
383
- * Without this rule, cost modifiers only affect post-solve calculation.
384
- * When present, the solver actively prefers cheaper assignments.
385
- *
386
- * For hourly members, penalizes each assignment proportionally to cost.
387
- * For salaried members, adds a fixed weekly salary cost when they have
388
- * any assignment that week (zero marginal cost up to contracted hours).
389
- *
390
- * Cost modifiers adjust the calculation:
391
- * - `dayMultiplier(factor, opts?)` - multiply base rate on specific days
392
- * - `daySurcharge(amount, opts?)` - flat extra per hour on specific days
393
- * - `timeSurcharge(amount, window, opts?)` - flat extra per hour during a time window
394
- * - `overtimeMultiplier({ after, factor }, opts?)` - weekly overtime multiplier
395
- * - `overtimeSurcharge({ after, amount }, opts?)` - weekly overtime surcharge
396
- * - `dailyOvertimeMultiplier({ after, factor }, opts?)` - daily overtime multiplier
397
- * - `dailyOvertimeSurcharge({ after, amount }, opts?)` - daily overtime surcharge
398
- * - `tieredOvertimeMultiplier(tiers, opts?)` - multiple overtime thresholds
399
- *
400
- * @example
401
- * ```ts
402
- * minimizeCost()
403
- * ```
404
- *
405
- * @category Cost Optimization
406
- */
407
- export function minimizeCost(opts) {
408
- return makeRule("minimize-cost", { ...opts });
409
- }
410
- /**
411
- * Multiplies the base rate for assignments on specified days.
412
- *
413
- * @remarks
414
- * The base cost (1x) is already counted by {@link minimizeCost};
415
- * this rule adds only the extra portion above 1x.
416
- *
417
- * @category Cost Optimization
418
- *
419
- * @example Weekend multiplier
420
- * ```typescript
421
- * dayMultiplier(1.5, { dayOfWeek: weekend })
422
- * ```
423
- */
424
- export function dayMultiplier(factor, opts) {
425
- return makeRule("day-cost-multiplier", { factor, ...opts });
426
- }
427
- /**
428
- * Adds a flat extra amount per hour for assignments on specified days.
429
- *
430
- * @remarks
431
- * The surcharge is independent of the member's base rate.
432
- *
433
- * @category Cost Optimization
434
- *
435
- * @example Weekend surcharge
436
- * ```typescript
437
- * daySurcharge(500, { dayOfWeek: weekend })
438
- * ```
439
- */
440
- export function daySurcharge(amountPerHour, opts) {
441
- return makeRule("day-cost-surcharge", { amountPerHour, ...opts });
442
- }
443
- /**
444
- * Adds a flat surcharge per hour for the portion of a shift that overlaps a time-of-day window.
445
- *
446
- * @remarks
447
- * The window supports overnight spans (e.g., 22:00-06:00). The surcharge
448
- * is independent of the member's base rate.
449
- *
450
- * @param amountPerHour - Flat surcharge per hour in smallest currency unit
451
- * @param window - Time-of-day window
452
- * @param opts - Entity and time scoping
453
- *
454
- * @category Cost Optimization
455
- *
456
- * @example Night differential
457
- * ```typescript
458
- * timeSurcharge(200, { from: t(22), until: t(6) })
459
- * ```
460
- */
461
- export function timeSurcharge(amountPerHour, window, opts) {
462
- return makeRule("time-cost-surcharge", { amountPerHour, window, ...opts });
463
- }
464
- /**
465
- * Applies a multiplier to hours beyond a weekly threshold.
466
- *
467
- * @remarks
468
- * Only the extra portion above 1x is added (the base cost is already
469
- * counted by {@link minimizeCost}).
470
- *
471
- * @category Cost Optimization
472
- *
473
- * @example
474
- * ```typescript
475
- * overtimeMultiplier({ after: 40, factor: 1.5 })
476
- * ```
477
- */
478
- export function overtimeMultiplier(opts) {
479
- return makeRule("overtime-weekly-multiplier", { ...opts });
480
- }
481
- /**
482
- * Adds a flat surcharge per hour beyond a weekly threshold.
483
- *
484
- * @remarks
485
- * The surcharge is independent of the member's base rate.
486
- *
487
- * @category Cost Optimization
488
- *
489
- * @example
490
- * ```typescript
491
- * overtimeSurcharge({ after: 40, amount: 1000 })
492
- * ```
493
- */
494
- export function overtimeSurcharge(opts) {
495
- return makeRule("overtime-weekly-surcharge", { ...opts });
496
- }
497
- /**
498
- * Applies a multiplier to hours beyond a daily threshold.
499
- *
500
- * @remarks
501
- * Only the extra portion above 1x is added (the base cost is already
502
- * counted by {@link minimizeCost}).
503
- *
504
- * @category Cost Optimization
505
- *
506
- * @example
507
- * ```typescript
508
- * dailyOvertimeMultiplier({ after: 8, factor: 1.5 })
509
- * ```
510
- */
511
- export function dailyOvertimeMultiplier(opts) {
512
- return makeRule("overtime-daily-multiplier", { ...opts });
513
- }
514
- /**
515
- * Adds a flat surcharge per hour beyond a daily threshold.
516
- *
517
- * @remarks
518
- * The surcharge is independent of the member's base rate.
519
- *
520
- * @category Cost Optimization
521
- *
522
- * @example
523
- * ```typescript
524
- * dailyOvertimeSurcharge({ after: 8, amount: 500 })
525
- * ```
526
- */
527
- export function dailyOvertimeSurcharge(opts) {
528
- return makeRule("overtime-daily-surcharge", { ...opts });
529
- }
530
- /**
531
- * Applies multiple overtime thresholds with increasing multipliers.
532
- *
533
- * @remarks
534
- * Each tier applies only to the hours between its threshold and the next.
535
- * Tiers must be sorted by threshold ascending.
536
- *
537
- * @category Cost Optimization
538
- *
539
- * @example
540
- * ```typescript
541
- * // Hours 0-40: base rate
542
- * // Hours 40-48: 1.5x
543
- * // Hours 48+: 2.0x
544
- * tieredOvertimeMultiplier([
545
- * { after: 40, factor: 1.5 },
546
- * { after: 48, factor: 2.0 },
547
- * ])
548
- * ```
549
- */
550
- export function tieredOvertimeMultiplier(tiers, opts) {
551
- return makeRule("overtime-tiered-multiplier", { tiers, ...opts });
552
- }
553
- /**
554
- * Block assignments during specified periods.
555
- * Requires at least one time scope (`dayOfWeek`, `dateRange`, `dates`, or `from`/`until`).
556
- *
557
- * @example
558
- * ```typescript
559
- * // Full days off
560
- * timeOff({ appliesTo: "alice", dateRange: { start: "2024-02-01", end: "2024-02-05" } })
561
- *
562
- * // Every weekend off
563
- * timeOff({ appliesTo: "mauro", dayOfWeek: weekend })
564
- *
565
- * // Wednesday afternoons off
566
- * timeOff({ appliesTo: "student", dayOfWeek: ["wednesday"], from: t(14) })
567
- * ```
568
- *
569
- * @category Rules
570
- */
571
- export function timeOff(opts) {
572
- const { from, until, ...rest } = opts;
573
- return defineRule("time-off", { from, until, ...rest }, (ctx) => {
574
- if (!rest.dayOfWeek && !rest.dateRange && !rest.dates && !rest.recurringPeriods) {
575
- throw new Error("timeOff() requires at least one time scope (dayOfWeek, dateRange, dates, or recurringPeriods).");
576
- }
577
- const { appliesTo, dates, ...passthrough } = rest;
578
- const entityScope = resolveAppliesTo(appliesTo, ctx.roles, ctx.skills, ctx.memberIds);
579
- const resolvedDates = dates ? { specificDates: dates } : {};
580
- const partialDay = {};
581
- if (from && until) {
582
- partialDay.startTime = from;
583
- partialDay.endTime = until;
584
- }
585
- else if (from) {
586
- partialDay.startTime = from;
587
- partialDay.endTime = { hours: 23, minutes: 59 };
588
- }
589
- else if (until) {
590
- partialDay.startTime = { hours: 0, minutes: 0 };
591
- partialDay.endTime = until;
592
- }
593
- return {
594
- name: "time-off",
595
- ...passthrough,
596
- ...entityScope,
597
- ...resolvedDates,
598
- ...partialDay,
599
- };
600
- });
601
- }
602
- /**
603
- * Members work the same shifts on days they are both assigned.
604
- *
605
- * @example
606
- * ```typescript
607
- * assignTogether(["alice", "bob"])
608
- * assignTogether(["alice", "bob", "charlie"], { priority: "HIGH" })
609
- * ```
610
- *
611
- * @category Rules
612
- */
613
- export function assignTogether(memberIds, opts) {
614
- return defineRule("assign-together", { members: memberIds, ...opts }, (ctx) => {
615
- for (const member of memberIds) {
616
- if (!ctx.memberIds.has(member)) {
617
- throw new Error(`assignTogether references unknown member "${member}". ` +
618
- `Known member IDs: ${[...ctx.memberIds].join(", ")}`);
619
- }
620
- }
621
- return {
622
- name: "assign-together",
623
- groupMemberIds: memberIds,
624
- ...opts,
625
- };
626
- });
627
- }
628
- // ============================================================================
629
- // Schedule class
630
- // ============================================================================
631
- /**
632
- * An immutable schedule definition.
633
- *
634
- * Created by {@link schedule}, composed via {@link Schedule.with},
635
- * and solved via {@link Schedule.solve}.
636
- *
637
- * @category Schedule Definition
638
- */
639
- export class Schedule {
640
- #config;
641
- /** @internal */
642
- constructor(config) {
643
- this.#config = config;
644
- }
645
- /** @internal Returns a defensive copy of the config for merging. */
646
- _getConfig() {
647
- return {
648
- ...this.#config,
649
- roleIds: [...this.#config.roleIds],
650
- skillIds: [...this.#config.skillIds],
651
- times: { ...this.#config.times },
652
- coverage: [...this.#config.coverage],
653
- shiftPatterns: [...this.#config.shiftPatterns],
654
- rules: [...this.#config.rules],
655
- ruleFactories: { ...this.#config.ruleFactories },
656
- members: [...this.#config.members],
657
- };
658
- }
659
- // --------------------------------------------------------------------------
660
- // Inspection
661
- // --------------------------------------------------------------------------
662
- /** Declared role IDs. */
663
- get roleIds() {
664
- return this.#config.roleIds;
665
- }
666
- /** Declared skill IDs. */
667
- get skillIds() {
668
- return this.#config.skillIds;
669
- }
670
- /** Names of declared semantic times. */
671
- get timeNames() {
672
- return Object.keys(this.#config.times);
673
- }
674
- /** Shift pattern IDs. */
675
- get shiftPatternIds() {
676
- return this.#config.shiftPatterns.map((sp) => sp.id);
677
- }
678
- /** Internal rule identifiers in kebab-case. */
679
- get ruleNames() {
680
- return this.#config.rules.map((r) => r._rule);
681
- }
682
- // --------------------------------------------------------------------------
683
- // Composition
684
- // --------------------------------------------------------------------------
685
- /**
686
- * Merges schedules or members onto this schedule, returning a new
687
- * immutable `Schedule`. The original is untouched.
688
- *
689
- * Accepts any mix of `Schedule` instances and `SchedulingMember[]` arrays.
690
- *
691
- * Merge semantics (when merging schedules):
692
- * - Roles: union (additive)
693
- * - Skills: union (additive)
694
- * - Times: additive; error on name collision
695
- * - Coverage: additive
696
- * - Shift patterns: additive; error on ID collision
697
- * - Rules: additive
698
- * - Members: additive; error on duplicate ID
699
- *
700
- * Validation runs eagerly: role/skill disjointness, coverage targets
701
- * referencing declared roles/skills, member role references, etc.
702
- */
703
- with(...args) {
704
- const merged = mergeConfig(this.#config, args);
705
- return new Schedule(merged);
706
- }
707
- // --------------------------------------------------------------------------
708
- // Solve / compile
709
- // --------------------------------------------------------------------------
710
- /**
711
- * Compiles, validates, solves, and parses in one call.
712
- *
713
- * @param client - Solver client (e.g., `new HttpSolverClient(fetch, url)`)
714
- * @param options - Date range and optional pinned assignments
715
- */
716
- async solve(client, options) {
717
- const compiled = this.compile(options);
718
- if (!compiled.canSolve) {
719
- return {
720
- status: "infeasible",
721
- assignments: [],
722
- validation: compiled.validation,
723
- };
724
- }
725
- const response = await client.solve(compiled.request);
726
- return buildSolveResult(response, compiled, this.#config);
727
- }
728
- /**
729
- * Diagnostic escape hatch. Compiles the schedule without solving.
730
- *
731
- * @param options - Date range and optional pinned assignments
732
- */
733
- compile(options) {
734
- if (options.pinned && options.pinned.length > 0) {
735
- throw new Error("Pinned assignments are not yet supported.");
736
- }
737
- const modelConfig = resolveToModelConfig(this.#config, options);
738
- const builder = new ModelBuilder(modelConfig);
739
- const result = builder.compile();
740
- return { ...result, builder };
741
- }
742
- }
743
- // ============================================================================
744
- // schedule() factory
745
- // ============================================================================
746
- /**
747
- * Create a schedule definition.
748
- *
749
- * Returns an immutable {@link Schedule} that can be composed via `.with()`
750
- * and solved via `.solve()`.
751
- *
752
- * @example
753
- * ```typescript
754
- * const venue = schedule({
755
- * roleIds: ["waiter", "runner", "manager"],
756
- * skillIds: ["senior"],
757
- * times: {
758
- * lunch: time({ startTime: t(12), endTime: t(15) }),
759
- * dinner: time(
760
- * { startTime: t(17), endTime: t(21) },
761
- * { startTime: t(18), endTime: t(22), dayOfWeek: weekend },
762
- * ),
763
- * },
764
- * coverage: [
765
- * cover("lunch", "waiter", 2),
766
- * cover("dinner", "waiter", 4, { dayOfWeek: weekdays }),
767
- * cover("dinner", "waiter", 5, { dayOfWeek: weekend }),
768
- * cover("dinner", "manager", 1),
769
- * ],
770
- * shiftPatterns: [
771
- * shift("lunch_shift", t(11, 30), t(15)),
772
- * shift("evening", t(17), t(22)),
773
- * ],
774
- * rules: [
775
- * maxHoursPerDay(10),
776
- * maxHoursPerWeek(48),
777
- * minRestBetweenShifts(11),
778
- * ],
779
- * });
780
- * ```
781
- *
782
- * @category Schedule Definition
783
- */
784
- export function schedule(config) {
785
- const merged = buildMergedConfig(config);
786
- validateConfig(merged);
787
- return new Schedule(merged);
788
- }
789
- /**
790
- * Create a partial schedule for composition via `.with()`.
791
- *
792
- * Unlike {@link schedule}, all fields are optional. Use this for
793
- * schedules that layer rules, coverage, or other config onto a
794
- * complete base schedule.
795
- *
796
- * @example
797
- * ```typescript
798
- * const companyPolicy = partialSchedule({
799
- * rules: [maxHoursPerWeek(40), minRestBetweenShifts(11)],
800
- * });
801
- *
802
- * const ready = venue.with(companyPolicy, teamMembers);
803
- * ```
804
- *
805
- * @category Schedule Definition
806
- */
807
- export function partialSchedule(config) {
808
- const merged = buildMergedConfig({
809
- roleIds: [],
810
- times: {},
811
- coverage: [],
812
- shiftPatterns: [],
813
- ...config,
814
- });
815
- validateConfig(merged);
816
- return new Schedule(merged);
817
- }
818
- // ============================================================================
819
- // Internal: Build merged config from user input
820
- // ============================================================================
821
- function buildMergedConfig(config) {
822
- return {
823
- roleIds: [...config.roleIds],
824
- skillIds: [...(config.skillIds ?? [])],
825
- times: { ...config.times },
826
- coverage: [...config.coverage],
827
- shiftPatterns: [...config.shiftPatterns],
828
- rules: [...(config.rules ?? [])],
829
- ruleFactories: config.ruleFactories ? { ...config.ruleFactories } : {},
830
- members: [...(config.members ?? [])],
831
- dayOfWeek: config.dayOfWeek,
832
- weekStartsOn: config.weekStartsOn,
833
- };
834
- }
835
- // ============================================================================
836
- // Internal: Validate merged config
837
- // ============================================================================
838
- function validateConfig(config) {
839
- const roles = new Set(config.roleIds);
840
- const skills = new Set(config.skillIds);
841
- // Validate custom rule factories don't override built-in names
842
- for (const name of Object.keys(config.ruleFactories)) {
843
- if (name in builtInCpsatRuleFactories) {
844
- throw new Error(`Custom rule factory "${name}" conflicts with a built-in rule. Choose a different name.`);
845
- }
846
- }
847
- // Validate role/skill disjointness
848
- for (const skill of skills) {
849
- if (roles.has(skill)) {
850
- throw new Error(`"${skill}" is declared as both a role and a skill. Roles and skills must be disjoint.`);
851
- }
852
- }
853
- // Validate shift pattern role references
854
- for (const sp of config.shiftPatterns) {
855
- if (sp.roleIds) {
856
- for (const role of sp.roleIds) {
857
- if (!roles.has(role)) {
858
- throw new Error(`Shift pattern "${sp.id}" references unknown role "${role}". ` +
859
- `Declared roles: ${[...roles].join(", ")}`);
860
- }
861
- }
862
- }
863
- }
864
- // Validate coverage entries
865
- for (const entry of config.coverage) {
866
- validateCoverageEntry(entry, roles, skills);
867
- }
868
- // Validate member references
869
- const memberIds = new Set();
870
- for (const member of config.members) {
871
- if (memberIds.has(member.id)) {
872
- throw new Error(`Duplicate member ID "${member.id}".`);
873
- }
874
- memberIds.add(member.id);
875
- if (roles.has(member.id)) {
876
- throw new Error(`Member ID "${member.id}" collides with a declared role name.`);
877
- }
878
- if (skills.has(member.id)) {
879
- throw new Error(`Member ID "${member.id}" collides with a declared skill name.`);
880
- }
881
- for (const role of member.roleIds) {
882
- if (!roles.has(role)) {
883
- throw new Error(`Member "${member.id}" references unknown role "${role}". ` +
884
- `Declared roles: ${[...roles].join(", ")}`);
885
- }
886
- }
887
- if (member.skillIds) {
888
- for (const skill of member.skillIds) {
889
- if (!skills.has(skill)) {
890
- throw new Error(`Member "${member.id}" references unknown skill "${skill}". ` +
891
- `Declared skills: ${[...skills].join(", ")}`);
892
- }
893
- }
894
- }
895
- }
896
- }
897
- function validateCoverageEntry(entry, roles, skills) {
898
- const targets = Array.isArray(entry.target) ? entry.target : [entry.target];
899
- if (Array.isArray(entry.target)) {
900
- for (const target of targets) {
901
- if (!roles.has(target)) {
902
- throw new Error(`Coverage for "${entry.timeName}" references "${target}" in a role OR group, ` +
903
- `but it is not a declared role. Declared roles: ${[...roles].join(", ")}`);
904
- }
905
- }
906
- }
907
- else {
908
- if (!roles.has(entry.target) && !skills.has(entry.target)) {
909
- throw new Error(`Coverage for "${entry.timeName}" references unknown target "${entry.target}". ` +
910
- `Declared roles: ${[...roles].join(", ")}. ` +
911
- `Declared skills: ${[...skills].join(", ")}`);
912
- }
913
- }
914
- if (entry.options.skillIds) {
915
- for (const s of entry.options.skillIds) {
916
- if (!skills.has(s)) {
917
- throw new Error(`Coverage for "${entry.timeName}" uses skill filter "${s}" ` +
918
- `which is not a declared skill. Declared skills: ${[...skills].join(", ")}`);
919
- }
920
- }
921
- }
922
- }
923
- // ============================================================================
924
- // Internal: Merge logic
925
- // ============================================================================
926
- function mergeConfig(base, args) {
927
- const result = {
928
- roleIds: [...base.roleIds],
929
- skillIds: [...base.skillIds],
930
- times: { ...base.times },
931
- coverage: [...base.coverage],
932
- shiftPatterns: [...base.shiftPatterns],
933
- rules: [...base.rules],
934
- ruleFactories: { ...base.ruleFactories },
935
- members: [...base.members],
936
- dayOfWeek: base.dayOfWeek,
937
- weekStartsOn: base.weekStartsOn,
938
- };
939
- for (const arg of args) {
940
- if (arg instanceof Schedule) {
941
- mergeScheduleFragment(result, arg);
942
- }
943
- else if (Array.isArray(arg)) {
944
- mergeMembers(result, arg);
945
- }
946
- else {
947
- throw new Error(`Unexpected argument passed to .with(): expected Schedule or SchedulingMember[], got ${typeof arg}`);
948
- }
949
- }
950
- // Validate the merged result
951
- validateConfig(result);
952
- return result;
953
- }
954
- function mergeScheduleFragment(result, s) {
955
- const other = s._getConfig();
956
- // dayOfWeek: error on conflict (semantics of union vs intersection are ambiguous)
957
- if (other.dayOfWeek !== undefined) {
958
- if (result.dayOfWeek !== undefined) {
959
- const baseSet = new Set(result.dayOfWeek);
960
- const same = result.dayOfWeek.length === other.dayOfWeek.length &&
961
- other.dayOfWeek.every((d) => baseSet.has(d));
962
- if (!same) {
963
- throw new Error("Cannot merge schedules with different dayOfWeek filters. " +
964
- `Base has [${result.dayOfWeek.join(", ")}], ` +
965
- `incoming has [${other.dayOfWeek.join(", ")}].`);
966
- }
967
- }
968
- else {
969
- result.dayOfWeek = other.dayOfWeek;
970
- }
971
- }
972
- // weekStartsOn: error on conflict (only one week boundary for weekly rules)
973
- if (other.weekStartsOn !== undefined) {
974
- if (result.weekStartsOn !== undefined && result.weekStartsOn !== other.weekStartsOn) {
975
- throw new Error("Cannot merge schedules with different weekStartsOn values. " +
976
- `Base has "${result.weekStartsOn}", incoming has "${other.weekStartsOn}".`);
977
- }
978
- result.weekStartsOn = other.weekStartsOn;
979
- }
980
- // Roles: union
981
- for (const role of other.roleIds) {
982
- if (!result.roleIds.includes(role)) {
983
- result.roleIds.push(role);
984
- }
985
- }
986
- // Skills: union
987
- for (const skill of other.skillIds) {
988
- if (!result.skillIds.includes(skill)) {
989
- result.skillIds.push(skill);
990
- }
991
- }
992
- // Times: additive, error on collision
993
- for (const [name, entry] of Object.entries(other.times)) {
994
- if (name in result.times) {
995
- throw new Error(`Time name "${name}" already exists. Cannot merge schedules with colliding time names.`);
996
- }
997
- result.times[name] = entry;
998
- }
999
- // Coverage: additive
1000
- result.coverage.push(...other.coverage);
1001
- // Shift patterns: additive, error on ID collision
1002
- const existingIds = new Set(result.shiftPatterns.map((sp) => sp.id));
1003
- for (const sp of other.shiftPatterns) {
1004
- if (existingIds.has(sp.id)) {
1005
- throw new Error(`Shift pattern ID "${sp.id}" already exists. Cannot merge schedules with colliding shift pattern IDs.`);
1006
- }
1007
- result.shiftPatterns.push(sp);
1008
- existingIds.add(sp.id);
1009
- }
1010
- // Rules: additive
1011
- result.rules.push(...other.rules);
1012
- // Rule factories: merge, error on collision
1013
- for (const [name, factory] of Object.entries(other.ruleFactories)) {
1014
- if (name in result.ruleFactories && result.ruleFactories[name] !== factory) {
1015
- throw new Error(`Rule factory "${name}" already registered. Cannot merge schedules with colliding rule factories.`);
1016
- }
1017
- result.ruleFactories[name] = factory;
1018
- }
1019
- // Members: additive, error on duplicate ID
1020
- const existingMemberIds = new Set(result.members.map((m) => m.id));
1021
- for (const member of other.members) {
1022
- if (existingMemberIds.has(member.id)) {
1023
- throw new Error(`Duplicate member ID "${member.id}". Cannot merge schedules with colliding member IDs.`);
1024
- }
1025
- result.members.push(member);
1026
- existingMemberIds.add(member.id);
1027
- }
1028
- }
1029
- function mergeMembers(result, incoming) {
1030
- const existingIds = new Set(result.members.map((m) => m.id));
1031
- for (const member of incoming) {
1032
- if (existingIds.has(member.id)) {
1033
- throw new Error(`Duplicate member ID "${member.id}". Cannot merge members with colliding IDs.`);
1034
- }
1035
- result.members.push(member);
1036
- existingIds.add(member.id);
1037
- }
1038
- }
1039
- // ============================================================================
1040
- // Internal: Resolve to ModelBuilderConfig
1041
- // ============================================================================
1042
- function resolveToModelConfig(config, options) {
1043
- const roles = new Set(config.roleIds);
1044
- const skills = new Set(config.skillIds);
1045
- const memberIds = new Set(config.members.map((m) => m.id));
1046
- // Build semantic time context
1047
- const semanticTimes = defineSemanticTimes(config.times);
1048
- // Convert coverage entries to semantic coverage requirements
1049
- const coverageReqs = buildCoverageRequirements(config.coverage, roles, skills);
1050
- // Resolve scheduling period with dayOfWeek filter
1051
- const schedulingPeriod = {
1052
- dateRange: options.dateRange,
1053
- };
1054
- const resolvedPeriod = applyDaysFilter(schedulingPeriod, config.dayOfWeek);
1055
- const days = resolveDaysFromPeriod(resolvedPeriod);
1056
- // Resolve coverage
1057
- const resolvedCoverage = semanticTimes.resolve(coverageReqs, days);
1058
- // Resolve rules
1059
- const allRules = [...config.rules];
1060
- // Validate pay data when cost rules are present
1061
- const costRuleNames = new Set([
1062
- "minimize-cost",
1063
- "day-cost-multiplier",
1064
- "day-cost-surcharge",
1065
- "time-cost-surcharge",
1066
- "overtime-weekly-multiplier",
1067
- "overtime-weekly-surcharge",
1068
- "overtime-daily-multiplier",
1069
- "overtime-daily-surcharge",
1070
- "overtime-tiered-multiplier",
1071
- ]);
1072
- const hasCostRules = allRules.some((r) => costRuleNames.has(r._rule));
1073
- if (hasCostRules) {
1074
- const missingPay = config.members.filter((m) => !m.pay).map((m) => m.id);
1075
- if (missingPay.length > 0) {
1076
- throw new Error(`Cost rules require pay data on all members. Missing pay: ${missingPay.join(", ")}`);
1077
- }
1078
- }
1079
- // Sort rules so minimize-cost compiles before modifier rules
1080
- const sortedRules = sortCostRulesFirst(allRules);
1081
- const ruleConfigs = resolveRules(sortedRules, roles, skills, memberIds);
1082
- return {
1083
- members: config.members,
1084
- shiftPatterns: config.shiftPatterns,
1085
- schedulingPeriod: resolvedPeriod,
1086
- coverage: resolvedCoverage,
1087
- ruleConfigs,
1088
- ruleFactories: Object.keys(config.ruleFactories).length > 0
1089
- ? { ...builtInCpsatRuleFactories, ...config.ruleFactories }
1090
- : undefined,
1091
- weekStartsOn: config.weekStartsOn,
1092
- };
1093
- }
1094
- // ============================================================================
1095
- // Internal: Build SolveResult from solver response
1096
- // ============================================================================
1097
- function mapSolverStatus(solverStatus) {
1098
- switch (solverStatus) {
1099
- case "OPTIMAL":
1100
- return "optimal";
1101
- case "FEASIBLE":
1102
- return "feasible";
1103
- case "INFEASIBLE":
1104
- return "infeasible";
1105
- case "TIMEOUT":
1106
- case "ERROR":
1107
- return "no_solution";
1108
- default:
1109
- return "no_solution";
1110
- }
1111
- }
1112
- function buildSolveResult(response, compiled, config) {
1113
- const status = mapSolverStatus(response.status);
1114
- const parsed = parseSolverResponse(response);
1115
- // Run post-solve validation when a solution exists
1116
- if (parsed.assignments.length > 0 && (status === "optimal" || status === "feasible")) {
1117
- const resolved = resolveAssignments(parsed.assignments, compiled.builder.shiftPatterns);
1118
- compiled.builder.reporter.analyzeSolution(response);
1119
- compiled.builder.validateSolution(resolved);
1120
- }
1121
- const validation = compiled.builder.reporter.getValidation();
1122
- const result = {
1123
- status,
1124
- assignments: parsed.assignments,
1125
- validation,
1126
- };
1127
- // Compute cost breakdown when cost rules are present and a solution was found
1128
- if (parsed.assignments.length > 0 && (status === "optimal" || status === "feasible")) {
1129
- const hasCostRules = config.rules.some((r) => r._rule === "minimize-cost");
1130
- if (hasCostRules) {
1131
- result.cost = calculateScheduleCost(parsed.assignments, {
1132
- members: config.members,
1133
- shiftPatterns: config.shiftPatterns,
1134
- rules: compiled.builder.rules,
1135
- });
1136
- }
1137
- }
1138
- return result;
1139
- }
1140
- // ============================================================================
1141
- // Internal: Coverage Translation
1142
- // ============================================================================
1143
- function buildCoverageRequirements(entries, roles, skills) {
1144
- return entries.map((entry) => {
1145
- // Variant form: produce a VariantCoverageRequirement
1146
- if (entry.variants) {
1147
- return buildVariantCoverageRequirement(entry, roles, skills);
1148
- }
1149
- // Simple form: produce a SemanticCoverageRequirement
1150
- const base = {
1151
- semanticTime: entry.timeName,
1152
- targetCount: entry.count,
1153
- };
1154
- if (entry.options.priority)
1155
- base.priority = entry.options.priority;
1156
- if (entry.options.dayOfWeek && entry.options.dayOfWeek.length > 0) {
1157
- base.dayOfWeek = entry.options.dayOfWeek;
1158
- }
1159
- if (entry.options.dates)
1160
- base.dates = entry.options.dates;
1161
- return buildSimpleCoverageTarget(entry, base, roles, skills);
1162
- });
1163
- }
1164
- /**
1165
- * Resolve the target (role/skill) for a simple coverage entry.
1166
- */
1167
- function buildSimpleCoverageTarget(entry, base, roles, skills) {
1168
- if (Array.isArray(entry.target)) {
1169
- return {
1170
- ...base,
1171
- roleIds: entry.target,
1172
- };
1173
- }
1174
- const singleTarget = entry.target;
1175
- if (roles.has(singleTarget)) {
1176
- if (entry.options.skillIds) {
1177
- return {
1178
- ...base,
1179
- roleIds: [singleTarget],
1180
- skillIds: entry.options.skillIds,
1181
- };
1182
- }
1183
- return {
1184
- ...base,
1185
- roleIds: [singleTarget],
1186
- };
1187
- }
1188
- if (skills.has(singleTarget)) {
1189
- return {
1190
- ...base,
1191
- skillIds: [singleTarget],
1192
- };
1193
- }
1194
- throw new Error(`Coverage target "${singleTarget}" is not a declared role or skill.`);
1195
- }
1196
- /**
1197
- * Build a VariantCoverageRequirement from a variant-form CoverageEntry.
1198
- */
1199
- function buildVariantCoverageRequirement(entry, roles, skills) {
1200
- const variants = entry.variants;
1201
- const resolveTarget = () => {
1202
- if (Array.isArray(entry.target)) {
1203
- return { roleIds: entry.target };
1204
- }
1205
- const singleTarget = entry.target;
1206
- if (roles.has(singleTarget)) {
1207
- return { roleIds: [singleTarget] };
1208
- }
1209
- if (skills.has(singleTarget)) {
1210
- return { skillIds: [singleTarget] };
1211
- }
1212
- throw new Error(`Coverage target "${singleTarget}" is not a declared role or skill.`);
1213
- };
1214
- return {
1215
- semanticTime: entry.timeName,
1216
- variants,
1217
- ...resolveTarget(),
1218
- };
1219
- }
1220
- // ============================================================================
1221
- // Internal: Rule Translation
1222
- // ============================================================================
1223
- /**
1224
- * Resolves an `appliesTo` value into entity scope fields.
1225
- *
1226
- * Each target string is checked against roles, skills, then member IDs.
1227
- * If all targets resolve to the same namespace, they are combined into one
1228
- * scope field. If they span namespaces, an error is thrown; the caller
1229
- * should use separate rule entries instead.
1230
- */
1231
- function resolveAppliesTo(appliesTo, roles, skills, memberIds) {
1232
- if (!appliesTo)
1233
- return {};
1234
- const targets = Array.isArray(appliesTo) ? appliesTo : [appliesTo];
1235
- if (targets.length === 0)
1236
- return {};
1237
- const resolvedRoles = [];
1238
- const resolvedSkills = [];
1239
- const resolvedMembers = [];
1240
- for (const target of targets) {
1241
- if (roles.has(target)) {
1242
- resolvedRoles.push(target);
1243
- }
1244
- else if (skills.has(target)) {
1245
- resolvedSkills.push(target);
1246
- }
1247
- else if (memberIds.has(target)) {
1248
- resolvedMembers.push(target);
1249
- }
1250
- else {
1251
- throw new Error(`appliesTo target "${target}" is not a declared role, skill, or member ID.`);
1252
- }
1253
- }
1254
- // Count how many namespaces were used
1255
- const namespacesUsed = [resolvedRoles, resolvedSkills, resolvedMembers].filter((arr) => arr.length > 0).length;
1256
- if (namespacesUsed > 1) {
1257
- throw new Error(`appliesTo targets span multiple namespaces (roles: [${resolvedRoles.join(", ")}], ` +
1258
- `skills: [${resolvedSkills.join(", ")}], members: [${resolvedMembers.join(", ")}]). ` +
1259
- `Use separate rule entries for each namespace.`);
1260
- }
1261
- if (resolvedRoles.length > 0) {
1262
- return { roleIds: resolvedRoles };
1263
- }
1264
- if (resolvedSkills.length > 0) {
1265
- return { skillIds: resolvedSkills };
1266
- }
1267
- if (resolvedMembers.length > 0) {
1268
- return { memberIds: resolvedMembers };
1269
- }
1270
- return {};
1271
- }
1272
- function resolveRules(rules, roles, skills, memberIds) {
1273
- const ctx = { roles, skills, memberIds };
1274
- return rules.map((rule) => {
1275
- // Rules with custom resolvers handle their own translation
1276
- if (rule._resolve) {
1277
- return rule._resolve(ctx);
1278
- }
1279
- // Default resolution: appliesTo → entity scope, dates → specificDates
1280
- const { _type, _rule, _resolve, appliesTo, dates, ...passthrough } = rule;
1281
- const entityScope = resolveAppliesTo(appliesTo, roles, skills, memberIds);
1282
- const resolvedDates = dates ? { specificDates: dates } : {};
1283
- return {
1284
- name: _rule,
1285
- ...passthrough,
1286
- ...entityScope,
1287
- ...resolvedDates,
1288
- };
1289
- });
1290
- }
1291
- // ============================================================================
1292
- // Internal: Cost Rule Ordering
1293
- // ============================================================================
1294
- /**
1295
- * Sorts rules so that `minimize-cost` compiles before cost modifier rules.
1296
- *
1297
- * The `minimize-cost` rule must be compiled first because modifier rules
1298
- * (multipliers, surcharges) reference cost variables it creates.
1299
- * Non-cost rules retain their original relative order.
1300
- */
1301
- function sortCostRulesFirst(rules) {
1302
- return rules.toSorted((a, b) => {
1303
- const aIsCostBase = a._rule === "minimize-cost" ? 0 : 1;
1304
- const bIsCostBase = b._rule === "minimize-cost" ? 0 : 1;
1305
- return aIsCostBase - bIsCostBase;
1306
- });
1307
- }
1308
- // ============================================================================
1309
- // Internal: Scheduling Period Helpers
1310
- // ============================================================================
1311
- function applyDaysFilter(schedulingPeriod, dayOfWeek) {
1312
- if (!dayOfWeek || dayOfWeek.length === 0) {
1313
- return schedulingPeriod;
1314
- }
1315
- const existingDays = schedulingPeriod.dayOfWeek;
1316
- if (!existingDays || existingDays.length === 0) {
1317
- return { ...schedulingPeriod, dayOfWeek: [...dayOfWeek] };
1318
- }
1319
- const existingSet = new Set(existingDays);
1320
- const intersected = dayOfWeek.filter((day) => existingSet.has(day));
1321
- return { ...schedulingPeriod, dayOfWeek: intersected };
1322
- }
1323
- //# sourceMappingURL=schedule.js.map