dabke 0.83.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +22 -27
  3. package/dist/client.d.ts +39 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js.map +1 -1
  6. package/dist/client.schemas.d.ts +118 -18
  7. package/dist/client.schemas.d.ts.map +1 -1
  8. package/dist/client.schemas.js +110 -6
  9. package/dist/client.schemas.js.map +1 -1
  10. package/dist/client.types.d.ts +63 -7
  11. package/dist/client.types.d.ts.map +1 -1
  12. package/dist/client.types.js.map +1 -1
  13. package/dist/cpsat/cost.d.ts +5 -2
  14. package/dist/cpsat/cost.d.ts.map +1 -1
  15. package/dist/cpsat/cost.js +13 -6
  16. package/dist/cpsat/cost.js.map +1 -1
  17. package/dist/cpsat/model-builder.d.ts +42 -61
  18. package/dist/cpsat/model-builder.d.ts.map +1 -1
  19. package/dist/cpsat/model-builder.js +451 -56
  20. package/dist/cpsat/model-builder.js.map +1 -1
  21. package/dist/cpsat/response.d.ts +19 -7
  22. package/dist/cpsat/response.d.ts.map +1 -1
  23. package/dist/cpsat/response.js +48 -9
  24. package/dist/cpsat/response.js.map +1 -1
  25. package/dist/cpsat/rule-descriptor.d.ts +250 -0
  26. package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
  27. package/dist/cpsat/rule-descriptor.js +51 -0
  28. package/dist/cpsat/rule-descriptor.js.map +1 -0
  29. package/dist/cpsat/rules/artifacts.d.ts +9 -0
  30. package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
  31. package/dist/cpsat/rules/artifacts.js +31 -0
  32. package/dist/cpsat/rules/artifacts.js.map +1 -0
  33. package/dist/cpsat/rules/assign-together.d.ts +18 -20
  34. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  35. package/dist/cpsat/rules/assign-together.js +90 -54
  36. package/dist/cpsat/rules/assign-together.js.map +1 -1
  37. package/dist/cpsat/rules/assigned-day.d.ts +12 -0
  38. package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
  39. package/dist/cpsat/rules/assigned-day.js +43 -0
  40. package/dist/cpsat/rules/assigned-day.js.map +1 -0
  41. package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
  42. package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
  43. package/dist/cpsat/rules/assignment-priority.js +47 -50
  44. package/dist/cpsat/rules/assignment-priority.js.map +1 -1
  45. package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
  46. package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
  47. package/dist/cpsat/rules/assignment-terms.js +19 -0
  48. package/dist/cpsat/rules/assignment-terms.js.map +1 -0
  49. package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
  50. package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
  51. package/dist/cpsat/rules/concurrent-intervals.js +57 -0
  52. package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
  53. package/dist/cpsat/rules/cost-utils.d.ts +1 -3
  54. package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
  55. package/dist/cpsat/rules/cost-utils.js +0 -7
  56. package/dist/cpsat/rules/cost-utils.js.map +1 -1
  57. package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
  58. package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
  59. package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
  60. package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
  61. package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
  62. package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
  63. package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
  64. package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
  65. package/dist/cpsat/rules/index.d.ts +30 -24
  66. package/dist/cpsat/rules/index.d.ts.map +1 -1
  67. package/dist/cpsat/rules/index.js +30 -24
  68. package/dist/cpsat/rules/index.js.map +1 -1
  69. package/dist/cpsat/rules/location-preference.d.ts +14 -21
  70. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  71. package/dist/cpsat/rules/location-preference.js +45 -45
  72. package/dist/cpsat/rules/location-preference.js.map +1 -1
  73. package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
  74. package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
  75. package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
  76. package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
  77. package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
  78. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  79. package/dist/cpsat/rules/max-consecutive-days.js +67 -66
  80. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  81. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
  82. package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
  83. package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
  84. package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
  85. package/dist/cpsat/rules/max-days-week.d.ts +17 -31
  86. package/dist/cpsat/rules/max-days-week.d.ts.map +1 -1
  87. package/dist/cpsat/rules/max-days-week.js +69 -81
  88. package/dist/cpsat/rules/max-days-week.js.map +1 -1
  89. package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
  90. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  91. package/dist/cpsat/rules/max-hours-day.js +56 -72
  92. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  93. package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
  94. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  95. package/dist/cpsat/rules/max-hours-week.js +60 -75
  96. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  97. package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
  98. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  99. package/dist/cpsat/rules/max-shifts-day.js +57 -73
  100. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  101. package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
  102. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  103. package/dist/cpsat/rules/min-consecutive-days.js +123 -94
  104. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  105. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
  106. package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
  107. package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
  108. package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
  109. package/dist/cpsat/rules/min-days-week.d.ts +13 -26
  110. package/dist/cpsat/rules/min-days-week.d.ts.map +1 -1
  111. package/dist/cpsat/rules/min-days-week.js +85 -74
  112. package/dist/cpsat/rules/min-days-week.js.map +1 -1
  113. package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
  114. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  115. package/dist/cpsat/rules/min-hours-day.js +72 -54
  116. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  117. package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
  118. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  119. package/dist/cpsat/rules/min-hours-week.js +75 -57
  120. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  121. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
  122. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  123. package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
  124. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  125. package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
  126. package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
  127. package/dist/cpsat/rules/minimize-cost.js +151 -163
  128. package/dist/cpsat/rules/minimize-cost.js.map +1 -1
  129. package/dist/cpsat/rules/must-assign.d.ts +5 -42
  130. package/dist/cpsat/rules/must-assign.d.ts.map +1 -1
  131. package/dist/cpsat/rules/must-assign.js +43 -75
  132. package/dist/cpsat/rules/must-assign.js.map +1 -1
  133. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
  134. package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
  135. package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
  136. package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
  137. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
  138. package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
  139. package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
  140. package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
  141. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
  142. package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
  143. package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
  144. package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
  145. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
  146. package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
  147. package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
  148. package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
  149. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
  150. package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
  151. package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
  152. package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
  153. package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
  154. package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
  155. package/dist/cpsat/rules/pattern-eligibility.js +16 -0
  156. package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
  157. package/dist/cpsat/rules/pattern-time.d.ts +5 -0
  158. package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
  159. package/dist/cpsat/rules/pattern-time.js +20 -0
  160. package/dist/cpsat/rules/pattern-time.js.map +1 -0
  161. package/dist/cpsat/rules/registry.d.ts +217 -30
  162. package/dist/cpsat/rules/registry.d.ts.map +1 -1
  163. package/dist/cpsat/rules/registry.js +98 -34
  164. package/dist/cpsat/rules/registry.js.map +1 -1
  165. package/dist/cpsat/rules/resolver.d.ts +9 -7
  166. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  167. package/dist/cpsat/rules/resolver.js +19 -18
  168. package/dist/cpsat/rules/resolver.js.map +1 -1
  169. package/dist/cpsat/rules/role-preference.d.ts +24 -0
  170. package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
  171. package/dist/cpsat/rules/role-preference.js +72 -0
  172. package/dist/cpsat/rules/role-preference.js.map +1 -0
  173. package/dist/cpsat/rules/rules.types.d.ts +90 -17
  174. package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
  175. package/dist/cpsat/rules/scope.types.d.ts +24 -11
  176. package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
  177. package/dist/cpsat/rules/scope.types.js +14 -19
  178. package/dist/cpsat/rules/scope.types.js.map +1 -1
  179. package/dist/cpsat/rules/target-days-week.d.ts +31 -0
  180. package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
  181. package/dist/cpsat/rules/target-days-week.js +80 -0
  182. package/dist/cpsat/rules/target-days-week.js.map +1 -0
  183. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
  184. package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
  185. package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
  186. package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
  187. package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
  188. package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
  189. package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
  190. package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
  191. package/dist/cpsat/rules/time-off.d.ts +21 -57
  192. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  193. package/dist/cpsat/rules/time-off.js +114 -127
  194. package/dist/cpsat/rules/time-off.js.map +1 -1
  195. package/dist/cpsat/rules/variables.d.ts +4 -0
  196. package/dist/cpsat/rules/variables.d.ts.map +1 -0
  197. package/dist/cpsat/rules/variables.js +10 -0
  198. package/dist/cpsat/rules/variables.js.map +1 -0
  199. package/dist/cpsat/rules.d.ts +20 -3
  200. package/dist/cpsat/rules.d.ts.map +1 -1
  201. package/dist/cpsat/rules.js +19 -2
  202. package/dist/cpsat/rules.js.map +1 -1
  203. package/dist/cpsat/semantic-time.d.ts +7 -6
  204. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  205. package/dist/cpsat/semantic-time.js +15 -28
  206. package/dist/cpsat/semantic-time.js.map +1 -1
  207. package/dist/cpsat/types.d.ts +47 -4
  208. package/dist/cpsat/types.d.ts.map +1 -1
  209. package/dist/cpsat/types.js +24 -1
  210. package/dist/cpsat/types.js.map +1 -1
  211. package/dist/cpsat/utils.d.ts +23 -6
  212. package/dist/cpsat/utils.d.ts.map +1 -1
  213. package/dist/cpsat/utils.js +76 -15
  214. package/dist/cpsat/utils.js.map +1 -1
  215. package/dist/cpsat/validation-reporter.d.ts +6 -2
  216. package/dist/cpsat/validation-reporter.d.ts.map +1 -1
  217. package/dist/cpsat/validation-reporter.js +122 -10
  218. package/dist/cpsat/validation-reporter.js.map +1 -1
  219. package/dist/cpsat/validation.types.d.ts +21 -10
  220. package/dist/cpsat/validation.types.d.ts.map +1 -1
  221. package/dist/datetime.utils.d.ts +7 -13
  222. package/dist/datetime.utils.d.ts.map +1 -1
  223. package/dist/datetime.utils.js +29 -36
  224. package/dist/datetime.utils.js.map +1 -1
  225. package/dist/index.d.ts +12 -8
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +8 -3
  228. package/dist/index.js.map +1 -1
  229. package/dist/schedule/cost.d.ts +48 -60
  230. package/dist/schedule/cost.d.ts.map +1 -1
  231. package/dist/schedule/cost.js +15 -51
  232. package/dist/schedule/cost.js.map +1 -1
  233. package/dist/schedule/coverage.d.ts +57 -28
  234. package/dist/schedule/coverage.d.ts.map +1 -1
  235. package/dist/schedule/coverage.js +24 -1
  236. package/dist/schedule/coverage.js.map +1 -1
  237. package/dist/schedule/definition.d.ts +138 -19
  238. package/dist/schedule/definition.d.ts.map +1 -1
  239. package/dist/schedule/definition.js +99 -90
  240. package/dist/schedule/definition.js.map +1 -1
  241. package/dist/schedule/index.d.ts +4 -4
  242. package/dist/schedule/index.d.ts.map +1 -1
  243. package/dist/schedule/index.js +2 -2
  244. package/dist/schedule/index.js.map +1 -1
  245. package/dist/schedule/rules.d.ts +438 -113
  246. package/dist/schedule/rules.d.ts.map +1 -1
  247. package/dist/schedule/rules.js +310 -108
  248. package/dist/schedule/rules.js.map +1 -1
  249. package/dist/schedule/shift-patterns.d.ts +0 -17
  250. package/dist/schedule/shift-patterns.d.ts.map +1 -1
  251. package/dist/schedule/shift-patterns.js +0 -17
  252. package/dist/schedule/shift-patterns.js.map +1 -1
  253. package/dist/schedule/time-periods.d.ts +37 -27
  254. package/dist/schedule/time-periods.d.ts.map +1 -1
  255. package/dist/schedule/time-periods.js +37 -27
  256. package/dist/schedule/time-periods.js.map +1 -1
  257. package/dist/types.d.ts +67 -3
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +64 -0
  260. package/dist/types.js.map +1 -1
  261. package/package.json +8 -9
  262. package/solver/src/solver/models.py +41 -4
  263. package/solver/src/solver/solver.py +546 -157
  264. package/solver/tests/test_solver.py +363 -7
  265. package/src/client.schemas.ts +127 -10
  266. package/src/client.ts +3 -0
  267. package/src/client.types.ts +73 -7
  268. package/src/cpsat/cost.ts +17 -7
  269. package/src/cpsat/model-builder.ts +614 -129
  270. package/src/cpsat/response.ts +57 -13
  271. package/src/cpsat/rule-descriptor.ts +311 -0
  272. package/src/cpsat/rules/artifacts.ts +56 -0
  273. package/src/cpsat/rules/assign-together.ts +104 -69
  274. package/src/cpsat/rules/assigned-day.ts +70 -0
  275. package/src/cpsat/rules/assignment-priority.ts +54 -58
  276. package/src/cpsat/rules/assignment-terms.ts +43 -0
  277. package/src/cpsat/rules/concurrent-intervals.ts +93 -0
  278. package/src/cpsat/rules/cost-utils.ts +1 -9
  279. package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
  280. package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
  281. package/src/cpsat/rules/index.ts +30 -24
  282. package/src/cpsat/rules/location-preference.ts +47 -52
  283. package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
  284. package/src/cpsat/rules/max-consecutive-days.ts +74 -91
  285. package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
  286. package/src/cpsat/rules/max-days-week.ts +89 -115
  287. package/src/cpsat/rules/max-hours-day.ts +64 -84
  288. package/src/cpsat/rules/max-hours-week.ts +74 -90
  289. package/src/cpsat/rules/max-shifts-day.ts +65 -85
  290. package/src/cpsat/rules/min-consecutive-days.ts +161 -127
  291. package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
  292. package/src/cpsat/rules/min-days-week.ts +93 -91
  293. package/src/cpsat/rules/min-hours-day.ts +79 -58
  294. package/src/cpsat/rules/min-hours-week.ts +87 -62
  295. package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
  296. package/src/cpsat/rules/minimize-cost.ts +175 -188
  297. package/src/cpsat/rules/must-assign.ts +44 -81
  298. package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
  299. package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
  300. package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
  301. package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
  302. package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
  303. package/src/cpsat/rules/pattern-eligibility.ts +26 -0
  304. package/src/cpsat/rules/pattern-time.ts +30 -0
  305. package/src/cpsat/rules/registry.ts +140 -62
  306. package/src/cpsat/rules/resolver.ts +58 -30
  307. package/src/cpsat/rules/role-preference.ts +92 -0
  308. package/src/cpsat/rules/rules.types.ts +89 -17
  309. package/src/cpsat/rules/scope.types.ts +31 -26
  310. package/src/cpsat/rules/target-days-week.ts +105 -0
  311. package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
  312. package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
  313. package/src/cpsat/rules/time-off.ts +154 -163
  314. package/src/cpsat/rules/variables.ts +11 -0
  315. package/src/cpsat/rules.ts +43 -17
  316. package/src/cpsat/semantic-time.ts +29 -43
  317. package/src/cpsat/types.ts +53 -4
  318. package/src/cpsat/utils.ts +92 -20
  319. package/src/cpsat/validation-reporter.ts +170 -19
  320. package/src/cpsat/validation.types.ts +22 -11
  321. package/src/datetime.utils.ts +45 -44
  322. package/src/index.ts +64 -12
  323. package/src/schedule/cost.ts +40 -61
  324. package/src/schedule/coverage.ts +57 -28
  325. package/src/schedule/definition.ts +565 -76
  326. package/src/schedule/index.ts +23 -3
  327. package/src/schedule/rules.ts +631 -132
  328. package/src/schedule/shift-patterns.ts +0 -17
  329. package/src/schedule/time-periods.ts +37 -27
  330. package/src/types.ts +114 -2
