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>
70 lines
2.1 KiB
TypeScript
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) }]
|
|
}
|
|
}
|
|
}
|