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
|
@@ -1,223 +1,210 @@
|
|
|
1
1
|
import type * as z from "zod";
|
|
2
|
-
import type { DayOfWeek } from "../../types.js";
|
|
3
|
-
import
|
|
4
|
-
import type {
|
|
5
|
-
import type { ShiftAssignment } from "../response.js";
|
|
2
|
+
import type { DateString, DayOfWeek } from "../../types.js";
|
|
3
|
+
import { defineRuleDescriptor } from "../rule-descriptor.js";
|
|
4
|
+
import type { CostArtifact } from "../rule-descriptor.js";
|
|
6
5
|
import { COST_CATEGORY } from "../cost.js";
|
|
7
6
|
import { OBJECTIVE_WEIGHTS, splitIntoWeeks } from "../utils.js";
|
|
8
7
|
import {
|
|
9
8
|
entityScope,
|
|
10
|
-
timeScope,
|
|
11
9
|
parseEntityScope,
|
|
12
10
|
parseTimeScope,
|
|
13
|
-
resolveMembersFromScope,
|
|
14
11
|
resolveActiveDaysFromScope,
|
|
12
|
+
resolveMembersFromScope,
|
|
13
|
+
timeScope,
|
|
15
14
|
} from "./scope.types.js";
|
|
16
|
-
import { getHourlyRate, getSalariedPay
|
|
15
|
+
import { getHourlyRate, getSalariedPay } from "./cost-utils.js";
|
|
16
|
+
import { patternDurationMinutes } from "./pattern-time.js";
|
|
17
17
|
|
|
18
18
|
const MinimizeCostSchema = entityScope(["members", "roles", "skills"]).and(
|
|
19
19
|
timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]),
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
/** Configuration for {@link
|
|
22
|
+
/** Configuration for {@link minimizeCostRuleDescriptor}. */
|
|
23
23
|
export type MinimizeCostConfig = z.infer<typeof MinimizeCostSchema>;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (targetMembers.length === 0 || activeDays.length === 0) return;
|
|
55
|
-
|
|
56
|
-
// Compute normalization factor: max raw cost of any single assignment
|
|
57
|
-
// (hourly) or weekly salary (salaried)
|
|
58
|
-
let maxRawCost = 0;
|
|
59
|
-
for (const member of targetMembers) {
|
|
60
|
-
const rate = getHourlyRate(member);
|
|
61
|
-
if (rate !== undefined) {
|
|
62
|
-
for (const pattern of b.shiftPatterns) {
|
|
63
|
-
if (!b.canAssign(member, pattern)) continue;
|
|
64
|
-
const duration = patternDurationMinutes(pattern);
|
|
65
|
-
const rawCost = (rate * duration) / 60;
|
|
66
|
-
if (rawCost > maxRawCost) maxRawCost = rawCost;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const salaried = getSalariedPay(member);
|
|
70
|
-
if (salaried !== undefined) {
|
|
71
|
-
const weeklyCost = salaried.annual / 52;
|
|
72
|
-
if (weeklyCost > maxRawCost) maxRawCost = weeklyCost;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (maxRawCost === 0) return;
|
|
77
|
-
|
|
78
|
-
const normalizationFactor = maxRawCost / OBJECTIVE_WEIGHTS.COST;
|
|
79
|
-
|
|
80
|
-
b.costContext = {
|
|
81
|
-
normalizationFactor,
|
|
82
|
-
active: true,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Add penalty terms for hourly members
|
|
86
|
-
for (const member of targetMembers) {
|
|
87
|
-
const rate = getHourlyRate(member);
|
|
88
|
-
if (rate !== undefined) {
|
|
89
|
-
for (const pattern of b.shiftPatterns) {
|
|
90
|
-
if (!b.canAssign(member, pattern)) continue;
|
|
91
|
-
for (const day of activeDays) {
|
|
92
|
-
if (!b.patternAvailableOnDay(pattern, day)) continue;
|
|
25
|
+
export const minimizeCostRuleDescriptor = defineRuleDescriptor({
|
|
26
|
+
name: "minimize-cost",
|
|
27
|
+
schema: MinimizeCostSchema,
|
|
28
|
+
compile(config) {
|
|
29
|
+
const entityScopeValue = parseEntityScope(config);
|
|
30
|
+
const timeScopeValue = parseTimeScope(config);
|
|
31
|
+
let resolvedWeekStartsOn: DayOfWeek = "monday";
|
|
32
|
+
|
|
33
|
+
const costArtifact: CostArtifact = {
|
|
34
|
+
kind: "cost",
|
|
35
|
+
validation: {
|
|
36
|
+
strategy: "skip",
|
|
37
|
+
category: "no-meaningful-feedback",
|
|
38
|
+
rationale:
|
|
39
|
+
"Cost optimization influences the objective and contributes post-solve accounting, not validation feedback.",
|
|
40
|
+
},
|
|
41
|
+
compileObjective(builder) {
|
|
42
|
+
resolvedWeekStartsOn = builder.weekStartsOn;
|
|
43
|
+
|
|
44
|
+
const targetMembers = resolveMembersFromScope(entityScopeValue, builder.members);
|
|
45
|
+
const activeDays = resolveActiveDaysFromScope(timeScopeValue, builder.days);
|
|
46
|
+
if (targetMembers.length === 0 || activeDays.length === 0) return;
|
|
47
|
+
|
|
48
|
+
let maxRawCost = 0;
|
|
49
|
+
for (const member of targetMembers) {
|
|
50
|
+
const rate = getHourlyRate(member);
|
|
51
|
+
if (rate !== undefined) {
|
|
52
|
+
for (const pattern of builder.shiftPatterns) {
|
|
53
|
+
if (!builder.canAssign(member, pattern)) continue;
|
|
93
54
|
const duration = patternDurationMinutes(pattern);
|
|
94
55
|
const rawCost = (rate * duration) / 60;
|
|
95
|
-
|
|
96
|
-
b.addPenalty(
|
|
97
|
-
b.assignment(member.id, pattern.id, day),
|
|
98
|
-
Math.max(1, normalizedPenalty),
|
|
99
|
-
);
|
|
56
|
+
if (rawCost > maxRawCost) maxRawCost = rawCost;
|
|
100
57
|
}
|
|
101
58
|
}
|
|
102
|
-
|
|
59
|
+
const salaried = getSalariedPay(member);
|
|
60
|
+
if (salaried !== undefined) {
|
|
61
|
+
const weeklyCost = salaried.annual / 52;
|
|
62
|
+
if (weeklyCost > maxRawCost) maxRawCost = weeklyCost;
|
|
63
|
+
}
|
|
103
64
|
}
|
|
104
65
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
66
|
+
if (maxRawCost === 0) return;
|
|
67
|
+
|
|
68
|
+
const normalizationFactor = maxRawCost / OBJECTIVE_WEIGHTS.COST;
|
|
69
|
+
builder.costContext = {
|
|
70
|
+
normalizationFactor,
|
|
71
|
+
active: true,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
for (const member of targetMembers) {
|
|
75
|
+
const rate = getHourlyRate(member);
|
|
76
|
+
if (rate !== undefined) {
|
|
77
|
+
for (const pattern of builder.shiftPatterns) {
|
|
78
|
+
if (!builder.canAssign(member, pattern)) continue;
|
|
79
|
+
for (const day of activeDays) {
|
|
80
|
+
if (!builder.patternAvailableOnDay(pattern, day)) continue;
|
|
81
|
+
const duration = patternDurationMinutes(pattern);
|
|
82
|
+
const rawCost = (rate * duration) / 60;
|
|
83
|
+
const normalizedPenalty = rawCost / normalizationFactor;
|
|
84
|
+
builder.addPenalty(
|
|
85
|
+
builder.assignment(member.id, pattern.id, day),
|
|
86
|
+
Math.max(1, normalizedPenalty),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
122
89
|
}
|
|
90
|
+
continue;
|
|
123
91
|
}
|
|
124
92
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Bool var: true if this salaried member works any shift this week
|
|
128
|
-
const activeVar = b.boolVar(`active:cost:${member.id}:w${weekIdx}`);
|
|
129
|
-
|
|
130
|
-
// If any assignment is true, activeVar must be true
|
|
131
|
-
// activeVar OR NOT(assignment_1) OR NOT(assignment_2) OR ...
|
|
132
|
-
// This is equivalent to: for each assignment, assignment => activeVar
|
|
133
|
-
for (const assignVar of weekAssignmentVars) {
|
|
134
|
-
b.addImplication(assignVar, activeVar);
|
|
135
|
-
}
|
|
93
|
+
const salaried = getSalariedPay(member);
|
|
94
|
+
if (salaried === undefined) continue;
|
|
136
95
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const allDays = [...new Set(assignments.map((a) => a.day))].toSorted();
|
|
151
|
-
const activeDays = new Set(resolveActiveDaysFromScope(timeScopeValue, allDays));
|
|
152
|
-
const targetMemberIds = new Set(
|
|
153
|
-
resolveMembersFromScope(entityScopeValue, [...members]).map((m) => m.id),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
const entries: CostContribution["entries"] = [];
|
|
157
|
-
|
|
158
|
-
// Track which salaried members are active per week for weekly cost
|
|
159
|
-
const salariedWeekActive = new Map<string, Set<string>>();
|
|
160
|
-
|
|
161
|
-
for (const a of assignments) {
|
|
162
|
-
if (!activeDays.has(a.day)) continue;
|
|
163
|
-
if (!targetMemberIds.has(a.memberId)) continue;
|
|
164
|
-
const member = memberMap.get(a.memberId);
|
|
165
|
-
if (!member) continue;
|
|
166
|
-
|
|
167
|
-
const pattern = patternMap.get(a.shiftPatternId);
|
|
168
|
-
if (!pattern) continue;
|
|
169
|
-
const duration = patternDurationMinutes(pattern);
|
|
170
|
-
|
|
171
|
-
const rate = getHourlyRate(member);
|
|
172
|
-
if (rate !== undefined) {
|
|
173
|
-
entries.push({
|
|
174
|
-
memberId: a.memberId,
|
|
175
|
-
day: a.day,
|
|
176
|
-
category: COST_CATEGORY.BASE,
|
|
177
|
-
amount: (rate * duration) / 60,
|
|
178
|
-
});
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
96
|
+
const weeklyCost = salaried.annual / 52;
|
|
97
|
+
const normalizedWeeklyCost = weeklyCost / normalizationFactor;
|
|
98
|
+
const weeks = splitIntoWeeks(activeDays, builder.weekStartsOn);
|
|
99
|
+
|
|
100
|
+
for (const [weekIndex, weekDays] of weeks.entries()) {
|
|
101
|
+
const weekAssignmentVars: string[] = [];
|
|
102
|
+
for (const day of weekDays) {
|
|
103
|
+
for (const pattern of builder.shiftPatterns) {
|
|
104
|
+
if (!builder.canAssign(member, pattern)) continue;
|
|
105
|
+
if (!builder.patternAvailableOnDay(pattern, day)) continue;
|
|
106
|
+
weekAssignmentVars.push(builder.assignment(member.id, pattern.id, day));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
181
109
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
110
|
+
if (weekAssignmentVars.length === 0) continue;
|
|
111
|
+
|
|
112
|
+
const activeVar = builder.boolVar(`active:cost:${member.id}:w${weekIndex}`);
|
|
113
|
+
for (const assignVar of weekAssignmentVars) {
|
|
114
|
+
builder.addLinear(
|
|
115
|
+
[
|
|
116
|
+
{ var: activeVar, coeff: 1 },
|
|
117
|
+
{ var: assignVar, coeff: -1 },
|
|
118
|
+
],
|
|
119
|
+
">=",
|
|
120
|
+
0,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
185
123
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
days = new Set();
|
|
189
|
-
salariedWeekActive.set(a.memberId, days);
|
|
124
|
+
builder.addPenalty(activeVar, Math.max(1, Math.round(normalizedWeeklyCost)));
|
|
125
|
+
}
|
|
190
126
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
127
|
+
},
|
|
128
|
+
calculateCost(assignments, costContext) {
|
|
129
|
+
const memberMap = new Map(costContext.members.map((member) => [member.id, member]));
|
|
130
|
+
const patternMap = new Map(
|
|
131
|
+
costContext.shiftPatterns.map((pattern) => [pattern.id, pattern]),
|
|
132
|
+
);
|
|
133
|
+
const activeDays = new Set(
|
|
134
|
+
resolveActiveDaysFromScope(timeScopeValue, [...costContext.days]).map((day) => day.iso),
|
|
135
|
+
);
|
|
136
|
+
const targetMemberIds = new Set(
|
|
137
|
+
resolveMembersFromScope(entityScopeValue, [...costContext.members]).map(
|
|
138
|
+
(member) => member.id,
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const entries: Array<{ memberId: string; day: string; category: string; amount: number }> =
|
|
143
|
+
[];
|
|
144
|
+
const salariedWeekActive = new Map<string, Set<DateString>>();
|
|
145
|
+
|
|
146
|
+
for (const assignment of assignments) {
|
|
147
|
+
if (!activeDays.has(assignment.day) || !targetMemberIds.has(assignment.memberId))
|
|
148
|
+
continue;
|
|
149
|
+
const member = memberMap.get(assignment.memberId);
|
|
150
|
+
if (!member) continue;
|
|
151
|
+
const pattern = patternMap.get(assignment.shiftPatternId);
|
|
152
|
+
if (!pattern) continue;
|
|
153
|
+
const duration = patternDurationMinutes(pattern);
|
|
154
|
+
|
|
155
|
+
const rate = getHourlyRate(member);
|
|
156
|
+
if (rate !== undefined) {
|
|
210
157
|
entries.push({
|
|
211
|
-
memberId,
|
|
212
|
-
day,
|
|
158
|
+
memberId: assignment.memberId,
|
|
159
|
+
day: assignment.day,
|
|
213
160
|
category: COST_CATEGORY.BASE,
|
|
214
|
-
amount:
|
|
161
|
+
amount: (rate * duration) / 60,
|
|
215
162
|
});
|
|
163
|
+
continue;
|
|
216
164
|
}
|
|
165
|
+
|
|
166
|
+
const salaried = getSalariedPay(member);
|
|
167
|
+
if (salaried === undefined) continue;
|
|
168
|
+
let memberDays = salariedWeekActive.get(assignment.memberId);
|
|
169
|
+
if (!memberDays) {
|
|
170
|
+
memberDays = new Set();
|
|
171
|
+
salariedWeekActive.set(assignment.memberId, memberDays);
|
|
172
|
+
}
|
|
173
|
+
memberDays.add(assignment.day);
|
|
217
174
|
}
|
|
218
|
-
}
|
|
219
175
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
176
|
+
for (const [memberId, activeDaysSet] of salariedWeekActive) {
|
|
177
|
+
const member = memberMap.get(memberId);
|
|
178
|
+
if (!member) continue;
|
|
179
|
+
const salaried = getSalariedPay(member);
|
|
180
|
+
if (!salaried) continue;
|
|
181
|
+
|
|
182
|
+
const activeDateSet = new Set(activeDaysSet);
|
|
183
|
+
const activeList = costContext.days.filter((day) => activeDateSet.has(day.iso));
|
|
184
|
+
const weeks = splitIntoWeeks(activeList, resolvedWeekStartsOn);
|
|
185
|
+
|
|
186
|
+
for (const weekDays of weeks) {
|
|
187
|
+
if (weekDays.length === 0) continue;
|
|
188
|
+
const weeklyCost = salaried.annual / 52;
|
|
189
|
+
const perDay = weeklyCost / weekDays.length;
|
|
190
|
+
for (const day of weekDays) {
|
|
191
|
+
entries.push({
|
|
192
|
+
memberId,
|
|
193
|
+
day: day.iso,
|
|
194
|
+
category: COST_CATEGORY.BASE,
|
|
195
|
+
amount: perDay,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { entries };
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
rule: "minimize-cost",
|
|
207
|
+
artifacts: [costArtifact],
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
import { DayOfWeekSchema } from "../../types.js";
|
|
3
|
-
import
|
|
4
|
-
import type { Term } from "../types.js";
|
|
3
|
+
import { defineRuleDescriptor } from "../rule-descriptor.js";
|
|
5
4
|
import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
|
|
6
5
|
import {
|
|
7
6
|
entityScope,
|
|
@@ -9,100 +8,64 @@ import {
|
|
|
9
8
|
resolveMembersFromScope,
|
|
10
9
|
ruleGroup,
|
|
11
10
|
} from "./scope.types.js";
|
|
11
|
+
import { assignmentTermsForDays } from "./assignment-terms.js";
|
|
12
|
+
import { reportValidation, softConstraint } from "./artifacts.js";
|
|
12
13
|
|
|
13
14
|
/** Internally always HIGH. Not user-configurable. */
|
|
14
15
|
const MUST_ASSIGN_PENALTY = priorityToPenalty("HIGH");
|
|
15
16
|
|
|
16
|
-
const MustAssignSchema = z
|
|
17
|
+
export const MustAssignSchema = z
|
|
17
18
|
.object({
|
|
18
19
|
weekStartsOn: DayOfWeekSchema.optional(),
|
|
19
20
|
})
|
|
20
21
|
.and(entityScope(["members", "roles", "skills"]));
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
* Configuration for {@link
|
|
24
|
-
*
|
|
25
|
-
* - `weekStartsOn` (optional): which day starts the week; defaults to
|
|
26
|
-
* {@link ModelBuilder.weekStartsOn}
|
|
27
|
-
*
|
|
28
|
-
* Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
|
|
24
|
+
* Configuration for {@link mustAssignRuleDescriptor}.
|
|
29
25
|
*/
|
|
30
26
|
export type MustAssignConfig = z.infer<typeof MustAssignSchema>;
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* Always compiles as a soft constraint (HIGH priority internally) so the
|
|
42
|
-
* schedule still generates when a member genuinely cannot be placed
|
|
43
|
-
* (e.g., full week of absences, no compatible shift patterns). Violations
|
|
44
|
-
* surface as validation warnings with distinct messaging from
|
|
45
|
-
* `min-days-week`.
|
|
46
|
-
*
|
|
47
|
-
* Unlike {@link createMinDaysWeekRule}, this rule communicates a staffing
|
|
48
|
-
* obligation rather than a scheduling preference. Validation messages
|
|
49
|
-
* reflect this: "Alice was not assigned (staffing obligation)" rather
|
|
50
|
-
* than "Alice worked 0 days, minimum was 1."
|
|
51
|
-
*
|
|
52
|
-
* @param config - See {@link MustAssignConfig}
|
|
53
|
-
*
|
|
54
|
-
* @example Ensure a salaried manager is always rostered
|
|
55
|
-
* ```ts
|
|
56
|
-
* createMustAssignRule({ memberIds: ["diana"] });
|
|
57
|
-
* ```
|
|
58
|
-
*
|
|
59
|
-
* @example Ensure all supervisors appear on the rota
|
|
60
|
-
* ```ts
|
|
61
|
-
* createMustAssignRule({ roleIds: ["supervisor"] });
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export function createMustAssignRule(config: MustAssignConfig): CompilationRule {
|
|
65
|
-
const parsed = MustAssignSchema.parse(config);
|
|
66
|
-
const scope = parseEntityScope(parsed);
|
|
67
|
-
const { weekStartsOn } = parsed;
|
|
68
|
-
const group = ruleGroup("must-assign", "Must assign", scope);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
compile(b) {
|
|
72
|
-
const members = resolveMembersFromScope(scope, b.members);
|
|
73
|
-
const weeks = splitIntoWeeks(b.days, weekStartsOn ?? b.weekStartsOn);
|
|
28
|
+
export const mustAssignRuleDescriptor = defineRuleDescriptor({
|
|
29
|
+
name: "must-assign",
|
|
30
|
+
schema: MustAssignSchema,
|
|
31
|
+
compile(config, ctx) {
|
|
32
|
+
const scope = parseEntityScope(config);
|
|
33
|
+
const { weekStartsOn } = config;
|
|
34
|
+
const group = ruleGroup("must-assign", "Must assign", scope);
|
|
35
|
+
const members = resolveMembersFromScope(scope, [...ctx.members]);
|
|
36
|
+
const weeks = splitIntoWeeks([...ctx.days], weekStartsOn ?? ctx.weekStartsOn);
|
|
74
37
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
38
|
+
const artifacts = members.flatMap((member) =>
|
|
39
|
+
weeks.flatMap((weekDays) => {
|
|
40
|
+
const weekStart = weekDays[0];
|
|
41
|
+
if (!weekStart) return [];
|
|
78
42
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (!b.canAssign(emp, pattern)) continue;
|
|
82
|
-
if (!b.patternAvailableOnDay(pattern, day)) continue;
|
|
83
|
-
terms.push({ var: b.assignment(emp.id, pattern.id, day), coeff: 1 });
|
|
84
|
-
}
|
|
85
|
-
}
|
|
43
|
+
const terms = assignmentTermsForDays(member, weekDays, ctx.shiftPatterns, () => 1);
|
|
44
|
+
if (terms.length === 0) return [];
|
|
86
45
|
|
|
87
|
-
|
|
46
|
+
const description = `${member.id} not assigned in week starting ${weekStart.iso} (staffing obligation)`;
|
|
47
|
+
const context = { memberIds: [member.id], days: weekDays.map((day) => day.iso) };
|
|
48
|
+
const constraintId = `must-assign:${member.id}:${weekStart.iso}`;
|
|
88
49
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
b.addSoftLinear(terms, ">=", 1, MUST_ASSIGN_PENALTY, constraintId);
|
|
93
|
-
b.reporter.trackConstraint({
|
|
94
|
-
id: constraintId,
|
|
95
|
-
type: "rule",
|
|
96
|
-
rule: "must-assign",
|
|
97
|
-
description: `${emp.id} not assigned in week starting ${weekLabel} (staffing obligation)`,
|
|
98
|
-
targetValue: 1,
|
|
99
|
-
comparator: ">=",
|
|
100
|
-
day: weekLabel,
|
|
101
|
-
context: { memberIds: [emp.id], days: weekDays },
|
|
50
|
+
return [
|
|
51
|
+
softConstraint({
|
|
102
52
|
group,
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
53
|
+
description,
|
|
54
|
+
context,
|
|
55
|
+
validation: reportValidation(),
|
|
56
|
+
terms,
|
|
57
|
+
comparator: ">=",
|
|
58
|
+
targetValue: 1,
|
|
59
|
+
penalty: MUST_ASSIGN_PENALTY,
|
|
60
|
+
constraintId,
|
|
61
|
+
}),
|
|
62
|
+
];
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
rule: "must-assign",
|
|
68
|
+
artifacts,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|