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
|
@@ -5,19 +5,24 @@ import {
|
|
|
5
5
|
type ParsedEntityScope,
|
|
6
6
|
type ParsedTimeScope,
|
|
7
7
|
} from "./scope.types.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
assertNoBuiltInCpsatRuleOverrides,
|
|
10
|
+
assertValidCpsatRuleRegistry,
|
|
11
|
+
builtInCpsatRuleRegistry,
|
|
12
|
+
} from "./registry.js";
|
|
9
13
|
import type {
|
|
14
|
+
AnyCpsatRuleConfigEntry,
|
|
15
|
+
BuiltInCpsatRuleRegistry,
|
|
10
16
|
CpsatRuleConfigEntry,
|
|
11
|
-
|
|
12
|
-
CpsatRuleName,
|
|
17
|
+
CpsatRuleConfigEntryFor,
|
|
13
18
|
CpsatRuleRegistry,
|
|
14
19
|
} from "./rules.types.js";
|
|
15
|
-
import type {
|
|
20
|
+
import type { CompiledRule, RuleCompileContext } from "../rule-descriptor.js";
|
|
21
|
+
import { compileRuleDescriptor } from "../rule-descriptor.js";
|
|
16
22
|
|
|
17
23
|
type InternalEntry = {
|
|
18
24
|
index: number;
|
|
19
|
-
|
|
20
|
-
config: CpsatRuleRegistry[CpsatRuleName];
|
|
25
|
+
entry: AnyCpsatRuleConfigEntry;
|
|
21
26
|
entityScope: ParsedEntityScope;
|
|
22
27
|
timeScope: ParsedTimeScope;
|
|
23
28
|
};
|
|
@@ -25,9 +30,9 @@ type InternalEntry = {
|
|
|
25
30
|
/**
|
|
26
31
|
* Extract the config portion from a flat rule entry (everything except `name`).
|
|
27
32
|
*/
|
|
28
|
-
function entryConfig(entry:
|
|
33
|
+
function entryConfig(entry: AnyCpsatRuleConfigEntry): Record<string, unknown> {
|
|
29
34
|
const { name: _name, ...config } = entry;
|
|
30
|
-
return config
|
|
35
|
+
return config;
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/**
|
|
@@ -97,7 +102,9 @@ function timeScopeKey(time: ParsedTimeScope): string {
|
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
/**
|
|
100
|
-
*
|
|
105
|
+
* Built-in rules that don't use standard scoping and should pass through unchanged.
|
|
106
|
+
* Custom rules also pass through unchanged so they can receive their declared
|
|
107
|
+
* scope fields directly.
|
|
101
108
|
*/
|
|
102
109
|
const NON_SCOPED_RULES = new Set<string>(["assign-together"]);
|
|
103
110
|
|
|
@@ -105,22 +112,22 @@ const NON_SCOPED_RULES = new Set<string>(["assign-together"]);
|
|
|
105
112
|
* Resolves overlapping scopes so that more specific scopes override broader ones.
|
|
106
113
|
*
|
|
107
114
|
* Resolution rules:
|
|
108
|
-
* 1.
|
|
109
|
-
* 2.
|
|
115
|
+
* 1. Custom rules and built-in rules in NON_SCOPED_RULES pass through unchanged
|
|
116
|
+
* 2. Built-in rules with different time scopes coexist (different time windows don't compete)
|
|
110
117
|
* 3. Within the same time scope, rules are sorted by specificity (person > role > skill > global)
|
|
111
118
|
* and then by insertion order (later wins for same specificity, i.e., last insert overrides)
|
|
112
119
|
* 4. More specific rules claim their IDs first; less specific rules get the remainder
|
|
113
120
|
* 5. All scope types (person, role, skill, global) are expanded to explicit IDs
|
|
114
121
|
*/
|
|
115
122
|
export function resolveRuleScopes(
|
|
116
|
-
entries:
|
|
123
|
+
entries: readonly AnyCpsatRuleConfigEntry[],
|
|
117
124
|
members: SchedulingMember[],
|
|
118
|
-
):
|
|
119
|
-
const resolved:
|
|
120
|
-
const scopedEntries: Array<{ entry:
|
|
125
|
+
): AnyCpsatRuleConfigEntry[] {
|
|
126
|
+
const resolved: AnyCpsatRuleConfigEntry[] = [];
|
|
127
|
+
const scopedEntries: Array<{ entry: AnyCpsatRuleConfigEntry; index: number }> = [];
|
|
121
128
|
|
|
122
129
|
entries.forEach((entry, index) => {
|
|
123
|
-
if (NON_SCOPED_RULES.has(entry.name)) {
|
|
130
|
+
if (NON_SCOPED_RULES.has(entry.name) || !(entry.name in builtInCpsatRuleRegistry)) {
|
|
124
131
|
resolved.push(entry);
|
|
125
132
|
} else {
|
|
126
133
|
scopedEntries.push({ entry, index });
|
|
@@ -141,8 +148,7 @@ export function resolveRuleScopes(
|
|
|
141
148
|
const list = grouped.get(groupKey) ?? [];
|
|
142
149
|
list.push({
|
|
143
150
|
index,
|
|
144
|
-
|
|
145
|
-
config,
|
|
151
|
+
entry,
|
|
146
152
|
entityScope: entity,
|
|
147
153
|
timeScope: time,
|
|
148
154
|
});
|
|
@@ -159,8 +165,8 @@ export function resolveRuleScopes(
|
|
|
159
165
|
|
|
160
166
|
const assignedMembers = new Set<string>();
|
|
161
167
|
|
|
162
|
-
for (const
|
|
163
|
-
const targetIds = getMemberIdsForScope(
|
|
168
|
+
for (const item of sorted) {
|
|
169
|
+
const targetIds = getMemberIdsForScope(item.entityScope, members);
|
|
164
170
|
const remaining = subtractIds(targetIds, assignedMembers);
|
|
165
171
|
if (remaining.length === 0) continue;
|
|
166
172
|
|
|
@@ -171,12 +177,12 @@ export function resolveRuleScopes(
|
|
|
171
177
|
roleIds: _roleIds,
|
|
172
178
|
skillIds: _skillIds,
|
|
173
179
|
...configWithoutEntityScope
|
|
174
|
-
} = entry
|
|
180
|
+
} = entryConfig(item.entry);
|
|
175
181
|
resolved.push({
|
|
176
|
-
name: entry.name,
|
|
182
|
+
name: item.entry.name,
|
|
177
183
|
...configWithoutEntityScope,
|
|
178
184
|
memberIds: remaining,
|
|
179
|
-
}
|
|
185
|
+
});
|
|
180
186
|
}
|
|
181
187
|
}
|
|
182
188
|
|
|
@@ -184,22 +190,44 @@ export function resolveRuleScopes(
|
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
/**
|
|
187
|
-
* Builds
|
|
193
|
+
* Builds compiled rules from named configs, applying scoping resolution.
|
|
188
194
|
*/
|
|
189
195
|
export function buildCpsatRules(
|
|
190
|
-
entries: CpsatRuleConfigEntry[],
|
|
196
|
+
entries: readonly CpsatRuleConfigEntry[],
|
|
197
|
+
members: SchedulingMember[],
|
|
198
|
+
compileContext: RuleCompileContext,
|
|
199
|
+
ruleRegistry?: BuiltInCpsatRuleRegistry,
|
|
200
|
+
): CompiledRule[];
|
|
201
|
+
export function buildCpsatRules<Registry extends CpsatRuleRegistry>(
|
|
202
|
+
entries: readonly CpsatRuleConfigEntryFor<Registry>[],
|
|
203
|
+
members: SchedulingMember[],
|
|
204
|
+
compileContext: RuleCompileContext,
|
|
205
|
+
ruleRegistry: Registry,
|
|
206
|
+
): CompiledRule[];
|
|
207
|
+
export function buildCpsatRules(
|
|
208
|
+
entries: readonly AnyCpsatRuleConfigEntry[],
|
|
191
209
|
members: SchedulingMember[],
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
compileContext: RuleCompileContext,
|
|
211
|
+
ruleRegistry: CpsatRuleRegistry,
|
|
212
|
+
): CompiledRule[];
|
|
213
|
+
export function buildCpsatRules(
|
|
214
|
+
entries: readonly AnyCpsatRuleConfigEntry[],
|
|
215
|
+
members: SchedulingMember[],
|
|
216
|
+
compileContext: RuleCompileContext,
|
|
217
|
+
ruleRegistry: CpsatRuleRegistry = builtInCpsatRuleRegistry,
|
|
218
|
+
): CompiledRule[] {
|
|
194
219
|
if (entries.length === 0) return [];
|
|
195
220
|
|
|
221
|
+
assertValidCpsatRuleRegistry(ruleRegistry);
|
|
222
|
+
assertNoBuiltInCpsatRuleOverrides(ruleRegistry);
|
|
223
|
+
|
|
196
224
|
const resolved = resolveRuleScopes(entries, members);
|
|
197
225
|
|
|
198
226
|
return resolved.map((entry) => {
|
|
199
|
-
const
|
|
200
|
-
if (!
|
|
227
|
+
const descriptor = ruleRegistry[entry.name];
|
|
228
|
+
if (!descriptor) {
|
|
201
229
|
throw new Error(`Unknown CP-SAT rule "${entry.name}"`);
|
|
202
230
|
}
|
|
203
|
-
return
|
|
231
|
+
return compileRuleDescriptor(descriptor, entryConfig(entry), compileContext);
|
|
204
232
|
});
|
|
205
233
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { defineRuleDescriptor } from "../rule-descriptor.js";
|
|
3
|
+
import type { RuleArtifact } from "../rule-descriptor.js";
|
|
4
|
+
import { PREFERENCE_WEIGHTS } from "../utils.js";
|
|
5
|
+
import {
|
|
6
|
+
PrioritySchema,
|
|
7
|
+
entityScope,
|
|
8
|
+
parseEntityScope,
|
|
9
|
+
resolveMembersFromScope,
|
|
10
|
+
} from "./scope.types.js";
|
|
11
|
+
import { assignmentVar } from "./variables.js";
|
|
12
|
+
import { canAssignMemberToPattern, isPatternAvailableOnDay } from "./pattern-eligibility.js";
|
|
13
|
+
|
|
14
|
+
export const RolePreferenceSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
roleId: z.string(),
|
|
17
|
+
priority: PrioritySchema,
|
|
18
|
+
})
|
|
19
|
+
.and(entityScope(["members", "roles", "skills"]));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for {@link rolePreferenceRuleDescriptor}.
|
|
23
|
+
*/
|
|
24
|
+
export type RolePreferenceConfig = z.infer<typeof RolePreferenceSchema>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Low-level descriptor for role preference rules.
|
|
28
|
+
*
|
|
29
|
+
* @category Rules
|
|
30
|
+
*/
|
|
31
|
+
export const rolePreferenceRuleDescriptor = defineRuleDescriptor({
|
|
32
|
+
name: "role-preference",
|
|
33
|
+
schema: RolePreferenceSchema,
|
|
34
|
+
compile(config, ctx) {
|
|
35
|
+
const scope = parseEntityScope(config);
|
|
36
|
+
const { roleId } = config;
|
|
37
|
+
const weight = PREFERENCE_WEIGHTS[config.priority as keyof typeof PREFERENCE_WEIGHTS] ?? 0;
|
|
38
|
+
if (weight === 0) {
|
|
39
|
+
return { rule: "role-preference", artifacts: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const members = resolveMembersFromScope(scope, [...ctx.members]);
|
|
43
|
+
if (members.length === 0) {
|
|
44
|
+
return { rule: "role-preference", artifacts: [] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const membersWithRole = members.filter((member) => member.roleIds.includes(roleId));
|
|
48
|
+
const artifacts: RuleArtifact[] = [];
|
|
49
|
+
|
|
50
|
+
if (membersWithRole.length === 0) {
|
|
51
|
+
artifacts.push({
|
|
52
|
+
kind: "pre-solve-feedback",
|
|
53
|
+
run(_precheckContext, reporter) {
|
|
54
|
+
const ids = members.map((member) => member.id).join(", ");
|
|
55
|
+
reporter.reportRuleError({
|
|
56
|
+
rule: "role-preference",
|
|
57
|
+
message:
|
|
58
|
+
`None of the targeted members (${ids}) have role "${roleId}". ` +
|
|
59
|
+
`Add "${roleId}" to their roleIds, or remove this preferRole rule.`,
|
|
60
|
+
context: { memberIds: members.map((member) => member.id) },
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return { rule: "role-preference", artifacts };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const terms = membersWithRole.flatMap((member) =>
|
|
68
|
+
ctx.shiftPatterns.flatMap((pattern) => {
|
|
69
|
+
if (!canAssignMemberToPattern(member, pattern)) return [];
|
|
70
|
+
if (pattern.roleIds?.includes(roleId)) return [];
|
|
71
|
+
return ctx.days
|
|
72
|
+
.filter((day) => isPatternAvailableOnDay(pattern, day))
|
|
73
|
+
.map((day) => ({ var: assignmentVar(member.id, pattern.id, day.iso), coeff: weight }));
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
artifacts.push({
|
|
78
|
+
kind: "objective",
|
|
79
|
+
terms,
|
|
80
|
+
validation: {
|
|
81
|
+
strategy: "skip",
|
|
82
|
+
category: "no-meaningful-feedback",
|
|
83
|
+
rationale:
|
|
84
|
+
terms.length === 0
|
|
85
|
+
? `Role preference for "${roleId}" produced no objective terms.`
|
|
86
|
+
: "Preference rules influence the objective but do not emit validation feedback.",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return { rule: "role-preference", artifacts };
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RuleDescriptor } from "../rule-descriptor.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Maps built-in CP-SAT rule names to their validated config types.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Use this when you need the exact config shape for a known built-in rule.
|
|
8
|
+
* For registry-driven APIs, prefer `CpsatRuleRegistry`, `CpsatRuleConfigByName`,
|
|
9
|
+
* and `CpsatRuleConfigEntryFor`.
|
|
10
|
+
*
|
|
11
|
+
* @category Rules
|
|
12
|
+
*/
|
|
13
|
+
export interface BuiltInCpsatRuleConfigRegistry {
|
|
7
14
|
"assign-together": import("./assign-together.js").AssignTogetherConfig;
|
|
8
15
|
"assignment-priority": import("./assignment-priority.js").AssignmentPriorityConfig;
|
|
9
16
|
"location-preference": import("./location-preference.js").LocationPreferenceConfig;
|
|
17
|
+
"role-preference": import("./role-preference.js").RolePreferenceConfig;
|
|
18
|
+
"max-concurrent-assignments": import("./max-concurrent-assignments.js").MaxConcurrentAssignmentsConfig;
|
|
19
|
+
"target-peak-concurrent-assignments": import("./target-peak-concurrent-assignments.js").TargetPeakConcurrentAssignmentsConfig;
|
|
20
|
+
"target-days-week": import("./target-days-week.js").TargetDaysWeekConfig;
|
|
10
21
|
"max-consecutive-days": import("./max-consecutive-days.js").MaxConsecutiveDaysConfig;
|
|
11
22
|
"max-days-week": import("./max-days-week.js").MaxDaysWeekConfig;
|
|
12
23
|
"max-hours-day": import("./max-hours-day.js").MaxHoursDayConfig;
|
|
@@ -28,25 +39,85 @@ export interface CpsatRuleRegistry {
|
|
|
28
39
|
"overtime-daily-multiplier": import("./overtime-daily-multiplier.js").OvertimeDailyMultiplierConfig;
|
|
29
40
|
"overtime-daily-surcharge": import("./overtime-daily-surcharge.js").OvertimeDailySurchargeConfig;
|
|
30
41
|
"overtime-tiered-multiplier": import("./overtime-tiered-multiplier.js").OvertimeTieredMultiplierConfig;
|
|
42
|
+
"max-days-of-week-per-period": import("./max-days-of-week-per-period.js").MaxDaysOfWeekPerPeriodConfig;
|
|
43
|
+
"min-days-of-week-per-period": import("./min-days-of-week-per-period.js").MinDaysOfWeekPerPeriodConfig;
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
|
|
46
|
+
/**
|
|
47
|
+
* The union of all built-in CP-SAT rule names.
|
|
48
|
+
*
|
|
49
|
+
* @category Rules
|
|
50
|
+
*/
|
|
51
|
+
export type CpsatRuleName = keyof BuiltInCpsatRuleConfigRegistry;
|
|
34
52
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
/**
|
|
54
|
+
* A registry of CP-SAT rule descriptors keyed by rule name.
|
|
55
|
+
*
|
|
56
|
+
* @remarks
|
|
57
|
+
* The registry key must exactly match each descriptor's `name` field.
|
|
58
|
+
* Use `createCpsatRuleRegistry` to validate that invariant when building
|
|
59
|
+
* custom registries.
|
|
60
|
+
*
|
|
61
|
+
* @category Rules
|
|
62
|
+
*/
|
|
63
|
+
export type CpsatRuleRegistry = Record<string, RuleDescriptor<string, unknown>>;
|
|
38
64
|
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The built-in CP-SAT rule registry shape.
|
|
67
|
+
*
|
|
68
|
+
* @category Rules
|
|
69
|
+
*/
|
|
70
|
+
export type BuiltInCpsatRuleRegistry = {
|
|
71
|
+
[K in CpsatRuleName]: RuleDescriptor<K, BuiltInCpsatRuleConfigRegistry[K]>;
|
|
41
72
|
};
|
|
42
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Extracts the config type from a rule descriptor.
|
|
76
|
+
*
|
|
77
|
+
* @category Rules
|
|
78
|
+
*/
|
|
43
79
|
export type InferCpsatRuleConfig<T> =
|
|
44
|
-
T extends
|
|
80
|
+
T extends RuleDescriptor<string, infer Config> ? Config : never;
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Maps a rule registry to a name-to-config lookup type.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* type ConfigByName = CpsatRuleConfigByName<typeof builtInCpsatRuleRegistry>;
|
|
88
|
+
* // ^? { "max-hours-day": MaxHoursDayConfig; ... }
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @category Rules
|
|
92
|
+
*/
|
|
93
|
+
export type CpsatRuleConfigByName<Registry extends CpsatRuleRegistry> = {
|
|
94
|
+
[K in keyof Registry & string]: InferCpsatRuleConfig<Registry[K]>;
|
|
48
95
|
};
|
|
49
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Builds the discriminated rule-entry union for a rule registry.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* This preserves the pairing between each `name` value and the config fields
|
|
102
|
+
* allowed for that rule.
|
|
103
|
+
*
|
|
104
|
+
* @category Rules
|
|
105
|
+
*/
|
|
106
|
+
export type CpsatRuleConfigEntryFor<Registry extends CpsatRuleRegistry> = {
|
|
107
|
+
[K in keyof Registry & string]: { name: K } & InferCpsatRuleConfig<Registry[K]>;
|
|
108
|
+
}[keyof Registry & string];
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A normalized rule entry with an unconstrained string rule name.
|
|
112
|
+
*
|
|
113
|
+
* @remarks
|
|
114
|
+
* This is used internally at points where the specific registry is no longer
|
|
115
|
+
* known at the type level.
|
|
116
|
+
*
|
|
117
|
+
* @category Rules
|
|
118
|
+
*/
|
|
119
|
+
export type AnyCpsatRuleConfigEntry = { name: string } & Record<string, unknown>;
|
|
120
|
+
|
|
50
121
|
/**
|
|
51
122
|
* A named rule configuration entry.
|
|
52
123
|
*
|
|
@@ -62,7 +133,8 @@ export type CpsatRuleRegistryFromFactories<F extends CpsatRuleFactories> = {
|
|
|
62
133
|
* { name: "time-off", memberIds: ["alice"], dayOfWeek: ["monday"], priority: "MANDATORY" },
|
|
63
134
|
* ];
|
|
64
135
|
* ```
|
|
136
|
+
*
|
|
137
|
+
* @category Rules
|
|
65
138
|
*/
|
|
66
|
-
export type CpsatRuleConfigEntry =
|
|
67
|
-
|
|
68
|
-
}[CpsatRuleName];
|
|
139
|
+
export type CpsatRuleConfigEntry<Registry extends CpsatRuleRegistry = BuiltInCpsatRuleRegistry> =
|
|
140
|
+
CpsatRuleConfigEntryFor<Registry>;
|
|
@@ -34,10 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import * as z from "zod";
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
import {
|
|
38
|
+
DayOfWeekSchema,
|
|
39
|
+
type DateString,
|
|
40
|
+
type DayOfWeek,
|
|
41
|
+
type SchedulingDay,
|
|
42
|
+
} from "../../types.js";
|
|
43
|
+
import { PRIORITY_VALUES, SOFT_PRIORITY_VALUES, type SchedulingMember } from "../types.js";
|
|
41
44
|
import { assertSafeKeySegments, type ValidationGroup } from "../validation.types.js";
|
|
42
45
|
|
|
43
46
|
// ============================================================================
|
|
@@ -50,9 +53,12 @@ import { assertSafeKeySegments, type ValidationGroup } from "../validation.types
|
|
|
50
53
|
* Rules that use priority should include this in their schema so the
|
|
51
54
|
* default is co-located with the rule system, not injected externally.
|
|
52
55
|
*/
|
|
53
|
-
export const PrioritySchema = z
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
export const PrioritySchema = z.enum(PRIORITY_VALUES).default("MANDATORY");
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Zod schema for soft-only priorities.
|
|
60
|
+
*/
|
|
61
|
+
export const SoftPrioritySchema = z.enum(SOFT_PRIORITY_VALUES);
|
|
56
62
|
|
|
57
63
|
// ============================================================================
|
|
58
64
|
// Scope Keys
|
|
@@ -104,8 +110,8 @@ export interface RecurringPeriod {
|
|
|
104
110
|
* The field shape when a time scope variant is active (selected).
|
|
105
111
|
*/
|
|
106
112
|
type ActiveTimeFields = {
|
|
107
|
-
dateRange: { dateRange: { start:
|
|
108
|
-
specificDates: { specificDates: NonEmptyArray<
|
|
113
|
+
dateRange: { dateRange: { start: DateString; end: DateString } };
|
|
114
|
+
specificDates: { specificDates: NonEmptyArray<DateString> };
|
|
109
115
|
dayOfWeek: { dayOfWeek: NonEmptyArray<DayOfWeek> };
|
|
110
116
|
recurring: { recurringPeriods: NonEmptyArray<RecurringPeriod> };
|
|
111
117
|
};
|
|
@@ -407,7 +413,7 @@ export type ParsedEntityScope =
|
|
|
407
413
|
export type ParsedTimeScope =
|
|
408
414
|
| { type: "none" }
|
|
409
415
|
| { type: "dateRange"; start: string; end: string }
|
|
410
|
-
| { type: "specificDates"; dates:
|
|
416
|
+
| { type: "specificDates"; dates: DateString[] }
|
|
411
417
|
| { type: "dayOfWeek"; days: DayOfWeek[] }
|
|
412
418
|
| { type: "recurring"; periods: RecurringPeriod[] };
|
|
413
419
|
|
|
@@ -421,8 +427,8 @@ export interface EntityScopeInput {
|
|
|
421
427
|
|
|
422
428
|
/** Input shape accepted by {@link parseTimeScope}. */
|
|
423
429
|
export interface TimeScopeInput {
|
|
424
|
-
dateRange?: { start:
|
|
425
|
-
specificDates?:
|
|
430
|
+
dateRange?: { start: DateString; end: DateString };
|
|
431
|
+
specificDates?: DateString[];
|
|
426
432
|
dayOfWeek?: DayOfWeek[];
|
|
427
433
|
recurringPeriods?: RecurringPeriod[];
|
|
428
434
|
[key: string]: unknown;
|
|
@@ -488,28 +494,27 @@ export function resolveMembersFromScope(
|
|
|
488
494
|
/**
|
|
489
495
|
* Resolves which days a rule applies to based on time scope.
|
|
490
496
|
*/
|
|
491
|
-
export function resolveActiveDaysFromScope(
|
|
497
|
+
export function resolveActiveDaysFromScope(
|
|
498
|
+
scope: ParsedTimeScope,
|
|
499
|
+
allDays: SchedulingDay[],
|
|
500
|
+
): SchedulingDay[] {
|
|
492
501
|
switch (scope.type) {
|
|
493
502
|
case "none":
|
|
494
503
|
return allDays;
|
|
495
504
|
case "dateRange":
|
|
496
|
-
return allDays.filter((day) => day >= scope.start && day <= scope.end);
|
|
497
|
-
case "specificDates":
|
|
498
|
-
|
|
505
|
+
return allDays.filter((day) => day.iso >= scope.start && day.iso <= scope.end);
|
|
506
|
+
case "specificDates": {
|
|
507
|
+
const dateSet = new Set(scope.dates);
|
|
508
|
+
return allDays.filter((day) => dateSet.has(day.iso));
|
|
509
|
+
}
|
|
499
510
|
case "dayOfWeek": {
|
|
500
511
|
const targetDays = new Set(scope.days);
|
|
501
|
-
return allDays.filter((day) =>
|
|
502
|
-
const date = parseDayString(day);
|
|
503
|
-
return targetDays.has(toDayOfWeekUTC(date));
|
|
504
|
-
});
|
|
512
|
+
return allDays.filter((day) => targetDays.has(day.dayOfWeek));
|
|
505
513
|
}
|
|
506
514
|
case "recurring":
|
|
507
|
-
return allDays.filter((day) =>
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const dayOfMonth = date.getUTCDate();
|
|
511
|
-
return scope.periods.some((period) => isDateInRecurringPeriod(month, dayOfMonth, period));
|
|
512
|
-
});
|
|
515
|
+
return allDays.filter((day) =>
|
|
516
|
+
scope.periods.some((period) => isDateInRecurringPeriod(day.month, day.dayOfMonth, period)),
|
|
517
|
+
);
|
|
513
518
|
}
|
|
514
519
|
}
|
|
515
520
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { DayOfWeekSchema } from "../../types.js";
|
|
3
|
+
import { defineRuleDescriptor } from "../rule-descriptor.js";
|
|
4
|
+
import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
|
|
5
|
+
import {
|
|
6
|
+
entityScope,
|
|
7
|
+
parseEntityScope,
|
|
8
|
+
resolveMembersFromScope,
|
|
9
|
+
ruleGroup,
|
|
10
|
+
SoftPrioritySchema,
|
|
11
|
+
} from "./scope.types.js";
|
|
12
|
+
import { assignmentVarsForDay } from "./assignment-terms.js";
|
|
13
|
+
import { assignedDayVariableName } from "./variables.js";
|
|
14
|
+
import { reportValidation, softConstraint } from "./artifacts.js";
|
|
15
|
+
import { buildAssignedDayIndicator } from "./assigned-day.js";
|
|
16
|
+
|
|
17
|
+
const TargetDaysWeekBase = z.object({
|
|
18
|
+
days: z.number().int().min(0),
|
|
19
|
+
priority: SoftPrioritySchema.default("HIGH"),
|
|
20
|
+
weekStartsOn: DayOfWeekSchema.optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const TargetDaysWeekSchema = TargetDaysWeekBase.and(
|
|
24
|
+
entityScope(["members", "roles", "skills"]),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for {@link targetDaysWeekRuleDescriptor}.
|
|
29
|
+
*/
|
|
30
|
+
export type TargetDaysWeekConfig = z.infer<typeof TargetDaysWeekSchema>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Low-level descriptor for the `target-days-week` rule.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Softly targets assigned days per scheduling week. Deviations in either
|
|
37
|
+
* direction are penalized, so working fewer than the target or more than the
|
|
38
|
+
* target both count as misses. Use this for stated weekly patterns like
|
|
39
|
+
* "works a 4-day week" when that pattern should remain flexible.
|
|
40
|
+
*
|
|
41
|
+
* @category Rules
|
|
42
|
+
*/
|
|
43
|
+
export const targetDaysWeekRuleDescriptor = defineRuleDescriptor({
|
|
44
|
+
name: "target-days-week",
|
|
45
|
+
schema: TargetDaysWeekSchema,
|
|
46
|
+
compile(config, ctx) {
|
|
47
|
+
const scope = parseEntityScope(config);
|
|
48
|
+
const { days, priority, weekStartsOn } = config;
|
|
49
|
+
const group = ruleGroup(`target-days-week:${days}`, `Target ${days}d per week`, scope);
|
|
50
|
+
const members = resolveMembersFromScope(scope, [...ctx.members]);
|
|
51
|
+
const weeks = splitIntoWeeks([...ctx.days], weekStartsOn ?? ctx.weekStartsOn);
|
|
52
|
+
|
|
53
|
+
const artifacts = members.flatMap((member) =>
|
|
54
|
+
weeks.flatMap((weekDays) => {
|
|
55
|
+
const weekStart = weekDays[0];
|
|
56
|
+
if (!weekStart) return [];
|
|
57
|
+
|
|
58
|
+
const supportArtifacts = weekDays.flatMap(
|
|
59
|
+
(day) =>
|
|
60
|
+
buildAssignedDayIndicator({
|
|
61
|
+
memberId: member.id,
|
|
62
|
+
day,
|
|
63
|
+
assignmentVars: assignmentVarsForDay(member, day, ctx.shiftPatterns),
|
|
64
|
+
}).artifacts,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const assignedDayVars = weekDays.map((day) => assignedDayVariableName(member.id, day.iso));
|
|
68
|
+
|
|
69
|
+
const context = { memberIds: [member.id], days: weekDays.map((day) => day.iso) };
|
|
70
|
+
const terms = assignedDayVars.map((varName) => ({ var: varName, coeff: 1 }));
|
|
71
|
+
const penalty = priorityToPenalty(priority);
|
|
72
|
+
|
|
73
|
+
return supportArtifacts.concat([
|
|
74
|
+
softConstraint({
|
|
75
|
+
group,
|
|
76
|
+
description: `${member.id} target ${days}d per week starting ${weekStart.iso} (under)`,
|
|
77
|
+
context,
|
|
78
|
+
validation: reportValidation(),
|
|
79
|
+
terms,
|
|
80
|
+
comparator: ">=",
|
|
81
|
+
targetValue: days,
|
|
82
|
+
penalty,
|
|
83
|
+
constraintId: `target-days-week:under:${member.id}:${weekStart.iso}`,
|
|
84
|
+
}),
|
|
85
|
+
softConstraint({
|
|
86
|
+
group,
|
|
87
|
+
description: `${member.id} target ${days}d per week starting ${weekStart.iso} (over)`,
|
|
88
|
+
context,
|
|
89
|
+
validation: reportValidation(),
|
|
90
|
+
terms,
|
|
91
|
+
comparator: "<=",
|
|
92
|
+
targetValue: days,
|
|
93
|
+
penalty,
|
|
94
|
+
constraintId: `target-days-week:over:${member.id}:${weekStart.iso}`,
|
|
95
|
+
}),
|
|
96
|
+
]);
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
rule: "target-days-week",
|
|
102
|
+
artifacts,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|