dabke 0.78.2 → 0.80.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.
Files changed (129) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +68 -31
  3. package/dist/client.types.d.ts +58 -0
  4. package/dist/client.types.d.ts.map +1 -1
  5. package/dist/client.types.js.map +1 -1
  6. package/dist/cpsat/model-builder.d.ts +13 -2
  7. package/dist/cpsat/model-builder.d.ts.map +1 -1
  8. package/dist/cpsat/model-builder.js.map +1 -1
  9. package/dist/cpsat/response.d.ts +12 -3
  10. package/dist/cpsat/response.d.ts.map +1 -1
  11. package/dist/cpsat/response.js.map +1 -1
  12. package/dist/cpsat/rules/assign-together.d.ts +7 -0
  13. package/dist/cpsat/rules/assign-together.d.ts.map +1 -1
  14. package/dist/cpsat/rules/assign-together.js +1 -0
  15. package/dist/cpsat/rules/assign-together.js.map +1 -1
  16. package/dist/cpsat/rules/employee-assignment-priority.d.ts +11 -37
  17. package/dist/cpsat/rules/employee-assignment-priority.d.ts.map +1 -1
  18. package/dist/cpsat/rules/employee-assignment-priority.js +12 -104
  19. package/dist/cpsat/rules/employee-assignment-priority.js.map +1 -1
  20. package/dist/cpsat/rules/location-preference.d.ts +12 -10
  21. package/dist/cpsat/rules/location-preference.d.ts.map +1 -1
  22. package/dist/cpsat/rules/location-preference.js +16 -14
  23. package/dist/cpsat/rules/location-preference.js.map +1 -1
  24. package/dist/cpsat/rules/max-consecutive-days.d.ts +12 -13
  25. package/dist/cpsat/rules/max-consecutive-days.d.ts.map +1 -1
  26. package/dist/cpsat/rules/max-consecutive-days.js +11 -12
  27. package/dist/cpsat/rules/max-consecutive-days.js.map +1 -1
  28. package/dist/cpsat/rules/max-hours-day.d.ts +12 -28
  29. package/dist/cpsat/rules/max-hours-day.d.ts.map +1 -1
  30. package/dist/cpsat/rules/max-hours-day.js +12 -95
  31. package/dist/cpsat/rules/max-hours-day.js.map +1 -1
  32. package/dist/cpsat/rules/max-hours-week.d.ts +14 -34
  33. package/dist/cpsat/rules/max-hours-week.d.ts.map +1 -1
  34. package/dist/cpsat/rules/max-hours-week.js +12 -103
  35. package/dist/cpsat/rules/max-hours-week.js.map +1 -1
  36. package/dist/cpsat/rules/max-shifts-day.d.ts +14 -39
  37. package/dist/cpsat/rules/max-shifts-day.d.ts.map +1 -1
  38. package/dist/cpsat/rules/max-shifts-day.js +14 -106
  39. package/dist/cpsat/rules/max-shifts-day.js.map +1 -1
  40. package/dist/cpsat/rules/min-consecutive-days.d.ts +12 -13
  41. package/dist/cpsat/rules/min-consecutive-days.d.ts.map +1 -1
  42. package/dist/cpsat/rules/min-consecutive-days.js +11 -12
  43. package/dist/cpsat/rules/min-consecutive-days.js.map +1 -1
  44. package/dist/cpsat/rules/min-hours-day.d.ts +12 -13
  45. package/dist/cpsat/rules/min-hours-day.d.ts.map +1 -1
  46. package/dist/cpsat/rules/min-hours-day.js +11 -12
  47. package/dist/cpsat/rules/min-hours-day.js.map +1 -1
  48. package/dist/cpsat/rules/min-hours-week.d.ts +13 -13
  49. package/dist/cpsat/rules/min-hours-week.d.ts.map +1 -1
  50. package/dist/cpsat/rules/min-hours-week.js +10 -14
  51. package/dist/cpsat/rules/min-hours-week.js.map +1 -1
  52. package/dist/cpsat/rules/min-rest-between-shifts.d.ts +13 -14
  53. package/dist/cpsat/rules/min-rest-between-shifts.d.ts.map +1 -1
  54. package/dist/cpsat/rules/min-rest-between-shifts.js +11 -12
  55. package/dist/cpsat/rules/min-rest-between-shifts.js.map +1 -1
  56. package/dist/cpsat/rules/resolver.d.ts +2 -2
  57. package/dist/cpsat/rules/resolver.d.ts.map +1 -1
  58. package/dist/cpsat/rules/resolver.js +55 -30
  59. package/dist/cpsat/rules/resolver.js.map +1 -1
  60. package/dist/cpsat/rules/scope.types.d.ts +267 -0
  61. package/dist/cpsat/rules/scope.types.d.ts.map +1 -0
  62. package/dist/cpsat/rules/scope.types.js +325 -0
  63. package/dist/cpsat/rules/scope.types.js.map +1 -0
  64. package/dist/cpsat/rules/time-off.d.ts +21 -25
  65. package/dist/cpsat/rules/time-off.d.ts.map +1 -1
  66. package/dist/cpsat/rules/time-off.js +20 -110
  67. package/dist/cpsat/rules/time-off.js.map +1 -1
  68. package/dist/cpsat/semantic-time.d.ts +2 -0
  69. package/dist/cpsat/semantic-time.d.ts.map +1 -1
  70. package/dist/cpsat/semantic-time.js +2 -4
  71. package/dist/cpsat/semantic-time.js.map +1 -1
  72. package/dist/cpsat/types.d.ts +22 -6
  73. package/dist/cpsat/types.d.ts.map +1 -1
  74. package/dist/cpsat/utils.d.ts +1 -1
  75. package/dist/cpsat/utils.js +1 -1
  76. package/dist/cpsat/validation-reporter.js +1 -1
  77. package/dist/cpsat/validation-reporter.js.map +1 -1
  78. package/dist/datetime.utils.d.ts +14 -14
  79. package/dist/datetime.utils.d.ts.map +1 -1
  80. package/dist/datetime.utils.js +26 -27
  81. package/dist/datetime.utils.js.map +1 -1
  82. package/dist/index.d.ts +4 -3
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -2
  85. package/dist/index.js.map +1 -1
  86. package/dist/llms.d.ts +1 -1
  87. package/dist/llms.d.ts.map +1 -1
  88. package/dist/llms.js +1 -1
  89. package/dist/llms.js.map +1 -1
  90. package/dist/testing/index.d.ts +1 -1
  91. package/dist/testing/index.js +1 -1
  92. package/dist/testing/solver-container.js +3 -1
  93. package/dist/testing/solver-container.js.map +1 -1
  94. package/dist/types.d.ts +18 -20
  95. package/dist/types.d.ts.map +1 -1
  96. package/llms.txt +516 -263
  97. package/package.json +25 -25
  98. package/src/client.types.ts +58 -0
  99. package/src/cpsat/model-builder.ts +19 -7
  100. package/src/cpsat/response.ts +12 -3
  101. package/src/cpsat/rules/assign-together.ts +7 -0
  102. package/src/cpsat/rules/employee-assignment-priority.ts +28 -128
  103. package/src/cpsat/rules/location-preference.ts +24 -17
  104. package/src/cpsat/rules/max-consecutive-days.ts +19 -15
  105. package/src/cpsat/rules/max-hours-day.ts +29 -119
  106. package/src/cpsat/rules/max-hours-week.ts +42 -135
  107. package/src/cpsat/rules/max-shifts-day.ts +31 -130
  108. package/src/cpsat/rules/min-consecutive-days.ts +19 -15
  109. package/src/cpsat/rules/min-hours-day.ts +19 -15
  110. package/src/cpsat/rules/min-hours-week.ts +28 -26
  111. package/src/cpsat/rules/min-rest-between-shifts.ts +21 -17
  112. package/src/cpsat/rules/resolver.ts +66 -45
  113. package/src/cpsat/rules/scope.types.ts +534 -0
  114. package/src/cpsat/rules/time-off.ts +48 -145
  115. package/src/cpsat/semantic-time.ts +10 -8
  116. package/src/cpsat/types.ts +22 -6
  117. package/src/cpsat/utils.ts +1 -1
  118. package/src/cpsat/validation-reporter.ts +1 -1
  119. package/src/datetime.utils.ts +27 -29
  120. package/src/index.ts +11 -7
  121. package/src/llms.ts +1 -1
  122. package/src/testing/index.ts +1 -1
  123. package/src/testing/solver-container.ts +3 -3
  124. package/src/types.ts +27 -31
  125. package/dist/cpsat/rules/scoping.d.ts +0 -129
  126. package/dist/cpsat/rules/scoping.d.ts.map +0 -1
  127. package/dist/cpsat/rules/scoping.js +0 -190
  128. package/dist/cpsat/rules/scoping.js.map +0 -1
  129. package/src/cpsat/rules/scoping.ts +0 -340
package/dist/llms.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * LLM-friendly API documentation for dabke
3
3
  */