@@ -1,5 +1,16 @@
1
- from solver.models import Constraint, Objective, SolverRequest, Term, Variable
2
- from solver.solver import solve_request
1
+ import pytest
2
+
3
+ from solver.models import (
4
+ Constraint,
5
+ Objective,
6
+ Options,
7
+ SolverObjectiveStage,
8
+ SolverRequest,
9
+ SolverStageResult,
10
+ Term,
11
+ Variable,
12
+ )
13
+ from solver.solver import _aggregate_stage_status, solve_request
3
14
 
4
15
 
5
16
  def test_linear_feasible_solution():
@@ -134,11 +145,11 @@ def test_soft_constraint_reports_violation_when_id_provided():
134
145
  response = solve_request(request)
135
146
 
136
147
  assert response.status == "OPTIMAL"
137
- assert response.softViolations is not None
138
- assert response.softViolations[0].constraintId == "coverage:test:2024-02-01:540"
139
- assert response.softViolations[0].violationAmount == 1
140
- assert response.softViolations[0].targetValue == 1
141
- assert response.softViolations[0].actualValue == 0
148
+ assert response.softConstraintViolations is not None
149
+ assert response.softConstraintViolations[0].constraintId == "coverage:test:2024-02-01:540"
150
+ assert response.softConstraintViolations[0].violationAmount == 1
151
+ assert response.softConstraintViolations[0].targetValue == 1
152
+ assert response.softConstraintViolations[0].actualValue == 0
142
153
 
