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.
- package/CHANGELOG.md +21 -0
- package/README.md +22 -27
- package/dist/client.d.ts +39 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/client.schemas.d.ts +118 -18
- package/dist/client.schemas.d.ts.map +1 -1
- package/dist/client.schemas.js +110 -6
- package/dist/client.schemas.js.map +1 -1
- package/dist/client.types.d.ts +63 -7
- package/dist/client.types.d.ts.map +1 -1
- package/dist/client.types.js.map +1 -1
- package/dist/cpsat/cost.d.ts +5 -2
- package/dist/cpsat/cost.d.ts.map +1 -1
- package/dist/cpsat/cost.js +13 -6
- package/dist/cpsat/cost.js.map +1 -1
- package/dist/cpsat/model-builder.d.ts +42 -61
- package/dist/cpsat/model-builder.d.ts.map +1 -1
- package/dist/cpsat/model-builder.js +451 -56
- package/dist/cpsat/model-builder.js.map +1 -1
- package/dist/cpsat/response.d.ts +19 -7
- package/dist/cpsat/response.d.ts.map +1 -1
- package/dist/cpsat/response.js +48 -9
- package/dist/cpsat/response.js.map +1 -1
- package/dist/cpsat/rule-descriptor.d.ts +250 -0
- package/dist/cpsat/rule-descriptor.d.ts.map +1 -0
- package/dist/cpsat/rule-descriptor.js +51 -0
- package/dist/cpsat/rule-descriptor.js.map +1 -0
- package/dist/cpsat/rules/artifacts.d.ts +9 -0
- package/dist/cpsat/rules/artifacts.d.ts.map +1 -0
- package/dist/cpsat/rules/artifacts.js +31 -0
- package/dist/cpsat/rules/artifacts.js.map +1 -0
- package/dist/cpsat/rules/assign-together.d.ts +18 -20
- package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
- package/dist/cpsat/rules/assign-together.js +90 -54
- package/dist/cpsat/rules/assign-together.js.map +1 -1
- package/dist/cpsat/rules/assigned-day.d.ts +12 -0
- package/dist/cpsat/rules/assigned-day.d.ts.map +1 -0
- package/dist/cpsat/rules/assigned-day.js +43 -0
- package/dist/cpsat/rules/assigned-day.js.map +1 -0
- package/dist/cpsat/rules/assignment-priority.d.ts +15 -29
- package/dist/cpsat/rules/assignment-priority.d.ts.map +1 -1
- package/dist/cpsat/rules/assignment-priority.js +47 -50
- package/dist/cpsat/rules/assignment-priority.js.map +1 -1
- package/dist/cpsat/rules/assignment-terms.d.ts +6 -0
- package/dist/cpsat/rules/assignment-terms.d.ts.map +1 -0
- package/dist/cpsat/rules/assignment-terms.js +19 -0
- package/dist/cpsat/rules/assignment-terms.js.map +1 -0
- package/dist/cpsat/rules/concurrent-intervals.d.ts +19 -0
- package/dist/cpsat/rules/concurrent-intervals.d.ts.map +1 -0
- package/dist/cpsat/rules/concurrent-intervals.js +57 -0
- package/dist/cpsat/rules/concurrent-intervals.js.map +1 -0
- package/dist/cpsat/rules/cost-utils.d.ts +1 -3
- package/dist/cpsat/rules/cost-utils.d.ts.map +1 -1
- package/dist/cpsat/rules/cost-utils.js +0 -7
- package/dist/cpsat/rules/cost-utils.js.map +1 -1
- package/dist/cpsat/rules/day-cost-multiplier.d.ts +10 -10
- package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-multiplier.js +88 -77
- package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.d.ts +10 -10
- package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.js +76 -65
- package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/index.d.ts +30 -24
- package/dist/cpsat/rules/index.d.ts.map +1 -1
- package/dist/cpsat/rules/index.js +30 -24
- package/dist/cpsat/rules/index.js.map +1 -1
- package/dist/cpsat/rules/location-preference.d.ts +14 -21
- package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
- package/dist/cpsat/rules/location-preference.js +45 -45
- package/dist/cpsat/rules/location-preference.js.map +1 -1
- package/dist/cpsat/rules/max-concurrent-assignments.d.ts +46 -0
- package/dist/cpsat/rules/max-concurrent-assignments.d.ts.map +1 -0
- package/dist/cpsat/rules/max-concurrent-assignments.js +121 -0
- package/dist/cpsat/rules/max-concurrent-assignments.js.map +1 -0
- package/dist/cpsat/rules/max-consecutive-days.d.ts +18 -17
- package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/max-consecutive-days.js +67 -66
- package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/max-days-of-week-per-period.d.ts +45 -0
- package/dist/cpsat/rules/max-days-of-week-per-period.d.ts.map +1 -0
- package/dist/cpsat/rules/max-days-of-week-per-period.js +95 -0
- package/dist/cpsat/rules/max-days-of-week-per-period.js.map +1 -0
- package/dist/cpsat/rules/max-days-week.d.ts +17 -31
- package/dist/cpsat/rules/max-days-week.d.ts.map +1 -1
- package/dist/cpsat/rules/max-days-week.js +69 -81
- package/dist/cpsat/rules/max-days-week.js.map +1 -1
- package/dist/cpsat/rules/max-hours-day.d.ts +14 -31
- package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-day.js +56 -72
- package/dist/cpsat/rules/max-hours-day.js.map +1 -1
- package/dist/cpsat/rules/max-hours-week.d.ts +15 -31
- package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-week.js +60 -75
- package/dist/cpsat/rules/max-hours-week.js.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.d.ts +17 -33
- package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.js +57 -73
- package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.d.ts +18 -18
- package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.js +123 -94
- package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/min-days-of-week-per-period.d.ts +35 -0
- package/dist/cpsat/rules/min-days-of-week-per-period.d.ts.map +1 -0
- package/dist/cpsat/rules/min-days-of-week-per-period.js +108 -0
- package/dist/cpsat/rules/min-days-of-week-per-period.js.map +1 -0
- package/dist/cpsat/rules/min-days-week.d.ts +13 -26
- package/dist/cpsat/rules/min-days-week.d.ts.map +1 -1
- package/dist/cpsat/rules/min-days-week.js +85 -74
- package/dist/cpsat/rules/min-days-week.js.map +1 -1
- package/dist/cpsat/rules/min-hours-day.d.ts +14 -17
- package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-day.js +72 -54
- package/dist/cpsat/rules/min-hours-day.js.map +1 -1
- package/dist/cpsat/rules/min-hours-week.d.ts +15 -18
- package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-week.js +75 -57
- package/dist/cpsat/rules/min-hours-week.js.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.d.ts +14 -17
- package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.js +148 -138
- package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
- package/dist/cpsat/rules/minimize-cost.d.ts +2 -18
- package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
- package/dist/cpsat/rules/minimize-cost.js +151 -163
- package/dist/cpsat/rules/minimize-cost.js.map +1 -1
- package/dist/cpsat/rules/must-assign.d.ts +5 -42
- package/dist/cpsat/rules/must-assign.d.ts.map +1 -1
- package/dist/cpsat/rules/must-assign.js +43 -75
- package/dist/cpsat/rules/must-assign.js.map +1 -1
- package/dist/cpsat/rules/overtime-daily-multiplier.d.ts +6 -10
- package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-multiplier.js +118 -112
- package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.d.ts +6 -11
- package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.js +104 -99
- package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +9 -21
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.js +181 -199
- package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts +13 -12
- package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.js +143 -143
- package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts +7 -11
- package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.js +124 -119
- package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
- package/dist/cpsat/rules/pattern-eligibility.d.ts +6 -0
- package/dist/cpsat/rules/pattern-eligibility.d.ts.map +1 -0
- package/dist/cpsat/rules/pattern-eligibility.js +16 -0
- package/dist/cpsat/rules/pattern-eligibility.js.map +1 -0
- package/dist/cpsat/rules/pattern-time.d.ts +5 -0
- package/dist/cpsat/rules/pattern-time.d.ts.map +1 -0
- package/dist/cpsat/rules/pattern-time.js +20 -0
- package/dist/cpsat/rules/pattern-time.js.map +1 -0
- package/dist/cpsat/rules/registry.d.ts +217 -30
- package/dist/cpsat/rules/registry.d.ts.map +1 -1
- package/dist/cpsat/rules/registry.js +98 -34
- package/dist/cpsat/rules/registry.js.map +1 -1
- package/dist/cpsat/rules/resolver.d.ts +9 -7
- package/dist/cpsat/rules/resolver.d.ts.map +1 -1
- package/dist/cpsat/rules/resolver.js +19 -18
- package/dist/cpsat/rules/resolver.js.map +1 -1
- package/dist/cpsat/rules/role-preference.d.ts +24 -0
- package/dist/cpsat/rules/role-preference.d.ts.map +1 -0
- package/dist/cpsat/rules/role-preference.js +72 -0
- package/dist/cpsat/rules/role-preference.js.map +1 -0
- package/dist/cpsat/rules/rules.types.d.ts +90 -17
- package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.d.ts +24 -11
- package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.js +14 -19
- package/dist/cpsat/rules/scope.types.js.map +1 -1
- package/dist/cpsat/rules/target-days-week.d.ts +31 -0
- package/dist/cpsat/rules/target-days-week.d.ts.map +1 -0
- package/dist/cpsat/rules/target-days-week.js +80 -0
- package/dist/cpsat/rules/target-days-week.js.map +1 -0
- package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts +33 -0
- package/dist/cpsat/rules/target-peak-concurrent-assignments.d.ts.map +1 -0
- package/dist/cpsat/rules/target-peak-concurrent-assignments.js +146 -0
- package/dist/cpsat/rules/target-peak-concurrent-assignments.js.map +1 -0
- package/dist/cpsat/rules/time-cost-surcharge.d.ts +15 -14
- package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/time-cost-surcharge.js +82 -96
- package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/time-off.d.ts +21 -57
- package/dist/cpsat/rules/time-off.d.ts.map +1 -1
- package/dist/cpsat/rules/time-off.js +114 -127
- package/dist/cpsat/rules/time-off.js.map +1 -1
- package/dist/cpsat/rules/variables.d.ts +4 -0
- package/dist/cpsat/rules/variables.d.ts.map +1 -0
- package/dist/cpsat/rules/variables.js +10 -0
- package/dist/cpsat/rules/variables.js.map +1 -0
- package/dist/cpsat/rules.d.ts +20 -3
- package/dist/cpsat/rules.d.ts.map +1 -1
- package/dist/cpsat/rules.js +19 -2
- package/dist/cpsat/rules.js.map +1 -1
- package/dist/cpsat/semantic-time.d.ts +7 -6
- package/dist/cpsat/semantic-time.d.ts.map +1 -1
- package/dist/cpsat/semantic-time.js +15 -28
- package/dist/cpsat/semantic-time.js.map +1 -1
- package/dist/cpsat/types.d.ts +47 -4
- package/dist/cpsat/types.d.ts.map +1 -1
- package/dist/cpsat/types.js +24 -1
- package/dist/cpsat/types.js.map +1 -1
- package/dist/cpsat/utils.d.ts +23 -6
- package/dist/cpsat/utils.d.ts.map +1 -1
- package/dist/cpsat/utils.js +76 -15
- package/dist/cpsat/utils.js.map +1 -1
- package/dist/cpsat/validation-reporter.d.ts +6 -2
- package/dist/cpsat/validation-reporter.d.ts.map +1 -1
- package/dist/cpsat/validation-reporter.js +122 -10
- package/dist/cpsat/validation-reporter.js.map +1 -1
- package/dist/cpsat/validation.types.d.ts +21 -10
- package/dist/cpsat/validation.types.d.ts.map +1 -1
- package/dist/datetime.utils.d.ts +7 -13
- package/dist/datetime.utils.d.ts.map +1 -1
- package/dist/datetime.utils.js +29 -36
- package/dist/datetime.utils.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/schedule/cost.d.ts +48 -60
- package/dist/schedule/cost.d.ts.map +1 -1
- package/dist/schedule/cost.js +15 -51
- package/dist/schedule/cost.js.map +1 -1
- package/dist/schedule/coverage.d.ts +57 -28
- package/dist/schedule/coverage.d.ts.map +1 -1
- package/dist/schedule/coverage.js +24 -1
- package/dist/schedule/coverage.js.map +1 -1
- package/dist/schedule/definition.d.ts +138 -19
- package/dist/schedule/definition.d.ts.map +1 -1
- package/dist/schedule/definition.js +99 -90
- package/dist/schedule/definition.js.map +1 -1
- package/dist/schedule/index.d.ts +4 -4
- package/dist/schedule/index.d.ts.map +1 -1
- package/dist/schedule/index.js +2 -2
- package/dist/schedule/index.js.map +1 -1
- package/dist/schedule/rules.d.ts +438 -113
- package/dist/schedule/rules.d.ts.map +1 -1
- package/dist/schedule/rules.js +310 -108
- package/dist/schedule/rules.js.map +1 -1
- package/dist/schedule/shift-patterns.d.ts +0 -17
- package/dist/schedule/shift-patterns.d.ts.map +1 -1
- package/dist/schedule/shift-patterns.js +0 -17
- package/dist/schedule/shift-patterns.js.map +1 -1
- package/dist/schedule/time-periods.d.ts +37 -27
- package/dist/schedule/time-periods.d.ts.map +1 -1
- package/dist/schedule/time-periods.js +37 -27
- package/dist/schedule/time-periods.js.map +1 -1
- package/dist/types.d.ts +67 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +64 -0
- package/dist/types.js.map +1 -1
- package/package.json +8 -9
- package/solver/src/solver/models.py +41 -4
- package/solver/src/solver/solver.py +546 -157
- package/solver/tests/test_solver.py +363 -7
- package/src/client.schemas.ts +127 -10
- package/src/client.ts +3 -0
- package/src/client.types.ts +73 -7
- package/src/cpsat/cost.ts +17 -7
- package/src/cpsat/model-builder.ts +614 -129
- package/src/cpsat/response.ts +57 -13
- package/src/cpsat/rule-descriptor.ts +311 -0
- package/src/cpsat/rules/artifacts.ts +56 -0
- package/src/cpsat/rules/assign-together.ts +104 -69
- package/src/cpsat/rules/assigned-day.ts +70 -0
- package/src/cpsat/rules/assignment-priority.ts +54 -58
- package/src/cpsat/rules/assignment-terms.ts +43 -0
- package/src/cpsat/rules/concurrent-intervals.ts +93 -0
- package/src/cpsat/rules/cost-utils.ts +1 -9
- package/src/cpsat/rules/day-cost-multiplier.ts +101 -81
- package/src/cpsat/rules/day-cost-surcharge.ts +91 -70
- package/src/cpsat/rules/index.ts +30 -24
- package/src/cpsat/rules/location-preference.ts +47 -52
- package/src/cpsat/rules/max-concurrent-assignments.ts +170 -0
- package/src/cpsat/rules/max-consecutive-days.ts +74 -91
- package/src/cpsat/rules/max-days-of-week-per-period.ts +127 -0
- package/src/cpsat/rules/max-days-week.ts +89 -115
- package/src/cpsat/rules/max-hours-day.ts +64 -84
- package/src/cpsat/rules/max-hours-week.ts +74 -90
- package/src/cpsat/rules/max-shifts-day.ts +65 -85
- package/src/cpsat/rules/min-consecutive-days.ts +161 -127
- package/src/cpsat/rules/min-days-of-week-per-period.ts +144 -0
- package/src/cpsat/rules/min-days-week.ts +93 -91
- package/src/cpsat/rules/min-hours-day.ts +79 -58
- package/src/cpsat/rules/min-hours-week.ts +87 -62
- package/src/cpsat/rules/min-rest-between-shifts.ts +191 -178
- package/src/cpsat/rules/minimize-cost.ts +175 -188
- package/src/cpsat/rules/must-assign.ts +44 -81
- package/src/cpsat/rules/overtime-daily-multiplier.ts +158 -140
- package/src/cpsat/rules/overtime-daily-surcharge.ts +141 -126
- package/src/cpsat/rules/overtime-tiered-multiplier.ts +234 -253
- package/src/cpsat/rules/overtime-weekly-multiplier.ts +183 -175
- package/src/cpsat/rules/overtime-weekly-surcharge.ts +161 -150
- package/src/cpsat/rules/pattern-eligibility.ts +26 -0
- package/src/cpsat/rules/pattern-time.ts +30 -0
- package/src/cpsat/rules/registry.ts +140 -62
- package/src/cpsat/rules/resolver.ts +58 -30
- package/src/cpsat/rules/role-preference.ts +92 -0
- package/src/cpsat/rules/rules.types.ts +89 -17
- package/src/cpsat/rules/scope.types.ts +31 -26
- package/src/cpsat/rules/target-days-week.ts +105 -0
- package/src/cpsat/rules/target-peak-concurrent-assignments.ts +212 -0
- package/src/cpsat/rules/time-cost-surcharge.ts +116 -122
- package/src/cpsat/rules/time-off.ts +154 -163
- package/src/cpsat/rules/variables.ts +11 -0
- package/src/cpsat/rules.ts +43 -17
- package/src/cpsat/semantic-time.ts +29 -43
- package/src/cpsat/types.ts +53 -4
- package/src/cpsat/utils.ts +92 -20
- package/src/cpsat/validation-reporter.ts +170 -19
- package/src/cpsat/validation.types.ts +22 -11
- package/src/datetime.utils.ts +45 -44
- package/src/index.ts +64 -12
- package/src/schedule/cost.ts +40 -61
- package/src/schedule/coverage.ts +57 -28
- package/src/schedule/definition.ts +565 -76
- package/src/schedule/index.ts +23 -3
- package/src/schedule/rules.ts +631 -132
- package/src/schedule/shift-patterns.ts +0 -17
- package/src/schedule/time-periods.ts +37 -27
- package/src/types.ts +114 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { DayOfWeek, SchedulingPeriod } from "../types.js";
|
|
7
|
+
import type { DateString, DayOfWeek, SchedulingPeriod } from "../types.js";
|
|
8
8
|
import type { SemanticTimeEntry } from "../cpsat/semantic-time.js";
|
|
9
9
|
import type { MixedCoverageRequirement, CoverageVariant } from "../cpsat/semantic-time.js";
|
|
10
10
|
import { defineSemanticTimes } from "../cpsat/semantic-time.js";
|
|
@@ -12,9 +12,18 @@ import { resolveDaysFromPeriod } from "../datetime.utils.js";
|
|
|
12
12
|
import type { ModelBuilderConfig, CompilationResult } from "../cpsat/model-builder.js";
|
|
13
13
|
import { ModelBuilder } from "../cpsat/model-builder.js";
|
|
14
14
|
import type { SchedulingMember, ShiftPattern, Priority } from "../cpsat/types.js";
|
|
15
|
-
import type {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
import type {
|
|
16
|
+
AnyCpsatRuleConfigEntry,
|
|
17
|
+
BuiltInCpsatRuleRegistry,
|
|
18
|
+
CpsatRuleConfigEntryFor,
|
|
19
|
+
CpsatRuleRegistry,
|
|
20
|
+
} from "../cpsat/rules/rules.types.js";
|
|
21
|
+
import {
|
|
22
|
+
assertNoBuiltInCpsatRuleOverrides,
|
|
23
|
+
assertValidCpsatRuleRegistry,
|
|
24
|
+
builtInCpsatRuleRegistry,
|
|
25
|
+
} from "../cpsat/rules/registry.js";
|
|
26
|
+
import type { SolverClient, SolverRequest, SolverResponse } from "../client.types.js";
|
|
18
27
|
import { parseSolverResponse, resolveAssignments } from "../cpsat/response.js";
|
|
19
28
|
import type { ShiftAssignment } from "../cpsat/response.js";
|
|
20
29
|
import type { ScheduleValidation } from "../cpsat/validation.types.js";
|
|
@@ -22,11 +31,239 @@ import { calculateScheduleCost } from "../cpsat/cost.js";
|
|
|
22
31
|
import type { CostBreakdown } from "../cpsat/cost.js";
|
|
23
32
|
|
|
24
33
|
import type { CoverageEntry } from "./coverage.js";
|
|
25
|
-
import type { RuleEntry, RuleResolveContext } from "./rules.js";
|
|
34
|
+
import type { RuleEntry, RuleResolveContext, ScheduleRuleEntry } from "./rules.js";
|
|
26
35
|
import { resolveAppliesTo } from "./rules.js";
|
|
27
36
|
|
|
28
37
|
/** A value that can be passed to {@link Schedule.with}. */
|
|
29
|
-
type WithArg = Schedule | SchedulingMember[];
|
|
38
|
+
type WithArg = Schedule<CpsatRuleRegistry> | SchedulingMember[];
|
|
39
|
+
|
|
40
|
+
type EmptyRuleRegistry = Record<never, never>;
|
|
41
|
+
type BuiltInRuleRegistry = BuiltInCpsatRuleRegistry;
|
|
42
|
+
type CustomRuleRegistry<TRuleRegistry extends BuiltInRuleRegistry> = CpsatRuleRegistry & {
|
|
43
|
+
[K in Extract<Exclude<keyof TRuleRegistry, keyof BuiltInRuleRegistry>, string>]: TRuleRegistry[K];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type RegistryFromWithArg<T> = T extends Schedule<infer Registry> ? Registry : EmptyRuleRegistry;
|
|
47
|
+
|
|
48
|
+
type RuleEntryFields<T> = T extends RuleEntry<any, infer Fields> ? Fields : never;
|
|
49
|
+
|
|
50
|
+
type BuiltInScheduleRuleAuthoringFields<Name extends keyof BuiltInRuleRegistry> =
|
|
51
|
+
Name extends "max-hours-day"
|
|
52
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").maxHoursPerDay>>
|
|
53
|
+
: Name extends "max-hours-week"
|
|
54
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").maxHoursPerWeek>>
|
|
55
|
+
: Name extends "min-hours-day"
|
|
56
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").minHoursPerDay>>
|
|
57
|
+
: Name extends "min-hours-week"
|
|
58
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").minHoursPerWeek>>
|
|
59
|
+
: Name extends "max-days-week"
|
|
60
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").maxDaysPerWeek>>
|
|
61
|
+
: Name extends "min-days-week"
|
|
62
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").minDaysPerWeek>>
|
|
63
|
+
: Name extends "target-days-week"
|
|
64
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").targetDaysPerWeek>>
|
|
65
|
+
: Name extends "max-shifts-day"
|
|
66
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").maxShiftsPerDay>>
|
|
67
|
+
: Name extends "max-concurrent-assignments"
|
|
68
|
+
? RuleEntryFields<
|
|
69
|
+
ReturnType<typeof import("./rules.js").maxConcurrentAssignments>
|
|
70
|
+
>
|
|
71
|
+
: Name extends "target-peak-concurrent-assignments"
|
|
72
|
+
? RuleEntryFields<
|
|
73
|
+
ReturnType<typeof import("./rules.js").targetPeakConcurrentAssignments>
|
|
74
|
+
>
|
|
75
|
+
: Name extends "max-consecutive-days"
|
|
76
|
+
? RuleEntryFields<
|
|
77
|
+
ReturnType<typeof import("./rules.js").maxConsecutiveDays>
|
|
78
|
+
>
|
|
79
|
+
: Name extends "min-consecutive-days"
|
|
80
|
+
? RuleEntryFields<
|
|
81
|
+
ReturnType<typeof import("./rules.js").minConsecutiveDays>
|
|
82
|
+
>
|
|
83
|
+
: Name extends "min-rest-between-shifts"
|
|
84
|
+
? RuleEntryFields<
|
|
85
|
+
ReturnType<typeof import("./rules.js").minRestBetweenShifts>
|
|
86
|
+
>
|
|
87
|
+
: Name extends "must-assign"
|
|
88
|
+
? RuleEntryFields<ReturnType<typeof import("./rules.js").mustAssign>>
|
|
89
|
+
: Name extends "assignment-priority"
|
|
90
|
+
? RuleEntryFields<
|
|
91
|
+
ReturnType<typeof import("./rules.js").preferAssignment>
|
|
92
|
+
>
|
|
93
|
+
: Name extends "role-preference"
|
|
94
|
+
? RuleEntryFields<
|
|
95
|
+
ReturnType<typeof import("./rules.js").preferRole>
|
|
96
|
+
>
|
|
97
|
+
: Name extends "location-preference"
|
|
98
|
+
? RuleEntryFields<
|
|
99
|
+
ReturnType<typeof import("./rules.js").preferLocation>
|
|
100
|
+
>
|
|
101
|
+
: Name extends "time-off"
|
|
102
|
+
? RuleEntryFields<
|
|
103
|
+
ReturnType<typeof import("./rules.js").timeOff>
|
|
104
|
+
>
|
|
105
|
+
: Name extends "assign-together"
|
|
106
|
+
? RuleEntryFields<
|
|
107
|
+
ReturnType<typeof import("./rules.js").assignTogether>
|
|
108
|
+
>
|
|
109
|
+
: Name extends "max-days-of-week-per-period"
|
|
110
|
+
? RuleEntryFields<
|
|
111
|
+
ReturnType<
|
|
112
|
+
typeof import("./rules.js").maxDaysOfWeekPerPeriod
|
|
113
|
+
>
|
|
114
|
+
>
|
|
115
|
+
: Name extends "min-days-of-week-per-period"
|
|
116
|
+
? RuleEntryFields<
|
|
117
|
+
ReturnType<
|
|
118
|
+
typeof import("./rules.js").minDaysOfWeekPerPeriod
|
|
119
|
+
>
|
|
120
|
+
>
|
|
121
|
+
: Name extends "minimize-cost"
|
|
122
|
+
? RuleEntryFields<
|
|
123
|
+
ReturnType<
|
|
124
|
+
typeof import("./cost.js").minimizeCost
|
|
125
|
+
>
|
|
126
|
+
>
|
|
127
|
+
: Name extends "day-cost-multiplier"
|
|
128
|
+
? RuleEntryFields<
|
|
129
|
+
ReturnType<
|
|
130
|
+
typeof import("./cost.js").dayMultiplier
|
|
131
|
+
>
|
|
132
|
+
>
|
|
133
|
+
: Name extends "day-cost-surcharge"
|
|
134
|
+
? RuleEntryFields<
|
|
135
|
+
ReturnType<
|
|
136
|
+
typeof import("./cost.js").daySurcharge
|
|
137
|
+
>
|
|
138
|
+
>
|
|
139
|
+
: Name extends "time-cost-surcharge"
|
|
140
|
+
? RuleEntryFields<
|
|
141
|
+
ReturnType<
|
|
142
|
+
typeof import("./cost.js").timeSurcharge
|
|
143
|
+
>
|
|
144
|
+
>
|
|
145
|
+
: Name extends "overtime-weekly-multiplier"
|
|
146
|
+
? RuleEntryFields<
|
|
147
|
+
ReturnType<
|
|
148
|
+
typeof import("./cost.js").overtimeMultiplier
|
|
149
|
+
>
|
|
150
|
+
>
|
|
151
|
+
: Name extends "overtime-weekly-surcharge"
|
|
152
|
+
? RuleEntryFields<
|
|
153
|
+
ReturnType<
|
|
154
|
+
typeof import("./cost.js").overtimeSurcharge
|
|
155
|
+
>
|
|
156
|
+
>
|
|
157
|
+
: Name extends "overtime-daily-multiplier"
|
|
158
|
+
? RuleEntryFields<
|
|
159
|
+
ReturnType<
|
|
160
|
+
typeof import("./cost.js").dailyOvertimeMultiplier
|
|
161
|
+
>
|
|
162
|
+
>
|
|
163
|
+
: Name extends "overtime-daily-surcharge"
|
|
164
|
+
? RuleEntryFields<
|
|
165
|
+
ReturnType<
|
|
166
|
+
typeof import("./cost.js").dailyOvertimeSurcharge
|
|
167
|
+
>
|
|
168
|
+
>
|
|
169
|
+
: Name extends "overtime-tiered-multiplier"
|
|
170
|
+
? RuleEntryFields<
|
|
171
|
+
ReturnType<
|
|
172
|
+
typeof import("./cost.js").tieredOvertimeMultiplier
|
|
173
|
+
>
|
|
174
|
+
>
|
|
175
|
+
: never;
|
|
176
|
+
|
|
177
|
+
type ValidateScheduleRuleEntry<TRuleRegistry extends CpsatRuleRegistry, Entry> =
|
|
178
|
+
Entry extends ScheduleRuleEntry<TRuleRegistry>
|
|
179
|
+
? Entry
|
|
180
|
+
: Entry extends RuleEntry<infer Name, infer Fields>
|
|
181
|
+
? string extends Name
|
|
182
|
+
? Entry
|
|
183
|
+
: Name extends keyof TRuleRegistry & string
|
|
184
|
+
? Name extends keyof BuiltInRuleRegistry
|
|
185
|
+
? Fields extends BuiltInScheduleRuleAuthoringFields<Name>
|
|
186
|
+
? Entry
|
|
187
|
+
: never
|
|
188
|
+
: Fields extends Omit<CpsatRuleConfigEntryFor<Pick<TRuleRegistry, Name>>, "name">
|
|
189
|
+
? Entry
|
|
190
|
+
: never
|
|
191
|
+
: never
|
|
192
|
+
: never;
|
|
193
|
+
|
|
194
|
+
type ValidateScheduleRules<
|
|
195
|
+
TRuleRegistry extends CpsatRuleRegistry,
|
|
196
|
+
TRules extends readonly unknown[],
|
|
197
|
+
> = {
|
|
198
|
+
readonly [Index in keyof TRules]: ValidateScheduleRuleEntry<TRuleRegistry, TRules[Index]>;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
type BuiltInScheduleConfigInput<
|
|
202
|
+
R extends readonly string[],
|
|
203
|
+
S extends readonly string[],
|
|
204
|
+
T extends Record<string, SemanticTimeEntry>,
|
|
205
|
+
TRules extends readonly unknown[],
|
|
206
|
+
> = Omit<ScheduleConfig<R, S, T, BuiltInRuleRegistry>, "rules" | "ruleRegistry"> & {
|
|
207
|
+
rules?: ValidateScheduleRules<BuiltInRuleRegistry, TRules>;
|
|
208
|
+
ruleRegistry?: undefined;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
type CustomScheduleConfigInput<
|
|
212
|
+
R extends readonly string[],
|
|
213
|
+
S extends readonly string[],
|
|
214
|
+
T extends Record<string, SemanticTimeEntry>,
|
|
215
|
+
TCustomRuleRegistry extends CpsatRuleRegistry,
|
|
216
|
+
TRules extends readonly unknown[],
|
|
217
|
+
> = Omit<
|
|
218
|
+
ScheduleConfig<R, S, T, BuiltInRuleRegistry & TCustomRuleRegistry>,
|
|
219
|
+
"rules" | "ruleRegistry"
|
|
220
|
+
> & {
|
|
221
|
+
ruleRegistry: TCustomRuleRegistry;
|
|
222
|
+
rules?: ValidateScheduleRules<BuiltInRuleRegistry & TCustomRuleRegistry, TRules>;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type BuiltInPartialScheduleConfigInput<TRules extends readonly unknown[]> = Partial<
|
|
226
|
+
Omit<
|
|
227
|
+
ScheduleConfig<
|
|
228
|
+
readonly string[],
|
|
229
|
+
readonly string[],
|
|
230
|
+
Record<string, SemanticTimeEntry>,
|
|
231
|
+
BuiltInRuleRegistry
|
|
232
|
+
>,
|
|
233
|
+
"rules" | "ruleRegistry"
|
|
234
|
+
>
|
|
235
|
+
> & {
|
|
236
|
+
rules?: ValidateScheduleRules<BuiltInRuleRegistry, TRules>;
|
|
237
|
+
ruleRegistry?: undefined;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
type CustomPartialScheduleConfigInput<
|
|
241
|
+
TCustomRuleRegistry extends CpsatRuleRegistry,
|
|
242
|
+
TRules extends readonly unknown[],
|
|
243
|
+
> = Partial<
|
|
244
|
+
Omit<
|
|
245
|
+
ScheduleConfig<
|
|
246
|
+
readonly string[],
|
|
247
|
+
readonly string[],
|
|
248
|
+
Record<string, SemanticTimeEntry>,
|
|
249
|
+
BuiltInRuleRegistry & TCustomRuleRegistry
|
|
250
|
+
>,
|
|
251
|
+
"rules" | "ruleRegistry"
|
|
252
|
+
>
|
|
253
|
+
> & {
|
|
254
|
+
ruleRegistry: TCustomRuleRegistry;
|
|
255
|
+
rules?: ValidateScheduleRules<BuiltInRuleRegistry & TCustomRuleRegistry, TRules>;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
type UnionToIntersection<T> = (T extends unknown ? (value: T) => void : never) extends (
|
|
259
|
+
value: infer Result,
|
|
260
|
+
) => void
|
|
261
|
+
? Result
|
|
262
|
+
: never;
|
|
263
|
+
|
|
264
|
+
type MergedRegistryFromArgs<Args extends readonly WithArg[]> = UnionToIntersection<
|
|
265
|
+
RegistryFromWithArg<Args[number]>
|
|
266
|
+
>;
|
|
30
267
|
|
|
31
268
|
// ============================================================================
|
|
32
269
|
// SolveResult
|
|
@@ -35,9 +272,36 @@ type WithArg = Schedule | SchedulingMember[];
|
|
|
35
272
|
/** Status of a solve attempt, using idiomatic lowercase TypeScript literals. */
|
|
36
273
|
export type SolveStatus = "optimal" | "feasible" | "infeasible" | "no_solution";
|
|
37
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Strategy for {@link Schedule.solve} and {@link Schedule.compile}.
|
|
277
|
+
*
|
|
278
|
+
* @remarks
|
|
279
|
+
* `{ type: "optimize" }` optimizes objectives and soft constraints.
|
|
280
|
+
* `{ type: "feasibility-only" }` asks the solver for a hard-feasible solution
|
|
281
|
+
* only, ignoring objectives and soft-constraint penalties. Feasibility-only
|
|
282
|
+
* results do not guarantee cost, objective optimality, or soft-constraint
|
|
283
|
+
* diagnostics.
|
|
284
|
+
*
|
|
285
|
+
* @category Schedule Definition
|
|
286
|
+
*/
|
|
287
|
+
export type SolveStrategy =
|
|
288
|
+
| {
|
|
289
|
+
/** Optimize objectives and soft constraints. */
|
|
290
|
+
readonly type: "optimize";
|
|
291
|
+
}
|
|
292
|
+
| {
|
|
293
|
+
/** Find a hard-feasible solution without optimizing objectives or soft constraints. */
|
|
294
|
+
readonly type: "feasibility-only";
|
|
295
|
+
};
|
|
296
|
+
|
|
38
297
|
/**
|
|
39
298
|
* Result of {@link Schedule.solve}.
|
|
40
299
|
*
|
|
300
|
+
* @remarks
|
|
301
|
+
* Feasibility-only solves return hard-feasible assignments when one is found,
|
|
302
|
+
* but they do not optimize objectives or compute objective-derived metadata.
|
|
303
|
+
* Cost is therefore only produced for optimized solves.
|
|
304
|
+
*
|
|
41
305
|
* @category Schedule Definition
|
|
42
306
|
*/
|
|
43
307
|
export interface SolveResult {
|
|
@@ -45,9 +309,9 @@ export interface SolveResult {
|
|
|
45
309
|
status: SolveStatus;
|
|
46
310
|
/** Shift assignments (empty when infeasible or no solution). */
|
|
47
311
|
assignments: ShiftAssignment[];
|
|
48
|
-
/** Validation
|
|
312
|
+
/** Validation results collected during compilation and solving. */
|
|
49
313
|
validation: ScheduleValidation;
|
|
50
|
-
/** Cost breakdown
|
|
314
|
+
/** Cost breakdown for optimized solves when cost rules are used and a solution is found. */
|
|
51
315
|
cost?: CostBreakdown;
|
|
52
316
|
}
|
|
53
317
|
|
|
@@ -58,7 +322,18 @@ export interface SolveResult {
|
|
|
58
322
|
*/
|
|
59
323
|
export interface SolveOptions {
|
|
60
324
|
/** The date range to schedule. */
|
|
61
|
-
dateRange: { start:
|
|
325
|
+
dateRange: { start: DateString; end: DateString };
|
|
326
|
+
/** Solver time limit in seconds. */
|
|
327
|
+
timeLimitSeconds?: number;
|
|
328
|
+
/**
|
|
329
|
+
* Strategy used by the solver.
|
|
330
|
+
*
|
|
331
|
+
* `{ type: "optimize" }` optimizes objectives and soft constraints.
|
|
332
|
+
* `{ type: "feasibility-only" }` finds any hard-feasible solution, ignoring
|
|
333
|
+
* objectives, soft-constraint penalties, and objective-derived metadata such
|
|
334
|
+
* as cost.
|
|
335
|
+
*/
|
|
336
|
+
strategy?: SolveStrategy;
|
|
62
337
|
/**
|
|
63
338
|
* Fixed assignments from a prior solve (e.g., rolling schedule).
|
|
64
339
|
* These are injected as fixed variables in the solver.
|
|
@@ -84,12 +359,17 @@ export interface SolveOptions {
|
|
|
84
359
|
* `roleIds`, `times`, `coverage`, and `shiftPatterns` are required.
|
|
85
360
|
* These four fields form the minimum solvable schedule.
|
|
86
361
|
*
|
|
362
|
+
* The `TRuleRegistry` generic represents the full active rule registry for the
|
|
363
|
+
* schedule. In `schedule()`, that registry always includes the built-in rules,
|
|
364
|
+
* and `ruleRegistry` adds any custom extensions on top.
|
|
365
|
+
*
|
|
87
366
|
* @category Schedule Definition
|
|
88
367
|
*/
|
|
89
368
|
export interface ScheduleConfig<
|
|
90
369
|
R extends readonly string[] = readonly string[],
|
|
91
370
|
S extends readonly string[] = readonly [],
|
|
92
371
|
T extends Record<string, SemanticTimeEntry> = Record<string, SemanticTimeEntry>,
|
|
372
|
+
TRuleRegistry extends BuiltInRuleRegistry = BuiltInRuleRegistry,
|
|
93
373
|
> {
|
|
94
374
|
/** Declared role IDs. */
|
|
95
375
|
roleIds: R;
|
|
@@ -102,13 +382,51 @@ export interface ScheduleConfig<
|
|
|
102
382
|
/** Available shift patterns. */
|
|
103
383
|
shiftPatterns: ShiftPattern[];
|
|
104
384
|
/** Scheduling rules and constraints. */
|
|
105
|
-
rules?:
|
|
385
|
+
rules?: ScheduleRuleEntry<TRuleRegistry>[];
|
|
106
386
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
387
|
+
* Additional rule descriptors keyed by rule name.
|
|
388
|
+
*
|
|
389
|
+
* @remarks
|
|
390
|
+
* These entries are merged with the built-in rule registry at compile time.
|
|
109
391
|
* Built-in rule names cannot be overridden.
|
|
110
392
|
*/
|
|
111
|
-
|
|
393
|
+
ruleRegistry?: CustomRuleRegistry<TRuleRegistry>;
|
|
394
|
+
/** Team members (typically added via `.with()` at runtime). */
|
|
395
|
+
members?: SchedulingMember[];
|
|
396
|
+
/** Days of the week the business operates (inclusion filter). */
|
|
397
|
+
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
398
|
+
/** Which day starts the week for weekly rules. Defaults to `"monday"`. */
|
|
399
|
+
weekStartsOn?: DayOfWeek;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Configuration for {@link scheduleWithRuleRegistry}.
|
|
404
|
+
*
|
|
405
|
+
* @remarks
|
|
406
|
+
* Unlike {@link ScheduleConfig}, this config does not implicitly include the
|
|
407
|
+
* built-in rule registry. The provided registry is the complete active rule
|
|
408
|
+
* registry for the schedule.
|
|
409
|
+
*
|
|
410
|
+
* @category Schedule Definition
|
|
411
|
+
*/
|
|
412
|
+
export interface ScheduleWithRuleRegistryConfig<
|
|
413
|
+
R extends readonly string[] = readonly string[],
|
|
414
|
+
S extends readonly string[] = readonly [],
|
|
415
|
+
T extends Record<string, SemanticTimeEntry> = Record<string, SemanticTimeEntry>,
|
|
416
|
+
TRuleRegistry extends CpsatRuleRegistry = CpsatRuleRegistry,
|
|
417
|
+
> {
|
|
418
|
+
/** Declared role IDs. */
|
|
419
|
+
roleIds: R;
|
|
420
|
+
/** Declared skill IDs. When omitted, coverage targets can only be roles. */
|
|
421
|
+
skillIds?: S;
|
|
422
|
+
/** Named semantic time periods. */
|
|
423
|
+
times: T;
|
|
424
|
+
/** Staffing requirements per time period (entries stack additively). */
|
|
425
|
+
coverage: NoInfer<CoverageEntry<keyof T & string, R[number] | S[number]>>[];
|
|
426
|
+
/** Available shift patterns. */
|
|
427
|
+
shiftPatterns: ShiftPattern[];
|
|
428
|
+
/** Scheduling rules and constraints. */
|
|
429
|
+
rules?: ScheduleRuleEntry<TRuleRegistry>[];
|
|
112
430
|
/** Team members (typically added via `.with()` at runtime). */
|
|
113
431
|
members?: SchedulingMember[];
|
|
114
432
|
/** Days of the week the business operates (inclusion filter). */
|
|
@@ -128,8 +446,8 @@ interface MergedScheduleConfig {
|
|
|
128
446
|
times: Record<string, SemanticTimeEntry>;
|
|
129
447
|
coverage: CoverageEntry[];
|
|
130
448
|
shiftPatterns: ShiftPattern[];
|
|
131
|
-
rules:
|
|
132
|
-
|
|
449
|
+
rules: ScheduleRuleEntry<CpsatRuleRegistry>[];
|
|
450
|
+
ruleRegistry: CpsatRuleRegistry;
|
|
133
451
|
members: SchedulingMember[];
|
|
134
452
|
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
135
453
|
weekStartsOn?: DayOfWeek;
|
|
@@ -147,7 +465,7 @@ interface MergedScheduleConfig {
|
|
|
147
465
|
*
|
|
148
466
|
* @category Schedule Definition
|
|
149
467
|
*/
|
|
150
|
-
export class Schedule {
|
|
468
|
+
export class Schedule<TRuleRegistry extends CpsatRuleRegistry = BuiltInRuleRegistry> {
|
|
151
469
|
readonly #config: Readonly<MergedScheduleConfig>;
|
|
152
470
|
|
|
153
471
|
/** @internal */
|
|
@@ -165,7 +483,7 @@ export class Schedule {
|
|
|
165
483
|
coverage: [...this.#config.coverage],
|
|
166
484
|
shiftPatterns: [...this.#config.shiftPatterns],
|
|
167
485
|
rules: [...this.#config.rules],
|
|
168
|
-
|
|
486
|
+
ruleRegistry: { ...this.#config.ruleRegistry },
|
|
169
487
|
members: [...this.#config.members],
|
|
170
488
|
};
|
|
171
489
|
}
|
|
@@ -221,7 +539,9 @@ export class Schedule {
|
|
|
221
539
|
* Validation runs eagerly: role/skill disjointness, coverage targets
|
|
222
540
|
* referencing declared roles/skills, member role references, etc.
|
|
223
541
|
*/
|
|
224
|
-
with
|
|
542
|
+
with<const Args extends readonly WithArg[]>(
|
|
543
|
+
...args: Args
|
|
544
|
+
): Schedule<TRuleRegistry & MergedRegistryFromArgs<Args>> {
|
|
225
545
|
const merged = mergeConfig(this.#config, args);
|
|
226
546
|
return new Schedule(merged);
|
|
227
547
|
}
|
|
@@ -237,6 +557,7 @@ export class Schedule {
|
|
|
237
557
|
* @param options - Date range and optional pinned assignments
|
|
238
558
|
*/
|
|
239
559
|
async solve(client: SolverClient, options: SolveOptions): Promise<SolveResult> {
|
|
560
|
+
const strategy = resolveSolveStrategy(options.strategy);
|
|
240
561
|
const compiled = this.compile(options);
|
|
241
562
|
if (!compiled.canSolve) {
|
|
242
563
|
return {
|
|
@@ -247,7 +568,7 @@ export class Schedule {
|
|
|
247
568
|
}
|
|
248
569
|
|
|
249
570
|
const response = await client.solve(compiled.request);
|
|
250
|
-
return buildSolveResult(response, compiled, this.#config);
|
|
571
|
+
return buildSolveResult(response, compiled, this.#config, strategy);
|
|
251
572
|
}
|
|
252
573
|
|
|
253
574
|
/**
|
|
@@ -260,7 +581,8 @@ export class Schedule {
|
|
|
260
581
|
throw new Error("Pinned assignments are not yet supported.");
|
|
261
582
|
}
|
|
262
583
|
|
|
263
|
-
const
|
|
584
|
+
const strategy = resolveSolveStrategy(options.strategy);
|
|
585
|
+
const modelConfig = resolveToModelConfig(this.#config, options, strategy);
|
|
264
586
|
const builder = new ModelBuilder(modelConfig);
|
|
265
587
|
const result = builder.compile();
|
|
266
588
|
return { ...result, builder };
|
|
@@ -277,6 +599,10 @@ export class Schedule {
|
|
|
277
599
|
* Returns an immutable {@link Schedule} that can be composed via `.with()`
|
|
278
600
|
* and solved via `.solve()`.
|
|
279
601
|
*
|
|
602
|
+
* @remarks
|
|
603
|
+
* `schedule()` always uses the built-in rule registry as its default. Passing
|
|
604
|
+
* `ruleRegistry` extends that default with custom rules.
|
|
605
|
+
*
|
|
280
606
|
* @example
|
|
281
607
|
* ```typescript
|
|
282
608
|
* const venue = schedule({
|
|
@@ -313,8 +639,65 @@ export function schedule<
|
|
|
313
639
|
const R extends readonly string[],
|
|
314
640
|
const S extends readonly string[] = readonly [],
|
|
315
641
|
const T extends Record<string, SemanticTimeEntry> = Record<string, SemanticTimeEntry>,
|
|
316
|
-
|
|
317
|
-
|
|
642
|
+
const TRules extends readonly unknown[] = readonly ScheduleRuleEntry<BuiltInRuleRegistry>[],
|
|
643
|
+
>(config: BuiltInScheduleConfigInput<R, S, T, TRules>): Schedule<BuiltInRuleRegistry>;
|
|
644
|
+
export function schedule<
|
|
645
|
+
const R extends readonly string[],
|
|
646
|
+
const S extends readonly string[] = readonly [],
|
|
647
|
+
const T extends Record<string, SemanticTimeEntry> = Record<string, SemanticTimeEntry>,
|
|
648
|
+
TCustomRuleRegistry extends CpsatRuleRegistry = CpsatRuleRegistry,
|
|
649
|
+
const TRules extends readonly unknown[] = readonly ScheduleRuleEntry<
|
|
650
|
+
BuiltInRuleRegistry & TCustomRuleRegistry
|
|
651
|
+
>[],
|
|
652
|
+
>(
|
|
653
|
+
config: CustomScheduleConfigInput<R, S, T, TCustomRuleRegistry, TRules>,
|
|
654
|
+
): Schedule<BuiltInRuleRegistry & TCustomRuleRegistry>;
|
|
655
|
+
export function schedule(config: {
|
|
656
|
+
roleIds: readonly string[];
|
|
657
|
+
skillIds?: readonly string[];
|
|
658
|
+
times: Record<string, SemanticTimeEntry>;
|
|
659
|
+
coverage: CoverageEntry[];
|
|
660
|
+
shiftPatterns: ShiftPattern[];
|
|
661
|
+
rules?: readonly RuleEntry[];
|
|
662
|
+
ruleRegistry?: CpsatRuleRegistry;
|
|
663
|
+
members?: SchedulingMember[];
|
|
664
|
+
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
665
|
+
weekStartsOn?: DayOfWeek;
|
|
666
|
+
}): Schedule<CpsatRuleRegistry> {
|
|
667
|
+
const customRuleRegistry = config.ruleRegistry ?? {};
|
|
668
|
+
validateRuleRegistryExtension(customRuleRegistry);
|
|
669
|
+
const merged = buildMergedConfig(config, {
|
|
670
|
+
...builtInCpsatRuleRegistry,
|
|
671
|
+
...customRuleRegistry,
|
|
672
|
+
});
|
|
673
|
+
validateConfig(merged);
|
|
674
|
+
return new Schedule(merged);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Create a schedule definition with an explicit complete rule registry.
|
|
679
|
+
*
|
|
680
|
+
* @remarks
|
|
681
|
+
* Use this when you want full control over the active rule registry, including
|
|
682
|
+
* schedules that do not include the built-in rules.
|
|
683
|
+
*
|
|
684
|
+
* @category Schedule Definition
|
|
685
|
+
*/
|
|
686
|
+
export function scheduleWithRuleRegistry<
|
|
687
|
+
const R extends readonly string[],
|
|
688
|
+
const S extends readonly string[] = readonly [],
|
|
689
|
+
const T extends Record<string, SemanticTimeEntry> = Record<string, SemanticTimeEntry>,
|
|
690
|
+
TRuleRegistry extends CpsatRuleRegistry = CpsatRuleRegistry,
|
|
691
|
+
const TRules extends readonly unknown[] = readonly ScheduleRuleEntry<TRuleRegistry>[],
|
|
692
|
+
>(
|
|
693
|
+
ruleRegistry: TRuleRegistry,
|
|
694
|
+
config: Omit<ScheduleWithRuleRegistryConfig<R, S, T, TRuleRegistry>, "rules"> & {
|
|
695
|
+
rules?: ValidateScheduleRules<TRuleRegistry, TRules>;
|
|
696
|
+
},
|
|
697
|
+
): Schedule<TRuleRegistry> {
|
|
698
|
+
assertValidCpsatRuleRegistry(ruleRegistry);
|
|
699
|
+
assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
|
|
700
|
+
const merged = buildMergedConfig(config, ruleRegistry);
|
|
318
701
|
validateConfig(merged);
|
|
319
702
|
return new Schedule(merged);
|
|
320
703
|
}
|
|
@@ -337,16 +720,46 @@ export function schedule<
|
|
|
337
720
|
*
|
|
338
721
|
* @category Schedule Definition
|
|
339
722
|
*/
|
|
723
|
+
export function partialSchedule<
|
|
724
|
+
const TRules extends readonly unknown[] = readonly ScheduleRuleEntry<BuiltInRuleRegistry>[],
|
|
725
|
+
>(config: BuiltInPartialScheduleConfigInput<TRules>): Schedule<BuiltInRuleRegistry>;
|
|
726
|
+
export function partialSchedule<
|
|
727
|
+
TCustomRuleRegistry extends CpsatRuleRegistry = CpsatRuleRegistry,
|
|
728
|
+
const TRules extends readonly unknown[] = readonly ScheduleRuleEntry<
|
|
729
|
+
BuiltInRuleRegistry & TCustomRuleRegistry
|
|
730
|
+
>[],
|
|
731
|
+
>(
|
|
732
|
+
config: CustomPartialScheduleConfigInput<TCustomRuleRegistry, TRules>,
|
|
733
|
+
): Schedule<BuiltInRuleRegistry & TCustomRuleRegistry>;
|
|
340
734
|
export function partialSchedule(
|
|
341
|
-
config: Partial<
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
735
|
+
config: Partial<{
|
|
736
|
+
roleIds: readonly string[];
|
|
737
|
+
skillIds: readonly string[];
|
|
738
|
+
times: Record<string, SemanticTimeEntry>;
|
|
739
|
+
coverage: CoverageEntry[];
|
|
740
|
+
shiftPatterns: ShiftPattern[];
|
|
741
|
+
rules: readonly RuleEntry[];
|
|
742
|
+
ruleRegistry: CpsatRuleRegistry;
|
|
743
|
+
members: SchedulingMember[];
|
|
744
|
+
dayOfWeek: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
745
|
+
weekStartsOn: DayOfWeek;
|
|
746
|
+
}>,
|
|
747
|
+
): Schedule<CpsatRuleRegistry> {
|
|
748
|
+
const customRuleRegistry = config.ruleRegistry ?? {};
|
|
749
|
+
validateRuleRegistryExtension(customRuleRegistry);
|
|
750
|
+
const merged = buildMergedConfig(
|
|
751
|
+
{
|
|
752
|
+
roleIds: [],
|
|
753
|
+
times: {},
|
|
754
|
+
coverage: [],
|
|
755
|
+
shiftPatterns: [],
|
|
756
|
+
...config,
|
|
757
|
+
} as ScheduleConfig,
|
|
758
|
+
{
|
|
759
|
+
...builtInCpsatRuleRegistry,
|
|
760
|
+
...customRuleRegistry,
|
|
761
|
+
},
|
|
762
|
+
);
|
|
350
763
|
validateConfig(merged);
|
|
351
764
|
return new Schedule(merged);
|
|
352
765
|
}
|
|
@@ -355,15 +768,30 @@ export function partialSchedule(
|
|
|
355
768
|
// Internal: Build merged config from user input
|
|
356
769
|
// ============================================================================
|
|
357
770
|
|
|
358
|
-
function buildMergedConfig
|
|
771
|
+
function buildMergedConfig<
|
|
772
|
+
R extends readonly string[],
|
|
773
|
+
S extends readonly string[],
|
|
774
|
+
T extends Record<string, SemanticTimeEntry>,
|
|
775
|
+
TRuleRegistry extends CpsatRuleRegistry,
|
|
776
|
+
TRules extends readonly unknown[] = readonly ScheduleRuleEntry<CpsatRuleRegistry>[],
|
|
777
|
+
>(
|
|
778
|
+
config:
|
|
779
|
+
| (Omit<ScheduleConfig<R, S, T, BuiltInRuleRegistry & TRuleRegistry>, "rules"> & {
|
|
780
|
+
rules?: TRules;
|
|
781
|
+
})
|
|
782
|
+
| (Omit<ScheduleWithRuleRegistryConfig<R, S, T, TRuleRegistry>, "rules"> & {
|
|
783
|
+
rules?: TRules;
|
|
784
|
+
}),
|
|
785
|
+
ruleRegistry: CpsatRuleRegistry,
|
|
786
|
+
): MergedScheduleConfig {
|
|
359
787
|
return {
|
|
360
788
|
roleIds: [...config.roleIds],
|
|
361
789
|
skillIds: [...(config.skillIds ?? [])],
|
|
362
790
|
times: { ...config.times },
|
|
363
791
|
coverage: [...config.coverage],
|
|
364
792
|
shiftPatterns: [...config.shiftPatterns],
|
|
365
|
-
rules: [...(config.rules ?? [])],
|
|
366
|
-
|
|
793
|
+
rules: [...(config.rules ?? [])] as ScheduleRuleEntry<CpsatRuleRegistry>[],
|
|
794
|
+
ruleRegistry: { ...ruleRegistry },
|
|
367
795
|
members: [...(config.members ?? [])],
|
|
368
796
|
dayOfWeek: config.dayOfWeek,
|
|
369
797
|
weekStartsOn: config.weekStartsOn,
|
|
@@ -374,18 +802,16 @@ function buildMergedConfig(config: ScheduleConfig): MergedScheduleConfig {
|
|
|
374
802
|
// Internal: Validate merged config
|
|
375
803
|
// ============================================================================
|
|
376
804
|
|
|
805
|
+
function validateRuleRegistryExtension(ruleRegistry: CpsatRuleRegistry): void {
|
|
806
|
+
assertValidCpsatRuleRegistry(ruleRegistry);
|
|
807
|
+
assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
|
|
808
|
+
}
|
|
809
|
+
|
|
377
810
|
function validateConfig(config: MergedScheduleConfig): void {
|
|
378
811
|
const roles = new Set<string>(config.roleIds);
|
|
379
812
|
const skills = new Set<string>(config.skillIds);
|
|
380
813
|
|
|
381
|
-
|
|
382
|
-
for (const name of Object.keys(config.ruleFactories)) {
|
|
383
|
-
if (name in builtInCpsatRuleFactories) {
|
|
384
|
-
throw new Error(
|
|
385
|
-
`Custom rule factory "${name}" conflicts with a built-in rule. Choose a different name.`,
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
814
|
+
assertValidCpsatRuleRegistry(config.ruleRegistry);
|
|
389
815
|
|
|
390
816
|
// Validate role/skill disjointness
|
|
391
817
|
for (const skill of skills) {
|
|
@@ -491,7 +917,10 @@ function validateCoverageEntry(
|
|
|
491
917
|
// Internal: Merge logic
|
|
492
918
|
// ============================================================================
|
|
493
919
|
|
|
494
|
-
function mergeConfig(
|
|
920
|
+
function mergeConfig(
|
|
921
|
+
base: Readonly<MergedScheduleConfig>,
|
|
922
|
+
args: readonly WithArg[],
|
|
923
|
+
): MergedScheduleConfig {
|
|
495
924
|
const result: MergedScheduleConfig = {
|
|
496
925
|
roleIds: [...base.roleIds],
|
|
497
926
|
skillIds: [...base.skillIds],
|
|
@@ -499,7 +928,7 @@ function mergeConfig(base: Readonly<MergedScheduleConfig>, args: WithArg[]): Mer
|
|
|
499
928
|
coverage: [...base.coverage],
|
|
500
929
|
shiftPatterns: [...base.shiftPatterns],
|
|
501
930
|
rules: [...base.rules],
|
|
502
|
-
|
|
931
|
+
ruleRegistry: { ...base.ruleRegistry },
|
|
503
932
|
members: [...base.members],
|
|
504
933
|
dayOfWeek: base.dayOfWeek,
|
|
505
934
|
weekStartsOn: base.weekStartsOn,
|
|
@@ -522,7 +951,7 @@ function mergeConfig(base: Readonly<MergedScheduleConfig>, args: WithArg[]): Mer
|
|
|
522
951
|
return result;
|
|
523
952
|
}
|
|
524
953
|
|
|
525
|
-
function mergeScheduleFragment(result: MergedScheduleConfig, s: Schedule): void {
|
|
954
|
+
function mergeScheduleFragment(result: MergedScheduleConfig, s: Schedule<CpsatRuleRegistry>): void {
|
|
526
955
|
const other = s._getConfig();
|
|
527
956
|
|
|
528
957
|
// dayOfWeek: error on conflict (semantics of union vs intersection are ambiguous)
|
|
@@ -597,14 +1026,14 @@ function mergeScheduleFragment(result: MergedScheduleConfig, s: Schedule): void
|
|
|
597
1026
|
// Rules: additive
|
|
598
1027
|
result.rules.push(...other.rules);
|
|
599
1028
|
|
|
600
|
-
// Rule
|
|
601
|
-
for (const [name,
|
|
602
|
-
if (name in result.
|
|
1029
|
+
// Rule registry: merge, error on collision
|
|
1030
|
+
for (const [name, descriptor] of Object.entries(other.ruleRegistry)) {
|
|
1031
|
+
if (name in result.ruleRegistry && result.ruleRegistry[name] !== descriptor) {
|
|
603
1032
|
throw new Error(
|
|
604
|
-
`Rule
|
|
1033
|
+
`Rule registry entry "${name}" already registered. Cannot merge schedules with colliding rule registries.`,
|
|
605
1034
|
);
|
|
606
1035
|
}
|
|
607
|
-
result.
|
|
1036
|
+
result.ruleRegistry[name] = descriptor;
|
|
608
1037
|
}
|
|
609
1038
|
|
|
610
1039
|
// Members: additive, error on duplicate ID
|
|
@@ -640,7 +1069,8 @@ function mergeMembers(result: MergedScheduleConfig, incoming: SchedulingMember[]
|
|
|
640
1069
|
function resolveToModelConfig(
|
|
641
1070
|
config: Readonly<MergedScheduleConfig>,
|
|
642
1071
|
options: SolveOptions,
|
|
643
|
-
|
|
1072
|
+
strategy: SolveStrategy,
|
|
1073
|
+
): ModelBuilderConfig<CpsatRuleRegistry, AnyCpsatRuleConfigEntry> {
|
|
644
1074
|
const roles = new Set<string>(config.roleIds);
|
|
645
1075
|
const skills = new Set<string>(config.skillIds);
|
|
646
1076
|
const memberIds = new Set<string>(config.members.map((m) => m.id));
|
|
@@ -677,7 +1107,7 @@ function resolveToModelConfig(
|
|
|
677
1107
|
"overtime-tiered-multiplier",
|
|
678
1108
|
]);
|
|
679
1109
|
const hasCostRules = allRules.some((r) => costRuleNames.has(r._rule));
|
|
680
|
-
if (hasCostRules) {
|
|
1110
|
+
if (strategy.type === "optimize" && hasCostRules) {
|
|
681
1111
|
const missingPay = config.members.filter((m) => !m.pay).map((m) => m.id);
|
|
682
1112
|
if (missingPay.length > 0) {
|
|
683
1113
|
throw new Error(
|
|
@@ -696,11 +1126,10 @@ function resolveToModelConfig(
|
|
|
696
1126
|
schedulingPeriod: resolvedPeriod,
|
|
697
1127
|
coverage: resolvedCoverage,
|
|
698
1128
|
ruleConfigs,
|
|
699
|
-
|
|
700
|
-
Object.keys(config.ruleFactories).length > 0
|
|
701
|
-
? { ...builtInCpsatRuleFactories, ...config.ruleFactories }
|
|
702
|
-
: undefined,
|
|
1129
|
+
ruleRegistry: config.ruleRegistry,
|
|
703
1130
|
weekStartsOn: config.weekStartsOn,
|
|
1131
|
+
solverOptions: buildSolverOptions(options),
|
|
1132
|
+
strategy,
|
|
704
1133
|
};
|
|
705
1134
|
}
|
|
706
1135
|
|
|
@@ -708,10 +1137,37 @@ function resolveToModelConfig(
|
|
|
708
1137
|
// Internal: Build SolveResult from solver response
|
|
709
1138
|
// ============================================================================
|
|
710
1139
|
|
|
711
|
-
function
|
|
1140
|
+
function resolveSolveStrategy(strategy: unknown): SolveStrategy {
|
|
1141
|
+
if (strategy === undefined) {
|
|
1142
|
+
return { type: "optimize" };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
if (typeof strategy !== "object" || strategy === null || !("type" in strategy)) {
|
|
1146
|
+
throw new Error("Solve strategy must be an object with a type field.");
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const strategyType = (strategy as { type: unknown }).type;
|
|
1150
|
+
if (strategyType === "optimize" || strategyType === "feasibility-only") {
|
|
1151
|
+
return { type: strategyType };
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
throw new Error(`Unknown solve strategy "${String(strategyType)}".`);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function buildSolverOptions(options: SolveOptions): SolverRequest["options"] | undefined {
|
|
1158
|
+
if (options.timeLimitSeconds === undefined) {
|
|
1159
|
+
return undefined;
|
|
1160
|
+
}
|
|
1161
|
+
return { timeLimitSeconds: options.timeLimitSeconds };
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function mapSolverStatus(
|
|
1165
|
+
solverStatus: SolverResponse["status"],
|
|
1166
|
+
strategy: SolveStrategy,
|
|
1167
|
+
): SolveStatus {
|
|
712
1168
|
switch (solverStatus) {
|
|
713
1169
|
case "OPTIMAL":
|
|
714
|
-
return "optimal";
|
|
1170
|
+
return strategy.type === "feasibility-only" ? "feasible" : "optimal";
|
|
715
1171
|
case "FEASIBLE":
|
|
716
1172
|
return "feasible";
|
|
717
1173
|
case "INFEASIBLE":
|
|
@@ -728,14 +1184,26 @@ function buildSolveResult(
|
|
|
728
1184
|
response: SolverResponse,
|
|
729
1185
|
compiled: CompilationResult & { builder: ModelBuilder },
|
|
730
1186
|
config: Readonly<MergedScheduleConfig>,
|
|
1187
|
+
strategy: SolveStrategy,
|
|
731
1188
|
): SolveResult {
|
|
732
|
-
const status = mapSolverStatus(response.status);
|
|
733
|
-
const
|
|
1189
|
+
const status = mapSolverStatus(response.status, strategy);
|
|
1190
|
+
const hasFinalSolution = status === "optimal" || status === "feasible";
|
|
1191
|
+
const parsed = hasFinalSolution
|
|
1192
|
+
? parseSolverResponse(response)
|
|
1193
|
+
: {
|
|
1194
|
+
status: response.status,
|
|
1195
|
+
assignments: [],
|
|
1196
|
+
statistics: response.statistics,
|
|
1197
|
+
error: response.error,
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
compiled.builder.reporter.analyzeSolution(response, {
|
|
1201
|
+
analyzeSoftConstraints: strategy.type === "optimize",
|
|
1202
|
+
});
|
|
734
1203
|
|
|
735
1204
|
// Run post-solve validation when a solution exists
|
|
736
|
-
if (parsed.assignments.length > 0 &&
|
|
1205
|
+
if (parsed.assignments.length > 0 && hasFinalSolution && strategy.type === "optimize") {
|
|
737
1206
|
const resolved = resolveAssignments(parsed.assignments, compiled.builder.shiftPatterns);
|
|
738
|
-
compiled.builder.reporter.analyzeSolution(response);
|
|
739
1207
|
compiled.builder.validateSolution(resolved);
|
|
740
1208
|
}
|
|
741
1209
|
|
|
@@ -747,14 +1215,18 @@ function buildSolveResult(
|
|
|
747
1215
|
validation,
|
|
748
1216
|
};
|
|
749
1217
|
|
|
750
|
-
// Compute cost breakdown when cost
|
|
751
|
-
if (parsed.assignments.length > 0 &&
|
|
752
|
-
const
|
|
753
|
-
|
|
1218
|
+
// Compute cost breakdown when cost artifacts are present and a solution was found
|
|
1219
|
+
if (parsed.assignments.length > 0 && hasFinalSolution && strategy.type === "optimize") {
|
|
1220
|
+
const hasCostArtifacts = compiled.builder.rules.some((rule) =>
|
|
1221
|
+
rule.artifacts.some((artifact) => artifact.kind === "cost"),
|
|
1222
|
+
);
|
|
1223
|
+
if (hasCostArtifacts) {
|
|
754
1224
|
result.cost = calculateScheduleCost(parsed.assignments, {
|
|
755
1225
|
members: config.members,
|
|
756
1226
|
shiftPatterns: config.shiftPatterns,
|
|
757
1227
|
rules: compiled.builder.rules,
|
|
1228
|
+
days: compiled.builder.days,
|
|
1229
|
+
weekStartsOn: compiled.builder.weekStartsOn,
|
|
758
1230
|
});
|
|
759
1231
|
}
|
|
760
1232
|
}
|
|
@@ -783,7 +1255,7 @@ function buildCoverageRequirements<T extends string>(
|
|
|
783
1255
|
targetCount: number;
|
|
784
1256
|
priority?: Priority;
|
|
785
1257
|
dayOfWeek?: [DayOfWeek, ...DayOfWeek[]];
|
|
786
|
-
dates?:
|
|
1258
|
+
dates?: DateString[];
|
|
787
1259
|
} = {
|
|
788
1260
|
semanticTime: entry.timeName,
|
|
789
1261
|
targetCount: entry.count,
|
|
@@ -809,7 +1281,7 @@ function buildSimpleCoverageTarget<T extends string>(
|
|
|
809
1281
|
targetCount: number;
|
|
810
1282
|
priority?: Priority;
|
|
811
1283
|
dayOfWeek?: [DayOfWeek, ...DayOfWeek[]];
|
|
812
|
-
dates?:
|
|
1284
|
+
dates?: DateString[];
|
|
813
1285
|
},
|
|
814
1286
|
roles: Set<string>,
|
|
815
1287
|
skills: Set<string>,
|
|
@@ -849,12 +1321,27 @@ function buildSimpleCoverageTarget<T extends string>(
|
|
|
849
1321
|
/**
|
|
850
1322
|
* Build a VariantCoverageRequirement from a variant-form CoverageEntry.
|
|
851
1323
|
*/
|
|
1324
|
+
function requireCoverageVariants<T extends string>(
|
|
1325
|
+
entry: CoverageEntry<T, string>,
|
|
1326
|
+
): [CoverageVariant, ...CoverageVariant[]] {
|
|
1327
|
+
if (!entry.variants || entry.variants.length === 0) {
|
|
1328
|
+
throw new Error(`Coverage entry for "${entry.timeName}" must define at least one variant.`);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const first = entry.variants[0];
|
|
1332
|
+
if (!first) {
|
|
1333
|
+
throw new Error(`Coverage entry for "${entry.timeName}" must define at least one variant.`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
return [first, ...entry.variants.slice(1)];
|
|
1337
|
+
}
|
|
1338
|
+
|
|
852
1339
|
function buildVariantCoverageRequirement<T extends string>(
|
|
853
1340
|
entry: CoverageEntry<T, string>,
|
|
854
1341
|
roles: Set<string>,
|
|
855
1342
|
skills: Set<string>,
|
|
856
1343
|
): MixedCoverageRequirement<T> {
|
|
857
|
-
const variants = entry
|
|
1344
|
+
const variants = requireCoverageVariants(entry);
|
|
858
1345
|
|
|
859
1346
|
const resolveTarget = (): {
|
|
860
1347
|
roleIds?: [string, ...string[]];
|
|
@@ -885,23 +1372,23 @@ function buildVariantCoverageRequirement<T extends string>(
|
|
|
885
1372
|
// ============================================================================
|
|
886
1373
|
|
|
887
1374
|
function resolveRules(
|
|
888
|
-
rules:
|
|
1375
|
+
rules: ScheduleRuleEntry<CpsatRuleRegistry>[],
|
|
889
1376
|
roles: Set<string>,
|
|
890
1377
|
skills: Set<string>,
|
|
891
1378
|
memberIds: Set<string>,
|
|
892
|
-
):
|
|
1379
|
+
): AnyCpsatRuleConfigEntry[] {
|
|
893
1380
|
const ctx: RuleResolveContext = { roles, skills, memberIds };
|
|
894
1381
|
|
|
895
1382
|
return rules.map((rule) => {
|
|
896
1383
|
// Rules with custom resolvers handle their own translation
|
|
897
1384
|
if (rule._resolve) {
|
|
898
|
-
return rule._resolve(ctx) as
|
|
1385
|
+
return rule._resolve(ctx) as AnyCpsatRuleConfigEntry;
|
|
899
1386
|
}
|
|
900
1387
|
|
|
901
1388
|
// Default resolution: appliesTo → entity scope, dates → specificDates
|
|
902
1389
|
const { _type, _rule, _resolve, appliesTo, dates, ...passthrough } = rule as RuleEntry & {
|
|
903
1390
|
appliesTo?: string | string[];
|
|
904
|
-
dates?:
|
|
1391
|
+
dates?: DateString[];
|
|
905
1392
|
};
|
|
906
1393
|
|
|
907
1394
|
const entityScope = resolveAppliesTo(appliesTo, roles, skills, memberIds);
|
|
@@ -912,8 +1399,8 @@ function resolveRules(
|
|
|
912
1399
|
...passthrough,
|
|
913
1400
|
...entityScope,
|
|
914
1401
|
...resolvedDates,
|
|
915
|
-
} as
|
|
916
|
-
}) as
|
|
1402
|
+
} as AnyCpsatRuleConfigEntry;
|
|
1403
|
+
}) as AnyCpsatRuleConfigEntry[];
|
|
917
1404
|
}
|
|
918
1405
|
|
|
919
1406
|
// ============================================================================
|
|
@@ -927,7 +1414,9 @@ function resolveRules(
|
|
|
927
1414
|
* (multipliers, surcharges) reference cost variables it creates.
|
|
928
1415
|
* Non-cost rules retain their original relative order.
|
|
929
1416
|
*/
|
|
930
|
-
function sortCostRulesFirst(
|
|
1417
|
+
function sortCostRulesFirst(
|
|
1418
|
+
rules: ScheduleRuleEntry<CpsatRuleRegistry>[],
|
|
1419
|
+
): ScheduleRuleEntry<CpsatRuleRegistry>[] {
|
|
931
1420
|
return rules.toSorted((a, b) => {
|
|
932
1421
|
const aIsCostBase = a._rule === "minimize-cost" ? 0 : 1;
|
|
933
1422
|
const bIsCostBase = b._rule === "minimize-cost" ? 0 : 1;
|