4
- export declare const apiDocs = "# dabke\n\n> Scheduling library powered by constraint programming (CP-SAT)\n\nScheduling library powered by constraint programming (CP-SAT).\n\nDefine teams, shifts, coverage, and rules \u2014 dabke turns them\ninto an optimized schedule.\n\n## Core Concepts\n\n**ModelBuilder**: Creates the constraint programming model from your team,\nshift patterns, coverage requirements, and rules.\n\n**Semantic Time**: Flexible time period definitions that can vary by day or date.\n- `{ name: \"morning\", startTime: { hours: 8 }, endTime: { hours: 12 } }`\n- The same semantic name can map to different times based on context\n- Enables business-friendly scheduling: \"Need 3 waiters during lunch_rush\"\n\n**Rules System**: Translate business requirements into scheduling constraints.\n- 12 built-in rules: hours limits, time-off, rest periods, prioritization, etc.\n- Scoping: Apply rules globally, per person, per role, or per time period\n- Priority levels: MANDATORY (hard constraint) vs LOW/MEDIUM/HIGH (soft preferences)\n\n---\n\n# CP-SAT Scheduling API\n\nCore scheduling types for the CP-SAT solver.\n\n## Interfaces\n\n### TimeOfDay\nTime of day representation (hours and minutes, with optional seconds/nanos).\n\nUsed for defining shift start/end times and semantic time boundaries.\nHours are in 24-hour format (0-23).\n\n**Example:**\n```typescript\nconst morningStart: TimeOfDay = {\n hours: 9,\n minutes: 0\n};\n\nconst afternoonEnd: TimeOfDay = {\n hours: 17,\n minutes: 30\n};\n```\n\n**Properties:**\n- `hours: number`\n- `minutes: number`\n- `seconds?: number | undefined`\n- `nanos?: number | undefined`\n\n### CalendarDate\nCalendar date representation (year, month, day).\n\n**Example:**\n```typescript\nconst christmas: CalendarDate = {\n year: 2025,\n month: 12,\n day: 25\n};\n```\n\n**Properties:**\n- `year: number`\n- `month: number`\n- `day: number`\n\n### TimeHorizon\nTime horizon defining the start and end dates for scheduling.\n\nSpecifies the date range over which the schedule should be generated.\nThe range is inclusive of start date and exclusive of end date.\n\n**Example:**\n```typescript\n// One week schedule starting Monday, March 3, 2025\nconst horizon: TimeHorizon = {\n start: new Date('2025-03-03'), // Monday\n end: new Date('2025-03-10') // Following Monday (exclusive)\n};\n```\n\n**Properties:**\n- `start: Date`\n- `end: Date`\n\n### DateTimeComponents\n**Properties:**\n- `year?: number | undefined`\n- `month?: number | undefined`\n- `day?: number | undefined`\n- `hours?: number | undefined`\n- `minutes?: number | undefined`\n- `seconds?: number | undefined`\n- `nanos?: number | undefined`\n\n### DateTimeRange\nRepresents a time range with start and end DateTimes.\nUsed for checking overlaps and scheduling constraints.\n\n**Properties:**\n- `start: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n- `end: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n### SolverClient\n**Properties:**\n- `solve: (request: SolverRequest, options?: { signal?: AbortSignal; }) => Promise<SolverResponse>`\n- `health?: (() => Promise<void>) | undefined`\n\n### ModelBuilderConfig\nConfiguration for ModelBuilder.\n\n**Example:**\nDate range with day-of-week filtering (restaurant closed Mon/Tue)\n```typescript\nconst config: ModelBuilderConfig = {\nemployees: [...],\nshiftPatterns: [...],\ncoverage: [...],\nschedulingPeriod: {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],\n},\n};\n```\n\n**Properties:**\n- `employees: SchedulingEmployee[]`\n- `shiftPatterns: ShiftPattern[]`\n- `schedulingPeriod: { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }` - Defines when scheduling should occur. Can specify either a date range\n(with optional day-of-week filtering) or a list of specific dates.\n- `coverage: CoverageRequirement[]`\n- `rules?: CompilationRule[] | undefined` - Pre-compiled rules; use this for custom rules that are not part of the registry.\n- `ruleConfigs?: CpsatRuleConfigEntry[] | undefined` - Named rule configurations that will be compiled using the provided rule factories.\n- `ruleFactories?: CpsatRuleFactories | undefined` - Rule factories to use when compiling ruleConfigs. Defaults to built-in CP-SAT rules.\n- `reporter?: ValidationReporter | undefined` - Optional validation reporter for diagnostics.\n\n### CompilationResult\n**Properties:**\n- `request: { variables: ({ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; })[]; constraints: ({ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; })[]; objective?: { sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; } | undefined; options?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined; }`\n- `validation: ScheduleValidation`\n- `canSolve: boolean`\n\n### CompilationRule\n**Properties:**\n- `compile: (builder: ModelBuilder) => void`\n- `validate?: ((assignments: ResolvedShiftAssignment[], reporter: ValidationReporter, context: RuleValidationContext) => void) | undefined`\n\n### RuleValidationContext\nContext provided to rules during post-solve validation.\n\n**Properties:**\n- `employees: SchedulingEmployee[]`\n- `days: string[]`\n- `shiftPatterns: ShiftPattern[]`\n\n### ShiftAssignment\nA shift assignment extracted from the solver response.\n\n**Properties:**\n- `employeeId: string`\n- `shiftPatternId: string`\n- `day: string`\n\n### ResolvedShiftAssignment\nA shift assignment with resolved times.\n\n**Properties:**\n- `employeeId: string`\n- `day: string`\n- `startTime: TimeOfDay`\n- `endTime: TimeOfDay`\n\n### SolverResult\nParsed solver result with assignments and metadata.\n\n**Properties:**\n- `status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"`\n- `assignments: ShiftAssignment[]`\n- `statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined`\n- `error?: string | undefined`\n\n### CpsatRuleRegistry\n**Properties:**\n- `\"assign-together\": { groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n- `\"employee-assignment-priority\": { preference: \"high\" | \"low\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n- `\"location-preference\": { locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"max-consecutive-days\": { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"max-hours-day\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n- `\"max-hours-week\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n- `\"max-shifts-day\": { shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n- `\"min-consecutive-days\": { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"min-hours-day\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"min-hours-week\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"min-rest-between-shifts\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n- `\"time-off\": { priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n### SemanticTimeDef\nBase definition for a semantic time period.\n\n**Properties:**\n- `startTime: TimeOfDay`\n- `endTime: TimeOfDay`\n\n### SemanticTimeVariant\nVariant of a semantic time that applies to specific days or dates.\n\n**Properties:**\n- `days?: DayOfWeek[] | undefined` - Apply this variant only on these days of the week\n- `dates?: string[] | undefined` - Apply this variant only on these specific dates (YYYY-MM-DD)\n\n### SemanticTimeContext\nResult of defineSemanticTimes - provides type-safe coverage function.\n\n**Properties:**\n- `defs: { [P in S]: SemanticTimeEntry; }` - The semantic time definitions\n- `coverage: (reqs: MixedCoverageRequirement<S>[]) => MixedCoverageRequirement<S>[]` - Create coverage requirements with type-safe semantic time names.\nAccepts both semantic references and concrete one-off requirements.\n- `resolve: (reqs: MixedCoverageRequirement<S>[], days: string[]) => CoverageRequirement[]` - Resolve all coverage requirements to concrete CoverageRequirement[]\nfor the given days in the scheduling horizon.\n\n### SchedulingEmployee\n**Properties:**\n- `id: string`\n- `roleIds: string[]`\n- `skillIds?: string[] | undefined`\n\n### ShiftPattern\nA shift pattern defines WHEN people can work \u2014 the time slots available for assignment.\n\nShift patterns are templates that repeat across all scheduling days. The solver assigns\nteam members to these patterns based on coverage requirements and constraints.\n\n**Example:**\n// Simple venue: one shift type, anyone can work it\nconst patterns: ShiftPattern[] = [\n { id: \"day\", startTime: { hours: 9 }, endTime: { hours: 17 } }\n];\n\n**Example:**\n// Restaurant: different shifts for different roles\nconst patterns: ShiftPattern[] = [\n { id: \"kitchen_morning\", startTime: { hours: 6 }, endTime: { hours: 14 }, roleIds: [\"chef\", \"prep_cook\"] },\n { id: \"floor_lunch\", startTime: { hours: 11 }, endTime: { hours: 15 }, roleIds: [\"waiter\", \"host\"] },\n];\n\n**Properties:**\n- `id: string` - Unique identifier for this shift pattern.\nUsed in assignments and rule configurations.\n- `roleIds?: [string, ...string[]] | undefined` - Restricts who can be assigned to this shift based on their roles.\n\n- If omitted: anyone can work this shift\n- If provided: only team members whose roleIds overlap with this list can be assigned\n\nMost venues have the same shifts for everyone and don't need this.\nUse it when different roles have different schedules (e.g., kitchen staff starts\nearlier than floor staff).\n- `daysOfWeek?: DayOfWeek[] | undefined` - Restricts which days of the week this shift pattern can be used.\n\n- If omitted: shift can be used on any day\n- If provided: shift can only be assigned on the specified days\n\n**Example:**\n```typescript\n// Saturday-only short shift\n{ id: \"saturday_shift\", startTime: t(9), endTime: t(14), daysOfWeek: [\"saturday\"] }\n\n// Weekday-only full shift\n{ id: \"full_shift\", startTime: t(9), endTime: t(18), daysOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"] }\n```\n- `locationId?: string | undefined` - Physical location where this shift takes place.\nUsed for multi-location scheduling and location-based constraints.\n- `startTime: TimeOfDay` - When the shift starts (e.g., { hours: 9, minutes: 0 } for 9:00 AM)\n- `endTime: TimeOfDay` - When the shift ends (e.g., { hours: 17, minutes: 30 } for 5:30 PM)\n\n### TimeInterval\n**Properties:**\n- `day: string`\n- `startTime: TimeOfDay`\n- `endTime: TimeOfDay`\n\n### ModelBuilderOptions\n**Properties:**\n- `weekStartsOn?: DayOfWeek | undefined`\n- `solverOptions?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined`\n- `coverageBucketMinutes?: number | undefined` - Bucket size used when translating coverage requirements into time-indexed constraints.\nSmaller buckets are more accurate but increase the number of constraints.\n- `fairDistribution?: boolean | undefined` - Whether to enable fair distribution of shifts across team members.\n\nWhen enabled (default), the solver minimizes the maximum number of shifts\nany single person works, ensuring work is distributed evenly. Each person\nworks between floor(total/n) and ceil(total/n) shifts.\n\nDisable this if you want other rules (like employee-assignment-priority)\nto have full control over shift distribution.\n\n### ValidationReporter\n**Properties:**\n- `excludeFromCoverage: (exclusion: CoverageExclusion) => void`\n- `reportCoverageError: (error: Omit<CoverageError, \"type\" | \"id\">) => void`\n- `reportRuleError: (error: Omit<RuleError, \"type\" | \"id\">) => void`\n- `reportSolverError: (reason: string) => void`\n- `reportCoverageViolation: (violation: Omit<CoverageViolation, \"type\" | \"id\">) => void`\n- `reportRuleViolation: (violation: Omit<RuleViolation, \"type\" | \"id\">) => void`\n- `reportCoveragePassed: (passed: Omit<CoveragePassed, \"type\" | \"id\">) => void`\n- `reportRulePassed: (passed: Omit<RulePassed, \"type\" | \"id\">) => void`\n- `trackConstraint: (constraint: TrackedConstraint) => void`\n- `hasErrors: () => boolean`\n- `getValidation: () => ScheduleValidation`\n- `getExclusions: () => CoverageExclusion[]`\n- `analyzeSolution: (response: SolverResponse) => void`\n\n### TrackedConstraint\n**Properties:**\n- `id: string`\n- `type: \"coverage\" | \"rule\"`\n- `rule?: string | undefined`\n- `description: string`\n- `targetValue: number`\n- `comparator: \"<=\" | \">=\"`\n- `day?: string | undefined`\n- `timeSlot?: string | undefined`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `context: ValidationContext`\n- `groupKey?: GroupKey | undefined`\n\n### CoverageExclusion\nCoverage exclusion - indicates a team member is unavailable for coverage during a time period.\nUsed during compile-time to determine coverage feasibility.\n\n**Properties:**\n- `employeeId: string`\n- `day: string`\n- `startTime?: TimeOfDay | undefined`\n- `endTime?: TimeOfDay | undefined`\n\n### ScheduleValidation\n**Properties:**\n- `errors: readonly ScheduleError[]`\n- `violations: readonly ScheduleViolation[]`\n- `passed: readonly SchedulePassed[]`\n\n### CoverageError\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `reason: string`\n- `suggestions?: readonly string[] | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### CoverageViolation\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `targetCount: number`\n- `actualCount: number`\n- `shortfall: number`\n- `groupKey?: GroupKey | undefined`\n\n### CoveragePassed\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `description: string`\n- `groupKey?: GroupKey | undefined`\n\n### RuleError\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `reason: string`\n- `context: ValidationContext`\n- `suggestions?: readonly string[] | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### RuleViolation\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `reason: string`\n- `context: ValidationContext`\n- `shortfall?: number | undefined`\n- `overflow?: number | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### RulePassed\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `description: string`\n- `context: ValidationContext`\n- `groupKey?: GroupKey | undefined`\n\n### SolverError\n**Properties:**\n- `id: string`\n- `type: \"solver\"`\n- `reason: string`\n\n### ValidationContext\nContext shared across validation results for grouping/display.\n\n**Properties:**\n- `days?: string[] | undefined`\n- `timeSlots?: string[] | undefined`\n- `employeeIds?: string[] | undefined`\n\n### ValidationSummary\nSummary of validation items grouped by their source instruction.\nUse `summarizeValidation()` to create these from a ScheduleValidation.\n\n**Properties:**\n- `groupKey: string & { readonly [GroupKeyBrand]: never; }`\n- `type: \"coverage\" | \"rule\"`\n- `description: string`\n- `days: readonly string[]`\n- `status: \"passed\" | \"partial\" | \"failed\"`\n- `passedCount: number`\n- `violatedCount: number`\n- `errorCount: number`\n\n### CoverageValidationResult\nResult of coverage role validation.\n\n**Properties:**\n- `valid: boolean`\n- `unknownRoles: string[]` - Role IDs used in coverage that don't match any team member\n- `knownRoles: string[]` - Role IDs used in coverage that match team members\n\n### SkillValidationResult\nResult of coverage skill validation.\n\n**Properties:**\n- `valid: boolean`\n- `unknownSkills: string[]` - Skill IDs used in coverage that don't match any team member\n- `knownSkills: string[]` - Skill IDs used in coverage that match team members\n\n### CoverageConfigValidationResult\nCombined validation result for coverage requirements.\n\n**Properties:**\n- `valid: boolean`\n- `roles: CoverageValidationResult`\n- `skills: SkillValidationResult`\n- `errors: string[]` - Human-readable error messages\n\n## Type Aliases\n\n### DayOfWeek\n```typescript\n\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"\n```\n\n\n### DateTime\nDate and time representation supporting both UTC offset and timezone-aware formats.\n\nCan be specified either with a UTC offset (e.g., \"-08:00\") or with a timezone ID\n(e.g., \"America/Los_Angeles\").\n\n```typescript\nDateTimeWithUtcOffset | DateTimeWithTimeZone\n```\n\n\n### SchedulingPeriod\nDefines a scheduling period either as a date range or specific dates.\n\nUse this to specify when scheduling should occur. This is more expressive\nthan a simple list of days because it can filter by day-of-week.\n\n**Example:**\nDate range with day-of-week filtering (restaurant closed Mon/Tue)\n```typescript\nconst period: SchedulingPeriod = {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],\n};\n```\n\n**Example:**\nDate range for all days\n```typescript\nconst period: SchedulingPeriod = {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\n};\n```\n\n**Example:**\nSpecific dates (non-contiguous or custom selection)\n```typescript\nconst period: SchedulingPeriod = {\nspecificDates: ['2025-02-05', '2025-02-07', '2025-02-10'],\n};\n```\n\n```typescript\n{ dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }\n```\n\n\n### SolverRequest\n```typescript\n{ variables: ({ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; })[]; constraints: ({ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; })[]; objective?: { sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; } | undefined; options?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined; }\n```\n\n\n### SolverResponse\n```typescript\n{ status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"; values?: Record<string, number> | undefined; statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined; error?: string | undefined; solutionInfo?: string | undefined; softViolations?: { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }[] | undefined; }\n```\n\n\n### SolverVariable\n```typescript\n{ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; }\n```\n\n\n### SolverConstraint\n```typescript\n{ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; }\n```\n\n\n### SolverTerm\n```typescript\n{ var: string; coeff: number; }\n```\n\n\n### SolverObjective\n```typescript\n{ sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; }\n```\n\n\n### SolverStatus\n```typescript\n\"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"\n```\n\n\n### SoftConstraintViolation\n```typescript\n{ constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }\n```\n\n\n### FetcherLike\n```typescript\n((input: string | URL | Request, init?: RequestInit) => Promise<Response>) | { fetch: typeof fetch; }\n```\n\n\n### AssignTogetherConfig\n```typescript\n{ groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }\n```\n\n\n### EmployeeAssignmentPriorityConfig\n```typescript\n{ preference: \"high\" | \"low\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }\n```\n\n\n### LocationPreferenceConfig\n```typescript\n{ locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### MaxConsecutiveDaysConfig\n```typescript\n{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### MaxHoursDayConfig\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }\n```\n\n\n### MaxHoursWeekConfig\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }\n```\n\n\n### MaxShiftsDayConfig\n```typescript\n{ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }\n```\n\n\n### MinConsecutiveDaysConfig\n```typescript\n{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### MinHoursDayConfig\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### MinHoursWeekConfig\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### MinRestBetweenShiftsConfig\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }\n```\n\n\n### TimeOffConfig\n```typescript\n{ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }\n```\n\n\n### CpsatRuleName\n```typescript\nkeyof CpsatRuleRegistry\n```\n\n\n### CpsatRuleConfigEntry\n```typescript\n{ name: K; config: CpsatRuleRegistry[K]; }\n```\n\n\n### CpsatRuleFactories\n```typescript\n{ [ruleName: string]: CreateCpsatRuleFunction<any>; }\n```\n\n\n### BuiltInCpsatRuleFactories\n```typescript\n[Complex type: __type]\n```\n\n\n### CreateCpsatRuleFunction\n```typescript\n(config: TConfig) => CompilationRule\n```\n\n\n### CpsatRuleRegistryFromFactories\n```typescript\n{ [K in keyof F]: InferCpsatRuleConfig<F[K]>; }\n```\n\n\n### SemanticTimeEntry\nA semantic time can be a simple definition (applies every day)\nor an array of variants with different times for different days/dates.\n\n```typescript\nSemanticTimeDef | SemanticTimeVariant[]\n```\n\n\n### SemanticCoverageRequirement\nCoverage requirement that references a semantic time by name.\nType-safe: S is constrained to known semantic time names.\n\nThis is a discriminated union enforcing at compile time that at least\none of `roleIds` or `skillIds` must be provided.\n\n```typescript\nRoleBasedSemanticCoverageRequirement<S> | SkillBasedSemanticCoverageRequirement<S>\n```\n\n\n### ConcreteCoverageRequirement\nConcrete coverage requirement with explicit day and times.\nUsed for one-off requirements that don't fit a semantic time.\n\nThis is a discriminated union enforcing at compile time that at least\none of `roleIds` or `skillIds` must be provided.\n\n```typescript\nRoleBasedConcreteCoverageRequirement | SkillBasedConcreteCoverageRequirement\n```\n\n\n### MixedCoverageRequirement\nUnion type for coverage - either semantic (type-safe) or concrete.\n\n```typescript\nConcreteCoverageRequirement | SemanticCoverageRequirement<S>\n```\n\n\n### Employee\n```typescript\nSchedulingEmployee\n```\n\n\n### CoverageRequirement\nDefines staffing needs for a specific time period.\n\nThis is a discriminated union that enforces at compile time that at least\none of `roleIds` or `skillIds` must be provided:\n\n- Role-based: `{ roleIds: [\"waiter\"], ... }` - anyone with ANY of these roles (OR logic)\n- Role + skill: `{ roleIds: [\"waiter\"], skillIds: [\"senior\"], ... }` - role AND skills\n- Skill-only: `{ skillIds: [\"keyholder\"], ... }` - any role with ALL skills (AND logic)\n\n**Example:**\n// Need 2 waiters during lunch (role-based)\n{ day: \"2024-01-01\", startTime: { hours: 11 }, endTime: { hours: 14 }, roleIds: [\"waiter\"], targetCount: 2, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 manager OR supervisor during service (OR logic on roles)\n{ day: \"2024-01-01\", startTime: { hours: 11 }, endTime: { hours: 22 }, roleIds: [\"manager\", \"supervisor\"], targetCount: 1, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 keyholder for opening (skill-only, any role)\n{ day: \"2024-01-01\", startTime: { hours: 6 }, endTime: { hours: 8 }, skillIds: [\"keyholder\"], targetCount: 1, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 senior waiter for training shift (role + skill filter)\n{ day: \"2024-01-01\", startTime: { hours: 9 }, endTime: { hours: 17 }, roleIds: [\"waiter\"], skillIds: [\"senior\"], targetCount: 1, priority: \"HIGH\" }\n\n```typescript\nRoleBasedCoverageRequirement | SkillBasedCoverageRequirement\n```\n\n\n### Priority\n```typescript\n\"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"\n```\n\n\n### ScheduleError\n```typescript\nCoverageError | RuleError | SolverError\n```\n\n\n### ScheduleViolation\n```typescript\nCoverageViolation | RuleViolation\n```\n\n\n### SchedulePassed\n```typescript\nCoveragePassed | RulePassed\n```\n\n\n### GroupKey\nBranded type for validation group keys.\nGroups related validation items that originated from the same instruction.\n\n```typescript\nstring & { readonly [GroupKeyBrand]: never; }\n```\n\n\n### InferCpsatRuleConfig\n```typescript\nT extends CreateCpsatRuleFunction<infer Config> ? Config : never\n```\n\n\n### Term\n```typescript\n{ var: string; coeff: number; }\n```\n\n\n## Functions\n\n### dateToCalendarDate\nConverts a JavaScript Date to a CalendarDate\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `CalendarDate`\n\n\n### dateTimeToDate\nConverts a DateTime to a JavaScript Date\nInternal helper function\n\n**Parameters:**\n- `dateTime: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n**Returns:** `Date`\n\n\n### compareDateTimes\nCompares two DateTimes\nReturns:\n -1 if dateTime1 < dateTime2\n 0 if dateTime1 = dateTime2\n 1 if dateTime1 > dateTime2\n\n**Parameters:**\n- `dateTime1: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n- `dateTime2: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n**Returns:** `number`\n\n\n### toDayOfWeek\nHelper to get the day of week name from a Date (local time)\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n\n### toDayOfWeekUTC\nHelper to get the day of week name from a Date (UTC)\nUse this when working with date strings like \"2026-01-10\" that are timezone-agnostic.\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n\n### formatDateString\nFormats a date as YYYY-MM-DD string\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `string`\n\n\n### generateDays\nGenerates an array of day strings (YYYY-MM-DD) from a time horizon.\n\n**Example:**\n```typescript\nconst days = generateDays({\n start: new Date('2025-01-01'),\n end: new Date('2025-01-04')\n});\n// Returns: [\"2025-01-01\", \"2025-01-02\", \"2025-01-03\", \"2025-01-04\"]\n```\n\n**Parameters:**\n- `horizon: { start: Date; end: Date; }`\n\n**Returns:** `string[]`\n\n\n### splitPeriodIntoDays\nSplits a time period into consecutive day ranges.\n\nEach range represents a single calendar day within the period from start to end.\nThis is useful for rules that need to apply constraints on a per-day basis,\nsuch as maximum or minimum hours per day.\n\n**Example:**\n```typescript\nconst ranges = splitPeriodIntoDays({\n start: new Date('2025-01-01'),\n end: new Date('2025-01-03')\n});\n// Returns:\n// [\n// [Date('2025-01-01'), Date('2025-01-02')],\n// [Date('2025-01-02'), Date('2025-01-03')]\n// ]\n```\n\n**Parameters:**\n- `{ start, end }: { start: Date; end: Date; }`\n\n**Returns:** `[Date, Date][]`\n\n\n### splitPeriodIntoWeeks\nSplits a time period into consecutive week ranges.\n\nEach range represents a week period starting on the specified day of the week.\nThis is useful for rules that need to apply constraints on a per-week basis,\nsuch as maximum or minimum hours per week.\n\nThe first range starts at the provided start date (not necessarily on weekStartsOn).\nSubsequent ranges align to the weekStartsOn day. The last range's end date will be\nthe provided end date.\n\n**Example:**\n```typescript\nconst ranges = splitPeriodIntoWeeks({\n start: new Date('2025-01-01'), // Wednesday\n end: new Date('2025-01-15'),\n weekStartsOn: 'monday'\n});\n// Returns ranges starting from Jan 1 (Wed), then aligning to Mondays:\n// [\n// [Date('2025-01-01 Wed'), Date('2025-01-06 Mon')],\n// [Date('2025-01-06 Mon'), Date('2025-01-13 Mon')],\n// [Date('2025-01-13 Mon'), Date('2025-01-15 Wed')]\n// ]\n```\n\n**Parameters:**\n- `{\n start,\n end,\n weekStartsOn,\n}: { start: Date; end: Date; weekStartsOn: DayOfWeek; }`\n\n**Returns:** `[Date, Date][]`\n\n\n### dateTimeRangesOverlap\nChecks if two DateTime ranges overlap in both date and time.\nRanges overlap if they share any moment in time.\n\nTwo ranges overlap if: range1.start < range2.end AND range2.start < range1.end\n\n**Example:**\n```typescript\n// Same day, overlapping times (9-17 overlaps with 12-20)\ndateTimeRangesOverlap(\n {\n start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }\n },\n {\n start: { year: 2025, month: 6, day: 1, hours: 12, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 20, minutes: 0 }\n }\n); // true\n\n// Different days - no overlap\ndateTimeRangesOverlap(\n {\n start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }\n },\n {\n start: { year: 2025, month: 6, day: 2, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 2, hours: 17, minutes: 0 }\n }\n); // false\n\n// Works naturally with Shift objects\ndateTimeRangesOverlap(\n { start: shift1.startDateTime, end: shift1.endDateTime },\n { start: shift2.startDateTime, end: shift2.endDateTime }\n);\n```\n\n**Parameters:**\n- `range1: DateTimeRange`\n- `range2: DateTimeRange`\n\n**Returns:** `boolean`\n\n\n### daysBetween\nCalculates the number of complete days between two dates\n\n**Example:**\n```typescript\ndaysBetween(new Date('2025-01-01'), new Date('2025-01-05')); // 4\n```\n\n**Parameters:**\n- `start: Date`\n- `end: Date`\n\n**Returns:** `number`\n\n\n### resolveDaysFromPeriod\nComputes the list of day strings (YYYY-MM-DD) from a SchedulingPeriod.\n\nFor date ranges, generates all days between start and end (inclusive),\noptionally filtering to specific days of the week.\n\nFor specific dates, returns the dates as-is (sorted).\n\n**Example:**\nDate range with day-of-week filter\n```typescript\nconst days = resolveDaysFromPeriod({\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'friday'],\n});\n// Returns: ['2025-02-05', '2025-02-07'] (Wed and Fri only)\n```\n\n**Example:**\nDate range without filter\n```typescript\nconst days = resolveDaysFromPeriod({\ndateRange: { start: '2025-02-03', end: '2025-02-05' },\n});\n// Returns: ['2025-02-03', '2025-02-04', '2025-02-05']\n```\n\n**Example:**\nSpecific dates\n```typescript\nconst days = resolveDaysFromPeriod({\nspecificDates: ['2025-02-07', '2025-02-03', '2025-02-10'],\n});\n// Returns: ['2025-02-03', '2025-02-07', '2025-02-10'] (sorted)\n```\n\n**Parameters:**\n- `period: { dateRange: { start: string; end: string; }; daysOfWeek?: DayOfWeek[]; specificDates?: never; } | { specificDates: string[]; dateRange?: never; daysOfWeek?: never; }`\n\n**Returns:** `string[]`\n\n\n### parseSolverResponse\nExtracts shift assignments from solver response.\n\nParses variable names matching the pattern `assign:${employeeId}:${patternId}:${day}`\nand returns assignments where the variable value is 1 (true).\n\nIDs are validated by ModelBuilder to not contain colons,\nensuring unambiguous parsing.\n\n**Example:**\n```typescript\nconst response = await client.solve(request);\nconst result = parseSolverResponse(response);\n\nif (result.status === \"OPTIMAL\" || result.status === \"FEASIBLE\") {\n for (const assignment of result.assignments) {\n console.log(`${assignment.employeeId} works ${assignment.shiftPatternId} on ${assignment.day}`);\n }\n}\n```\n\n**Parameters:**\n- `response: { status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"; values?: Record<string, number> | undefined; statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined; error?: string | undefined; solutionInfo?: string | undefined; softViolations?: { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }[] | undefined; }`\n\n**Returns:** `SolverResult`\n\n\n### resolveAssignments\nResolves shift assignments to concrete times using shift patterns.\n\n**Example:**\n```typescript\nconst result = parseScheduleResult(response);\nconst resolved = resolveAssignments(result.assignments, shiftPatterns);\n\nfor (const shift of resolved) {\n console.log(`${shift.employeeId} works ${shift.day} from ${shift.startTime.hours}:${shift.startTime.minutes}`);\n}\n```\n\n**Parameters:**\n- `assignments: ShiftAssignment[]`\n- `shiftPatterns: ShiftPattern[]`\n\n**Returns:** `ResolvedShiftAssignment[]`\n\n\n### createAssignTogetherRule\nEncourages or enforces that team members in the group work the same shift patterns on a day.\nFor each pair of team members in the group, ensures they are assigned to the same shifts.\n\n**Example:**\n```ts\nconst rule = createAssignTogetherRule({\n groupEmployeeIds: [\"alice\", \"bob\", \"charlie\"],\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n\n**Returns:** `CompilationRule`\n\n\n### createEmployeeAssignmentPriorityRule\nAdds objective weight to prefer or avoid assigning team members.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nPrefer specific team members\n```ts\ncreateEmployeeAssignmentPriorityRule({\nemployeeIds: [\"alice\", \"bob\"],\npreference: \"high\",\n});\n```\n\n**Example:**\nAvoid assigning students on weekdays\n```ts\ncreateEmployeeAssignmentPriorityRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npreference: \"low\",\n});\n```\n\n**Example:**\nPrefer experienced staff on weekends\n```ts\ncreateEmployeeAssignmentPriorityRule({\nskillIds: [\"senior\"],\ndayOfWeek: [\"saturday\", \"sunday\"],\npreference: \"high\",\n});\n```\n\n**Parameters:**\n- `config: { preference: \"high\" | \"low\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createLocationPreferenceRule\nPrefers assigning a person to shift patterns matching a specific location.\n\n**Example:**\n```ts\nconst rule = createLocationPreferenceRule({\n locationId: \"terrace\",\n priority: \"HIGH\",\n employeeIds: [\"alice\"],\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxConsecutiveDaysRule\nLimits how many consecutive days a person can be assigned.\n\n**Example:**\n```ts\nconst rule = createMaxConsecutiveDaysRule({\n days: 5,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxHoursDayRule\nLimits how many hours a person can work in a single day.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nLimit everyone to 8 hours per day\n```ts\ncreateMaxHoursDayRule({\nhours: 8,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents limited to 4 hours on weekdays during term\n```ts\ncreateMaxHoursDayRule({\nroleIds: [\"student\"],\nhours: 4,\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxHoursWeekRule\nCaps total hours a person can work within each scheduling week.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nTime scoping filters which days within each week count toward the limit.\n\n**Example:**\nLimit everyone to 40 hours per week\n```ts\ncreateMaxHoursWeekRule({\nhours: 40,\npriority: \"HIGH\",\n});\n```\n\n**Example:**\nStudents limited to 20 hours during term time\n```ts\ncreateMaxHoursWeekRule({\nroleIds: [\"student\"],\nhours: 20,\nrecurringPeriods: [\n{ name: \"fall-term\", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },\n{ name: \"spring-term\", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },\n],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxShiftsDayRule\nLimits how many shifts a person can work in a single day.\n\nThis rule controls the maximum number of distinct shift assignments per day,\nregardless of shift duration. For limiting total hours worked, use `max-hours-day`.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nLimit to one shift per day (common for most schedules)\n```ts\ncreateMaxShiftsDayRule({\nshifts: 1,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nAllow up to two shifts per day for part-time workers\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"part-time\"],\nshifts: 2,\npriority: \"HIGH\",\n});\n```\n\n**Example:**\nStudents can work 2 shifts on weekends only\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"student\"],\nshifts: 2,\ndayOfWeek: [\"saturday\", \"sunday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: { shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMinConsecutiveDaysRule\nRequires that once a person starts working, they continue for a minimum\nnumber of consecutive days.\n\n**Example:**\n```ts\nconst rule = createMinConsecutiveDaysRule({\n days: 3,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMinHoursDayRule\nEnsures a person works at least a minimum number of hours per day.\n\n**Example:**\n```ts\nconst rule = createMinHoursDayRule({\n hours: 6,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMinHoursWeekRule\nEnforces a minimum total number of hours per scheduling week.\n\n**Example:**\n```ts\nconst rule = createMinHoursWeekRule({\n hours: 30,\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createMinRestBetweenShiftsRule\nEnforces a minimum rest period between any two shifts a person works.\n\n**Example:**\n```ts\nconst rule = createMinRestBetweenShiftsRule({\n hours: 10,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createTimeOffRule\nBlocks or penalizes assignments during specified time periods.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nOptionally supports partial-day time-off with startTime/endTime.\n\n**Example:**\nFull day vacation\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"alice\"],\ndateRange: { start: \"2024-02-01\", end: \"2024-02-05\" },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nEvery Wednesday afternoon off for students\n```ts\ncreateTimeOffRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"wednesday\"],\nstartTime: { hours: 14, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nSpecific date, partial day\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"bob\"],\nspecificDates: [\"2024-03-15\"],\nstartTime: { hours: 16, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: { priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n**Returns:** `CompilationRule`\n\n\n### createCpsatRuleFactory\nCreates a rule factory map, preventing overriding built-in rules.\n\n**Parameters:**\n- `factories: F`\n\n**Returns:** `F`\n\n\n### defineSemanticTimes\nDefine semantic times with type-safe names.\n\nReturns a context object that provides:\n- Type-safe coverage() function that only accepts defined semantic time names\n- resolve() function to expand semantic times to concrete requirements\n\n**Example:**\nBasic usage\n```typescript\nconst times = defineSemanticTimes({\nopening: { startTime: { hours: 6 }, endTime: { hours: 8 } },\nlunch: { startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 } },\nclosing: { startTime: { hours: 21 }, endTime: { hours: 23 } },\n});\n\nconst coverage = times.coverage([\n{ semanticTime: \"lunch\", roleId: \"server\", targetCount: 3 },\n{ semanticTime: \"opening\", roleId: \"keyholder\", targetCount: 1, priority: \"MANDATORY\" },\n// Type error: \"dinner\" is not a defined semantic time\n// { semanticTime: \"dinner\", roleId: \"server\", targetCount: 2 },\n]);\n```\n\n**Example:**\nVariants for different days\n```typescript\nconst times = defineSemanticTimes({\nlunch: [\n{ startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 }, days: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"] },\n{ startTime: { hours: 12 }, endTime: { hours: 15 }, days: [\"saturday\", \"sunday\"] },\n],\n});\n```\n\n**Example:**\nMixed semantic and concrete coverage\n```typescript\nconst coverage = times.coverage([\n{ semanticTime: \"lunch\", roleId: \"server\", targetCount: 3 },\n// One-off party - concrete time\n{ day: \"2026-01-14\", startTime: { hours: 15 }, endTime: { hours: 20 }, roleId: \"server\", targetCount: 5 },\n]);\n```\n\n**Parameters:**\n- `defs: T`\n\n**Returns:** `SemanticTimeContext<keyof T & string>`\n\n\n### isConcreteCoverage\nType guard to check if a requirement is concrete (has explicit day/times).\n\n**Parameters:**\n- `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`\n\n**Returns:** `boolean`\n\n\n### isSemanticCoverage\nType guard to check if a requirement is semantic (references a named time).\n\n**Parameters:**\n- `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`\n\n**Returns:** `boolean`\n\n\n### groupKey\nCreates a GroupKey from a description string.\nUse this to create keys that group related validation items together.\n\n**Example:**\n```typescript\nconst key = groupKey(\"2x waiter during lunch\");\ncoverage.groupKey = key;\n```\n\n**Parameters:**\n- `description: string`\n\n**Returns:** `string & { readonly [GroupKeyBrand]: never; }`\n\n\n### summarizeValidation\nAggregates validation items by their groupKey into summaries.\nThis is a pure function that doesn't modify the input.\n\nItems without a groupKey are grouped by their ID (ungrouped).\n\n**Example:**\n```typescript\nconst validation = reporter.getValidation();\nconst summaries = summarizeValidation(validation);\n// summaries[0] = {\n// groupKey: \"2x waiter during lunch\",\n// status: \"passed\",\n// passedCount: 180,\n// days: [\"2026-02-02\", \"2026-02-03\", ...]\n// }\n```\n\n**Parameters:**\n- `validation: ScheduleValidation`\n\n**Returns:** `readonly ValidationSummary[]`\n\n\n### validateCoverageRoles\nValidates that all roleIds used in coverage requirements match the team.\n\nThis catches a common LLM error where the model generates coverage requirements\nusing role names that don't match any team member's roleIds. Without this validation,\nsuch mismatches would result in valid but semantically wrong schedules (e.g.,\ncoverage requirements that no one can satisfy).\n\n**Example:**\n```typescript\nconst employees = [\n { id: \"alice\", roleIds: [\"cashier\"] },\n { id: \"bob\", roleIds: [\"stocker\"] },\n];\n\nconst coverage = [\n { roleId: \"cashier\", targetCount: 1, ... }, // OK\n { roleId: \"worker\", targetCount: 1, ... }, // Unknown role!\n];\n\nconst result = validateCoverageRoles(coverage, employees);\n// result.valid = false\n// result.unknownRoles = [\"worker\"]\n// result.knownRoles = [\"cashier\"]\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `CoverageValidationResult`\n\n\n### validateCoverageSkills\nValidates that all skillIds used in coverage requirements match the team.\n\nSimilar to role validation, this catches LLM hallucinations where skill names\nin coverage don't match any team member's skillIds.\n\n**Example:**\n```typescript\nconst employees = [\n { id: \"alice\", roleIds: [\"server\"], skillIds: [\"keyholder\"] },\n { id: \"bob\", roleIds: [\"server\"] },\n];\n\nconst coverage = [\n { skillIds: [\"keyholder\"], targetCount: 1, ... }, // OK\n { skillIds: [\"manager\"], targetCount: 1, ... }, // Unknown skill!\n];\n\nconst result = validateCoverageSkills(coverage, employees);\n// result.valid = false\n// result.unknownSkills = [\"manager\"]\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `SkillValidationResult`\n\n\n### validateCoverageConfig\nValidates coverage requirements against team roles and skills.\n\nThis is the primary validation function to call before building a scheduling model.\nIt checks both roles and skills, returning a combined result with error messages.\n\n**Example:**\n```typescript\nconst result = validateCoverageConfig(coverage, employees);\nif (!result.valid) {\n throw new Error(result.errors.join(\"; \"));\n}\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `CoverageConfigValidationResult`\n\n\n### addMinutesToDate\nAdds a number of minutes to a base date and returns a DateTime.\nTreats the base date as a reference point (typically midnight of horizon start),\nand the minutes parameter as absolute minutes from that point.\n\n**Example:**\n```typescript\n// Add 90 minutes from midnight\naddMinutesToDate(new Date('2025-01-01'), 90);\n// Returns: { year: 2025, month: 1, day: 1, hours: 1, minutes: 30 }\n\n// Add 1500 minutes (spans to next day)\naddMinutesToDate(new Date('2025-01-01'), 1500);\n// Returns: { year: 2025, month: 1, day: 2, hours: 1, minutes: 0 }\n```\n\n**Parameters:**\n- `baseDate: Date`\n- `minutes: number`\n\n**Returns:** `DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n\n### splitPoints\nReturns the points where a range should be split, filtered to within [start, end).\nAlways includes range start. Sorted ascending.\n\n**Parameters:**\n- `[start, end]: [number, number]`\n- `splitAt: number[]`\n\n**Returns:** `number[]`\n\n\n### parseDayString\nParse a day string (YYYY-MM-DD) to a UTC Date.\nUsed internally for day-of-week calculations and date comparisons.\n\n**Parameters:**\n- `day: string`\n\n**Returns:** `Date`\n\n\n### timeOfDayToMinutes\n**Parameters:**\n- `time: TimeOfDay`\n\n**Returns:** `number`\n\n\n### normalizeEndMinutes\n**Parameters:**\n- `startMinutes: number`\n- `endMinutes: number`\n\n**Returns:** `number`\n\n\n### priorityToPenalty\n**Parameters:**\n- `priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"`\n\n**Returns:** `number`\n\n\n### splitIntoWeeks\n**Parameters:**\n- `days: string[]`\n- `weekStartsOn: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n**Returns:** `string[][]`\n\n\n## Classes\n\n### ORSchedulingError\nError thrown when Google OR Tools scheduling API requests fail.\n\nContains the HTTP status code and raw response data from the API for debugging.\nCommon causes include infeasible constraints, invalid requests, or API unavailability.\n\n\n### HttpSolverClient\nGeneric HTTP client for the solver service.\n\n\n### ModelBuilder\nCompilation context that creates variables, constraints, and objectives\nand emits a `SolverRequest` for the Python CP-SAT solver service.\n\n\n### ValidationReporterImpl\n\n## Constants\n\n### DayOfWeekSchema\nZod schema for {@link DayOfWeek}.\nUseful for rule configs that need to accept a day-of-week string.\n\n**Type:** `z.ZodUnion<readonly [z.ZodLiteral<\"monday\">, z.ZodLiteral<\"tuesday\">, z.ZodLiteral<\"wednesday\">, z.ZodLiteral<\"thursday\">, z.ZodLiteral<\"friday\">, z.ZodLiteral<\"saturday\">, z.ZodLiteral<\"sunday\">]>`\n\n\n### SOLVER_STATUS\n**Type:** `{ readonly OPTIMAL: \"OPTIMAL\"; readonly FEASIBLE: \"FEASIBLE\"; readonly INFEASIBLE: \"INFEASIBLE\"; readonly TIMEOUT: \"TIMEOUT\"; readonly ERROR: \"ERROR\"; }`\n\n\n### SolverRequestSchema\n**Type:** `[Complex type: ZodObject]`\n\n\n### SolverResponseSchema\n**Type:** `z.ZodObject<{ status: z.ZodEnum<{ OPTIMAL: \"OPTIMAL\"; FEASIBLE: \"FEASIBLE\"; INFEASIBLE: \"INFEASIBLE\"; TIMEOUT: \"TIMEOUT\"; ERROR: \"ERROR\"; }>; values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; statistics: z.ZodOptional<z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>>; error: z.ZodOptional<z.ZodString>; solutionInfo: z.ZodOptional<z.ZodString>; softViolations: z.ZodOptional<z.ZodArray<z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>>>; }, z.core.$strip>`\n\n\n### builtInCpsatRuleFactories\n**Type:** `[Complex type: __type]`\n\n\n### OBJECTIVE_WEIGHTS\nStandard objective weights for the scheduling solver.\n\nThese weights define the relative importance of different objectives.\nHigher weights mean stronger preference. Rules can use these as reference\npoints when adding their own penalties.\n\nWeight hierarchy (highest to lowest priority):\n- SHIFT_ACTIVE (1000): Minimize number of active shift patterns\n- ASSIGNMENT_PREFERENCE (10): Per-assignment preference (e.g., prefer permanent staff)\n- FAIRNESS (5): Fair distribution of shifts across team members\n- ASSIGNMENT_BASE (1): Tiebreaker - minimize total assignments\n\n**Example:**\nUsing weights in a custom rule\n```ts\nimport { OBJECTIVE_WEIGHTS } from \"feasible\";\n\n// Prefer senior staff with same weight as employee-assignment-priority\nb.addPenalty(assignment, -OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);\n\n// Strong preference (2x normal)\nb.addPenalty(assignment, -2 * OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);\n```\n\n**Type:** `{ readonly SHIFT_ACTIVE: 1000; readonly ASSIGNMENT_PREFERENCE: 10; readonly FAIRNESS: 5; readonly ASSIGNMENT_BASE: 1; }`\n\n\n### DAY_OF_WEEK_MAP\n**Type:** `{ sunday: number; monday: number; tuesday: number; wednesday: number; thursday: number; friday: number; saturday: number; }`\n\n\n### SolverTermSchema\n**Type:** `z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>`\n\n\n### BoolVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool\">; name: z.ZodString; }, z.core.$strip>`\n\n\n### IntVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"int\">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>`\n\n\n### IntervalVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"interval\">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>`\n\n\n### SolverVariableSchema\n**Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<\"bool\">; name: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"int\">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"interval\">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>]>`\n\n\n### LinearConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">, z.ZodLiteral<\"==\">]>; rhs: z.ZodNumber; }, z.core.$strip>`\n\n\n### SoftLinearConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"soft_linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>`\n\n\n### ExactlyOneConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"exactly_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### AtMostOneConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"at_most_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### ImplicationConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"implication\">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>`\n\n\n### BoolOrConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool_or\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### BoolAndConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool_and\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### NoOverlapConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"no_overlap\">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### SolverConstraintSchema\n**Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<\"linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">, z.ZodLiteral<\"==\">]>; rhs: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"soft_linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"exactly_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"at_most_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"implication\">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"bool_or\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"bool_and\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"no_overlap\">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>]>`\n\n\n### SolverObjectiveSchema\n**Type:** `z.ZodObject<{ sense: z.ZodUnion<readonly [z.ZodLiteral<\"minimize\">, z.ZodLiteral<\"maximize\">]>; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; }, z.core.$strip>`\n\n\n### SolverOptionsSchema\n**Type:** `z.ZodObject<{ timeLimitSeconds: z.ZodOptional<z.ZodNumber>; solutionLimit: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`\n\n\n### SolverStatusSchema\n**Type:** `z.ZodEnum<{ OPTIMAL: \"OPTIMAL\"; FEASIBLE: \"FEASIBLE\"; INFEASIBLE: \"INFEASIBLE\"; TIMEOUT: \"TIMEOUT\"; ERROR: \"ERROR\"; }>`\n\n\n### SolverStatisticsSchema\n**Type:** `z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`\n\n\n### SoftConstraintViolationSchema\n**Type:** `z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>`\n\n\n### MINUTES_PER_DAY\n**Type:** `number`\n\n\n\n## Built-In Rules Reference\n\nRules are constraints and preferences you can apply to schedules using `ctx.addRule(ruleName, config)`.\n\n### assign-together\n\n\n\nEncourages or enforces that team members in the group work the same shift patterns on a day.\nFor each pair of team members in the group, ensures they are assigned to the same shifts.\n\n**Example:**\n```ts\nconst rule = createAssignTogetherRule({\n groupEmployeeIds: [\"alice\", \"bob\", \"charlie\"],\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n\n### employee-assignment-priority\n\n\n\nAdds objective weight to prefer or avoid assigning team members.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nPrefer specific team members\n```ts\ncreateEmployeeAssignmentPriorityRule({\nemployeeIds: [\"alice\", \"bob\"],\npreference: \"high\",\n});\n```\n\n**Example:**\nAvoid assigning students on weekdays\n```ts\ncreateEmployeeAssignmentPriorityRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npreference: \"low\",\n});\n```\n\n**Example:**\nPrefer experienced staff on weekends\n```ts\ncreateEmployeeAssignmentPriorityRule({\nskillIds: [\"senior\"],\ndayOfWeek: [\"saturday\", \"sunday\"],\npreference: \"high\",\n});\n```\n\n**Config:** `{ preference: \"high\" | \"low\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n### location-preference\n\n\n\nPrefers assigning a person to shift patterns matching a specific location.\n\n**Example:**\n```ts\nconst rule = createLocationPreferenceRule({\n locationId: \"terrace\",\n priority: \"HIGH\",\n employeeIds: [\"alice\"],\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### max-consecutive-days\n\n\n\nLimits how many consecutive days a person can be assigned.\n\n**Example:**\n```ts\nconst rule = createMaxConsecutiveDaysRule({\n days: 5,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### max-hours-day\n\n\n\nLimits how many hours a person can work in a single day.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nLimit everyone to 8 hours per day\n```ts\ncreateMaxHoursDayRule({\nhours: 8,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents limited to 4 hours on weekdays during term\n```ts\ncreateMaxHoursDayRule({\nroleIds: [\"student\"],\nhours: 4,\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n### max-hours-week\n\n\n\nCaps total hours a person can work within each scheduling week.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nTime scoping filters which days within each week count toward the limit.\n\n**Example:**\nLimit everyone to 40 hours per week\n```ts\ncreateMaxHoursWeekRule({\nhours: 40,\npriority: \"HIGH\",\n});\n```\n\n**Example:**\nStudents limited to 20 hours during term time\n```ts\ncreateMaxHoursWeekRule({\nroleIds: [\"student\"],\nhours: 20,\nrecurringPeriods: [\n{ name: \"fall-term\", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },\n{ name: \"spring-term\", startMonth: 1, startDay: 15, endMonth: 5, endDay: 31 },\n],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n### max-shifts-day\n\n\n\nLimits how many shifts a person can work in a single day.\n\nThis rule controls the maximum number of distinct shift assignments per day,\nregardless of shift duration. For limiting total hours worked, use `max-hours-day`.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\n\n**Example:**\nLimit to one shift per day (common for most schedules)\n```ts\ncreateMaxShiftsDayRule({\nshifts: 1,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nAllow up to two shifts per day for part-time workers\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"part-time\"],\nshifts: 2,\npriority: \"HIGH\",\n});\n```\n\n**Example:**\nStudents can work 2 shifts on weekends only\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"student\"],\nshifts: 2,\ndayOfWeek: [\"saturday\", \"sunday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `{ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n### min-consecutive-days\n\n\n\nRequires that once a person starts working, they continue for a minimum\nnumber of consecutive days.\n\n**Example:**\n```ts\nconst rule = createMinConsecutiveDaysRule({\n days: 3,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### min-hours-day\n\n\n\nEnsures a person works at least a minimum number of hours per day.\n\n**Example:**\n```ts\nconst rule = createMinHoursDayRule({\n hours: 6,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### min-hours-week\n\n\n\nEnforces a minimum total number of hours per scheduling week.\n\n**Example:**\n```ts\nconst rule = createMinHoursWeekRule({\n hours: 30,\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### min-rest-between-shifts\n\n\n\nEnforces a minimum rest period between any two shifts a person works.\n\n**Example:**\n```ts\nconst rule = createMinRestBetweenShiftsRule({\n hours: 10,\n priority: \"MANDATORY\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; }`\n\n### time-off\n\n\n\nBlocks or penalizes assignments during specified time periods.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nOptionally supports partial-day time-off with startTime/endTime.\n\n**Example:**\nFull day vacation\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"alice\"],\ndateRange: { start: \"2024-02-01\", end: \"2024-02-05\" },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nEvery Wednesday afternoon off for students\n```ts\ncreateTimeOffRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"wednesday\"],\nstartTime: { hours: 14, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nSpecific date, partial day\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"bob\"],\nspecificDates: [\"2024-03-15\"],\nstartTime: { hours: 16, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `{ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; employeeIds?: string[] | undefined; roleIds?: string[] | undefined; skillIds?: string[] | undefined; dateRange?: { start: string; end: string; } | undefined; specificDates?: string[] | undefined; dayOfWeek?: (\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\")[] | undefined; recurringPeriods?: { name: string; startMonth: number; startDay: number; endMonth: number; endDay: number; }[] | undefined; }`\n\n";
4
+ export declare const apiDocs = "# dabke\n\n> Scheduling library powered by constraint programming (CP-SAT)\n\nScheduling library powered by constraint programming (CP-SAT).\n\nDefine teams, shifts, coverage, and rules. dabke turns them\ninto an optimized schedule.\n\n## Core Concepts\n\n**ModelBuilder**: Creates the constraint programming model from your team,\nshift patterns, coverage requirements, and rules.\n\n**Semantic Time**: Flexible time period definitions that can vary by day or date.\n- `{ name: \"morning\", startTime: { hours: 8 }, endTime: { hours: 12 } }`\n- The same semantic name can map to different times based on context\n- Enables business-friendly scheduling: \"Need 3 waiters during lunch_rush\"\n\n**Rules System**: Translate business requirements into scheduling constraints.\n- 12 built-in rules: hours limits, time-off, rest periods, prioritization, etc.\n- Scoping: Apply rules globally, per person, per role, or per time period\n- Priority levels: MANDATORY (hard constraint) vs LOW/MEDIUM/HIGH (soft preferences)\n\n---\n\n# CP-SAT Scheduling API\n\nCore scheduling types for the CP-SAT solver.\n\n## Interfaces\n\n### TimeOfDay\nTime of day representation (hours and minutes, with optional seconds/nanos).\n\nUsed for defining shift start/end times and semantic time boundaries.\nHours are in 24-hour format (0-23).\n\n**Example:**\n```typescript\nconst morningStart: TimeOfDay = {\n hours: 9,\n minutes: 0\n};\n\nconst afternoonEnd: TimeOfDay = {\n hours: 17,\n minutes: 30\n};\n```\n\n**Properties:**\n- `hours: number`\n- `minutes: number`\n- `seconds?: number | undefined`\n- `nanos?: number | undefined`\n\n### CalendarDate\nCalendar date representation (year, month, day).\n\n**Example:**\n```typescript\nconst christmas: CalendarDate = {\n year: 2025,\n month: 12,\n day: 25\n};\n```\n\n**Properties:**\n- `year: number`\n- `month: number`\n- `day: number`\n\n### TimeHorizon\nTime horizon defining the start and end dates for scheduling.\n\nSpecifies the date range over which the schedule should be generated.\nThe range is inclusive of start date and exclusive of end date.\n\n**Example:**\n```typescript\n// One week schedule starting Monday, March 3, 2025\nconst horizon: TimeHorizon = {\n start: new Date('2025-03-03'), // Monday\n end: new Date('2025-03-10') // Following Monday (exclusive)\n};\n```\n\n**Properties:**\n- `start: Date`\n- `end: Date`\n\n### DateTimeComponents\n**Properties:**\n- `year?: number | undefined`\n- `month?: number | undefined`\n- `day?: number | undefined`\n- `hours?: number | undefined`\n- `minutes?: number | undefined`\n- `seconds?: number | undefined`\n- `nanos?: number | undefined`\n\n### DateTimeRange\nRepresents a time range with start and end DateTimes.\nUsed for checking overlaps and scheduling constraints.\n\n**Properties:**\n- `start: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n- `end: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n### SchedulingPeriod\nDefines a scheduling period as a date range with optional filters.\n\nThe `dateRange` specifies the overall scheduling window. Use `daysOfWeek`\nand/or `dates` to narrow which days within the range are included.\nFilters compose: a day must pass all specified filters to be included.\n\n**Example:**\nAll days in a week\n```typescript\nconst period: SchedulingPeriod = {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\n};\n```\n\n**Example:**\nOnly specific days of the week (restaurant closed Mon/Tue)\n```typescript\nconst period: SchedulingPeriod = {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],\n};\n```\n\n**Example:**\nOnly specific dates within the range\n```typescript\nconst period: SchedulingPeriod = {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndates: ['2025-02-05', '2025-02-07'],\n};\n```\n\n**Properties:**\n- `dateRange: { start: string; end: string; }` - The overall scheduling window (start and end are inclusive).\nDates should be in YYYY-MM-DD format.\n- `daysOfWeek?: DayOfWeek[] | undefined` - Include only these days of the week.\nIf omitted, all days of the week are included.\n- `dates?: string[] | undefined` - Include only these specific dates (YYYY-MM-DD) within the range.\nIf omitted, all dates in the range are included (subject to daysOfWeek filter).\n\n### SolverClient\n**Properties:**\n- `solve: (request: SolverRequest, options?: { signal?: AbortSignal; }) => Promise<SolverResponse>`\n- `health?: (() => Promise<void>) | undefined`\n\n### ModelBuilderConfig\nConfiguration for ModelBuilder.\n\n**Example:**\nDate range with day-of-week filtering (restaurant closed Mon/Tue)\n```typescript\nconst config: ModelBuilderConfig = {\nemployees: [...],\nshiftPatterns: [...],\ncoverage: [...],\nschedulingPeriod: {\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'thursday', 'friday', 'saturday', 'sunday'],\n},\n};\n```\n\n**Properties:**\n- `employees: SchedulingEmployee[]` - Team members available for scheduling.\n- `shiftPatterns: ShiftPattern[]` - Available shift patterns (time slots) that employees can be assigned to.\n- `schedulingPeriod: SchedulingPeriod` - Defines when scheduling should occur as a date range with optional\n`daysOfWeek` and `dates` filters that compose to narrow which days are included.\n- `coverage: CoverageRequirement[]`\n- `rules?: CompilationRule[] | undefined` - Pre-compiled rules; use this for custom rules that are not part of the registry.\n- `ruleConfigs?: CpsatRuleConfigEntry[] | undefined` - Named rule configurations that will be compiled using the provided rule factories.\n- `ruleFactories?: CpsatRuleFactories | undefined` - Rule factories to use when compiling ruleConfigs. Defaults to built-in CP-SAT rules.\n- `reporter?: ValidationReporter | undefined` - Optional validation reporter for diagnostics.\n\n### CompilationResult\n**Properties:**\n- `request: { variables: ({ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; })[]; constraints: ({ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; })[]; objective?: { sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; } | undefined; options?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined; }`\n- `validation: ScheduleValidation`\n- `canSolve: boolean`\n\n### CompilationRule\nA rule that adds constraints or objectives to the solver model.\n\nRules implement `compile` to emit solver constraints during model building,\nand optionally `validate` to check the solution after solving.\nUse the `create*Rule` functions to create built-in rules.\n\n**Properties:**\n- `compile: (builder: ModelBuilder) => void` - Emit constraints and objectives into the model builder.\n- `validate?: ((assignments: ResolvedShiftAssignment[], reporter: ValidationReporter, context: RuleValidationContext) => void) | undefined` - Validate the solved schedule and report violations.\n\n### RuleValidationContext\nContext provided to rules during post-solve validation.\n\n**Properties:**\n- `employees: SchedulingEmployee[]`\n- `days: string[]`\n- `shiftPatterns: ShiftPattern[]`\n\n### ShiftAssignment\nA raw assignment from the solver: which employee works which shift on which day.\n\n**Properties:**\n- `employeeId: string` - The assigned employee's ID.\n- `shiftPatternId: string` - The shift pattern this employee is assigned to.\n- `day: string` - The date of the assignment (YYYY-MM-DD).\n\n### ResolvedShiftAssignment\nA shift assignment with resolved times.\n\n**Properties:**\n- `employeeId: string` - The assigned employee's ID.\n- `day: string` - The date of the assignment (YYYY-MM-DD).\n- `startTime: TimeOfDay` - When the shift starts.\n- `endTime: TimeOfDay` - When the shift ends.\n\n### SolverResult\nParsed solver result with assignments and metadata.\n\n**Properties:**\n- `status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"` - The solver outcome: OPTIMAL, FEASIBLE, INFEASIBLE, TIMEOUT, or ERROR.\n- `assignments: ShiftAssignment[]` - The shift assignments extracted from the solution.\n- `statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined` - Solver performance statistics (branches, conflicts, solve time).\n- `error?: string | undefined` - Error message if the solver returned an error status.\n\n### RecurringPeriod\nRecurring calendar period for time scoping.\n\n**Properties:**\n- `name: string`\n- `startMonth: number`\n- `startDay: number`\n- `endMonth: number`\n- `endDay: number`\n\n### CpsatRuleRegistry\n**Properties:**\n- `\"assign-together\": { groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n- `\"employee-assignment-priority\": ({ preference: \"high\" | \"low\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n- `\"location-preference\": { locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"max-consecutive-days\": { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"max-hours-day\": ({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n- `\"max-hours-week\": ({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n- `\"max-shifts-day\": ({ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n- `\"min-consecutive-days\": { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"min-hours-day\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"min-hours-week\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"min-rest-between-shifts\": { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n- `\"time-off\": ({ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & RequiredTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n### SemanticTimeDef\nBase definition for a semantic time period.\n\n**Properties:**\n- `startTime: TimeOfDay` - When this time period starts.\n- `endTime: TimeOfDay` - When this time period ends.\n\n### SemanticTimeVariant\nVariant of a semantic time that applies to specific days or dates.\n\n**Properties:**\n- `days?: DayOfWeek[] | undefined` - Apply this variant only on these days of the week\n- `dates?: string[] | undefined` - Apply this variant only on these specific dates (YYYY-MM-DD)\n\n### SemanticTimeContext\nResult of defineSemanticTimes - provides type-safe coverage function.\n\n**Properties:**\n- `defs: { [P in S]: SemanticTimeEntry; }` - The semantic time definitions\n- `coverage: (reqs: MixedCoverageRequirement<S>[]) => MixedCoverageRequirement<S>[]` - Create coverage requirements with type-safe semantic time names.\nAccepts both semantic references and concrete one-off requirements.\n- `resolve: (reqs: MixedCoverageRequirement<S>[], days: string[]) => CoverageRequirement[]` - Resolve all coverage requirements to concrete CoverageRequirement[]\nfor the given days in the scheduling horizon.\n\n### SchedulingEmployee\nA team member available for scheduling.\n\nEmployees are assigned to shift patterns by the solver based on\ncoverage requirements, rules, and constraints.\n\n**Properties:**\n- `id: string` - Unique identifier for this employee. Must not contain colons.\n- `roleIds: string[]` - Roles this employee can fill (e.g. \"waiter\", \"chef\").\n- `skillIds?: string[] | undefined` - Skills this employee has (e.g. \"senior\", \"trainer\").\n\n### ShiftPattern\nA shift pattern defines WHEN people can work: the time slots available for assignment.\n\nShift patterns are templates that repeat across all scheduling days. The solver assigns\nteam members to these patterns based on coverage requirements and constraints.\n\n**Example:**\n// Simple venue: one shift type, anyone can work it\nconst patterns: ShiftPattern[] = [\n { id: \"day\", startTime: { hours: 9 }, endTime: { hours: 17 } }\n];\n\n**Example:**\n// Restaurant: different shifts for different roles\nconst patterns: ShiftPattern[] = [\n { id: \"kitchen_morning\", startTime: { hours: 6 }, endTime: { hours: 14 }, roleIds: [\"chef\", \"prep_cook\"] },\n { id: \"floor_lunch\", startTime: { hours: 11 }, endTime: { hours: 15 }, roleIds: [\"waiter\", \"host\"] },\n];\n\n**Properties:**\n- `id: string` - Unique identifier for this shift pattern.\nUsed in assignments and rule configurations.\n- `roleIds?: [string, ...string[]] | undefined` - Restricts who can be assigned to this shift based on their roles.\n\n- If omitted: anyone can work this shift\n- If provided: only team members whose roleIds overlap with this list can be assigned\n\nMost venues have the same shifts for everyone and don't need this.\nUse it when different roles have different schedules (e.g., kitchen staff starts\nearlier than floor staff).\n- `daysOfWeek?: DayOfWeek[] | undefined` - Restricts which days of the week this shift pattern can be used.\n\n- If omitted: shift can be used on any day\n- If provided: shift can only be assigned on the specified days\n\n**Example:**\n```typescript\n// Saturday-only short shift\n{ id: \"saturday_shift\", startTime: t(9), endTime: t(14), daysOfWeek: [\"saturday\"] }\n\n// Weekday-only full shift\n{ id: \"full_shift\", startTime: t(9), endTime: t(18), daysOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"] }\n```\n- `locationId?: string | undefined` - Physical location where this shift takes place.\nUsed for multi-location scheduling and location-based constraints.\n- `startTime: TimeOfDay` - When the shift starts (e.g., `{ hours: 9, minutes: 0 }` for 9:00 AM)\n- `endTime: TimeOfDay` - When the shift ends (e.g., `{ hours: 17, minutes: 30 }` for 5:30 PM)\n\n### TimeInterval\n**Properties:**\n- `day: string`\n- `startTime: TimeOfDay`\n- `endTime: TimeOfDay`\n\n### ModelBuilderOptions\nOptional settings for the model builder.\n\n**Properties:**\n- `weekStartsOn?: DayOfWeek | undefined` - Which day starts the week for weekly rules. Defaults to \"monday\".\n- `solverOptions?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined` - Solver-level options (time limit, solution limit).\n- `coverageBucketMinutes?: number | undefined` - Bucket size used when translating coverage requirements into time-indexed constraints.\nSmaller buckets are more accurate but increase the number of constraints.\n- `fairDistribution?: boolean | undefined` - Whether to enable fair distribution of shifts across team members.\n\nWhen enabled (default), the solver minimizes the maximum number of shifts\nany single person works, ensuring work is distributed evenly. Each person\nworks between floor(total/n) and ceil(total/n) shifts.\n\nDisable this if you want other rules (like employee-assignment-priority)\nto have full control over shift distribution. Defaults to true.\n\n### ValidationReporter\n**Properties:**\n- `excludeFromCoverage: (exclusion: CoverageExclusion) => void`\n- `reportCoverageError: (error: Omit<CoverageError, \"type\" | \"id\">) => void`\n- `reportRuleError: (error: Omit<RuleError, \"type\" | \"id\">) => void`\n- `reportSolverError: (reason: string) => void`\n- `reportCoverageViolation: (violation: Omit<CoverageViolation, \"type\" | \"id\">) => void`\n- `reportRuleViolation: (violation: Omit<RuleViolation, \"type\" | \"id\">) => void`\n- `reportCoveragePassed: (passed: Omit<CoveragePassed, \"type\" | \"id\">) => void`\n- `reportRulePassed: (passed: Omit<RulePassed, \"type\" | \"id\">) => void`\n- `trackConstraint: (constraint: TrackedConstraint) => void`\n- `hasErrors: () => boolean`\n- `getValidation: () => ScheduleValidation`\n- `getExclusions: () => CoverageExclusion[]`\n- `analyzeSolution: (response: SolverResponse) => void`\n\n### TrackedConstraint\n**Properties:**\n- `id: string`\n- `type: \"coverage\" | \"rule\"`\n- `rule?: string | undefined`\n- `description: string`\n- `targetValue: number`\n- `comparator: \"<=\" | \">=\"`\n- `day?: string | undefined`\n- `timeSlot?: string | undefined`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `context: ValidationContext`\n- `groupKey?: GroupKey | undefined`\n\n### CoverageExclusion\nCoverage exclusion - indicates a team member is unavailable for coverage during a time period.\nUsed during compile-time to determine coverage feasibility.\n\n**Properties:**\n- `employeeId: string`\n- `day: string`\n- `startTime?: TimeOfDay | undefined`\n- `endTime?: TimeOfDay | undefined`\n\n### ScheduleValidation\n**Properties:**\n- `errors: readonly ScheduleError[]`\n- `violations: readonly ScheduleViolation[]`\n- `passed: readonly SchedulePassed[]`\n\n### CoverageError\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `reason: string`\n- `suggestions?: readonly string[] | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### CoverageViolation\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `targetCount: number`\n- `actualCount: number`\n- `shortfall: number`\n- `groupKey?: GroupKey | undefined`\n\n### CoveragePassed\n**Properties:**\n- `id: string`\n- `type: \"coverage\"`\n- `day: string`\n- `timeSlots: readonly string[]`\n- `roleIds?: string[] | undefined`\n- `skillIds?: readonly string[] | undefined`\n- `description: string`\n- `groupKey?: GroupKey | undefined`\n\n### RuleError\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `reason: string`\n- `context: ValidationContext`\n- `suggestions?: readonly string[] | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### RuleViolation\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `reason: string`\n- `context: ValidationContext`\n- `shortfall?: number | undefined`\n- `overflow?: number | undefined`\n- `groupKey?: GroupKey | undefined`\n\n### RulePassed\n**Properties:**\n- `id: string`\n- `type: \"rule\"`\n- `rule: string`\n- `description: string`\n- `context: ValidationContext`\n- `groupKey?: GroupKey | undefined`\n\n### SolverError\n**Properties:**\n- `id: string`\n- `type: \"solver\"`\n- `reason: string`\n\n### ValidationContext\nContext shared across validation results for grouping/display.\n\n**Properties:**\n- `days?: string[] | undefined`\n- `timeSlots?: string[] | undefined`\n- `employeeIds?: string[] | undefined`\n\n### ValidationSummary\nSummary of validation items grouped by their source instruction.\nUse `summarizeValidation()` to create these from a ScheduleValidation.\n\n**Properties:**\n- `groupKey: string & { readonly [GroupKeyBrand]: never; }`\n- `type: \"coverage\" | \"rule\"`\n- `description: string`\n- `days: readonly string[]`\n- `status: \"passed\" | \"partial\" | \"failed\"`\n- `passedCount: number`\n- `violatedCount: number`\n- `errorCount: number`\n\n### CoverageValidationResult\nResult of coverage role validation.\n\n**Properties:**\n- `valid: boolean`\n- `unknownRoles: string[]` - Role IDs used in coverage that don't match any team member\n- `knownRoles: string[]` - Role IDs used in coverage that match team members\n\n### SkillValidationResult\nResult of coverage skill validation.\n\n**Properties:**\n- `valid: boolean`\n- `unknownSkills: string[]` - Skill IDs used in coverage that don't match any team member\n- `knownSkills: string[]` - Skill IDs used in coverage that match team members\n\n### CoverageConfigValidationResult\nCombined validation result for coverage requirements.\n\n**Properties:**\n- `valid: boolean`\n- `roles: CoverageValidationResult`\n- `skills: SkillValidationResult`\n- `errors: string[]` - Human-readable error messages\n\n### EntityScopeInput\nInput shape accepted by {@link parseEntityScope}.\n\n**Properties:**\n- `employeeIds?: string[] | undefined`\n- `roleIds?: string[] | undefined`\n- `skillIds?: string[] | undefined`\n- `unknown: any`\n\n### TimeScopeInput\nInput shape accepted by {@link parseTimeScope}.\n\n**Properties:**\n- `dateRange?: { start: string; end: string; } | undefined`\n- `specificDates?: string[] | undefined`\n- `dayOfWeek?: DayOfWeek[] | undefined`\n- `recurringPeriods?: RecurringPeriod[] | undefined`\n- `unknown: any`\n\n## Type Aliases\n\n### DayOfWeek\n```typescript\n\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"\n```\n\n\n### DateTime\nDate and time representation supporting both UTC offset and timezone-aware formats.\n\nCan be specified either with a UTC offset (e.g., \"-08:00\") or with a timezone ID\n(e.g., \"America/Los_Angeles\").\n\n```typescript\nDateTimeWithUtcOffset | DateTimeWithTimeZone\n```\n\n\n### SolverRequest\nThe full request payload sent to the CP-SAT solver service.\n\n- `variables` (required): all decision variables\n- `constraints` (required): all constraints\n- `objective` (optional): optimization objective\n- `timeoutSeconds` (optional): solver time limit\n\n```typescript\n{ variables: ({ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; })[]; constraints: ({ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; })[]; objective?: { sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; } | undefined; options?: { timeLimitSeconds?: number | undefined; solutionLimit?: number | undefined; } | undefined; }\n```\n\n\n### SolverResponse\nThe response payload returned by the CP-SAT solver service.\n\n- `status` (required): solve outcome (see {@link SolverStatus})\n- `values` (optional): variable assignments when a solution is found\n- `statistics` (optional): solve time, conflicts, branches\n- `softViolations` (optional): which soft constraints were violated\n- `error` (optional): error message on failure\n- `solutionInfo` (optional): solver diagnostic info\n\n```typescript\n{ status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"; values?: Record<string, number> | undefined; statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined; error?: string | undefined; solutionInfo?: string | undefined; softViolations?: { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }[] | undefined; }\n```\n\n\n### SolverVariable\nA decision variable in the CP-SAT model.\n\n- `name` (required): unique variable identifier\n- `lb` (required): lower bound\n- `ub` (required): upper bound\n- `isBoolean` (optional): whether this is a boolean variable\n- `isInterval` (optional): whether this is an interval variable\n- `start`, `end`, `size`, `presenceVar` (optional): interval variable fields\n\n```typescript\n{ type: \"bool\"; name: string; } | { type: \"int\"; name: string; min: number; max: number; } | { type: \"interval\"; name: string; start: number; end: number; size: number; presenceVar?: string | undefined; }\n```\n\n\n### SolverConstraint\nA constraint in the CP-SAT model.\n\n- `name` (required): constraint identifier\n- `type` (required): constraint kind (e.g. \"linear\", \"bool_and\", \"no_overlap\")\n- Additional fields vary by constraint type\n\n```typescript\n{ type: \"linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\" | \"==\"; rhs: number; } | { type: \"soft_linear\"; terms: { var: string; coeff: number; }[]; op: \"<=\" | \">=\"; rhs: number; penalty: number; id?: string | undefined; } | { type: \"exactly_one\"; vars: string[]; } | { type: \"at_most_one\"; vars: string[]; } | { type: \"implication\"; if: string; then: string; } | { type: \"bool_or\"; vars: string[]; } | { type: \"bool_and\"; vars: string[]; } | { type: \"no_overlap\"; intervals: string[]; }\n```\n\n\n### SolverTerm\nA single linear term in a constraint or objective.\n\n- `var` (required): variable name\n- `coeff` (required): integer coefficient\n\n```typescript\n{ var: string; coeff: number; }\n```\n\n\n### SolverObjective\nAn optimization objective for the solver.\n\n- `terms` (required): linear terms to minimize/maximize\n- `minimize` (required): whether to minimize (true) or maximize (false)\n\n```typescript\n{ sense: \"minimize\" | \"maximize\"; terms: { var: string; coeff: number; }[]; }\n```\n\n\n### SolverStatus\nSolver outcome status.\n\nOne of `\"OPTIMAL\"`, `\"FEASIBLE\"`, `\"INFEASIBLE\"`, `\"TIMEOUT\"`, or `\"ERROR\"`.\n\n```typescript\n\"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"\n```\n\n\n### SoftConstraintViolation\nA soft constraint violation reported by the solver.\n\n- `constraintId` (required): which soft constraint was violated\n- `violationAmount` (required): magnitude of the violation\n\n```typescript\n{ constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }\n```\n\n\n### FetcherLike\n```typescript\n((input: string | URL | Request, init?: RequestInit) => Promise<Response>) | { fetch: typeof fetch; }\n```\n\n\n### EntityScopeType\nEntity scope type for a subset of entity keys (at most one).\n\n```typescript\nExclusiveOne<ActiveEntityFields, InactiveEntityFields, K> | MergeValues<Pick<InactiveEntityFields, K>>\n```\n\n\n### OptionalTimeScopeType\nTime scope type for a subset of time keys (at most one, optional).\n\n```typescript\nExclusiveOne<ActiveTimeFields, InactiveTimeFields, K> | MergeValues<Pick<InactiveTimeFields, K>>\n```\n\n\n### RequiredTimeScopeType\nTime scope type for a subset of time keys (exactly one, required).\n\n```typescript\n{ [K in K]: ActiveTimeFields[K] & MergeValues<Pick<InactiveTimeFields, Exclude<K, K>>>; }[K]\n```\n\n\n### ParsedEntityScope\nParsed entity scope from a flat config.\nUsed internally by scope resolution functions.\n\n```typescript\n{ type: \"global\"; } | { type: \"employees\"; employeeIds: string[]; } | { type: \"roles\"; roleIds: string[]; } | { type: \"skills\"; skillIds: string[]; }\n```\n\n\n### ParsedTimeScope\nParsed time scope from a flat config.\nUsed internally by scope resolution functions.\n\n```typescript\n{ type: \"none\"; } | { type: \"dateRange\"; start: string; end: string; } | { type: \"specificDates\"; dates: string[]; } | { type: \"dayOfWeek\"; days: DayOfWeek[]; } | { type: \"recurring\"; periods: RecurringPeriod[]; }\n```\n\n\n### AssignTogetherConfig\nConfiguration for {@link createAssignTogetherRule}.\n\n- `groupEmployeeIds` (required): employee IDs to assign together (at least two, must be unique)\n- `priority` (required): how strictly the solver enforces this rule\n\n```typescript\n{ groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }\n```\n\n\n### EmployeeAssignmentPriorityConfig\nConfiguration for {@link createEmployeeAssignmentPriorityRule}.\n\n- `preference` (required): `\"high\"` to prefer assigning or `\"low\"` to avoid assigning\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\nTime scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`\n\n```typescript\n({ preference: \"high\" | \"low\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">\n```\n\n\n### LocationPreferenceConfig\nConfiguration for {@link createLocationPreferenceRule}.\n\n- `locationId` (required): the location ID to prefer for matching shift patterns\n- `priority` (required): how strongly to prefer this location\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### MaxConsecutiveDaysConfig\nConfiguration for {@link createMaxConsecutiveDaysRule}.\n\n- `days` (required): maximum consecutive days allowed\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### MaxHoursDayConfig\nConfiguration for {@link createMaxHoursDayRule}.\n\n- `hours` (required): maximum hours allowed per day\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\nTime scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`\n\n```typescript\n({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">\n```\n\n\n### MaxHoursWeekConfig\nConfiguration for {@link createMaxHoursWeekRule}.\n\n- `hours` (required): maximum hours allowed per scheduling week\n- `priority` (required): how strictly the solver enforces this rule\n- `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\nTime scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`\n\n```typescript\n({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">\n```\n\n\n### MaxShiftsDayConfig\nConfiguration for {@link createMaxShiftsDayRule}.\n\n- `shifts` (required): maximum number of shifts per day (at least 1)\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\nTime scoping (at most one, optional): `dateRange`, `specificDates`, `dayOfWeek`, `recurringPeriods`\n\n```typescript\n({ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">\n```\n\n\n### MinConsecutiveDaysConfig\nConfiguration for {@link createMinConsecutiveDaysRule}.\n\n- `days` (required): minimum consecutive days required once a person starts working\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### MinHoursDayConfig\nConfiguration for {@link createMinHoursDayRule}.\n\n- `hours` (required): minimum hours required per day when scheduled\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### MinHoursWeekConfig\nConfiguration for {@link createMinHoursWeekRule}.\n\n- `hours` (required): minimum hours required per scheduling week\n- `priority` (required): how strictly the solver enforces this rule\n- `weekStartsOn` (optional): which day starts the week; defaults to {@link ModelBuilder.weekStartsOn}\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### MinRestBetweenShiftsConfig\nConfiguration for {@link createMinRestBetweenShiftsRule}.\n\n- `hours` (required): minimum rest hours required between consecutive shifts\n- `priority` (required): how strictly the solver enforces this rule\n\nEntity scoping (at most one): `employeeIds`, `roleIds`, `skillIds`\n\n```typescript\n{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">\n```\n\n\n### TimeOffConfig\nConfiguration for {@link createTimeOffRule}.\n\n- `priority` (required): how strictly the solver enforces this rule\n- `startTime` (optional): start of the time-off window within each day; must be paired with `endTime`\n- `endTime` (optional): end of the time-off window within each day; must be paired with `startTime`\n\nEntity scoping (at most one):\n- `employeeIds`: restrict to specific employees\n- `roleIds`: restrict to employees with matching roles\n- `skillIds`: restrict to employees with matching skills\n\nTime scoping (exactly one required):\n- `dateRange`: contiguous date range\n- `specificDates`: specific dates\n- `dayOfWeek`: days of the week\n- `recurringPeriods`: recurring calendar periods\n\n```typescript\n({ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & RequiredTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">\n```\n\n\n### CpsatRuleName\n```typescript\nkeyof CpsatRuleRegistry\n```\n\n\n### CpsatRuleConfigEntry\n```typescript\n{ name: K; config: CpsatRuleRegistry[K]; }\n```\n\n\n### CpsatRuleFactories\n```typescript\n{ [ruleName: string]: CreateCpsatRuleFunction<any>; }\n```\n\n\n### BuiltInCpsatRuleFactories\n```typescript\n[Complex type: __type]\n```\n\n\n### CreateCpsatRuleFunction\n```typescript\n(config: TConfig) => CompilationRule\n```\n\n\n### CpsatRuleRegistryFromFactories\n```typescript\n{ [K in keyof F]: InferCpsatRuleConfig<F[K]>; }\n```\n\n\n### SemanticTimeEntry\nA semantic time can be a simple definition (applies every day)\nor an array of variants with different times for different days/dates.\n\n```typescript\nSemanticTimeDef | SemanticTimeVariant[]\n```\n\n\n### SemanticCoverageRequirement\nCoverage requirement that references a semantic time by name.\nType-safe: S is constrained to known semantic time names.\n\nThis is a discriminated union enforcing at compile time that at least\none of `roleIds` or `skillIds` must be provided.\n\n```typescript\nRoleBasedSemanticCoverageRequirement<S> | SkillBasedSemanticCoverageRequirement<S>\n```\n\n\n### ConcreteCoverageRequirement\nConcrete coverage requirement with explicit day and times.\nUsed for one-off requirements that don't fit a semantic time.\n\nThis is a discriminated union enforcing at compile time that at least\none of `roleIds` or `skillIds` must be provided.\n\n```typescript\nRoleBasedConcreteCoverageRequirement | SkillBasedConcreteCoverageRequirement\n```\n\n\n### MixedCoverageRequirement\nUnion type for coverage - either semantic (type-safe) or concrete.\n\n```typescript\nConcreteCoverageRequirement | SemanticCoverageRequirement<S>\n```\n\n\n### Employee\n```typescript\nSchedulingEmployee\n```\n\n\n### CoverageRequirement\nDefines staffing needs for a specific time period.\n\nThis is a discriminated union that enforces at compile time that at least\none of `roleIds` or `skillIds` must be provided:\n\n- Role-based: `{ roleIds: [\"waiter\"], ... }` - anyone with ANY of these roles (OR logic)\n- Role + skill: `{ roleIds: [\"waiter\"], skillIds: [\"senior\"], ... }` - role AND skills\n- Skill-only: `{ skillIds: [\"keyholder\"], ... }` - any role with ALL skills (AND logic)\n\n**Example:**\n// Need 2 waiters during lunch (role-based)\n{ day: \"2024-01-01\", startTime: { hours: 11 }, endTime: { hours: 14 }, roleIds: [\"waiter\"], targetCount: 2, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 manager OR supervisor during service (OR logic on roles)\n{ day: \"2024-01-01\", startTime: { hours: 11 }, endTime: { hours: 22 }, roleIds: [\"manager\", \"supervisor\"], targetCount: 1, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 keyholder for opening (skill-only, any role)\n{ day: \"2024-01-01\", startTime: { hours: 6 }, endTime: { hours: 8 }, skillIds: [\"keyholder\"], targetCount: 1, priority: \"MANDATORY\" }\n\n**Example:**\n// Need 1 senior waiter for training shift (role + skill filter)\n{ day: \"2024-01-01\", startTime: { hours: 9 }, endTime: { hours: 17 }, roleIds: [\"waiter\"], skillIds: [\"senior\"], targetCount: 1, priority: \"HIGH\" }\n\n```typescript\nRoleBasedCoverageRequirement | SkillBasedCoverageRequirement\n```\n\n\n### Priority\nHow strictly the solver enforces a rule.\n\n- `\"LOW\"`, `\"MEDIUM\"`, `\"HIGH\"`: soft constraints with increasing penalty for violations\n- `\"MANDATORY\"`: hard constraint; the solver will not produce a solution that violates it\n\n```typescript\n\"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"\n```\n\n\n### ScheduleError\n```typescript\nCoverageError | RuleError | SolverError\n```\n\n\n### ScheduleViolation\n```typescript\nCoverageViolation | RuleViolation\n```\n\n\n### SchedulePassed\n```typescript\nCoveragePassed | RulePassed\n```\n\n\n### GroupKey\nBranded type for validation group keys.\nGroups related validation items that originated from the same instruction.\n\n```typescript\nstring & { readonly [GroupKeyBrand]: never; }\n```\n\n\n### EntityKey\n```typescript\n\"employees\" | \"roles\" | \"skills\"\n```\n\n\n### TimeKey\n```typescript\n\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\"\n```\n\n\n### ExclusiveOne\nExactly one of the specified keys must be present.\nThe active key's field is required; all others are `?: never`.\n\n**Example:**\nExclusiveOne<ActiveEntityFields, InactiveEntityFields, \"employees\" | \"roles\">\n=\n | { employeeIds: [string, ...string[]]; roleIds?: never }\n | { roleIds: [string, ...string[]]; employeeIds?: never }\n\n```typescript\n{ [K in Keys]: Active[K] & MergeValues<Pick<Inactive, Exclude<Keys, K>>>; }[Keys]\n```\n\n\n### MaybeOne\nAt most one of the specified keys may be present (or none).\nSame as {@link ExclusiveOne} plus the case where all fields are `?: never`.\n\n```typescript\nExclusiveOne<Active, Inactive, Keys> | MergeValues<Pick<Inactive, Keys>>\n```\n\n\n### InferCpsatRuleConfig\n```typescript\nT extends CreateCpsatRuleFunction<infer Config> ? Config : never\n```\n\n\n### Term\n```typescript\n{ var: string; coeff: number; }\n```\n\n\n## Functions\n\n### dateToCalendarDate\nConverts a JavaScript Date to a CalendarDate\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `CalendarDate`\n\n\n### dateTimeToDate\nConverts a DateTime to a JavaScript Date\nInternal helper function\n\n**Parameters:**\n- `dateTime: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n**Returns:** `Date`\n\n\n### compareDateTimes\nCompares two DateTimes\nReturns:\n -1 if dateTime1 < dateTime2\n 0 if dateTime1 = dateTime2\n 1 if dateTime1 > dateTime2\n\n**Parameters:**\n- `dateTime1: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n- `dateTime2: DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n**Returns:** `number`\n\n\n### toDayOfWeek\nHelper to get the day of week name from a Date (local time)\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n\n### toDayOfWeekUTC\nHelper to get the day of week name from a Date (UTC)\nUse this when working with date strings like \"2026-01-10\" that are timezone-agnostic.\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `\"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n\n### formatDateString\nFormats a date as YYYY-MM-DD string\n\n**Parameters:**\n- `date: Date`\n\n**Returns:** `string`\n\n\n### generateDays\nGenerates an array of day strings (YYYY-MM-DD) from a time horizon.\n\n**Example:**\n```typescript\nconst days = generateDays({\n start: new Date('2025-01-01'),\n end: new Date('2025-01-04')\n});\n// Returns: [\"2025-01-01\", \"2025-01-02\", \"2025-01-03\", \"2025-01-04\"]\n```\n\n**Parameters:**\n- `horizon: { start: Date; end: Date; }`\n\n**Returns:** `string[]`\n\n\n### splitPeriodIntoDays\nSplits a time period into consecutive day ranges.\n\nEach range represents a single calendar day within the period from start to end.\nThis is useful for rules that need to apply constraints on a per-day basis,\nsuch as maximum or minimum hours per day.\n\n**Example:**\n```typescript\nconst ranges = splitPeriodIntoDays({\n start: new Date('2025-01-01'),\n end: new Date('2025-01-03')\n});\n// Returns:\n// [\n// [Date('2025-01-01'), Date('2025-01-02')],\n// [Date('2025-01-02'), Date('2025-01-03')]\n// ]\n```\n\n**Parameters:**\n- `{ start, end }: { start: Date; end: Date; }`\n\n**Returns:** `[Date, Date][]`\n\n\n### splitPeriodIntoWeeks\nSplits a time period into consecutive week ranges.\n\nEach range represents a week period starting on the specified day of the week.\nThis is useful for rules that need to apply constraints on a per-week basis,\nsuch as maximum or minimum hours per week.\n\nThe first range starts at the provided start date (not necessarily on weekStartsOn).\nSubsequent ranges align to the weekStartsOn day. The last range's end date will be\nthe provided end date.\n\n**Example:**\n```typescript\nconst ranges = splitPeriodIntoWeeks({\n start: new Date('2025-01-01'), // Wednesday\n end: new Date('2025-01-15'),\n weekStartsOn: 'monday'\n});\n// Returns ranges starting from Jan 1 (Wed), then aligning to Mondays:\n// [\n// [Date('2025-01-01 Wed'), Date('2025-01-06 Mon')],\n// [Date('2025-01-06 Mon'), Date('2025-01-13 Mon')],\n// [Date('2025-01-13 Mon'), Date('2025-01-15 Wed')]\n// ]\n```\n\n**Parameters:**\n- `{\n start,\n end,\n weekStartsOn,\n}: { start: Date; end: Date; weekStartsOn: DayOfWeek; }`\n\n**Returns:** `[Date, Date][]`\n\n\n### dateTimeRangesOverlap\nChecks if two DateTime ranges overlap in both date and time.\nRanges overlap if they share any moment in time.\n\nTwo ranges overlap if: range1.start < range2.end AND range2.start < range1.end\n\n**Example:**\n```typescript\n// Same day, overlapping times (9-17 overlaps with 12-20)\ndateTimeRangesOverlap(\n {\n start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }\n },\n {\n start: { year: 2025, month: 6, day: 1, hours: 12, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 20, minutes: 0 }\n }\n); // true\n\n// Different days - no overlap\ndateTimeRangesOverlap(\n {\n start: { year: 2025, month: 6, day: 1, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 1, hours: 17, minutes: 0 }\n },\n {\n start: { year: 2025, month: 6, day: 2, hours: 9, minutes: 0 },\n end: { year: 2025, month: 6, day: 2, hours: 17, minutes: 0 }\n }\n); // false\n\n// Works naturally with Shift objects\ndateTimeRangesOverlap(\n { start: shift1.startDateTime, end: shift1.endDateTime },\n { start: shift2.startDateTime, end: shift2.endDateTime }\n);\n```\n\n**Parameters:**\n- `range1: DateTimeRange`\n- `range2: DateTimeRange`\n\n**Returns:** `boolean`\n\n\n### daysBetween\nCalculates the number of complete days between two dates\n\n**Example:**\n```typescript\ndaysBetween(new Date('2025-01-01'), new Date('2025-01-05')); // 4\n```\n\n**Parameters:**\n- `start: Date`\n- `end: Date`\n\n**Returns:** `number`\n\n\n### resolveDaysFromPeriod\nComputes the list of day strings (YYYY-MM-DD) from a SchedulingPeriod.\n\nGenerates all days between start and end (inclusive), applying optional\ndaysOfWeek and dates filters. Filters compose: a day must pass all\nspecified filters to be included.\n\n**Example:**\nAll days in range\n```typescript\nconst days = resolveDaysFromPeriod({\ndateRange: { start: '2025-02-03', end: '2025-02-05' },\n});\n// Returns: ['2025-02-03', '2025-02-04', '2025-02-05']\n```\n\n**Example:**\nDay-of-week filter\n```typescript\nconst days = resolveDaysFromPeriod({\ndateRange: { start: '2025-02-03', end: '2025-02-09' },\ndaysOfWeek: ['wednesday', 'friday'],\n});\n// Returns: ['2025-02-05', '2025-02-07']\n```\n\n**Example:**\nSpecific dates filter\n```typescript\nconst days = resolveDaysFromPeriod({\ndateRange: { start: '2025-02-03', end: '2025-02-10' },\ndates: ['2025-02-05', '2025-02-07'],\n});\n// Returns: ['2025-02-05', '2025-02-07']\n```\n\n**Parameters:**\n- `period: SchedulingPeriod`\n\n**Returns:** `string[]`\n\n\n### parseSolverResponse\nExtracts shift assignments from solver response.\n\nParses variable names matching the pattern `assign:${employeeId}:${patternId}:${day}`\nand returns assignments where the variable value is 1 (true).\n\nIDs are validated by ModelBuilder to not contain colons,\nensuring unambiguous parsing.\n\n**Example:**\n```typescript\nconst response = await client.solve(request);\nconst result = parseSolverResponse(response);\n\nif (result.status === \"OPTIMAL\" || result.status === \"FEASIBLE\") {\n for (const assignment of result.assignments) {\n console.log(`${assignment.employeeId} works ${assignment.shiftPatternId} on ${assignment.day}`);\n }\n}\n```\n\n**Parameters:**\n- `response: { status: \"OPTIMAL\" | \"FEASIBLE\" | \"INFEASIBLE\" | \"TIMEOUT\" | \"ERROR\"; values?: Record<string, number> | undefined; statistics?: { solveTimeMs?: number | undefined; conflicts?: number | undefined; branches?: number | undefined; } | undefined; error?: string | undefined; solutionInfo?: string | undefined; softViolations?: { constraintId: string; violationAmount: number; targetValue: number; actualValue: number; }[] | undefined; }`\n\n**Returns:** `SolverResult`\n\n\n### resolveAssignments\nResolves shift assignments to concrete times using shift patterns.\n\n**Example:**\n```typescript\nconst result = parseScheduleResult(response);\nconst resolved = resolveAssignments(result.assignments, shiftPatterns);\n\nfor (const shift of resolved) {\n console.log(`${shift.employeeId} works ${shift.day} from ${shift.startTime.hours}:${shift.startTime.minutes}`);\n}\n```\n\n**Parameters:**\n- `assignments: ShiftAssignment[]`\n- `shiftPatterns: ShiftPattern[]`\n\n**Returns:** `ResolvedShiftAssignment[]`\n\n\n### createAssignTogetherRule\nEncourages or enforces that team members in the group work the same shift patterns on a day.\nFor each pair of team members in the group, ensures they are assigned to the same shifts.\n\n**Example:**\n```ts\nconst rule = createAssignTogetherRule({\n groupEmployeeIds: [\"alice\", \"bob\", \"charlie\"],\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Parameters:**\n- `config: { groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n\n**Returns:** `CompilationRule`\n\n\n### createEmployeeAssignmentPriorityRule\nAdds objective weight to prefer or avoid assigning team members.\n\n**Example:**\nPrefer specific team members\n```ts\ncreateEmployeeAssignmentPriorityRule({\nemployeeIds: [\"alice\", \"bob\"],\npreference: \"high\",\n});\n```\n\n**Example:**\nAvoid assigning students on weekdays\n```ts\ncreateEmployeeAssignmentPriorityRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npreference: \"low\",\n});\n```\n\n**Parameters:**\n- `config: ({ preference: \"high\" | \"low\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n**Returns:** `CompilationRule`\n\n\n### createLocationPreferenceRule\nPrefers assigning a person to shift patterns matching a specific location.\n\n**Example:**\n```ts\ncreateLocationPreferenceRule({\n locationId: \"terrace\",\n priority: \"HIGH\",\n employeeIds: [\"alice\"],\n});\n```\n\n**Parameters:**\n- `config: { locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxConsecutiveDaysRule\nLimits how many consecutive days a person can be assigned.\n\n**Example:**\n```ts\ncreateMaxConsecutiveDaysRule({ days: 5, priority: \"MANDATORY\" });\n```\n\n**Parameters:**\n- `config: { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxHoursDayRule\nLimits how many hours a person can work in a single day.\n\n**Example:**\nLimit everyone to 8 hours per day\n```ts\ncreateMaxHoursDayRule({\nhours: 8,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents limited to 4 hours on weekdays during term\n```ts\ncreateMaxHoursDayRule({\nroleIds: [\"student\"],\nhours: 4,\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: ({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxHoursWeekRule\nCaps total hours a person can work within each scheduling week.\n\n**Example:**\nLimit everyone to 40 hours per week\n```ts\ncreateMaxHoursWeekRule({ hours: 40, priority: \"HIGH\" });\n```\n\n**Example:**\nStudents limited to 20 hours during term time\n```ts\ncreateMaxHoursWeekRule({\nroleIds: [\"student\"],\nhours: 20,\nrecurringPeriods: [\n{ name: \"fall-term\", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },\n],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: ({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMaxShiftsDayRule\nLimits how many shifts a person can work in a single day.\n\nControls the maximum number of distinct shift assignments per day,\nregardless of shift duration. For limiting total hours worked, use `max-hours-day`.\n\n**Example:**\nLimit to one shift per day\n```ts\ncreateMaxShiftsDayRule({\nshifts: 1,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents can work 2 shifts on weekends only\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"student\"],\nshifts: 2,\ndayOfWeek: [\"saturday\", \"sunday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: ({ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMinConsecutiveDaysRule\nRequires that once a person starts working, they continue for a minimum\nnumber of consecutive days.\n\n**Example:**\n```ts\ncreateMinConsecutiveDaysRule({ days: 3, priority: \"MANDATORY\" });\n```\n\n**Parameters:**\n- `config: { days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMinHoursDayRule\nEnsures a person works at least a minimum number of hours per day.\n\n**Example:**\n```ts\ncreateMinHoursDayRule({ hours: 6, priority: \"MANDATORY\" });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMinHoursWeekRule\nEnforces a minimum total number of hours per scheduling week.\n\n**Example:**\n```ts\ncreateMinHoursWeekRule({ hours: 30, priority: \"HIGH\" });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createMinRestBetweenShiftsRule\nEnforces a minimum rest period between any two shifts a person works.\n\n**Example:**\n```ts\ncreateMinRestBetweenShiftsRule({ hours: 10, priority: \"MANDATORY\" });\n```\n\n**Parameters:**\n- `config: { hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n**Returns:** `CompilationRule`\n\n\n### createTimeOffRule\nBlocks or penalizes assignments during specified time periods.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nOptionally supports partial-day time-off with startTime/endTime.\n\n**Example:**\nFull day vacation\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"alice\"],\ndateRange: { start: \"2024-02-01\", end: \"2024-02-05\" },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nEvery Wednesday afternoon off for students\n```ts\ncreateTimeOffRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"wednesday\"],\nstartTime: { hours: 14, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nSpecific date, partial day\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"bob\"],\nspecificDates: [\"2024-03-15\"],\nstartTime: { hours: 16, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Parameters:**\n- `config: ({ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & RequiredTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n**Returns:** `CompilationRule`\n\n\n### createCpsatRuleFactory\nCreates a rule factory map, preventing overriding built-in rules.\n\n**Parameters:**\n- `factories: F`\n\n**Returns:** `F`\n\n\n### defineSemanticTimes\nDefine semantic times with type-safe names.\n\nReturns a context object that provides:\n- Type-safe coverage() function that only accepts defined semantic time names\n- resolve() function to expand semantic times to concrete requirements\n\n**Example:**\nBasic usage\n```typescript\nconst times = defineSemanticTimes({\nopening: { startTime: { hours: 6 }, endTime: { hours: 8 } },\nlunch: { startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 } },\nclosing: { startTime: { hours: 21 }, endTime: { hours: 23 } },\n});\n\nconst coverage = times.coverage([\n{ semanticTime: \"lunch\", roleId: \"server\", targetCount: 3 },\n{ semanticTime: \"opening\", roleId: \"keyholder\", targetCount: 1, priority: \"MANDATORY\" },\n// Type error: \"dinner\" is not a defined semantic time\n// { semanticTime: \"dinner\", roleId: \"server\", targetCount: 2 },\n]);\n```\n\n**Example:**\nVariants for different days\n```typescript\nconst times = defineSemanticTimes({\nlunch: [\n{ startTime: { hours: 11, minutes: 30 }, endTime: { hours: 14 }, days: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"] },\n{ startTime: { hours: 12 }, endTime: { hours: 15 }, days: [\"saturday\", \"sunday\"] },\n],\n});\n```\n\n**Example:**\nMixed semantic and concrete coverage\n```typescript\nconst coverage = times.coverage([\n{ semanticTime: \"lunch\", roleId: \"server\", targetCount: 3 },\n// One-off party - concrete time\n{ day: \"2026-01-14\", startTime: { hours: 15 }, endTime: { hours: 20 }, roleId: \"server\", targetCount: 5 },\n]);\n```\n\n**Parameters:**\n- `defs: T`\n\n**Returns:** `SemanticTimeContext<keyof T & string>`\n\n\n### isConcreteCoverage\nType guard to check if a requirement is concrete (has explicit day/times).\n\n**Parameters:**\n- `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`\n\n**Returns:** `boolean`\n\n\n### isSemanticCoverage\nType guard to check if a requirement is semantic (references a named time).\n\n**Parameters:**\n- `req: ConcreteCoverageRequirement | SemanticCoverageRequirement<S>`\n\n**Returns:** `boolean`\n\n\n### groupKey\nCreates a GroupKey from a description string.\nUse this to create keys that group related validation items together.\n\n**Example:**\n```typescript\nconst key = groupKey(\"2x waiter during lunch\");\ncoverage.groupKey = key;\n```\n\n**Parameters:**\n- `description: string`\n\n**Returns:** `string & { readonly [GroupKeyBrand]: never; }`\n\n\n### summarizeValidation\nAggregates validation items by their groupKey into summaries.\nThis is a pure function that doesn't modify the input.\n\nItems without a groupKey are grouped by their ID (ungrouped).\n\n**Example:**\n```typescript\nconst validation = reporter.getValidation();\nconst summaries = summarizeValidation(validation);\n// summaries[0] = {\n// groupKey: \"2x waiter during lunch\",\n// status: \"passed\",\n// passedCount: 180,\n// days: [\"2026-02-02\", \"2026-02-03\", ...]\n// }\n```\n\n**Parameters:**\n- `validation: ScheduleValidation`\n\n**Returns:** `readonly ValidationSummary[]`\n\n\n### validateCoverageRoles\nValidates that all roleIds used in coverage requirements match the team.\n\nThis catches a common LLM error where the model generates coverage requirements\nusing role names that don't match any team member's roleIds. Without this validation,\nsuch mismatches would result in valid but semantically wrong schedules (e.g.,\ncoverage requirements that no one can satisfy).\n\n**Example:**\n```typescript\nconst employees = [\n { id: \"alice\", roleIds: [\"cashier\"] },\n { id: \"bob\", roleIds: [\"stocker\"] },\n];\n\nconst coverage = [\n { roleId: \"cashier\", targetCount: 1, ... }, // OK\n { roleId: \"worker\", targetCount: 1, ... }, // Unknown role!\n];\n\nconst result = validateCoverageRoles(coverage, employees);\n// result.valid = false\n// result.unknownRoles = [\"worker\"]\n// result.knownRoles = [\"cashier\"]\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `CoverageValidationResult`\n\n\n### validateCoverageSkills\nValidates that all skillIds used in coverage requirements match the team.\n\nSimilar to role validation, this catches LLM hallucinations where skill names\nin coverage don't match any team member's skillIds.\n\n**Example:**\n```typescript\nconst employees = [\n { id: \"alice\", roleIds: [\"server\"], skillIds: [\"keyholder\"] },\n { id: \"bob\", roleIds: [\"server\"] },\n];\n\nconst coverage = [\n { skillIds: [\"keyholder\"], targetCount: 1, ... }, // OK\n { skillIds: [\"manager\"], targetCount: 1, ... }, // Unknown skill!\n];\n\nconst result = validateCoverageSkills(coverage, employees);\n// result.valid = false\n// result.unknownSkills = [\"manager\"]\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `SkillValidationResult`\n\n\n### validateCoverageConfig\nValidates coverage requirements against team roles and skills.\n\nThis is the primary validation function to call before building a scheduling model.\nIt checks both roles and skills, returning a combined result with error messages.\n\n**Example:**\n```typescript\nconst result = validateCoverageConfig(coverage, employees);\nif (!result.valid) {\n throw new Error(result.errors.join(\"; \"));\n}\n```\n\n**Parameters:**\n- `coverage: CoverageRequirement[]`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `CoverageConfigValidationResult`\n\n\n### addMinutesToDate\nAdds a number of minutes to a base date and returns a DateTime.\nTreats the base date as a reference point (typically midnight of horizon start),\nand the minutes parameter as absolute minutes from that point.\n\n**Example:**\n```typescript\n// Add 90 minutes from midnight\naddMinutesToDate(new Date('2025-01-01'), 90);\n// Returns: { year: 2025, month: 1, day: 1, hours: 1, minutes: 30 }\n\n// Add 1500 minutes (spans to next day)\naddMinutesToDate(new Date('2025-01-01'), 1500);\n// Returns: { year: 2025, month: 1, day: 2, hours: 1, minutes: 0 }\n```\n\n**Parameters:**\n- `baseDate: Date`\n- `minutes: number`\n\n**Returns:** `DateTimeWithUtcOffset | DateTimeWithTimeZone`\n\n\n### splitPoints\nReturns the points where a range should be split, filtered to within [start, end).\nAlways includes range start. Sorted ascending.\n\n**Parameters:**\n- `[start, end]: [number, number]`\n- `splitAt: number[]`\n\n**Returns:** `number[]`\n\n\n### entityScope\nCreates a Zod schema for optional entity scoping (at most one of the\nspecified entity variants).\n\nThe returned schema accepts flat fields (`employeeIds`, `roleIds`, `skillIds`)\nbut the TypeScript type enforces mutual exclusivity via `?: never`.\n\n**Example:**\n```ts\n// Supports all entity scopes\nentityScope([\"employees\", \"roles\", \"skills\"])\n\n// Only employee scoping\nentityScope([\"employees\"])\n```\n\n**Parameters:**\n- `keys: K`\n\n**Returns:** `z.ZodType<EntityScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<EntityScopeType<K[number]>, unknown>>`\n\n\n### timeScope\nCreates a Zod schema for optional time scoping (at most one of the\nspecified time variants, or none).\n\n**Example:**\n```ts\n// Supports all time scopes, all optional\ntimeScope([\"dateRange\", \"specificDates\", \"dayOfWeek\", \"recurring\"])\n```\n\n**Parameters:**\n- `keys: K`\n\n**Returns:** `z.ZodType<OptionalTimeScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<OptionalTimeScopeType<K[number]>, unknown>>`\n\n\n### requiredTimeScope\nCreates a Zod schema for required time scoping (exactly one of the\nspecified time variants must be present).\n\n**Example:**\n```ts\n// Exactly one time scope required (for time-off)\nrequiredTimeScope([\"dateRange\", \"specificDates\", \"dayOfWeek\", \"recurring\"])\n```\n\n**Parameters:**\n- `keys: K`\n\n**Returns:** `z.ZodType<RequiredTimeScopeType<K[number]>, unknown, z.core.$ZodTypeInternals<RequiredTimeScopeType<K[number]>, unknown>>`\n\n\n### parseEntityScope\nExtracts the entity scope from a parsed flat config.\n\n**Parameters:**\n- `config: EntityScopeInput`\n\n**Returns:** `{ type: \"global\"; } | { type: \"employees\"; employeeIds: string[]; } | { type: \"roles\"; roleIds: string[]; } | { type: \"skills\"; skillIds: string[]; }`\n\n\n### parseTimeScope\nExtracts the time scope from a parsed flat config.\n\n**Parameters:**\n- `config: TimeScopeInput`\n\n**Returns:** `{ type: \"none\"; } | { type: \"dateRange\"; start: string; end: string; } | { type: \"specificDates\"; dates: string[]; } | { type: \"dayOfWeek\"; days: DayOfWeek[]; } | { type: \"recurring\"; periods: RecurringPeriod[]; }`\n\n\n### resolveEmployeesFromScope\nResolves which employees a rule applies to based on entity scope.\n\n**Parameters:**\n- `scope: { type: \"global\"; } | { type: \"employees\"; employeeIds: string[]; } | { type: \"roles\"; roleIds: string[]; } | { type: \"skills\"; skillIds: string[]; }`\n- `employees: SchedulingEmployee[]`\n\n**Returns:** `SchedulingEmployee[]`\n\n\n### resolveActiveDaysFromScope\nResolves which days a rule applies to based on time scope.\n\n**Parameters:**\n- `scope: { type: \"none\"; } | { type: \"dateRange\"; start: string; end: string; } | { type: \"specificDates\"; dates: string[]; } | { type: \"dayOfWeek\"; days: DayOfWeek[]; } | { type: \"recurring\"; periods: RecurringPeriod[]; }`\n- `allDays: string[]`\n\n**Returns:** `string[]`\n\n\n### parseDayString\nParse a day string (YYYY-MM-DD) to a UTC Date.\nUsed internally for day-of-week calculations and date comparisons.\n\n**Parameters:**\n- `day: string`\n\n**Returns:** `Date`\n\n\n### timeOfDayToMinutes\n**Parameters:**\n- `time: TimeOfDay`\n\n**Returns:** `number`\n\n\n### normalizeEndMinutes\n**Parameters:**\n- `startMinutes: number`\n- `endMinutes: number`\n\n**Returns:** `number`\n\n\n### priorityToPenalty\n**Parameters:**\n- `priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"`\n\n**Returns:** `number`\n\n\n### splitIntoWeeks\n**Parameters:**\n- `days: string[]`\n- `weekStartsOn: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\"`\n\n**Returns:** `string[][]`\n\n\n## Classes\n\n### ORSchedulingError\nError thrown when Google OR Tools scheduling API requests fail.\n\nContains the HTTP status code and raw response data from the API for debugging.\nCommon causes include infeasible constraints, invalid requests, or API unavailability.\n\n\n### HttpSolverClient\nGeneric HTTP client for the solver service.\n\n\n### ModelBuilder\nCompilation context that creates variables, constraints, and objectives\nand emits a `SolverRequest` for the Python CP-SAT solver service.\n\n\n### ValidationReporterImpl\n\n## Constants\n\n### DayOfWeekSchema\nZod schema for {@link DayOfWeek}.\nUseful for rule configs that need to accept a day-of-week string.\n\n**Type:** `z.ZodUnion<readonly [z.ZodLiteral<\"monday\">, z.ZodLiteral<\"tuesday\">, z.ZodLiteral<\"wednesday\">, z.ZodLiteral<\"thursday\">, z.ZodLiteral<\"friday\">, z.ZodLiteral<\"saturday\">, z.ZodLiteral<\"sunday\">]>`\n\n\n### SOLVER_STATUS\n**Type:** `{ readonly OPTIMAL: \"OPTIMAL\"; readonly FEASIBLE: \"FEASIBLE\"; readonly INFEASIBLE: \"INFEASIBLE\"; readonly TIMEOUT: \"TIMEOUT\"; readonly ERROR: \"ERROR\"; }`\n\n\n### SolverRequestSchema\n**Type:** `[Complex type: ZodObject]`\n\n\n### SolverResponseSchema\n**Type:** `z.ZodObject<{ status: z.ZodEnum<{ OPTIMAL: \"OPTIMAL\"; FEASIBLE: \"FEASIBLE\"; INFEASIBLE: \"INFEASIBLE\"; TIMEOUT: \"TIMEOUT\"; ERROR: \"ERROR\"; }>; values: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>; statistics: z.ZodOptional<z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>>; error: z.ZodOptional<z.ZodString>; solutionInfo: z.ZodOptional<z.ZodString>; softViolations: z.ZodOptional<z.ZodArray<z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>>>; }, z.core.$strip>`\n\n\n### SolverStatusSchema\n**Type:** `z.ZodEnum<{ OPTIMAL: \"OPTIMAL\"; FEASIBLE: \"FEASIBLE\"; INFEASIBLE: \"INFEASIBLE\"; TIMEOUT: \"TIMEOUT\"; ERROR: \"ERROR\"; }>`\n\n\n### builtInCpsatRuleFactories\n**Type:** `[Complex type: __type]`\n\n\n### OBJECTIVE_WEIGHTS\nStandard objective weights for the scheduling solver.\n\nThese weights define the relative importance of different objectives.\nHigher weights mean stronger preference. Rules can use these as reference\npoints when adding their own penalties.\n\nWeight hierarchy (highest to lowest priority):\n- SHIFT_ACTIVE (1000): Minimize number of active shift patterns\n- ASSIGNMENT_PREFERENCE (10): Per-assignment preference (e.g., prefer permanent staff)\n- FAIRNESS (5): Fair distribution of shifts across team members\n- ASSIGNMENT_BASE (1): Tiebreaker - minimize total assignments\n\n**Example:**\nUsing weights in a custom rule\n```ts\nimport { OBJECTIVE_WEIGHTS } from \"dabke\";\n\n// Prefer senior staff with same weight as employee-assignment-priority\nb.addPenalty(assignment, -OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);\n\n// Strong preference (2x normal)\nb.addPenalty(assignment, -2 * OBJECTIVE_WEIGHTS.ASSIGNMENT_PREFERENCE);\n```\n\n**Type:** `{ readonly SHIFT_ACTIVE: 1000; readonly ASSIGNMENT_PREFERENCE: 10; readonly FAIRNESS: 5; readonly ASSIGNMENT_BASE: 1; }`\n\n\n### DAY_OF_WEEK_MAP\n**Type:** `{ sunday: number; monday: number; tuesday: number; wednesday: number; thursday: number; friday: number; saturday: number; }`\n\n\n### SolverTermSchema\n**Type:** `z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>`\n\n\n### BoolVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool\">; name: z.ZodString; }, z.core.$strip>`\n\n\n### IntVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"int\">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>`\n\n\n### IntervalVariableSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"interval\">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>`\n\n\n### SolverVariableSchema\n**Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<\"bool\">; name: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"int\">; name: z.ZodString; min: z.ZodNumber; max: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"interval\">; name: z.ZodString; start: z.ZodNumber; end: z.ZodNumber; size: z.ZodNumber; presenceVar: z.ZodOptional<z.ZodString>; }, z.core.$strip>]>`\n\n\n### LinearConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">, z.ZodLiteral<\"==\">]>; rhs: z.ZodNumber; }, z.core.$strip>`\n\n\n### SoftLinearConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"soft_linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>`\n\n\n### ExactlyOneConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"exactly_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### AtMostOneConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"at_most_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### ImplicationConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"implication\">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>`\n\n\n### BoolOrConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool_or\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### BoolAndConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"bool_and\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### NoOverlapConstraintSchema\n**Type:** `z.ZodObject<{ type: z.ZodLiteral<\"no_overlap\">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>`\n\n\n### SolverConstraintSchema\n**Type:** `z.ZodUnion<readonly [z.ZodObject<{ type: z.ZodLiteral<\"linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">, z.ZodLiteral<\"==\">]>; rhs: z.ZodNumber; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"soft_linear\">; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; op: z.ZodUnion<readonly [z.ZodLiteral<\"<=\">, z.ZodLiteral<\">=\">]>; rhs: z.ZodNumber; penalty: z.ZodNumber; id: z.ZodOptional<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"exactly_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"at_most_one\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"implication\">; if: z.ZodString; then: z.ZodString; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"bool_or\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"bool_and\">; vars: z.ZodArray<z.ZodString>; }, z.core.$strip>, z.ZodObject<{ type: z.ZodLiteral<\"no_overlap\">; intervals: z.ZodArray<z.ZodString>; }, z.core.$strip>]>`\n\n\n### SolverObjectiveSchema\n**Type:** `z.ZodObject<{ sense: z.ZodUnion<readonly [z.ZodLiteral<\"minimize\">, z.ZodLiteral<\"maximize\">]>; terms: z.ZodArray<z.ZodObject<{ var: z.ZodString; coeff: z.ZodNumber; }, z.core.$strip>>; }, z.core.$strip>`\n\n\n### SolverOptionsSchema\n**Type:** `z.ZodObject<{ timeLimitSeconds: z.ZodOptional<z.ZodNumber>; solutionLimit: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`\n\n\n### SolverStatisticsSchema\n**Type:** `z.ZodObject<{ solveTimeMs: z.ZodOptional<z.ZodNumber>; conflicts: z.ZodOptional<z.ZodNumber>; branches: z.ZodOptional<z.ZodNumber>; }, z.core.$strip>`\n\n\n### SoftConstraintViolationSchema\n**Type:** `z.ZodObject<{ constraintId: z.ZodString; violationAmount: z.ZodNumber; targetValue: z.ZodNumber; actualValue: z.ZodNumber; }, z.core.$strip>`\n\n\n### MINUTES_PER_DAY\n**Type:** `number`\n\n\n\n## Built-In Rules Reference\n\nRules are constraints and preferences you can apply to schedules using `ctx.addRule(ruleName, config)`.\n\n### assign-together\n\n\n\nEncourages or enforces that team members in the group work the same shift patterns on a day.\nFor each pair of team members in the group, ensures they are assigned to the same shifts.\n\n**Example:**\n```ts\nconst rule = createAssignTogetherRule({\n groupEmployeeIds: [\"alice\", \"bob\", \"charlie\"],\n priority: \"HIGH\",\n});\nbuilder = new ModelBuilder({ ...config, rules: [rule] });\n```\n\n**Config:** `{ groupEmployeeIds: [string, string, ...string[]]; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; }`\n\n### employee-assignment-priority\n\n\n\nAdds objective weight to prefer or avoid assigning team members.\n\n**Example:**\nPrefer specific team members\n```ts\ncreateEmployeeAssignmentPriorityRule({\nemployeeIds: [\"alice\", \"bob\"],\npreference: \"high\",\n});\n```\n\n**Example:**\nAvoid assigning students on weekdays\n```ts\ncreateEmployeeAssignmentPriorityRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npreference: \"low\",\n});\n```\n\n**Config:** `({ preference: \"high\" | \"low\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n### location-preference\n\n\n\nPrefers assigning a person to shift patterns matching a specific location.\n\n**Example:**\n```ts\ncreateLocationPreferenceRule({\n locationId: \"terrace\",\n priority: \"HIGH\",\n employeeIds: [\"alice\"],\n});\n```\n\n**Config:** `{ locationId: string; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### max-consecutive-days\n\n\n\nLimits how many consecutive days a person can be assigned.\n\n**Example:**\n```ts\ncreateMaxConsecutiveDaysRule({ days: 5, priority: \"MANDATORY\" });\n```\n\n**Config:** `{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### max-hours-day\n\n\n\nLimits how many hours a person can work in a single day.\n\n**Example:**\nLimit everyone to 8 hours per day\n```ts\ncreateMaxHoursDayRule({\nhours: 8,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents limited to 4 hours on weekdays during term\n```ts\ncreateMaxHoursDayRule({\nroleIds: [\"student\"],\nhours: 4,\ndayOfWeek: [\"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n### max-hours-week\n\n\n\nCaps total hours a person can work within each scheduling week.\n\n**Example:**\nLimit everyone to 40 hours per week\n```ts\ncreateMaxHoursWeekRule({ hours: 40, priority: \"HIGH\" });\n```\n\n**Example:**\nStudents limited to 20 hours during term time\n```ts\ncreateMaxHoursWeekRule({\nroleIds: [\"student\"],\nhours: 20,\nrecurringPeriods: [\n{ name: \"fall-term\", startMonth: 9, startDay: 1, endMonth: 12, endDay: 15 },\n],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `({ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n### max-shifts-day\n\n\n\nLimits how many shifts a person can work in a single day.\n\nControls the maximum number of distinct shift assignments per day,\nregardless of shift duration. For limiting total hours worked, use `max-hours-day`.\n\n**Example:**\nLimit to one shift per day\n```ts\ncreateMaxShiftsDayRule({\nshifts: 1,\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nStudents can work 2 shifts on weekends only\n```ts\ncreateMaxShiftsDayRule({\nroleIds: [\"student\"],\nshifts: 2,\ndayOfWeek: [\"saturday\", \"sunday\"],\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `({ shifts: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & OptionalTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n### min-consecutive-days\n\n\n\nRequires that once a person starts working, they continue for a minimum\nnumber of consecutive days.\n\n**Example:**\n```ts\ncreateMinConsecutiveDaysRule({ days: 3, priority: \"MANDATORY\" });\n```\n\n**Config:** `{ days: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### min-hours-day\n\n\n\nEnsures a person works at least a minimum number of hours per day.\n\n**Example:**\n```ts\ncreateMinHoursDayRule({ hours: 6, priority: \"MANDATORY\" });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### min-hours-week\n\n\n\nEnforces a minimum total number of hours per scheduling week.\n\n**Example:**\n```ts\ncreateMinHoursWeekRule({ hours: 30, priority: \"HIGH\" });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; weekStartsOn?: \"monday\" | \"tuesday\" | \"wednesday\" | \"thursday\" | \"friday\" | \"saturday\" | \"sunday\" | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### min-rest-between-shifts\n\n\n\nEnforces a minimum rest period between any two shifts a person works.\n\n**Example:**\n```ts\ncreateMinRestBetweenShiftsRule({ hours: 10, priority: \"MANDATORY\" });\n```\n\n**Config:** `{ hours: number; priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">`\n\n### time-off\n\n\n\nBlocks or penalizes assignments during specified time periods.\n\nSupports entity scoping (people, roles, skills) and time scoping\n(date ranges, specific dates, days of week, recurring periods).\nOptionally supports partial-day time-off with startTime/endTime.\n\n**Example:**\nFull day vacation\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"alice\"],\ndateRange: { start: \"2024-02-01\", end: \"2024-02-05\" },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nEvery Wednesday afternoon off for students\n```ts\ncreateTimeOffRule({\nroleIds: [\"student\"],\ndayOfWeek: [\"wednesday\"],\nstartTime: { hours: 14, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Example:**\nSpecific date, partial day\n```ts\ncreateTimeOffRule({\nemployeeIds: [\"bob\"],\nspecificDates: [\"2024-03-15\"],\nstartTime: { hours: 16, minutes: 0 },\nendTime: { hours: 23, minutes: 59 },\npriority: \"MANDATORY\",\n});\n```\n\n**Config:** `({ priority: \"LOW\" | \"MEDIUM\" | \"HIGH\" | \"MANDATORY\"; startTime?: { hours: number; minutes: number; } | undefined; endTime?: { hours: number; minutes: number; } | undefined; } & EntityScopeType<\"employees\" | \"roles\" | \"skills\">) & RequiredTimeScopeType<\"dateRange\" | \"specificDates\" | \"dayOfWeek\" | \"recurring\">`\n\n";
5
5
  //# sourceMappingURL=llms.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"llms.d.ts","sourceRoot":"","sources":["../src/llms.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,eAAO,MAAM,OAAO,qw6EACs03E,CAAC"}
1
+ {"version":3,"file":"llms.d.ts","sourceRoot":"","sources":["../src/llms.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,eAAO,MAAM,OAAO,84hFACou+E,CAAC"}