143
154
 
144
155
  def test_infeasible_solution_reports_solution_info():
@@ -154,3 +165,348 @@ def test_infeasible_solution_reports_solution_info():
154
165
 
155
166
  assert response.status == "INFEASIBLE"
156
167
  assert response.solutionInfo is not None
168
+
169
+
170
+ def test_infeasible_solution_reports_tracked_constraint_ids():
171
+ request = SolverRequest(
172
+ variables=[Variable(type="bool", name="x")],
173
+ options=Options(diagnostics="hard"),
174
+ constraints=[
175
+ Constraint(
176
+ type="linear",
177
+ terms=[Term(var="x", coeff=1)],
178
+ op="==",
179
+ rhs=0,
180
+ id="must-be-zero",
181
+ ),
182
+ Constraint(
183
+ type="linear",
184
+ terms=[Term(var="x", coeff=1)],
185
+ op="==",
186
+ rhs=1,
187
+ id="must-be-one",
188
+ ),
189
+ ],
190
+ )
191
+
192
+ response = solve_request(request)
193
+
194
+ assert response.status == "INFEASIBLE"
195
+ assert response.hardConstraintConflicts is not None
196
+ assert {conflict.constraintId for conflict in response.hardConstraintConflicts} == {
197
+ "must-be-zero",
198
+ "must-be-one",
199
+ }
200
+
201
+
202
+ def test_infeasible_solution_omits_hard_conflicts_by_default():
203
+ request = SolverRequest(
204
+ variables=[Variable(type="bool", name="x")],
205
+ constraints=[
206
+ Constraint(type="linear", terms=[Term(var="x", coeff=1)], op="==", rhs=0, id="zero"),
207
+ Constraint(type="linear", terms=[Term(var="x", coeff=1)], op="==", rhs=1, id="one"),
208
+ ],
209
+ )
210
+
211
+ response = solve_request(request)
212
+
213
+ assert response.status == "INFEASIBLE"
214
+ assert response.hardConstraintConflicts is None
215
+
216
+
217
+ def test_lexicographic_minimization_freezes_stage_1_before_stage_2():
218
+ request = SolverRequest(
219
+ variables=[Variable(type="bool", name="x")],
220
+ constraints=[],
221
+ objectiveStages=[
222
+ SolverObjectiveStage(id="minimize-x", sense="minimize", terms=[Term(var="x", coeff=1)]),
223
+ SolverObjectiveStage(id="maximize-x", sense="maximize", terms=[Term(var="x", coeff=1)]),
224
+ ],
225
+ )
226
+
227
+ response = solve_request(request)
228
+
229
+ assert response.status == "OPTIMAL"
230
+ assert response.values == {"x": 0}
231
+ assert response.stageResults is not None
232
+ assert [stage.id for stage in response.stageResults] == ["minimize-x", "maximize-x"]
233
+ assert [stage.objectiveValue for stage in response.stageResults] == [0, 0]
234
+
235
+
236
+ def test_lexicographic_maximization_freezes_stage_1_before_stage_2():
237
+ request = SolverRequest(
238
+ variables=[Variable(type="bool", name="x")],
239
+ constraints=[],
240
+ objectiveStages=[
241
+ SolverObjectiveStage(id="maximize-x", sense="maximize", terms=[Term(var="x", coeff=1)]),
242
+ SolverObjectiveStage(id="minimize-x", sense="minimize", terms=[Term(var="x", coeff=1)]),
243
+ ],
244
+ )
245
+
246
+ response = solve_request(request)
247
+
248
+ assert response.status == "OPTIMAL"
249
+ assert response.values == {"x": 1}
250
+ assert response.stageResults is not None
251
+ assert [stage.objectiveValue for stage in response.stageResults] == [1, 1]
252
+
253
+
254
+ def test_staged_soft_linear_slack_belongs_to_declared_stage():
255
+ request = SolverRequest(
256
+ variables=[Variable(type="bool", name="x")],
257
+ constraints=[
258
+ Constraint(
259
+ type="soft_linear",
260
+ terms=[Term(var="x", coeff=1)],
261
+ op=">=",
262
+ rhs=1,
263
+ penalty=10,
264
+ id="want-x",
265
+ stage="first",
266
+ ),
267
+ Constraint(
268
+ type="soft_linear",
269
+ terms=[Term(var="x", coeff=1)],
270
+ op="<=",
271
+ rhs=0,
272
+ penalty=100,
273
+ id="avoid-x",
274
+ stage="second",
275
+ ),
276
+ ],
277
+ objectiveStages=[
278
+ SolverObjectiveStage(id="first", sense="minimize", terms=[]),
279
+ SolverObjectiveStage(id="second", sense="minimize", terms=[]),
280
+ ],
281
+ )
282
+
283
+ response = solve_request(request)
284
+
285
+ assert response.status == "OPTIMAL"
286
+ assert response.values == {"x": 1}
287
+ assert response.stageResults is not None
288
+ assert [stage.objectiveValue for stage in response.stageResults] == [0, 100]
289
+ assert response.softConstraintViolations is not None
290
+ assert [violation.constraintId for violation in response.softConstraintViolations] == ["avoid-x"]
291
+
292
+
293
+ def test_unknown_soft_stage_is_rejected():
294
+ request = SolverRequest(
295
+ variables=[Variable(type="bool", name="x")],
296
+ constraints=[
297
+ Constraint(
298
+ type="soft_linear",
299
+ terms=[Term(var="x", coeff=1)],
300
+ op="<=",
301
+ rhs=0,
302
+ penalty=1,
303
+ stage="missing",
304
+ )
305
+ ],
306
+ objectiveStages=[SolverObjectiveStage(id="known", sense="minimize", terms=[Term(var="x", coeff=1)])],
307
+ )
308
+
309
+ response = solve_request(request)
310
+
311
+ assert response.status == "ERROR"
312
+ assert response.error is not None
313
+ assert "not declared" in response.error
314
+
315
+
316
+ def test_missing_soft_stage_is_rejected_in_staged_mode():
317
+ request = SolverRequest(
318
+ variables=[Variable(type="bool", name="x")],
319
+ constraints=[
320
+ Constraint(
321
+ type="soft_linear",
322
+ terms=[Term(var="x", coeff=1)],
323
+ op="<=",
324
+ rhs=0,
325
+ penalty=1,
326
+ )
327
+ ],
328
+ objectiveStages=[SolverObjectiveStage(id="known", sense="minimize", terms=[Term(var="x", coeff=1)])],
329
+ )
330
+
331
+ response = solve_request(request)
332
+
333
+ assert response.status == "ERROR"
334
+ assert response.error is not None
335
+ assert "requires stage" in response.error
336
+
337
+
338
+ def test_objective_and_objective_stages_are_rejected():
339
+ request = SolverRequest(
340
+ variables=[Variable(type="bool", name="x")],
341
+ constraints=[],
342
+ objective=Objective(sense="minimize", terms=[Term(var="x", coeff=1)]),
343
+ objectiveStages=[SolverObjectiveStage(id="stage", sense="minimize", terms=[Term(var="x", coeff=1)])],
344
+ )
345
+
346
+ response = solve_request(request)
347
+
348
+ assert response.status == "ERROR"
349
+ assert response.error is not None
350
+ assert "cannot both be present" in response.error
351
+
352
+
353
+ def test_satisfy_mode_ignores_soft_penalties_and_returns_hard_feasible_solution():
354
+ request = SolverRequest(
355
+ variables=[Variable(type="bool", name="x")],
356
+ constraints=[
357
+ Constraint(type="linear", terms=[Term(var="x", coeff=1)], op="==", rhs=1),
358
+ Constraint(
359
+ type="soft_linear",
360
+ terms=[Term(var="x", coeff=1)],
361
+ op="<=",
362
+ rhs=0,
363
+ penalty=100,
364
+ id="prefer-zero",
365
+ ),
366
+ ],
367
+ mode="satisfy",
368
+ )
369
+
370
+ response = solve_request(request)
371
+
372
+ assert response.status == "OPTIMAL"
373
+ assert response.values == {"x": 1}
374
+ assert response.softConstraintViolations == []
375
+ assert response.statistics is not None
376
+ assert "objectiveValue" not in response.statistics
377
+
378
+
379
+ def test_satisfy_mode_validates_soft_constraints_with_unknown_variables():
380
+ request = SolverRequest(
381
+ variables=[Variable(type="bool", name="x")],
382
+ constraints=[
383
+ Constraint(
384
+ type="soft_linear",
385
+ terms=[Term(var="missing", coeff=1)],
386
+ op="<=",
387
+ rhs=0,
388
+ penalty=100,
389
+ ),
390
+ ],
391
+ mode="satisfy",
392
+ )
393
+
394
+ response = solve_request(request)
395
+
396
+ assert response.status == "ERROR"
397
+ assert response.error is not None
398
+ assert "Unknown variable missing" in response.error
399
+
400
+
401
+ def test_satisfy_mode_validates_soft_constraint_required_fields():
402
+ request = SolverRequest(
403
+ variables=[Variable(type="bool", name="x")],
404
+ constraints=[
405
+ Constraint(
406
+ type="soft_linear",
407
+ terms=[Term(var="x", coeff=1)],
408
+ op="<=",
409
+ rhs=0,
410
+ ),
411
+ ],
412
+ mode="satisfy",
413
+ )
414
+
415
+ response = solve_request(request)
416
+
417
+ assert response.status == "ERROR"
418
+ assert response.error is not None
419
+ assert "requires terms, op, rhs, and penalty" in response.error
420
+
421
+
422
+ def test_objective_stages_with_solution_limit_one_is_rejected():
423
+ request = SolverRequest(
424
+ variables=[Variable(type="bool", name="x")],
425
+ constraints=[],
426
+ objectiveStages=[SolverObjectiveStage(id="stage", sense="minimize", terms=[Term(var="x", coeff=1)])],
427
+ options=Options(solutionLimit=1),
428
+ )
429
+
430
+ response = solve_request(request)
431
+
432
+ assert response.status == "ERROR"
433
+ assert response.error is not None
434
+ assert "solutionLimit=1" in response.error
435
+
436
+
437
+ def test_stage_metadata_is_returned():
438
+ request = SolverRequest(
439
+ variables=[Variable(type="bool", name="x")],
440
+ constraints=[],
441
+ objectiveStages=[SolverObjectiveStage(id="stage", sense="minimize", terms=[Term(var="x", coeff=1)])],
442
+ )
443
+
444
+ response = solve_request(request)
445
+
446
+ assert response.stageResults is not None
447
+ assert len(response.stageResults) == 1
448
+ stage_result = response.stageResults[0]
449
+ assert stage_result.id == "stage"
450
+ assert stage_result.status == "OPTIMAL"
451
+ assert stage_result.objectiveValue == 0
452
+ assert stage_result.bestObjectiveBound == 0
453
+ assert stage_result.solveTimeMs >= 0
454
+
455
+
456
+ def test_staged_failure_omits_values_when_no_stage_has_succeeded():
457
+ request = SolverRequest(
458
+ variables=[Variable(type="bool", name="x")],
459
+ constraints=[
460
+ Constraint(type="linear", terms=[Term(var="x", coeff=1)], op="==", rhs=0),
461
+ Constraint(type="linear", terms=[Term(var="x", coeff=1)], op="==", rhs=1),
462
+ ],
463
+ objectiveStages=[SolverObjectiveStage(id="stage", sense="minimize", terms=[Term(var="x", coeff=1)])],
464
+ )
465
+
466
+ response = solve_request(request)
467
+
468
+ assert response.status == "INFEASIBLE"
469
+ assert response.values is None
470
+ assert "values" not in response.model_dump(exclude_unset=True)
471
+
472
+
473
+ def test_aggregate_stage_status_returns_optimal_when_all_stages_are_optimal():
474
+ stage_results = [
475
+ SolverStageResult(id="stage-1", status="OPTIMAL", solveTimeMs=1),
476
+ SolverStageResult(id="stage-2", status="OPTIMAL", solveTimeMs=1),
477
+ ]
478
+
479
+ assert _aggregate_stage_status(stage_results) == "OPTIMAL"
480
+
481
+
482
+ def test_aggregate_stage_status_downgrades_when_later_stage_is_feasible():
483
+ stage_results = [
484
+ SolverStageResult(id="stage-1", status="OPTIMAL", solveTimeMs=1),
485
+ SolverStageResult(id="stage-2", status="FEASIBLE", solveTimeMs=1),
486
+ ]
487
+
488
+ assert _aggregate_stage_status(stage_results) == "FEASIBLE"
489
+
490
+
491
+ def test_aggregate_stage_status_downgrades_when_earlier_stage_is_feasible():
492
+ stage_results = [
493
+ SolverStageResult(id="stage-1", status="FEASIBLE", solveTimeMs=1),
494
+ SolverStageResult(id="stage-2", status="OPTIMAL", solveTimeMs=1),
495
+ ]
496
+
497
+ assert _aggregate_stage_status(stage_results) == "FEASIBLE"
498
+
499
+
500
+ def test_aggregate_stage_status_returns_first_terminal_status():
501
+ stage_results = [
502
+ SolverStageResult(id="stage-1", status="OPTIMAL", solveTimeMs=1),
503
+ SolverStageResult(id="stage-2", status="TIMEOUT", solveTimeMs=1),
504
+ SolverStageResult(id="stage-3", status="ERROR", solveTimeMs=1),
505
+ ]
506
+
507
+ assert _aggregate_stage_status(stage_results) == "TIMEOUT"
508
+
509
+
510
+ def test_aggregate_stage_status_rejects_empty_stage_results():
511
+ with pytest.raises(ValueError, match="empty stage results"):
512
+ _aggregate_stage_status([])
@@ -55,6 +55,7 @@ export const LinearConstraintSchema = z.object({
55
55
  terms: z.array(SolverTermSchema),
56
56
  op: z.union([z.literal("<="), z.literal(">="), z.literal("==")]),
57
57
  rhs: z.number(),
58
+ id: z.string().optional(),
58
59
  });
