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
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
parseTimeScope,
|
|
13
13
|
resolveMembersFromScope,
|
|
14
14
|
resolveActiveDaysFromScope,
|
|
15
|
+
ruleGroup,
|
|
15
16
|
} from "./scope.types.js";
|
|
16
17
|
|
|
17
18
|
const timeOfDaySchema = z.object({
|
|
@@ -108,6 +109,7 @@ export function createTimeOffRule(config: TimeOffConfig): CompilationRule {
|
|
|
108
109
|
|
|
109
110
|
const entityScopeValue = parseEntityScope(parsed);
|
|
110
111
|
const timeScopeValue = parseTimeScope(parsed);
|
|
112
|
+
const group = ruleGroup("time-off", "Time off", entityScopeValue, timeScopeValue);
|
|
111
113
|
|
|
112
114
|
return {
|
|
113
115
|
compile(builder) {
|
|
@@ -182,20 +184,22 @@ export function createTimeOffRule(config: TimeOffConfig): CompilationRule {
|
|
|
182
184
|
if (violated) {
|
|
183
185
|
reporter.reportRuleViolation({
|
|
184
186
|
rule: "time-off",
|
|
185
|
-
|
|
187
|
+
message: `Time-off request for ${emp.id} on ${day} could not be honored`,
|
|
186
188
|
context: {
|
|
187
189
|
memberIds: [emp.id],
|
|
188
190
|
days: [day],
|
|
189
191
|
},
|
|
192
|
+
group,
|
|
190
193
|
});
|
|
191
194
|
} else {
|
|
192
195
|
reporter.reportRulePassed({
|
|
193
196
|
rule: "time-off",
|
|
194
|
-
|
|
197
|
+
message: `Time-off honored for ${emp.id} on ${day}`,
|
|
195
198
|
context: {
|
|
196
199
|
memberIds: [emp.id],
|
|
197
200
|
days: [day],
|
|
198
201
|
},
|
|
202
|
+
group,
|
|
199
203
|
});
|
|
200
204
|
}
|
|
201
205
|
}
|
|
@@ -2,7 +2,20 @@ import type { DayOfWeek, TimeOfDay } from "../types.js";
|
|
|
2
2
|
import { toDayOfWeekUTC } from "../datetime.utils.js";
|
|
3
3
|
import { parseDayString } from "./utils.js";
|
|
4
4
|
import type { CoverageRequirement, Priority } from "./types.js";
|
|
5
|
-
import {
|
|
5
|
+
import { assertSafeKeySegment, type ValidationGroup } from "./validation.types.js";
|
|
6
|
+
|
|
7
|
+
/** Validates and joins role/skill IDs into a key-safe segment. */
|
|
8
|
+
function safeRoleOrSkills(roleIds?: readonly string[], skillIds?: readonly string[]): string {
|
|
9
|
+
if (roleIds && roleIds.length > 0) {
|
|
10
|
+
for (const r of roleIds) assertSafeKeySegment(r, "role ID");
|
|
11
|
+
return roleIds.join("/");
|
|
12
|
+
}
|
|
13
|
+
if (skillIds && skillIds.length > 0) {
|
|
14
|
+
for (const s of skillIds) assertSafeKeySegment(s, "skill ID");
|
|
15
|
+
return skillIds.join("+");
|
|
16
|
+
}
|
|
17
|
+
throw new Error("At least one role or skill is required");
|
|
18
|
+
}
|
|
6
19
|
|
|
7
20
|
/**
|
|
8
21
|
* Base definition for a semantic time period.
|
|
@@ -25,7 +38,7 @@ export interface SemanticTimeDef {
|
|
|
25
38
|
*/
|
|
26
39
|
export interface SemanticTimeVariant extends SemanticTimeDef {
|
|
27
40
|
/** Restrict this entry to specific days of the week. */
|
|
28
|
-
dayOfWeek?: readonly DayOfWeek[];
|
|
41
|
+
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
29
42
|
/** Restrict this entry to specific dates (YYYY-MM-DD). */
|
|
30
43
|
dates?: string[];
|
|
31
44
|
}
|
|
@@ -44,15 +57,15 @@ interface SemanticCoverageRequirementBase<S extends string> {
|
|
|
44
57
|
targetCount: number;
|
|
45
58
|
priority?: Priority;
|
|
46
59
|
/** Scope this requirement to specific days of the week */
|
|
47
|
-
dayOfWeek?: DayOfWeek[];
|
|
60
|
+
dayOfWeek?: [DayOfWeek, ...DayOfWeek[]];
|
|
48
61
|
/** Scope this requirement to specific dates (YYYY-MM-DD) */
|
|
49
62
|
dates?: string[];
|
|
50
63
|
/**
|
|
51
|
-
* Override the auto-generated group
|
|
52
|
-
* If not provided, a
|
|
64
|
+
* Override the auto-generated group for validation reporting.
|
|
65
|
+
* If not provided, a group is auto-generated from the semantic time name,
|
|
53
66
|
* role/skills, and target count.
|
|
54
67
|
*/
|
|
55
|
-
|
|
68
|
+
group?: ValidationGroup;
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
/**
|
|
@@ -62,14 +75,14 @@ interface RoleBasedSemanticCoverageRequirement<
|
|
|
62
75
|
S extends string,
|
|
63
76
|
> extends SemanticCoverageRequirementBase<S> {
|
|
64
77
|
/**
|
|
65
|
-
*
|
|
78
|
+
* Role IDs that satisfy this coverage (OR logic).
|
|
66
79
|
* Must have at least one role.
|
|
67
80
|
*/
|
|
68
|
-
|
|
81
|
+
roleIds: [string, ...string[]];
|
|
69
82
|
/**
|
|
70
|
-
* Additional skill filter (AND logic with roles).
|
|
83
|
+
* Additional skill ID filter (AND logic with roles).
|
|
71
84
|
*/
|
|
72
|
-
|
|
85
|
+
skillIds?: [string, ...string[]];
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
/**
|
|
@@ -78,12 +91,12 @@ interface RoleBasedSemanticCoverageRequirement<
|
|
|
78
91
|
interface SkillBasedSemanticCoverageRequirement<
|
|
79
92
|
S extends string,
|
|
80
93
|
> extends SemanticCoverageRequirementBase<S> {
|
|
81
|
-
|
|
94
|
+
roleIds?: never;
|
|
82
95
|
/**
|
|
83
|
-
*
|
|
96
|
+
* Skill IDs required (ALL required, AND logic).
|
|
84
97
|
* Must have at least one skill.
|
|
85
98
|
*/
|
|
86
|
-
|
|
99
|
+
skillIds: [string, ...string[]];
|
|
87
100
|
}
|
|
88
101
|
|
|
89
102
|
/**
|
|
@@ -91,14 +104,14 @@ interface SkillBasedSemanticCoverageRequirement<
|
|
|
91
104
|
* Type-safe: S is constrained to known semantic time names.
|
|
92
105
|
*
|
|
93
106
|
* This is a discriminated union enforcing at compile time that at least
|
|
94
|
-
* one of `
|
|
107
|
+
* one of `roleIds` or `skillIds` must be provided.
|
|
95
108
|
*
|
|
96
109
|
* @remarks
|
|
97
110
|
* **Fields:**
|
|
98
111
|
* - `semanticTime` (required) — name of a defined semantic time
|
|
99
112
|
* - `targetCount` (required) — how many people are needed
|
|
100
|
-
* - `
|
|
101
|
-
* - `
|
|
113
|
+
* - `roleIds` — role IDs that satisfy this (OR logic); at least one of `roleIds`/`skillIds` required
|
|
114
|
+
* - `skillIds` — skill IDs required (AND logic); at least one of `roleIds`/`skillIds` required
|
|
102
115
|
* - `dayOfWeek` — scope to specific days of the week (e.g. `["monday", "tuesday"]`)
|
|
103
116
|
* - `dates` — scope to specific dates (`"YYYY-MM-DD"` strings)
|
|
104
117
|
* - `priority` — `"MANDATORY"` | `"HIGH"` | `"MEDIUM"` | `"LOW"`
|
|
@@ -109,15 +122,15 @@ interface SkillBasedSemanticCoverageRequirement<
|
|
|
109
122
|
*
|
|
110
123
|
* @example Weekday vs weekend (mutually exclusive dayOfWeek)
|
|
111
124
|
* ```typescript
|
|
112
|
-
* { semanticTime: "day_shift",
|
|
125
|
+
* { semanticTime: "day_shift", roleIds: ["nurse"], targetCount: 3,
|
|
113
126
|
* dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"] },
|
|
114
|
-
* { semanticTime: "day_shift",
|
|
127
|
+
* { semanticTime: "day_shift", roleIds: ["nurse"], targetCount: 2,
|
|
115
128
|
* dayOfWeek: ["saturday", "sunday"] },
|
|
116
129
|
* ```
|
|
117
130
|
*
|
|
118
131
|
* @example Skill-based coverage (any role with the skill)
|
|
119
132
|
* ```typescript
|
|
120
|
-
* { semanticTime: "night_shift",
|
|
133
|
+
* { semanticTime: "night_shift", skillIds: ["charge_nurse"], targetCount: 1 },
|
|
121
134
|
* ```
|
|
122
135
|
*/
|
|
123
136
|
export type SemanticCoverageRequirement<S extends string> =
|
|
@@ -134,11 +147,11 @@ interface ConcreteCoverageRequirementBase {
|
|
|
134
147
|
targetCount: number;
|
|
135
148
|
priority?: Priority;
|
|
136
149
|
/**
|
|
137
|
-
* Override the auto-generated group
|
|
138
|
-
* If not provided, a
|
|
150
|
+
* Override the auto-generated group for validation reporting.
|
|
151
|
+
* If not provided, a group is auto-generated from the day, time range,
|
|
139
152
|
* role/skills, and target count.
|
|
140
153
|
*/
|
|
141
|
-
|
|
154
|
+
group?: ValidationGroup;
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
/**
|
|
@@ -146,26 +159,26 @@ interface ConcreteCoverageRequirementBase {
|
|
|
146
159
|
*/
|
|
147
160
|
interface RoleBasedConcreteCoverageRequirement extends ConcreteCoverageRequirementBase {
|
|
148
161
|
/**
|
|
149
|
-
*
|
|
162
|
+
* Role IDs that satisfy this coverage (OR logic).
|
|
150
163
|
* Must have at least one role.
|
|
151
164
|
*/
|
|
152
|
-
|
|
165
|
+
roleIds: [string, ...string[]];
|
|
153
166
|
/**
|
|
154
|
-
* Additional skill filter (AND logic with roles).
|
|
167
|
+
* Additional skill ID filter (AND logic with roles).
|
|
155
168
|
*/
|
|
156
|
-
|
|
169
|
+
skillIds?: [string, ...string[]];
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
/**
|
|
160
173
|
* Concrete coverage requiring specific skills only (any role).
|
|
161
174
|
*/
|
|
162
175
|
interface SkillBasedConcreteCoverageRequirement extends ConcreteCoverageRequirementBase {
|
|
163
|
-
|
|
176
|
+
roleIds?: never;
|
|
164
177
|
/**
|
|
165
|
-
*
|
|
178
|
+
* Skill IDs required (ALL required, AND logic).
|
|
166
179
|
* Must have at least one skill.
|
|
167
180
|
*/
|
|
168
|
-
|
|
181
|
+
skillIds: [string, ...string[]];
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
/**
|
|
@@ -173,7 +186,7 @@ interface SkillBasedConcreteCoverageRequirement extends ConcreteCoverageRequirem
|
|
|
173
186
|
* Used for one-off requirements that don't fit a semantic time.
|
|
174
187
|
*
|
|
175
188
|
* This is a discriminated union enforcing at compile time that at least
|
|
176
|
-
* one of `
|
|
189
|
+
* one of `roleIds` or `skillIds` must be provided.
|
|
177
190
|
*/
|
|
178
191
|
export type ConcreteCoverageRequirement =
|
|
179
192
|
| RoleBasedConcreteCoverageRequirement
|
|
@@ -199,12 +212,14 @@ export type ConcreteCoverageRequirement =
|
|
|
199
212
|
* { count: 2, dates: ["2025-12-24"] },
|
|
200
213
|
* )
|
|
201
214
|
* ```
|
|
215
|
+
*
|
|
216
|
+
* @category Coverage
|
|
202
217
|
*/
|
|
203
218
|
export interface CoverageVariant {
|
|
204
219
|
/** Number of people needed. */
|
|
205
220
|
count: number;
|
|
206
221
|
/** Restrict this variant to specific days of the week. */
|
|
207
|
-
dayOfWeek?: readonly DayOfWeek[];
|
|
222
|
+
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
208
223
|
/** Restrict this variant to specific dates (YYYY-MM-DD). */
|
|
209
224
|
dates?: string[];
|
|
210
225
|
/** Defaults to `"MANDATORY"`. */
|
|
@@ -218,7 +233,7 @@ interface VariantCoverageRequirementBase<S extends string> {
|
|
|
218
233
|
semanticTime: S;
|
|
219
234
|
/** At least one variant is required. At most one may be unscoped (the default). */
|
|
220
235
|
variants: [CoverageVariant, ...CoverageVariant[]];
|
|
221
|
-
|
|
236
|
+
group?: ValidationGroup;
|
|
222
237
|
}
|
|
223
238
|
|
|
224
239
|
/**
|
|
@@ -227,8 +242,8 @@ interface VariantCoverageRequirementBase<S extends string> {
|
|
|
227
242
|
interface RoleBasedVariantCoverageRequirement<
|
|
228
243
|
S extends string,
|
|
229
244
|
> extends VariantCoverageRequirementBase<S> {
|
|
230
|
-
|
|
231
|
-
|
|
245
|
+
roleIds: [string, ...string[]];
|
|
246
|
+
skillIds?: [string, ...string[]];
|
|
232
247
|
}
|
|
233
248
|
|
|
234
249
|
/**
|
|
@@ -237,8 +252,8 @@ interface RoleBasedVariantCoverageRequirement<
|
|
|
237
252
|
interface SkillBasedVariantCoverageRequirement<
|
|
238
253
|
S extends string,
|
|
239
254
|
> extends VariantCoverageRequirementBase<S> {
|
|
240
|
-
|
|
241
|
-
|
|
255
|
+
roleIds?: never;
|
|
256
|
+
skillIds: [string, ...string[]];
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
/**
|
|
@@ -254,7 +269,7 @@ interface SkillBasedVariantCoverageRequirement<
|
|
|
254
269
|
* @example Decrease from default on a specific date
|
|
255
270
|
* ```typescript
|
|
256
271
|
* {
|
|
257
|
-
* semanticTime: "peak_hours",
|
|
272
|
+
* semanticTime: "peak_hours", roleIds: ["agent"],
|
|
258
273
|
* variants: [
|
|
259
274
|
* { count: 4 }, // default
|
|
260
275
|
* { count: 2, dates: ["2025-12-24"] }, // Christmas Eve
|
|
@@ -341,10 +356,10 @@ export interface SemanticTimeContext<S extends string> {
|
|
|
341
356
|
* });
|
|
342
357
|
*
|
|
343
358
|
* const coverage = times.coverage([
|
|
344
|
-
* { semanticTime: "day_shift",
|
|
345
|
-
* { semanticTime: "morning_round",
|
|
359
|
+
* { semanticTime: "day_shift", roleIds: ["nurse"], targetCount: 3 },
|
|
360
|
+
* { semanticTime: "morning_round", skillIds: ["charge_nurse"], targetCount: 1, priority: "MANDATORY" },
|
|
346
361
|
* // Type error: "evening" is not a defined semantic time
|
|
347
|
-
* // { semanticTime: "evening",
|
|
362
|
+
* // { semanticTime: "evening", roleIds: ["nurse"], targetCount: 2 },
|
|
348
363
|
* ]);
|
|
349
364
|
* ```
|
|
350
365
|
*
|
|
@@ -361,9 +376,9 @@ export interface SemanticTimeContext<S extends string> {
|
|
|
361
376
|
* @example Mixed semantic and concrete coverage
|
|
362
377
|
* ```typescript
|
|
363
378
|
* const coverage = times.coverage([
|
|
364
|
-
* { semanticTime: "day_shift",
|
|
379
|
+
* { semanticTime: "day_shift", roleIds: ["nurse"], targetCount: 3 },
|
|
365
380
|
* // One-off event requiring extra staff
|
|
366
|
-
* { day: "2026-01-14", startTime: { hours: 8 }, endTime: { hours: 12 },
|
|
381
|
+
* { day: "2026-01-14", startTime: { hours: 8 }, endTime: { hours: 12 }, roleIds: ["nurse"], targetCount: 5 },
|
|
367
382
|
* ]);
|
|
368
383
|
* ```
|
|
369
384
|
*/
|
|
@@ -393,25 +408,25 @@ function buildCoverageRequirement(
|
|
|
393
408
|
day: string,
|
|
394
409
|
startTime: TimeOfDay,
|
|
395
410
|
endTime: TimeOfDay,
|
|
396
|
-
|
|
397
|
-
|
|
411
|
+
roleIds: [string, ...string[]] | undefined,
|
|
412
|
+
skillIds: [string, ...string[]] | undefined,
|
|
398
413
|
targetCount: number,
|
|
399
414
|
priority: Priority,
|
|
400
|
-
|
|
415
|
+
group: ValidationGroup,
|
|
401
416
|
): CoverageRequirement {
|
|
402
|
-
const base = { day, startTime, endTime, targetCount, priority,
|
|
417
|
+
const base = { day, startTime, endTime, targetCount, priority, group };
|
|
403
418
|
|
|
404
|
-
if (
|
|
419
|
+
if (roleIds && roleIds.length > 0) {
|
|
405
420
|
// Role-based (with optional skills)
|
|
406
|
-
return
|
|
407
|
-
} else if (
|
|
421
|
+
return skillIds && skillIds.length > 0 ? { ...base, roleIds, skillIds } : { ...base, roleIds };
|
|
422
|
+
} else if (skillIds && skillIds.length > 0) {
|
|
408
423
|
// Skill-only
|
|
409
|
-
return { ...base,
|
|
424
|
+
return { ...base, skillIds };
|
|
410
425
|
}
|
|
411
426
|
|
|
412
427
|
// This shouldn't happen if input types are correct, but handle gracefully
|
|
413
428
|
throw new Error(
|
|
414
|
-
`Coverage requirement for day "${day}" must have at least one of
|
|
429
|
+
`Coverage requirement for day "${day}" must have at least one of roleIds or skillIds`,
|
|
415
430
|
);
|
|
416
431
|
}
|
|
417
432
|
|
|
@@ -431,17 +446,17 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
431
446
|
if (isConcreteCoverage(req)) {
|
|
432
447
|
// Concrete requirement - pass through if day is in horizon
|
|
433
448
|
if (daySet.has(req.day)) {
|
|
434
|
-
const
|
|
449
|
+
const defaultGroup = concreteCoverageGroup(req);
|
|
435
450
|
result.push(
|
|
436
451
|
buildCoverageRequirement(
|
|
437
452
|
req.day,
|
|
438
453
|
req.startTime,
|
|
439
454
|
req.endTime,
|
|
440
|
-
req.
|
|
441
|
-
req.
|
|
455
|
+
req.roleIds,
|
|
456
|
+
req.skillIds,
|
|
442
457
|
req.targetCount,
|
|
443
458
|
req.priority ?? "MANDATORY",
|
|
444
|
-
req.
|
|
459
|
+
req.group ?? defaultGroup,
|
|
445
460
|
),
|
|
446
461
|
);
|
|
447
462
|
}
|
|
@@ -452,7 +467,7 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
452
467
|
throw new Error(`Unknown semantic time: ${req.semanticTime}`);
|
|
453
468
|
}
|
|
454
469
|
|
|
455
|
-
const
|
|
470
|
+
const defaultGroup = variantCoverageGroup(req);
|
|
456
471
|
|
|
457
472
|
for (const day of days) {
|
|
458
473
|
const resolved = resolveTimeForDay(entry, day);
|
|
@@ -466,11 +481,11 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
466
481
|
day,
|
|
467
482
|
resolved.startTime,
|
|
468
483
|
resolved.endTime,
|
|
469
|
-
req.
|
|
470
|
-
req.
|
|
484
|
+
req.roleIds,
|
|
485
|
+
req.skillIds,
|
|
471
486
|
variant.count,
|
|
472
487
|
variant.priority ?? "MANDATORY",
|
|
473
|
-
req.
|
|
488
|
+
req.group ?? defaultGroup,
|
|
474
489
|
),
|
|
475
490
|
);
|
|
476
491
|
}
|
|
@@ -481,7 +496,7 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
481
496
|
throw new Error(`Unknown semantic time: ${req.semanticTime}`);
|
|
482
497
|
}
|
|
483
498
|
|
|
484
|
-
const
|
|
499
|
+
const defaultGroup = semanticCoverageGroup(req);
|
|
485
500
|
const applicableDays = filterDays(days, req.dayOfWeek, req.dates);
|
|
486
501
|
|
|
487
502
|
for (const day of applicableDays) {
|
|
@@ -492,11 +507,11 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
492
507
|
day,
|
|
493
508
|
resolved.startTime,
|
|
494
509
|
resolved.endTime,
|
|
495
|
-
req.
|
|
496
|
-
req.
|
|
510
|
+
req.roleIds,
|
|
511
|
+
req.skillIds,
|
|
497
512
|
req.targetCount,
|
|
498
513
|
req.priority ?? "MANDATORY",
|
|
499
|
-
req.
|
|
514
|
+
req.group ?? defaultGroup,
|
|
500
515
|
),
|
|
501
516
|
);
|
|
502
517
|
}
|
|
@@ -507,33 +522,42 @@ function resolveSemanticCoverage<S extends string>(
|
|
|
507
522
|
return result;
|
|
508
523
|
}
|
|
509
524
|
|
|
510
|
-
/**
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
const roleOrSkills = req.
|
|
516
|
-
const
|
|
525
|
+
/** Generates deterministic group for a semantic coverage requirement. */
|
|
526
|
+
function semanticCoverageGroup<S extends string>(
|
|
527
|
+
req: SemanticCoverageRequirement<S>,
|
|
528
|
+
): ValidationGroup {
|
|
529
|
+
assertSafeKeySegment(req.semanticTime, "semantic time name");
|
|
530
|
+
const roleOrSkills = safeRoleOrSkills(req.roleIds, req.skillIds);
|
|
531
|
+
const title = `${req.targetCount}x ${roleOrSkills} during ${req.semanticTime}`;
|
|
517
532
|
|
|
518
|
-
|
|
533
|
+
let scopeSuffix = "";
|
|
519
534
|
if (req.dayOfWeek && req.dayOfWeek.length > 0 && req.dayOfWeek.length < 7) {
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return groupKey(`${base} (specific dates)`);
|
|
535
|
+
scopeSuffix = `:dow:${req.dayOfWeek.toSorted().join(",")}`;
|
|
536
|
+
} else if (req.dates && req.dates.length > 0) {
|
|
537
|
+
scopeSuffix = `:dates:${req.dates.toSorted().join(",")}`;
|
|
524
538
|
}
|
|
525
539
|
|
|
526
|
-
|
|
540
|
+
const key = `coverage:${req.semanticTime}:${roleOrSkills}:${req.targetCount}${scopeSuffix}`;
|
|
541
|
+
const titleSuffix =
|
|
542
|
+
scopeSuffix && req.dayOfWeek
|
|
543
|
+
? ` (${formatDaysScope(req.dayOfWeek)})`
|
|
544
|
+
: scopeSuffix && req.dates
|
|
545
|
+
? " (specific dates)"
|
|
546
|
+
: "";
|
|
547
|
+
|
|
548
|
+
return { key, title: `${title}${titleSuffix}` };
|
|
527
549
|
}
|
|
528
550
|
|
|
529
|
-
/**
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
551
|
+
/** Generates deterministic group for a variant coverage requirement. */
|
|
552
|
+
function variantCoverageGroup<S extends string>(
|
|
553
|
+
req: VariantCoverageRequirement<S>,
|
|
554
|
+
): ValidationGroup {
|
|
555
|
+
assertSafeKeySegment(req.semanticTime, "semantic time name");
|
|
556
|
+
const roleOrSkills = safeRoleOrSkills(req.roleIds, req.skillIds);
|
|
557
|
+
return {
|
|
558
|
+
key: `coverage-variant:${req.semanticTime}:${roleOrSkills}`,
|
|
559
|
+
title: `${roleOrSkills} during ${req.semanticTime}`,
|
|
560
|
+
};
|
|
537
561
|
}
|
|
538
562
|
|
|
539
563
|
/**
|
|
@@ -569,14 +593,14 @@ function resolveVariantForDay(
|
|
|
569
593
|
return dateMatch ?? dowMatch ?? defaultMatch;
|
|
570
594
|
}
|
|
571
595
|
|
|
572
|
-
/**
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
*/
|
|
576
|
-
function generateConcreteGroupKey(req: ConcreteCoverageRequirement): GroupKey {
|
|
577
|
-
const roleOrSkills = req.roles?.join("/") ?? req.skills?.join("+") ?? "staff";
|
|
596
|
+
/** Generates deterministic group for a concrete coverage requirement. */
|
|
597
|
+
function concreteCoverageGroup(req: ConcreteCoverageRequirement): ValidationGroup {
|
|
598
|
+
const roleOrSkills = safeRoleOrSkills(req.roleIds, req.skillIds);
|
|
578
599
|
const timeRange = `${formatTime(req.startTime)}-${formatTime(req.endTime)}`;
|
|
579
|
-
return
|
|
600
|
+
return {
|
|
601
|
+
key: `coverage-concrete:${req.day}:${roleOrSkills}:${req.targetCount}:${timeRange}`,
|
|
602
|
+
title: `${req.targetCount}x ${roleOrSkills} on ${req.day} ${timeRange}`,
|
|
603
|
+
};
|
|
580
604
|
}
|
|
581
605
|
|
|
582
606
|
/**
|
package/src/cpsat/types.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { TimeOfDay, DayOfWeek } from "../types.js";
|
|
2
2
|
import type { SolverRequest, SolverTerm } from "../client.types.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ValidationGroup } from "./validation.types.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Pay per hour in the caller's smallest currency unit (e.g., pence, cents).
|
|
7
|
+
*
|
|
8
|
+
* @category Supporting Types
|
|
7
9
|
*/
|
|
8
10
|
export interface HourlyPay {
|
|
9
11
|
/** Pay per hour in smallest currency unit. */
|
|
@@ -19,6 +21,8 @@ export interface HourlyPay {
|
|
|
19
21
|
*
|
|
20
22
|
* Note: overtime multiplier rules apply only to hourly members.
|
|
21
23
|
* Overtime surcharge rules apply to all members regardless of pay type.
|
|
24
|
+
*
|
|
25
|
+
* @category Supporting Types
|
|
22
26
|
*/
|
|
23
27
|
export interface SalariedPay {
|
|
24
28
|
/** Annual salary in smallest currency unit. */
|
|
@@ -32,6 +36,8 @@ export interface SalariedPay {
|
|
|
32
36
|
*
|
|
33
37
|
* - `"LOW"`, `"MEDIUM"`, `"HIGH"`: soft constraints with increasing penalty for violations
|
|
34
38
|
* - `"MANDATORY"`: hard constraint; the solver will not produce a solution that violates it
|
|
39
|
+
*
|
|
40
|
+
* @category Supporting Types
|
|
35
41
|
*/
|
|
36
42
|
export type Priority = "LOW" | "MEDIUM" | "HIGH" | "MANDATORY";
|
|
37
43
|
|
|
@@ -40,14 +46,16 @@ export type Priority = "LOW" | "MEDIUM" | "HIGH" | "MANDATORY";
|
|
|
40
46
|
*
|
|
41
47
|
* Members are assigned to shift patterns by the solver based on
|
|
42
48
|
* coverage requirements, rules, and constraints.
|
|
49
|
+
*
|
|
50
|
+
* @category Supporting Types
|
|
43
51
|
*/
|
|
44
52
|
export interface SchedulingMember {
|
|
45
53
|
/** Unique identifier for this member. Must not contain colons. */
|
|
46
54
|
id: string;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
|
|
55
|
+
/** Role IDs this member can fill (e.g. "nurse", "doctor"). */
|
|
56
|
+
roleIds: string[];
|
|
57
|
+
/** Skill IDs this member has (e.g. "charge_nurse", "forklift"). */
|
|
58
|
+
skillIds?: string[];
|
|
51
59
|
/** Base pay. Required when cost rules are used. */
|
|
52
60
|
pay?: HourlyPay | SalariedPay;
|
|
53
61
|
}
|
|
@@ -67,9 +75,11 @@ export interface SchedulingMember {
|
|
|
67
75
|
* @example
|
|
68
76
|
* // Role-restricted shifts
|
|
69
77
|
* const patterns: ShiftPattern[] = [
|
|
70
|
-
* { id: "ward_day", startTime: { hours: 7 }, endTime: { hours: 15 },
|
|
71
|
-
* { id: "reception", startTime: { hours: 8 }, endTime: { hours: 16 },
|
|
78
|
+
* { id: "ward_day", startTime: { hours: 7 }, endTime: { hours: 15 }, roleIds: ["nurse", "doctor"] },
|
|
79
|
+
* { id: "reception", startTime: { hours: 8 }, endTime: { hours: 16 }, roleIds: ["admin"] },
|
|
72
80
|
* ];
|
|
81
|
+
*
|
|
82
|
+
* @category Supporting Types
|
|
73
83
|
*/
|
|
74
84
|
export interface ShiftPattern {
|
|
75
85
|
/**
|
|
@@ -79,7 +89,7 @@ export interface ShiftPattern {
|
|
|
79
89
|
id: string;
|
|
80
90
|
|
|
81
91
|
/**
|
|
82
|
-
* Restricts who can be assigned to this shift based on their
|
|
92
|
+
* Restricts who can be assigned to this shift based on their role IDs.
|
|
83
93
|
*
|
|
84
94
|
* - If omitted: anyone can work this shift
|
|
85
95
|
* - If provided: only team members whose roles overlap with this list can be assigned
|
|
@@ -88,7 +98,7 @@ export interface ShiftPattern {
|
|
|
88
98
|
* Use it when different roles have different schedules (e.g., kitchen staff starts
|
|
89
99
|
* earlier than floor staff).
|
|
90
100
|
*/
|
|
91
|
-
|
|
101
|
+
roleIds?: [string, ...string[]];
|
|
92
102
|
|
|
93
103
|
/**
|
|
94
104
|
* Restricts which days of the week this shift pattern can be used.
|
|
@@ -105,7 +115,7 @@ export interface ShiftPattern {
|
|
|
105
115
|
* { id: "full_shift", startTime: t(9), endTime: t(18), dayOfWeek: ["monday", "tuesday", "wednesday", "thursday", "friday"] }
|
|
106
116
|
* ```
|
|
107
117
|
*/
|
|
108
|
-
dayOfWeek?: DayOfWeek[];
|
|
118
|
+
dayOfWeek?: readonly [DayOfWeek, ...DayOfWeek[]];
|
|
109
119
|
|
|
110
120
|
/**
|
|
111
121
|
* Physical location where this shift takes place.
|
|
@@ -133,13 +143,13 @@ interface CoverageRequirementBase extends TimeInterval {
|
|
|
133
143
|
targetCount: number;
|
|
134
144
|
priority: Priority;
|
|
135
145
|
/**
|
|
136
|
-
* Groups this requirement with others
|
|
137
|
-
*
|
|
138
|
-
*
|
|
146
|
+
* Groups this requirement with others for validation reporting.
|
|
147
|
+
* All coverage constraints generated from this requirement will share the
|
|
148
|
+
* same group, enabling meaningful aggregation in reports.
|
|
139
149
|
*
|
|
140
|
-
* If not provided, an auto-generated
|
|
150
|
+
* If not provided, an auto-generated group will be used based on the coverage parameters.
|
|
141
151
|
*/
|
|
142
|
-
|
|
152
|
+
group?: ValidationGroup;
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
/**
|
|
@@ -149,16 +159,16 @@ interface CoverageRequirementBase extends TimeInterval {
|
|
|
149
159
|
*/
|
|
150
160
|
interface RoleBasedCoverageRequirement extends CoverageRequirementBase {
|
|
151
161
|
/**
|
|
152
|
-
*
|
|
162
|
+
* Role IDs that satisfy this coverage (OR logic).
|
|
153
163
|
* A person matches if they have ANY of these roles.
|
|
154
164
|
* Must have at least one role.
|
|
155
165
|
*/
|
|
156
|
-
|
|
166
|
+
roleIds: [string, ...string[]];
|
|
157
167
|
/**
|
|
158
|
-
* Additional skill filter (AND logic with roles).
|
|
168
|
+
* Additional skill ID filter (AND logic with roles).
|
|
159
169
|
* If provided, team members must have ALL specified skills in addition to matching a role.
|
|
160
170
|
*/
|
|
161
|
-
|
|
171
|
+
skillIds?: [string, ...string[]];
|
|
162
172
|
}
|
|
163
173
|
|
|
164
174
|
/**
|
|
@@ -169,19 +179,19 @@ interface SkillBasedCoverageRequirement extends CoverageRequirementBase {
|
|
|
169
179
|
/**
|
|
170
180
|
* Must not be present for skill-based coverage.
|
|
171
181
|
*/
|
|
172
|
-
|
|
182
|
+
roleIds?: never;
|
|
173
183
|
/**
|
|
174
|
-
*
|
|
184
|
+
* Skill IDs required to satisfy this coverage (ALL required, AND logic).
|
|
175
185
|
* Must have at least one skill.
|
|
176
186
|
*/
|
|
177
|
-
|
|
187
|
+
skillIds: [string, ...string[]];
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
/**
|
|
181
191
|
* Defines staffing needs for a specific time period.
|
|
182
192
|
*
|
|
183
193
|
* This is a discriminated union that enforces at compile time that at least
|
|
184
|
-
* one of `
|
|
194
|
+
* one of `roleIds` or `skillIds` must be provided:
|
|
185
195
|
*
|
|
186
196
|
* - Role-based: `{ roles: ["waiter"], ... }` - anyone with ANY of these roles (OR logic)
|
|
187
197
|
* - Role + skill: `{ roles: ["waiter"], skills: ["senior"], ... }` - role AND skills
|
|
@@ -189,19 +199,19 @@ interface SkillBasedCoverageRequirement extends CoverageRequirementBase {
|
|
|
189
199
|
*
|
|
190
200
|
* @example
|
|
191
201
|
* // Need 2 waiters during lunch (role-based)
|
|
192
|
-
* { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 14 },
|
|
202
|
+
* { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 14 }, roleIds: ["waiter"], targetCount: 2, priority: "MANDATORY" }
|
|
193
203
|
*
|
|
194
204
|
* @example
|
|
195
205
|
* // Need 1 manager OR supervisor during service (OR logic on roles)
|
|
196
|
-
* { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 22 },
|
|
206
|
+
* { day: "2024-01-01", startTime: { hours: 11 }, endTime: { hours: 22 }, roleIds: ["manager", "supervisor"], targetCount: 1, priority: "MANDATORY" }
|
|
197
207
|
*
|
|
198
208
|
* @example
|
|
199
209
|
* // Need 1 keyholder for opening (skill-only, any role)
|
|
200
|
-
* { day: "2024-01-01", startTime: { hours: 6 }, endTime: { hours: 8 },
|
|
210
|
+
* { day: "2024-01-01", startTime: { hours: 6 }, endTime: { hours: 8 }, skillIds: ["keyholder"], targetCount: 1, priority: "MANDATORY" }
|
|
201
211
|
*
|
|
202
212
|
* @example
|
|
203
213
|
* // Need 1 senior waiter for training shift (role + skill filter)
|
|
204
|
-
* { day: "2024-01-01", startTime: { hours: 9 }, endTime: { hours: 17 },
|
|
214
|
+
* { day: "2024-01-01", startTime: { hours: 9 }, endTime: { hours: 17 }, roleIds: ["waiter"], skillIds: ["senior"], targetCount: 1, priority: "HIGH" }
|
|
205
215
|
*/
|
|
206
216
|
export type CoverageRequirement = RoleBasedCoverageRequirement | SkillBasedCoverageRequirement;
|
|
207
217
|
|