dabke 0.82.0 → 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 +23 -0
- package/README.md +6 -3
- 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-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/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/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/registry.d.ts +4 -1
- 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/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 +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- 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.js → schedule/definition.js} +9 -673
- 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/package.json +4 -9
- package/src/cpsat/rules/index.ts +3 -0
- package/src/cpsat/rules/max-days-week.ts +143 -0
- package/src/cpsat/rules/min-days-week.ts +120 -0
- package/src/cpsat/rules/must-assign.ts +108 -0
- package/src/cpsat/rules/registry.ts +6 -0
- package/src/cpsat/rules/rules.types.ts +3 -0
- package/src/cpsat/rules/scope.types.ts +1 -1
- package/src/index.ts +8 -3
- 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/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 -917
- package/dist/schedule.d.ts.map +0 -1
- package/dist/schedule.js.map +0 -1
- package/llms.txt +0 -758
- package/src/llms.ts +0 -3
- package/src/schedule.ts +0 -1960
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
entityScope,
|
|
8
|
+
parseEntityScope,
|
|
9
|
+
resolveMembersFromScope,
|
|
10
|
+
ruleGroup,
|
|
11
|
+
} from "./scope.types.js";
|
|
12
|
+
|
|
13
|
+
/** Internally always HIGH. Not user-configurable. */
|
|
14
|
+
const MUST_ASSIGN_PENALTY = priorityToPenalty("HIGH");
|
|
15
|
+
|
|
16
|
+
const MustAssignSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
weekStartsOn: DayOfWeekSchema.optional(),
|
|
19
|
+
})
|
|
20
|
+
.and(entityScope(["members", "roles", "skills"]));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for {@link createMustAssignRule}.
|
|
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`
|
|
29
|
+
*/
|
|
30
|
+
export type MustAssignConfig = z.infer<typeof MustAssignSchema>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Guarantees that targeted members appear on the schedule.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Use this for staffing obligations: salaried employees who are paid
|
|
37
|
+
* regardless of whether they work, or contracted staff who must be
|
|
38
|
+
* rostered. The solver ensures each targeted member has at least one
|
|
39
|
+
* assignment per scheduling week.
|
|
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);
|
|
74
|
+
|
|
75
|
+
for (const emp of members) {
|
|
76
|
+
for (const weekDays of weeks) {
|
|
77
|
+
const terms: Term[] = [];
|
|
78
|
+
|
|
79
|
+
for (const day of weekDays) {
|
|
80
|
+
for (const pattern of b.shiftPatterns) {
|
|
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
|
+
}
|
|
86
|
+
|
|
87
|
+
if (terms.length === 0) continue;
|
|
88
|
+
|
|
89
|
+
const weekLabel = weekDays[0]!;
|
|
90
|
+
const constraintId = `must-assign:${emp.id}:${weekLabel}`;
|
|
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 },
|
|
102
|
+
group,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -3,13 +3,16 @@ import {
|
|
|
3
3
|
createAssignmentPriorityRule,
|
|
4
4
|
createLocationPreferenceRule,
|
|
5
5
|
createMaxConsecutiveDaysRule,
|
|
6
|
+
createMaxDaysWeekRule,
|
|
6
7
|
createMaxHoursDayRule,
|
|
7
8
|
createMaxHoursWeekRule,
|
|
8
9
|
createMaxShiftsDayRule,
|
|
9
10
|
createMinConsecutiveDaysRule,
|
|
11
|
+
createMinDaysWeekRule,
|
|
10
12
|
createMinHoursDayRule,
|
|
11
13
|
createMinHoursWeekRule,
|
|
12
14
|
createMinRestBetweenShiftsRule,
|
|
15
|
+
createMustAssignRule,
|
|
13
16
|
createTimeOffRule,
|
|
14
17
|
createMinimizeCostRule,
|
|
15
18
|
createDayCostMultiplierRule,
|
|
@@ -32,13 +35,16 @@ export const builtInCpsatRuleFactories = {
|
|
|
32
35
|
"assignment-priority": createAssignmentPriorityRule,
|
|
33
36
|
"location-preference": createLocationPreferenceRule,
|
|
34
37
|
"max-consecutive-days": createMaxConsecutiveDaysRule,
|
|
38
|
+
"max-days-week": createMaxDaysWeekRule,
|
|
35
39
|
"max-hours-day": createMaxHoursDayRule,
|
|
36
40
|
"max-hours-week": createMaxHoursWeekRule,
|
|
37
41
|
"max-shifts-day": createMaxShiftsDayRule,
|
|
38
42
|
"min-consecutive-days": createMinConsecutiveDaysRule,
|
|
43
|
+
"min-days-week": createMinDaysWeekRule,
|
|
39
44
|
"min-hours-day": createMinHoursDayRule,
|
|
40
45
|
"min-hours-week": createMinHoursWeekRule,
|
|
41
46
|
"min-rest-between-shifts": createMinRestBetweenShiftsRule,
|
|
47
|
+
"must-assign": createMustAssignRule,
|
|
42
48
|
"time-off": createTimeOffRule,
|
|
43
49
|
"minimize-cost": createMinimizeCostRule,
|
|
44
50
|
"day-cost-multiplier": createDayCostMultiplierRule,
|
|
@@ -8,13 +8,16 @@ export interface CpsatRuleRegistry {
|
|
|
8
8
|
"assignment-priority": import("./assignment-priority.js").AssignmentPriorityConfig;
|
|
9
9
|
"location-preference": import("./location-preference.js").LocationPreferenceConfig;
|
|
10
10
|
"max-consecutive-days": import("./max-consecutive-days.js").MaxConsecutiveDaysConfig;
|
|
11
|
+
"max-days-week": import("./max-days-week.js").MaxDaysWeekConfig;
|
|
11
12
|
"max-hours-day": import("./max-hours-day.js").MaxHoursDayConfig;
|
|
12
13
|
"max-hours-week": import("./max-hours-week.js").MaxHoursWeekConfig;
|
|
13
14
|
"max-shifts-day": import("./max-shifts-day.js").MaxShiftsDayConfig;
|
|
14
15
|
"min-consecutive-days": import("./min-consecutive-days.js").MinConsecutiveDaysConfig;
|
|
16
|
+
"min-days-week": import("./min-days-week.js").MinDaysWeekConfig;
|
|
15
17
|
"min-hours-day": import("./min-hours-day.js").MinHoursDayConfig;
|
|
16
18
|
"min-hours-week": import("./min-hours-week.js").MinHoursWeekConfig;
|
|
17
19
|
"min-rest-between-shifts": import("./min-rest-between-shifts.js").MinRestBetweenShiftsConfig;
|
|
20
|
+
"must-assign": import("./must-assign.js").MustAssignConfig;
|
|
18
21
|
"time-off": import("./time-off.js").TimeOffConfig;
|
|
19
22
|
"minimize-cost": import("./minimize-cost.js").MinimizeCostConfig;
|
|
20
23
|
"day-cost-multiplier": import("./day-cost-multiplier.js").DayCostMultiplierConfig;
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Scheduling library powered by constraint programming (CP-SAT).
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
3
5
|
* ## Core Concepts
|
|
4
6
|
*
|
|
5
7
|
* **Schedule**: The primary API. Small, composable functions
|
|
@@ -158,10 +160,13 @@ export {
|
|
|
158
160
|
maxHoursPerWeek,
|
|
159
161
|
minHoursPerDay,
|
|
160
162
|
minHoursPerWeek,
|
|
163
|
+
maxDaysPerWeek,
|
|
164
|
+
minDaysPerWeek,
|
|
161
165
|
maxShiftsPerDay,
|
|
162
166
|
maxConsecutiveDays,
|
|
163
167
|
minConsecutiveDays,
|
|
164
168
|
minRestBetweenShifts,
|
|
169
|
+
mustAssign,
|
|
165
170
|
preference,
|
|
166
171
|
preferLocation,
|
|
167
172
|
timeOff,
|
|
@@ -178,7 +183,7 @@ export {
|
|
|
178
183
|
tieredOvertimeMultiplier,
|
|
179
184
|
weekdays,
|
|
180
185
|
weekend,
|
|
181
|
-
} from "./schedule.js";
|
|
186
|
+
} from "./schedule/index.js";
|
|
182
187
|
|
|
183
188
|
export type {
|
|
184
189
|
CoverageEntry,
|
|
@@ -195,4 +200,4 @@ export type {
|
|
|
195
200
|
SolveResult,
|
|
196
201
|
SolveStatus,
|
|
197
202
|
SolveOptions,
|
|
198
|
-
} from "./schedule.js";
|
|
203
|
+
} from "./schedule/index.js";
|