59
60
 
60
61
  export const SoftLinearConstraintSchema = z.object({
@@ -64,6 +65,7 @@ export const SoftLinearConstraintSchema = z.object({
64
65
  rhs: z.number(),
65
66
  penalty: z.number(),
66
67
  id: z.string().optional(),
68
+ stage: z.string().optional(),
67
69
  });
68
70
 
69
71
  export const ExactlyOneConstraintSchema = z.object({
@@ -118,6 +120,13 @@ export const SolverObjectiveSchema = z.object({
118
120
  terms: z.array(SolverTermSchema),
119
121
  });
120
122
 
123
+ export const SolverObjectiveStageSchema = SolverObjectiveSchema.extend({
124
+ id: z.string(),
125
+ });
126
+
127
+ export const SolverModeSchema = z.enum(["optimize", "satisfy"]);
128
+ export const SolverDiagnosticModeSchema = z.enum(["none", "hard"]);
129
+
121
130
  // --------------------------------------------------------------------------
122
131
  // Options schema
123
132
  // --------------------------------------------------------------------------
@@ -125,20 +134,109 @@ export const SolverObjectiveSchema = z.object({
125
134
  export const SolverOptionsSchema = z.object({
126
135
  timeLimitSeconds: z.number().optional(),
127
136
  solutionLimit: z.number().optional(),
137
+ diagnostics: SolverDiagnosticModeSchema.optional(),
128
138
  });
129
139
 
130
140
  // --------------------------------------------------------------------------
131
141
  // Request/Response schemas
132
142
  // --------------------------------------------------------------------------
133
143
 
134
- export const SolverRequestSchema = z.object({
135
- variables: z.array(SolverVariableSchema),
136
- constraints: z.array(SolverConstraintSchema),
137
- objective: SolverObjectiveSchema.optional(),
138
- options: SolverOptionsSchema.optional(),
144
+ export const SolverStatusSchema = z.enum(["OPTIMAL", "FEASIBLE", "INFEASIBLE", "TIMEOUT", "ERROR"]);
145
+
146
+ export const SolverStageResultSchema = z.object({
147
+ id: z.string(),
148
+ status: SolverStatusSchema,
149
+ objectiveValue: z.number().optional(),
150
+ bestObjectiveBound: z.number().optional(),
151
+ solveTimeMs: z.number(),
139
152
  });
140
153
 
141
- export const SolverStatusSchema = z.enum(["OPTIMAL", "FEASIBLE", "INFEASIBLE", "TIMEOUT", "ERROR"]);
154
+ export const SolverRequestSchema = z
155
+ .object({
156
+ variables: z.array(SolverVariableSchema),
157
+ constraints: z.array(SolverConstraintSchema),
158
+ objective: SolverObjectiveSchema.optional(),
159
+ objectiveStages: z.array(SolverObjectiveStageSchema).optional(),
160
+ mode: SolverModeSchema.optional(),
161
+ options: SolverOptionsSchema.optional(),
162
+ })
163
+ .superRefine((request, ctx) => {
164
+ if (request.objective && request.objectiveStages) {
165
+ ctx.addIssue({
166
+ code: "custom",
167
+ path: ["objectiveStages"],
168
+ message: "objective and objectiveStages cannot both be present",
169
+ });
170
+ }
171
+
172
+ if (request.objectiveStages !== undefined) {
173
+ if (request.objectiveStages.length === 0) {
174
+ ctx.addIssue({
175
+ code: "custom",
176
+ path: ["objectiveStages"],
177
+ message: "objectiveStages cannot be empty",
178
+ });
179
+ }
180
+
181
+ const seenStageIds = new Set<string>();
182
+ for (const [index, stage] of request.objectiveStages.entries()) {
183
+ if (seenStageIds.has(stage.id)) {
184
+ ctx.addIssue({
185
+ code: "custom",
186
+ path: ["objectiveStages", index, "id"],
187
+ message: `Duplicate objective stage id ${stage.id}`,
188
+ });
189
+ }
190
+ seenStageIds.add(stage.id);
191
+ }
192
+
193
+ if (request.options?.solutionLimit === 1) {
194
+ ctx.addIssue({
195
+ code: "custom",
196
+ path: ["options", "solutionLimit"],
197
+ message: "objectiveStages cannot be used with solutionLimit=1",
198
+ });
199
+ }
200
+
201
+ for (const [index, constraint] of request.constraints.entries()) {
202
+ if (constraint.type !== "soft_linear") continue;
203
+
204
+ if (constraint.stage === undefined) {
205
+ ctx.addIssue({
206
+ code: "custom",
207
+ path: ["constraints", index, "stage"],
208
+ message: "Soft linear constraint requires stage when objectiveStages are used",
209
+ });
210
+ continue;
211
+ }
212
+
213
+ if (!seenStageIds.has(constraint.stage)) {
214
+ ctx.addIssue({
215
+ code: "custom",
216
+ path: ["constraints", index, "stage"],
217
+ message: `Soft linear constraint stage ${constraint.stage} is not declared`,
218
+ });
219
+ }
220
+ }
221
+ }
222
+
223
+ if (request.mode === "satisfy") {
224
+ if (request.objective) {
225
+ ctx.addIssue({
226
+ code: "custom",
227
+ path: ["objective"],
228
+ message: 'mode "satisfy" cannot be used with objective',
229
+ });
230
+ }
231
+ if (request.objectiveStages) {
232
+ ctx.addIssue({
233
+ code: "custom",
234
+ path: ["objectiveStages"],
235
+ message: 'mode "satisfy" cannot be used with objectiveStages',
236
+ });
237
+ }
238
+ }
239
+ });
142
240
 
143
241
  export const SolverStatisticsSchema = z.object({
144
242
  solveTimeMs: z.number().optional(),
@@ -146,18 +244,37 @@ export const SolverStatisticsSchema = z.object({
146
244
  branches: z.number().optional(),
147
245
  });
148
246
 
149
- export const SoftConstraintViolationSchema = z.object({
247
+ export const SolverSoftConstraintViolationSchema = z.object({
150
248
  constraintId: z.string(),
151
249
  violationAmount: z.number(),
152
250
  targetValue: z.number(),
153
251
  actualValue: z.number(),
154
252
  });
155
253
 
156
- export const SolverResponseSchema = z.object({
157
- status: SolverStatusSchema,
254
+ export const SolverHardConstraintConflictSchema = z.object({
255
+ constraintId: z.string(),
256
+ });
257
+
258
+ const SolverResponseBaseSchema = z.object({
158
259
  values: z.record(z.string(), z.number()).optional(),
159
260
  statistics: SolverStatisticsSchema.optional(),
160
261
  error: z.string().optional(),
161
262
  solutionInfo: z.string().optional(),
162
- softViolations: z.array(SoftConstraintViolationSchema).optional(),
263
+ hardConstraintConflicts: z.array(SolverHardConstraintConflictSchema).optional(),
264
+ stageResults: z.array(SolverStageResultSchema).optional(),
163
265
  });
266
+
267
+ const SuccessfulSolverResponseSchema = SolverResponseBaseSchema.extend({
268
+ status: z.union([z.literal("OPTIMAL"), z.literal("FEASIBLE")]),
269
+ softConstraintViolations: z.array(SolverSoftConstraintViolationSchema),
270
+ });
271
+
272
+ const NonSuccessfulSolverResponseSchema = SolverResponseBaseSchema.extend({
273
+ status: z.union([z.literal("INFEASIBLE"), z.literal("TIMEOUT"), z.literal("ERROR")]),
274
+ softConstraintViolations: z.array(SolverSoftConstraintViolationSchema).optional(),
275
+ });
276
+
277
+ export const SolverResponseSchema = z.union([
278
+ SuccessfulSolverResponseSchema,
279
+ NonSuccessfulSolverResponseSchema,
280
+ ]);
package/src/client.ts CHANGED
@@ -9,6 +9,9 @@ export type {
9
9
  SolverConstraint,
10
10
  SolverTerm,
11
11
  SolverObjective,
12
+ SolverObjectiveStage,
13
+ SolverMode,
14
+ SolverStageResult,
12
15
  FetcherLike,
13
16
  } from "./client.types.js";
14
17