Files
shiftcraft/server/utils/solver/index.ts
Clanker 36e0946ee4 feat: complete ShiftCraft — AI-powered shift scheduling SaaS
Complete implementation including:
- Landing page with hero, features, how-it-works, pricing
- Employee management (CRUD with soft delete)
- AI constraint parser (Anthropic Claude API)
- German labor law templates (ArbZG §3, §5, §9)
- HiGHS ILP solver for optimal fair schedules
- Schedule calendar result view (employee × date grid)
- Shift framework configuration (periods + shifts)
- Subscription tiers: Free / Pro / Business
- PocketBase setup script with collection creation + seed data
- .env.example with all required variables documented

Pages: employees, constraints (list/new/templates), schedules (list/new/[id]),
       settings (organization/shifts/billing), dashboard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 07:47:31 +02:00

70 lines
2.1 KiB
TypeScript

import type { SolveInput, SolveResult } from '~/shared/types/schedule'
import { buildModel } from './modelBuilder'
import { parseAssignments } from './resultParser'
import { checkFeasibility } from './feasibilityCheck'
export async function solveSchedule(input: SolveInput): Promise<SolveResult> {
const startTime = Date.now()
// Pre-solve sanity check
const issues = checkFeasibility(input)
if (issues.length > 0 && issues.some(i => i.blocking)) {
return {
status: 'infeasible',
assignments: [],
infeasibility_hints: issues.map(i => ({ description: i.message }))
}
}
try {
// Dynamic import — HiGHS WASM is large
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const HiGHS = (await import('highs' as any)).default
const highs = await HiGHS()
const model = buildModel(input)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = highs.solve(model, {}) as Record<string, unknown>
const duration_ms = Date.now() - startTime
const status = result.Status as string
if (status === 'Infeasible' || status === 'Infeasible or Unbounded') {
return {
status: 'infeasible',
assignments: [],
duration_ms,
infeasibility_hints: [
{ description: 'Das Optimierungsproblem hat keine Lösung. Bitte überprüfen Sie Ihre Bedingungen, insbesondere ob genügend Mitarbeiter für alle Schichten verfügbar sind.' },
...issues.map(i => ({ description: i.message }))
]
}
}
if (status !== 'Optimal' && status !== 'Feasible') {
return {
status: 'error',
assignments: [],
duration_ms,
infeasibility_hints: [{ description: `Solver Status: ${status}` }]
}
}
const assignments = parseAssignments(result, input)
return {
status: 'solved',
assignments,
objective_value: result.ObjectiveValue as number,
duration_ms,
}
} catch (err) {
console.error('Solver error:', err)
return {
status: 'error',
assignments: [],
infeasibility_hints: [{ description: String(err) }]
}
}
}