dabke 0.81.1 → 0.83.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 +58 -0
- package/README.md +45 -27
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -1
- package/dist/client.js.map +1 -1
- package/dist/client.types.d.ts +9 -0
- package/dist/client.types.d.ts.map +1 -1
- package/dist/client.types.js +1 -0
- package/dist/client.types.js.map +1 -1
- package/dist/cpsat/model-builder.d.ts +9 -0
- package/dist/cpsat/model-builder.d.ts.map +1 -1
- package/dist/cpsat/model-builder.js +36 -34
- package/dist/cpsat/model-builder.js.map +1 -1
- package/dist/cpsat/response.d.ts +13 -1
- package/dist/cpsat/response.d.ts.map +1 -1
- package/dist/cpsat/response.js +4 -0
- package/dist/cpsat/response.js.map +1 -1
- package/dist/cpsat/rules/cost-utils.d.ts +11 -0
- package/dist/cpsat/rules/cost-utils.d.ts.map +1 -0
- package/dist/cpsat/rules/cost-utils.js +24 -0
- package/dist/cpsat/rules/cost-utils.js.map +1 -0
- package/dist/cpsat/rules/day-cost-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-multiplier.js +3 -14
- package/dist/cpsat/rules/day-cost-multiplier.js.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/day-cost-surcharge.js +3 -7
- package/dist/cpsat/rules/day-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/index.d.ts +3 -0
- package/dist/cpsat/rules/index.d.ts.map +1 -1
- package/dist/cpsat/rules/index.js +3 -0
- package/dist/cpsat/rules/index.js.map +1 -1
- package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/max-consecutive-days.js +16 -2
- package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/max-days-week.d.ts +44 -0
- package/dist/cpsat/rules/max-days-week.d.ts.map +1 -0
- package/dist/cpsat/rules/max-days-week.js +95 -0
- package/dist/cpsat/rules/max-days-week.js.map +1 -0
- package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-day.js +15 -2
- package/dist/cpsat/rules/max-hours-day.js.map +1 -1
- package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/max-hours-week.js +16 -2
- package/dist/cpsat/rules/max-hours-week.js.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
- package/dist/cpsat/rules/max-shifts-day.js +15 -2
- package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
- package/dist/cpsat/rules/min-consecutive-days.js +15 -2
- package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
- package/dist/cpsat/rules/min-days-week.d.ts +34 -0
- package/dist/cpsat/rules/min-days-week.d.ts.map +1 -0
- package/dist/cpsat/rules/min-days-week.js +84 -0
- package/dist/cpsat/rules/min-days-week.js.map +1 -0
- package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-day.js +15 -2
- package/dist/cpsat/rules/min-hours-day.js.map +1 -1
- package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
- package/dist/cpsat/rules/min-hours-week.js +16 -2
- package/dist/cpsat/rules/min-hours-week.js.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
- package/dist/cpsat/rules/min-rest-between-shifts.js +72 -2
- package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
- package/dist/cpsat/rules/minimize-cost.d.ts.map +1 -1
- package/dist/cpsat/rules/minimize-cost.js +2 -23
- package/dist/cpsat/rules/minimize-cost.js.map +1 -1
- package/dist/cpsat/rules/must-assign.d.ts +49 -0
- package/dist/cpsat/rules/must-assign.d.ts.map +1 -0
- package/dist/cpsat/rules/must-assign.js +86 -0
- package/dist/cpsat/rules/must-assign.js.map +1 -0
- package/dist/cpsat/rules/overtime-daily-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-daily-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-daily-surcharge.js +1 -5
- package/dist/cpsat/rules/overtime-daily-surcharge.js.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts +5 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-tiered-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-tiered-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-multiplier.js +1 -12
- package/dist/cpsat/rules/overtime-weekly-multiplier.js.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/overtime-weekly-surcharge.js +1 -5
- package/dist/cpsat/rules/overtime-weekly-surcharge.js.map +1 -1
- package/dist/cpsat/rules/registry.d.ts +28 -2
- package/dist/cpsat/rules/registry.d.ts.map +1 -1
- package/dist/cpsat/rules/registry.js +4 -1
- package/dist/cpsat/rules/registry.js.map +1 -1
- package/dist/cpsat/rules/resolver.js +2 -2
- package/dist/cpsat/rules/resolver.js.map +1 -1
- package/dist/cpsat/rules/rules.types.d.ts +3 -0
- package/dist/cpsat/rules/rules.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.d.ts +18 -1
- package/dist/cpsat/rules/scope.types.d.ts.map +1 -1
- package/dist/cpsat/rules/scope.types.js +59 -16
- package/dist/cpsat/rules/scope.types.js.map +1 -1
- package/dist/cpsat/rules/time-cost-surcharge.d.ts.map +1 -1
- package/dist/cpsat/rules/time-cost-surcharge.js +2 -1
- package/dist/cpsat/rules/time-cost-surcharge.js.map +1 -1
- package/dist/cpsat/rules/time-off.d.ts.map +1 -1
- package/dist/cpsat/rules/time-off.js +6 -3
- package/dist/cpsat/rules/time-off.js.map +1 -1
- package/dist/cpsat/semantic-time.d.ts +44 -42
- package/dist/cpsat/semantic-time.d.ts.map +1 -1
- package/dist/cpsat/semantic-time.js +64 -46
- package/dist/cpsat/semantic-time.js.map +1 -1
- package/dist/cpsat/types.d.ts +37 -27
- package/dist/cpsat/types.d.ts.map +1 -1
- package/dist/cpsat/utils.d.ts.map +1 -1
- package/dist/cpsat/utils.js +7 -12
- package/dist/cpsat/utils.js.map +1 -1
- package/dist/cpsat/validation-reporter.d.ts +10 -7
- package/dist/cpsat/validation-reporter.d.ts.map +1 -1
- package/dist/cpsat/validation-reporter.js +44 -72
- package/dist/cpsat/validation-reporter.js.map +1 -1
- package/dist/cpsat/validation.types.d.ts +54 -44
- package/dist/cpsat/validation.types.d.ts.map +1 -1
- package/dist/cpsat/validation.types.js +15 -10
- package/dist/cpsat/validation.types.js.map +1 -1
- package/dist/datetime.utils.d.ts +3 -203
- package/dist/datetime.utils.d.ts.map +1 -1
- package/dist/datetime.utils.js +1 -288
- package/dist/datetime.utils.js.map +1 -1
- package/dist/index.d.ts +14 -83
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -83
- package/dist/index.js.map +1 -1
- package/dist/schedule/cost.d.ts +204 -0
- package/dist/schedule/cost.d.ts.map +1 -0
- package/dist/schedule/cost.js +187 -0
- package/dist/schedule/cost.js.map +1 -0
- package/dist/schedule/coverage.d.ts +85 -0
- package/dist/schedule/coverage.d.ts.map +1 -0
- package/dist/schedule/coverage.js +33 -0
- package/dist/schedule/coverage.js.map +1 -0
- package/dist/schedule/definition.d.ts +227 -0
- package/dist/schedule/definition.d.ts.map +1 -0
- package/dist/schedule/definition.js +659 -0
- package/dist/schedule/definition.js.map +1 -0
- package/dist/schedule/index.d.ts +67 -0
- package/dist/schedule/index.d.ts.map +1 -0
- package/dist/schedule/index.js +69 -0
- package/dist/schedule/index.js.map +1 -0
- package/dist/schedule/rules.d.ts +353 -0
- package/dist/schedule/rules.d.ts.map +1 -0
- package/dist/schedule/rules.js +352 -0
- package/dist/schedule/rules.js.map +1 -0
- package/dist/schedule/shift-patterns.d.ts +34 -0
- package/dist/schedule/shift-patterns.d.ts.map +1 -0
- package/dist/schedule/shift-patterns.js +41 -0
- package/dist/schedule/shift-patterns.js.map +1 -0
- package/dist/schedule/time-periods.d.ts +69 -0
- package/dist/schedule/time-periods.d.ts.map +1 -0
- package/dist/schedule/time-periods.js +91 -0
- package/dist/schedule/time-periods.js.map +1 -0
- package/dist/types.d.ts +14 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -9
- package/solver/src/solver/app.py +1 -1
- package/solver/src/solver/solver.py +7 -4
- package/src/client.ts +6 -8
- package/src/client.types.ts +9 -0
- package/src/cpsat/model-builder.ts +44 -35
- package/src/cpsat/response.ts +13 -1
- package/src/cpsat/rules/cost-utils.ts +25 -0
- package/src/cpsat/rules/day-cost-multiplier.ts +3 -14
- package/src/cpsat/rules/day-cost-surcharge.ts +3 -8
- package/src/cpsat/rules/index.ts +3 -0
- package/src/cpsat/rules/max-consecutive-days.ts +17 -0
- package/src/cpsat/rules/max-days-week.ts +143 -0
- package/src/cpsat/rules/max-hours-day.ts +21 -1
- package/src/cpsat/rules/max-hours-week.ts +22 -1
- package/src/cpsat/rules/max-shifts-day.ts +21 -1
- package/src/cpsat/rules/min-consecutive-days.ts +16 -1
- package/src/cpsat/rules/min-days-week.ts +120 -0
- package/src/cpsat/rules/min-hours-day.ts +16 -1
- package/src/cpsat/rules/min-hours-week.ts +17 -1
- package/src/cpsat/rules/min-rest-between-shifts.ts +92 -2
- package/src/cpsat/rules/minimize-cost.ts +2 -29
- package/src/cpsat/rules/must-assign.ts +108 -0
- package/src/cpsat/rules/overtime-daily-multiplier.ts +1 -12
- package/src/cpsat/rules/overtime-daily-surcharge.ts +1 -6
- package/src/cpsat/rules/overtime-tiered-multiplier.ts +6 -13
- package/src/cpsat/rules/overtime-weekly-multiplier.ts +1 -12
- package/src/cpsat/rules/overtime-weekly-surcharge.ts +1 -6
- package/src/cpsat/rules/registry.ts +8 -2
- package/src/cpsat/rules/resolver.ts +2 -2
- package/src/cpsat/rules/rules.types.ts +3 -0
- package/src/cpsat/rules/scope.types.ts +73 -20
- package/src/cpsat/rules/time-cost-surcharge.ts +2 -1
- package/src/cpsat/rules/time-off.ts +6 -2
- package/src/cpsat/semantic-time.ts +115 -91
- package/src/cpsat/types.ts +37 -27
- package/src/cpsat/utils.ts +8 -12
- package/src/cpsat/validation-reporter.ts +51 -82
- package/src/cpsat/validation.types.ts +72 -47
- package/src/datetime.utils.ts +3 -334
- package/src/index.ts +35 -107
- package/src/schedule/cost.ts +242 -0
- package/src/schedule/coverage.ts +135 -0
- package/src/schedule/definition.ts +958 -0
- package/src/schedule/index.ts +112 -0
- package/src/schedule/rules.ts +529 -0
- package/src/schedule/shift-patterns.ts +46 -0
- package/src/schedule/time-periods.ts +110 -0
- package/src/types.ts +14 -88
- package/dist/errors.d.ts +0 -12
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -17
- package/dist/errors.js.map +0 -1
- package/dist/llms.d.ts +0 -2
- package/dist/llms.d.ts.map +0 -1
- package/dist/llms.js +0 -3
- package/dist/llms.js.map +0 -1
- package/dist/schedule.d.ts +0 -724
- package/dist/schedule.d.ts.map +0 -1
- package/dist/schedule.js +0 -899
- package/dist/schedule.js.map +0 -1
- package/dist/validation.d.ts +0 -105
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -130
- package/dist/validation.js.map +0 -1
- package/llms.txt +0 -925
- package/src/errors.ts +0 -17
- package/src/llms.ts +0 -3
- package/src/schedule.ts +0 -1419
- package/src/validation.ts +0 -188
|
@@ -2,7 +2,7 @@ import * as z from "zod";
|
|
|
2
2
|
import type { CompilationRule, CostContribution } from "../model-builder.js";
|
|
3
3
|
import type { ShiftPattern, SchedulingMember } from "../types.js";
|
|
4
4
|
import type { ShiftAssignment } from "../response.js";
|
|
5
|
-
import {
|
|
5
|
+
import { COST_CATEGORY } from "../cost.js";
|
|
6
6
|
import {
|
|
7
7
|
entityScope,
|
|
8
8
|
timeScope,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveMembersFromScope,
|
|
12
12
|
resolveActiveDaysFromScope,
|
|
13
13
|
} from "./scope.types.js";
|
|
14
|
+
import { patternDurationMinutes } from "./cost-utils.js";
|
|
14
15
|
|
|
15
16
|
const DayCostSurchargeSchema = z
|
|
16
17
|
.object({
|
|
@@ -22,12 +23,6 @@ const DayCostSurchargeSchema = z
|
|
|
22
23
|
/** Configuration for {@link createDayCostSurchargeRule}. */
|
|
23
24
|
export type DayCostSurchargeConfig = z.infer<typeof DayCostSurchargeSchema>;
|
|
24
25
|
|
|
25
|
-
function patternDurationMinutes(pattern: ShiftPattern): number {
|
|
26
|
-
const start = timeOfDayToMinutes(pattern.startTime);
|
|
27
|
-
const end = normalizeEndMinutes(start, timeOfDayToMinutes(pattern.endTime));
|
|
28
|
-
return end - start;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
26
|
/**
|
|
32
27
|
* Creates a day-based flat surcharge rule.
|
|
33
28
|
*
|
|
@@ -95,7 +90,7 @@ export function createDayCostSurchargeRule(config: DayCostSurchargeConfig): Comp
|
|
|
95
90
|
entries.push({
|
|
96
91
|
memberId: a.memberId,
|
|
97
92
|
day: a.day,
|
|
98
|
-
category:
|
|
93
|
+
category: COST_CATEGORY.PREMIUM,
|
|
99
94
|
amount,
|
|
100
95
|
});
|
|
101
96
|
}
|
package/src/cpsat/rules/index.ts
CHANGED
|
@@ -2,13 +2,16 @@ export { createAssignTogetherRule } from "./assign-together.js";
|
|
|
2
2
|
export { createAssignmentPriorityRule } from "./assignment-priority.js";
|
|
3
3
|
export { createLocationPreferenceRule } from "./location-preference.js";
|
|
4
4
|
export { createMaxConsecutiveDaysRule } from "./max-consecutive-days.js";
|
|
5
|
+
export { createMaxDaysWeekRule } from "./max-days-week.js";
|
|
5
6
|
export { createMaxHoursDayRule } from "./max-hours-day.js";
|
|
6
7
|
export { createMaxHoursWeekRule } from "./max-hours-week.js";
|
|
7
8
|
export { createMaxShiftsDayRule } from "./max-shifts-day.js";
|
|
8
9
|
export { createMinConsecutiveDaysRule } from "./min-consecutive-days.js";
|
|
10
|
+
export { createMinDaysWeekRule } from "./min-days-week.js";
|
|
9
11
|
export { createMinHoursDayRule } from "./min-hours-day.js";
|
|
10
12
|
export { createMinHoursWeekRule } from "./min-hours-week.js";
|
|
11
13
|
export { createMinRestBetweenShiftsRule } from "./min-rest-between-shifts.js";
|
|
14
|
+
export { createMustAssignRule } from "./must-assign.js";
|
|
12
15
|
export { createTimeOffRule } from "./time-off.js";
|
|
13
16
|
export { createMinimizeCostRule } from "./minimize-cost.js";
|
|
14
17
|
export { createDayCostMultiplierRule } from "./day-cost-multiplier.js";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
entityScope,
|
|
7
7
|
parseEntityScope,
|
|
8
8
|
resolveMembersFromScope,
|
|
9
|
+
ruleGroup,
|
|
9
10
|
} from "./scope.types.js";
|
|
10
11
|
|
|
11
12
|
const MaxConsecutiveDaysSchema = z
|
|
@@ -39,6 +40,7 @@ export function createMaxConsecutiveDaysRule(config: MaxConsecutiveDaysConfig):
|
|
|
39
40
|
const scope = parseEntityScope(parsed);
|
|
40
41
|
const { days, priority } = parsed;
|
|
41
42
|
const windowSize = days + 1;
|
|
43
|
+
const group = ruleGroup(`max-consecutive-days:${days}`, `Max ${days} consecutive days`, scope);
|
|
42
44
|
|
|
43
45
|
return {
|
|
44
46
|
compile(b) {
|
|
@@ -81,6 +83,9 @@ export function createMaxConsecutiveDaysRule(config: MaxConsecutiveDaysConfig):
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
const windowStart = windowDays[0]!;
|
|
87
|
+
const constraintId = `max-consecutive-days:${emp.id}:${windowStart}`;
|
|
88
|
+
|
|
84
89
|
if (priority === "MANDATORY") {
|
|
85
90
|
b.addLinear(
|
|
86
91
|
worksDayVars.map((v) => ({ var: v, coeff: 1 })),
|
|
@@ -93,7 +98,19 @@ export function createMaxConsecutiveDaysRule(config: MaxConsecutiveDaysConfig):
|
|
|
93
98
|
"<=",
|
|
94
99
|
days,
|
|
95
100
|
priorityToPenalty(priority),
|
|
101
|
+
constraintId,
|
|
96
102
|
);
|
|
103
|
+
b.reporter.trackConstraint({
|
|
104
|
+
id: constraintId,
|
|
105
|
+
type: "rule",
|
|
106
|
+
rule: "max-consecutive-days",
|
|
107
|
+
description: `${emp.id} max ${days} consecutive days from ${windowStart}`,
|
|
108
|
+
targetValue: days,
|
|
109
|
+
comparator: "<=",
|
|
110
|
+
day: windowStart,
|
|
111
|
+
context: { memberIds: [emp.id], days: windowDays },
|
|
112
|
+
group,
|
|
113
|
+
});
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { DayOfWeekSchema } from "../../types.js";
|
|
3
|
+
import type { CompilationRule } from "../model-builder.js";
|
|
4
|
+
import type { Term } from "../types.js";
|
|
5
|
+
import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
|
|
6
|
+
import {
|
|
7
|
+
PrioritySchema,
|
|
8
|
+
entityScope,
|
|
9
|
+
timeScope,
|
|
10
|
+
parseEntityScope,
|
|
11
|
+
parseTimeScope,
|
|
12
|
+
resolveMembersFromScope,
|
|
13
|
+
resolveActiveDaysFromScope,
|
|
14
|
+
ruleGroup,
|
|
15
|
+
} from "./scope.types.js";
|
|
16
|
+
|
|
17
|
+
const MaxDaysWeekBase = z.object({
|
|
18
|
+
days: z.number().int().min(0),
|
|
19
|
+
priority: PrioritySchema,
|
|
20
|
+
weekStartsOn: DayOfWeekSchema.optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const MaxDaysWeekSchema = MaxDaysWeekBase.and(entityScope(["members", "roles", "skills"])).and(
|
|
24
|
+
timeScope(["dateRange", "specificDates", "dayOfWeek", "recurring"]),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for {@link createMaxDaysWeekRule}.
|
|
29
|
+
*
|
|
30
|
+
* - `days` (required): maximum number of days allowed per scheduling week
|
|
31
|
+
* - `priority` (required): how strictly the solver enforces this rule
|
|
32
|
+
* - `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}
|
|
33
|
+
*
|
|
34
|
+
* Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
|
|
35
|
+
* Time scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`
|
|
36
|
+
*/
|
|
37
|
+
export type MaxDaysWeekConfig = z.infer<typeof MaxDaysWeekSchema>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Caps total number of days a person can work within each scheduling week.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Creates a binary "works on day" variable for each member and day, then
|
|
44
|
+
* constrains the weekly sum. This counts distinct days, regardless of how
|
|
45
|
+
* many shifts are assigned on a single day.
|
|
46
|
+
*
|
|
47
|
+
* @param config - See {@link MaxDaysWeekConfig}
|
|
48
|
+
* @example Limit everyone to 5 days per week
|
|
49
|
+
* ```ts
|
|
50
|
+
* createMaxDaysWeekRule({ days: 5, priority: "MANDATORY" });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example Part-time staff limited to 3 days
|
|
54
|
+
* ```ts
|
|
55
|
+
* createMaxDaysWeekRule({
|
|
56
|
+
* roleIds: ["part-time"],
|
|
57
|
+
* days: 3,
|
|
58
|
+
* priority: "HIGH",
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function createMaxDaysWeekRule(config: MaxDaysWeekConfig): CompilationRule {
|
|
63
|
+
const parsed = MaxDaysWeekSchema.parse(config);
|
|
64
|
+
const entityScopeValue = parseEntityScope(parsed);
|
|
65
|
+
const timeScopeValue = parseTimeScope(parsed);
|
|
66
|
+
const { days, priority, weekStartsOn } = parsed;
|
|
67
|
+
const group = ruleGroup(
|
|
68
|
+
`max-days-week:${days}`,
|
|
69
|
+
`Max ${days}d per week`,
|
|
70
|
+
entityScopeValue,
|
|
71
|
+
timeScopeValue,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
compile(b) {
|
|
76
|
+
const targetMembers = resolveMembersFromScope(entityScopeValue, b.members);
|
|
77
|
+
const activeDays = resolveActiveDaysFromScope(timeScopeValue, b.days);
|
|
78
|
+
|
|
79
|
+
if (targetMembers.length === 0 || activeDays.length === 0) return;
|
|
80
|
+
|
|
81
|
+
const weeks = splitIntoWeeks(activeDays, weekStartsOn ?? b.weekStartsOn);
|
|
82
|
+
|
|
83
|
+
for (const emp of targetMembers) {
|
|
84
|
+
for (const weekDays of weeks) {
|
|
85
|
+
const weekWorkVars: string[] = [];
|
|
86
|
+
|
|
87
|
+
for (const day of weekDays) {
|
|
88
|
+
const dayAssignments = b.shiftPatterns
|
|
89
|
+
.filter((p) => b.canAssign(emp, p) && b.patternAvailableOnDay(p, day))
|
|
90
|
+
.map((p) => b.assignment(emp.id, p.id, day));
|
|
91
|
+
|
|
92
|
+
if (dayAssignments.length === 0) continue;
|
|
93
|
+
|
|
94
|
+
const worksVar = b.boolVar(`works_day_${emp.id}_${day}`);
|
|
95
|
+
weekWorkVars.push(worksVar);
|
|
96
|
+
|
|
97
|
+
// worksVar >= each assignment (if any assignment is 1, worksVar must be 1)
|
|
98
|
+
for (const assignVar of dayAssignments) {
|
|
99
|
+
b.addLinear(
|
|
100
|
+
[
|
|
101
|
+
{ var: worksVar, coeff: 1 },
|
|
102
|
+
{ var: assignVar, coeff: -1 },
|
|
103
|
+
],
|
|
104
|
+
">=",
|
|
105
|
+
0,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// worksVar <= sum(assignments) (if no assignment is 1, worksVar must be 0)
|
|
110
|
+
b.addLinear(
|
|
111
|
+
[{ var: worksVar, coeff: 1 }, ...dayAssignments.map((v) => ({ var: v, coeff: -1 }))],
|
|
112
|
+
"<=",
|
|
113
|
+
0,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (weekWorkVars.length === 0) continue;
|
|
118
|
+
|
|
119
|
+
const weekLabel = weekDays[0]!;
|
|
120
|
+
const constraintId = `max-days-week:${emp.id}:${weekLabel}`;
|
|
121
|
+
const terms: Term[] = weekWorkVars.map((v) => ({ var: v, coeff: 1 }));
|
|
122
|
+
|
|
123
|
+
if (priority === "MANDATORY") {
|
|
124
|
+
b.addLinear(terms, "<=", days);
|
|
125
|
+
} else {
|
|
126
|
+
b.addSoftLinear(terms, "<=", days, priorityToPenalty(priority), constraintId);
|
|
127
|
+
b.reporter.trackConstraint({
|
|
128
|
+
id: constraintId,
|
|
129
|
+
type: "rule",
|
|
130
|
+
rule: "max-days-week",
|
|
131
|
+
description: `${emp.id} max ${days}d in week starting ${weekLabel}`,
|
|
132
|
+
targetValue: days,
|
|
133
|
+
comparator: "<=",
|
|
134
|
+
day: weekLabel,
|
|
135
|
+
context: { memberIds: [emp.id], days: weekDays },
|
|
136
|
+
group,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parseTimeScope,
|
|
11
11
|
resolveMembersFromScope,
|
|
12
12
|
resolveActiveDaysFromScope,
|
|
13
|
+
ruleGroup,
|
|
13
14
|
} from "./scope.types.js";
|
|
14
15
|
|
|
15
16
|
const MaxHoursDaySchema = z
|
|
@@ -59,6 +60,12 @@ export function createMaxHoursDayRule(config: MaxHoursDayConfig): CompilationRul
|
|
|
59
60
|
const timeScopeValue = parseTimeScope(parsed);
|
|
60
61
|
const { hours, priority } = parsed;
|
|
61
62
|
const maxMinutes = hours * 60;
|
|
63
|
+
const group = ruleGroup(
|
|
64
|
+
`max-hours-day:${hours}`,
|
|
65
|
+
`Max ${hours}h per day`,
|
|
66
|
+
entityScopeValue,
|
|
67
|
+
timeScopeValue,
|
|
68
|
+
);
|
|
62
69
|
|
|
63
70
|
return {
|
|
64
71
|
compile(b) {
|
|
@@ -81,10 +88,23 @@ export function createMaxHoursDayRule(config: MaxHoursDayConfig): CompilationRul
|
|
|
81
88
|
|
|
82
89
|
if (terms.length === 0) continue;
|
|
83
90
|
|
|
91
|
+
const constraintId = `max-hours-day:${emp.id}:${day}`;
|
|
92
|
+
|
|
84
93
|
if (priority === "MANDATORY") {
|
|
85
94
|
b.addLinear(terms, "<=", maxMinutes);
|
|
86
95
|
} else {
|
|
87
|
-
b.addSoftLinear(terms, "<=", maxMinutes, priorityToPenalty(priority));
|
|
96
|
+
b.addSoftLinear(terms, "<=", maxMinutes, priorityToPenalty(priority), constraintId);
|
|
97
|
+
b.reporter.trackConstraint({
|
|
98
|
+
id: constraintId,
|
|
99
|
+
type: "rule",
|
|
100
|
+
rule: "max-hours-day",
|
|
101
|
+
description: `${emp.id} max ${hours}h on ${day}`,
|
|
102
|
+
targetValue: maxMinutes,
|
|
103
|
+
comparator: "<=",
|
|
104
|
+
day,
|
|
105
|
+
context: { memberIds: [emp.id], days: [day] },
|
|
106
|
+
group,
|
|
107
|
+
});
|
|
88
108
|
}
|
|
89
109
|
}
|
|
90
110
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
parseTimeScope,
|
|
12
12
|
resolveMembersFromScope,
|
|
13
13
|
resolveActiveDaysFromScope,
|
|
14
|
+
ruleGroup,
|
|
14
15
|
} from "./scope.types.js";
|
|
15
16
|
|
|
16
17
|
const MaxHoursWeekBase = z.object({
|
|
@@ -62,6 +63,12 @@ export function createMaxHoursWeekRule(config: MaxHoursWeekConfig): CompilationR
|
|
|
62
63
|
const timeScopeValue = parseTimeScope(parsed);
|
|
63
64
|
const { hours, priority, weekStartsOn } = parsed;
|
|
64
65
|
const maxMinutes = hours * 60;
|
|
66
|
+
const group = ruleGroup(
|
|
67
|
+
`max-hours-week:${hours}`,
|
|
68
|
+
`Max ${hours}h per week`,
|
|
69
|
+
entityScopeValue,
|
|
70
|
+
timeScopeValue,
|
|
71
|
+
);
|
|
65
72
|
|
|
66
73
|
return {
|
|
67
74
|
compile(b) {
|
|
@@ -88,10 +95,24 @@ export function createMaxHoursWeekRule(config: MaxHoursWeekConfig): CompilationR
|
|
|
88
95
|
|
|
89
96
|
if (terms.length === 0) continue;
|
|
90
97
|
|
|
98
|
+
const weekLabel = weekDays[0]!;
|
|
99
|
+
const constraintId = `max-hours-week:${emp.id}:${weekLabel}`;
|
|
100
|
+
|
|
91
101
|
if (priority === "MANDATORY") {
|
|
92
102
|
b.addLinear(terms, "<=", maxMinutes);
|
|
93
103
|
} else {
|
|
94
|
-
b.addSoftLinear(terms, "<=", maxMinutes, priorityToPenalty(priority));
|
|
104
|
+
b.addSoftLinear(terms, "<=", maxMinutes, priorityToPenalty(priority), constraintId);
|
|
105
|
+
b.reporter.trackConstraint({
|
|
106
|
+
id: constraintId,
|
|
107
|
+
type: "rule",
|
|
108
|
+
rule: "max-hours-week",
|
|
109
|
+
description: `${emp.id} max ${hours}h in week starting ${weekLabel}`,
|
|
110
|
+
targetValue: maxMinutes,
|
|
111
|
+
comparator: "<=",
|
|
112
|
+
day: weekLabel,
|
|
113
|
+
context: { memberIds: [emp.id], days: weekDays },
|
|
114
|
+
group,
|
|
115
|
+
});
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
parseTimeScope,
|
|
11
11
|
resolveMembersFromScope,
|
|
12
12
|
resolveActiveDaysFromScope,
|
|
13
|
+
ruleGroup,
|
|
13
14
|
} from "./scope.types.js";
|
|
14
15
|
|
|
15
16
|
const MaxShiftsDaySchema = z
|
|
@@ -61,6 +62,12 @@ export function createMaxShiftsDayRule(config: MaxShiftsDayConfig): CompilationR
|
|
|
61
62
|
const entityScopeValue = parseEntityScope(parsed);
|
|
62
63
|
const timeScopeValue = parseTimeScope(parsed);
|
|
63
64
|
const { shifts, priority } = parsed;
|
|
65
|
+
const group = ruleGroup(
|
|
66
|
+
`max-shifts-day:${shifts}`,
|
|
67
|
+
`Max ${shifts} shift${shifts === 1 ? "" : "s"} per day`,
|
|
68
|
+
entityScopeValue,
|
|
69
|
+
timeScopeValue,
|
|
70
|
+
);
|
|
64
71
|
|
|
65
72
|
return {
|
|
66
73
|
compile(b) {
|
|
@@ -83,10 +90,23 @@ export function createMaxShiftsDayRule(config: MaxShiftsDayConfig): CompilationR
|
|
|
83
90
|
|
|
84
91
|
if (terms.length === 0) continue;
|
|
85
92
|
|
|
93
|
+
const constraintId = `max-shifts-day:${emp.id}:${day}`;
|
|
94
|
+
|
|
86
95
|
if (priority === "MANDATORY") {
|
|
87
96
|
b.addLinear(terms, "<=", shifts);
|
|
88
97
|
} else {
|
|
89
|
-
b.addSoftLinear(terms, "<=", shifts, priorityToPenalty(priority));
|
|
98
|
+
b.addSoftLinear(terms, "<=", shifts, priorityToPenalty(priority), constraintId);
|
|
99
|
+
b.reporter.trackConstraint({
|
|
100
|
+
id: constraintId,
|
|
101
|
+
type: "rule",
|
|
102
|
+
rule: "max-shifts-day",
|
|
103
|
+
description: `${emp.id} max ${shifts} shift${shifts === 1 ? "" : "s"} on ${day}`,
|
|
104
|
+
targetValue: shifts,
|
|
105
|
+
comparator: "<=",
|
|
106
|
+
day,
|
|
107
|
+
context: { memberIds: [emp.id], days: [day] },
|
|
108
|
+
group,
|
|
109
|
+
});
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
112
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
entityScope,
|
|
7
7
|
parseEntityScope,
|
|
8
8
|
resolveMembersFromScope,
|
|
9
|
+
ruleGroup,
|
|
9
10
|
} from "./scope.types.js";
|
|
10
11
|
|
|
11
12
|
const MinConsecutiveDaysSchema = z
|
|
@@ -39,6 +40,7 @@ export function createMinConsecutiveDaysRule(config: MinConsecutiveDaysConfig):
|
|
|
39
40
|
const parsed = MinConsecutiveDaysSchema.parse(config);
|
|
40
41
|
const scope = parseEntityScope(parsed);
|
|
41
42
|
const { days, priority } = parsed;
|
|
43
|
+
const group = ruleGroup(`min-consecutive-days:${days}`, `Min ${days} consecutive days`, scope);
|
|
42
44
|
|
|
43
45
|
return {
|
|
44
46
|
compile(b) {
|
|
@@ -132,10 +134,23 @@ export function createMinConsecutiveDaysRule(config: MinConsecutiveDaysConfig):
|
|
|
132
134
|
{ var: startVar, coeff: -days },
|
|
133
135
|
];
|
|
134
136
|
|
|
137
|
+
const constraintId = `min-consecutive-days:${emp.id}:${dayLabel}`;
|
|
138
|
+
|
|
135
139
|
if (priority === "MANDATORY") {
|
|
136
140
|
b.addLinear(terms, ">=", 0);
|
|
137
141
|
} else {
|
|
138
|
-
b.addSoftLinear(terms, ">=", 0, priorityToPenalty(priority));
|
|
142
|
+
b.addSoftLinear(terms, ">=", 0, priorityToPenalty(priority), constraintId);
|
|
143
|
+
b.reporter.trackConstraint({
|
|
144
|
+
id: constraintId,
|
|
145
|
+
type: "rule",
|
|
146
|
+
rule: "min-consecutive-days",
|
|
147
|
+
description: `${emp.id} min ${days} consecutive days from ${dayLabel}`,
|
|
148
|
+
targetValue: 0,
|
|
149
|
+
comparator: ">=",
|
|
150
|
+
day: dayLabel,
|
|
151
|
+
context: { memberIds: [emp.id], days: b.days.slice(i, i + days) },
|
|
152
|
+
group,
|
|
153
|
+
});
|
|
139
154
|
}
|
|
140
155
|
}
|
|
141
156
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { DayOfWeekSchema } from "../../types.js";
|
|
3
|
+
import type { CompilationRule } from "../model-builder.js";
|
|
4
|
+
import type { Term } from "../types.js";
|
|
5
|
+
import { priorityToPenalty, splitIntoWeeks } from "../utils.js";
|
|
6
|
+
import {
|
|
7
|
+
PrioritySchema,
|
|
8
|
+
entityScope,
|
|
9
|
+
parseEntityScope,
|
|
10
|
+
resolveMembersFromScope,
|
|
11
|
+
ruleGroup,
|
|
12
|
+
} from "./scope.types.js";
|
|
13
|
+
|
|
14
|
+
const MinDaysWeekBase = z.object({
|
|
15
|
+
days: z.number().int().min(0),
|
|
16
|
+
priority: PrioritySchema,
|
|
17
|
+
weekStartsOn: DayOfWeekSchema.optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const MinDaysWeekSchema = MinDaysWeekBase.and(entityScope(["members", "roles", "skills"]));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for {@link createMinDaysWeekRule}.
|
|
24
|
+
*
|
|
25
|
+
* - `days` (required): minimum number of days required per scheduling week
|
|
26
|
+
* - `priority` (required): how strictly the solver enforces this rule
|
|
27
|
+
* - `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}
|
|
28
|
+
*
|
|
29
|
+
* Entity scoping (at most one): `memberIds`, `roleIds`, `skillIds`
|
|
30
|
+
*/
|
|
31
|
+
export type MinDaysWeekConfig = z.infer<typeof MinDaysWeekSchema>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enforces a minimum number of days a person must work per scheduling week.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* Creates a binary "works on day" variable for each member and day, then
|
|
38
|
+
* constrains the weekly sum. This counts distinct days, regardless of how
|
|
39
|
+
* many shifts are assigned on a single day.
|
|
40
|
+
*
|
|
41
|
+
* @param config - See {@link MinDaysWeekConfig}
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* createMinDaysWeekRule({ days: 3, priority: "HIGH" });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function createMinDaysWeekRule(config: MinDaysWeekConfig): CompilationRule {
|
|
48
|
+
const parsed = MinDaysWeekSchema.parse(config);
|
|
49
|
+
const scope = parseEntityScope(parsed);
|
|
50
|
+
const { days, priority, weekStartsOn } = parsed;
|
|
51
|
+
const group = ruleGroup(`min-days-week:${days}`, `Min ${days}d per week`, scope);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
compile(b) {
|
|
55
|
+
if (days <= 0) return;
|
|
56
|
+
|
|
57
|
+
const members = resolveMembersFromScope(scope, b.members);
|
|
58
|
+
const weeks = splitIntoWeeks(b.days, weekStartsOn ?? b.weekStartsOn);
|
|
59
|
+
|
|
60
|
+
for (const emp of members) {
|
|
61
|
+
for (const weekDays of weeks) {
|
|
62
|
+
const weekWorkVars: string[] = [];
|
|
63
|
+
|
|
64
|
+
for (const day of weekDays) {
|
|
65
|
+
const dayAssignments = b.shiftPatterns
|
|
66
|
+
.filter((p) => b.canAssign(emp, p) && b.patternAvailableOnDay(p, day))
|
|
67
|
+
.map((p) => b.assignment(emp.id, p.id, day));
|
|
68
|
+
|
|
69
|
+
if (dayAssignments.length === 0) continue;
|
|
70
|
+
|
|
71
|
+
const worksVar = b.boolVar(`works_day_${emp.id}_${day}`);
|
|
72
|
+
weekWorkVars.push(worksVar);
|
|
73
|
+
|
|
74
|
+
// worksVar >= each assignment (if any assignment is 1, worksVar must be 1)
|
|
75
|
+
for (const assignVar of dayAssignments) {
|
|
76
|
+
b.addLinear(
|
|
77
|
+
[
|
|
78
|
+
{ var: worksVar, coeff: 1 },
|
|
79
|
+
{ var: assignVar, coeff: -1 },
|
|
80
|
+
],
|
|
81
|
+
">=",
|
|
82
|
+
0,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// worksVar <= sum(assignments) (if no assignment is 1, worksVar must be 0)
|
|
87
|
+
b.addLinear(
|
|
88
|
+
[{ var: worksVar, coeff: 1 }, ...dayAssignments.map((v) => ({ var: v, coeff: -1 }))],
|
|
89
|
+
"<=",
|
|
90
|
+
0,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (weekWorkVars.length === 0) continue;
|
|
95
|
+
|
|
96
|
+
const weekLabel = weekDays[0]!;
|
|
97
|
+
const constraintId = `min-days-week:${emp.id}:${weekLabel}`;
|
|
98
|
+
const terms: Term[] = weekWorkVars.map((v) => ({ var: v, coeff: 1 }));
|
|
99
|
+
|
|
100
|
+
if (priority === "MANDATORY") {
|
|
101
|
+
b.addLinear(terms, ">=", days);
|
|
102
|
+
} else {
|
|
103
|
+
b.addSoftLinear(terms, ">=", days, priorityToPenalty(priority), constraintId);
|
|
104
|
+
b.reporter.trackConstraint({
|
|
105
|
+
id: constraintId,
|
|
106
|
+
type: "rule",
|
|
107
|
+
rule: "min-days-week",
|
|
108
|
+
description: `${emp.id} min ${days}d in week starting ${weekLabel}`,
|
|
109
|
+
targetValue: days,
|
|
110
|
+
comparator: ">=",
|
|
111
|
+
day: weekLabel,
|
|
112
|
+
context: { memberIds: [emp.id], days: weekDays },
|
|
113
|
+
group,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
entityScope,
|
|
8
8
|
parseEntityScope,
|
|
9
9
|
resolveMembersFromScope,
|
|
10
|
+
ruleGroup,
|
|
10
11
|
} from "./scope.types.js";
|
|
11
12
|
|
|
12
13
|
const MinHoursDaySchema = z
|
|
@@ -40,6 +41,7 @@ export function createMinHoursDayRule(config: MinHoursDayConfig): CompilationRul
|
|
|
40
41
|
const scope = parseEntityScope(parsed);
|
|
41
42
|
const { hours, priority } = parsed;
|
|
42
43
|
const minMinutes = hours * 60;
|
|
44
|
+
const group = ruleGroup(`min-hours-day:${hours}`, `Min ${hours}h per day`, scope);
|
|
43
45
|
|
|
44
46
|
return {
|
|
45
47
|
compile(b) {
|
|
@@ -61,10 +63,23 @@ export function createMinHoursDayRule(config: MinHoursDayConfig): CompilationRul
|
|
|
61
63
|
|
|
62
64
|
if (terms.length === 0) continue;
|
|
63
65
|
|
|
66
|
+
const constraintId = `min-hours-day:${emp.id}:${day}`;
|
|
67
|
+
|
|
64
68
|
if (priority === "MANDATORY") {
|
|
65
69
|
b.addLinear(terms, ">=", minMinutes);
|
|
66
70
|
} else {
|
|
67
|
-
b.addSoftLinear(terms, ">=", minMinutes, priorityToPenalty(priority));
|
|
71
|
+
b.addSoftLinear(terms, ">=", minMinutes, priorityToPenalty(priority), constraintId);
|
|
72
|
+
b.reporter.trackConstraint({
|
|
73
|
+
id: constraintId,
|
|
74
|
+
type: "rule",
|
|
75
|
+
rule: "min-hours-day",
|
|
76
|
+
description: `${emp.id} min ${hours}h on ${day}`,
|
|
77
|
+
targetValue: minMinutes,
|
|
78
|
+
comparator: ">=",
|
|
79
|
+
day,
|
|
80
|
+
context: { memberIds: [emp.id], days: [day] },
|
|
81
|
+
group,
|
|
82
|
+
});
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
entityScope,
|
|
9
9
|
parseEntityScope,
|
|
10
10
|
resolveMembersFromScope,
|
|
11
|
+
ruleGroup,
|
|
11
12
|
} from "./scope.types.js";
|
|
12
13
|
|
|
13
14
|
const MinHoursWeekBase = z.object({
|
|
@@ -43,6 +44,7 @@ export function createMinHoursWeekRule(config: MinHoursWeekConfig): CompilationR
|
|
|
43
44
|
const scope = parseEntityScope(parsed);
|
|
44
45
|
const { hours, priority, weekStartsOn } = parsed;
|
|
45
46
|
const minMinutes = hours * 60;
|
|
47
|
+
const group = ruleGroup(`min-hours-week:${hours}`, `Min ${hours}h per week`, scope);
|
|
46
48
|
|
|
47
49
|
return {
|
|
48
50
|
compile(b) {
|
|
@@ -67,10 +69,24 @@ export function createMinHoursWeekRule(config: MinHoursWeekConfig): CompilationR
|
|
|
67
69
|
|
|
68
70
|
if (terms.length === 0) continue;
|
|
69
71
|
|
|
72
|
+
const weekLabel = weekDays[0]!;
|
|
73
|
+
const constraintId = `min-hours-week:${emp.id}:${weekLabel}`;
|
|
74
|
+
|
|
70
75
|
if (priority === "MANDATORY") {
|
|
71
76
|
b.addLinear(terms, ">=", minMinutes);
|
|
72
77
|
} else {
|
|
73
|
-
b.addSoftLinear(terms, ">=", minMinutes, priorityToPenalty(priority));
|
|
78
|
+
b.addSoftLinear(terms, ">=", minMinutes, priorityToPenalty(priority), constraintId);
|
|
79
|
+
b.reporter.trackConstraint({
|
|
80
|
+
id: constraintId,
|
|
81
|
+
type: "rule",
|
|
82
|
+
rule: "min-hours-week",
|
|
83
|
+
description: `${emp.id} min ${hours}h in week starting ${weekLabel}`,
|
|
84
|
+
targetValue: minMinutes,
|
|
85
|
+
comparator: ">=",
|
|
86
|
+
day: weekLabel,
|
|
87
|
+
context: { memberIds: [emp.id], days: weekDays },
|
|
88
|
+
group,
|
|
89
|
+
});
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
}
|