import { getAnthropicClient } from '~/server/utils/anthropic' import { getPBAdminClient } from '~/server/utils/pb-admin' import type { ParsedConstraintResult, ConstraintJSON } from '~/shared/types/constraint' export default defineEventHandler(async (event): Promise => { const body = await readBody(event) const { text, org_id } = body if (!text || !org_id) { throw createError({ statusCode: 400, message: 'text and org_id are required' }) } const pb = await getPBAdminClient() // Fetch employees and framework for context const [employeesResult, frameworkResult] = await Promise.allSettled([ pb.collection('employees').getFullList({ filter: `org_id = "${org_id}" && active = true`, fields: 'id,name,roles' }), pb.collection('shift_frameworks').getFirstListItem(`org_id = "${org_id}"`), ]) const employees = employeesResult.status === 'fulfilled' ? employeesResult.value.map((e: { id: string; name: string; roles: string[] }) => ({ id: e.id, name: e.name, roles: e.roles })) : [] const periods = frameworkResult.status === 'fulfilled' ? ((frameworkResult.value as { periods?: Array<{ id: string; name: string }> }).periods || []) : [] const SYSTEM_PROMPT = `You are a scheduling constraint parser for a workforce management application called ShiftCraft. Given free-text input from a manager, extract one or more scheduling constraints and output them as a JSON array. AVAILABLE EMPLOYEES: ${JSON.stringify(employees)} AVAILABLE SHIFT PERIODS: ${JSON.stringify(periods)} CONSTRAINT TYPES YOU CAN USE: - max_hours_per_day: params: {max_hours: number} - max_hours_per_week: params: {max_hours: number} - min_rest_between_shifts: params: {min_hours: number} - max_consecutive_shifts: params: {max_count: number} - max_consecutive_shift_type: params: {period_id: string, max_count: number} - min_consecutive_days_off: params: {min_days: number} - forbidden_shift_sequence: params: {first_period_id: string, second_period_id: string} - employee_avoids_period: params: {period_id: string} - employee_prefers_period: params: {period_id: string, prefer_count_per_week?: number} - max_weekend_shifts_per_month: params: {max_count: number} - fair_distribution: params: {metric: "total_shifts"|"night_shifts"|"weekend_shifts", max_deviation_percent: number} RULES: - Preference language ("doesn't like", "prefers", "mag keine", "bevorzugt") → hard: false, weight: 65 - Obligation language ("must not", "cannot", "never", "darf nicht") → hard: true - Resolve employee names to their IDs from the list above. If ambiguous, use scope: {type: "global"}. - Always include natural_language_summary in the same language as the input. - Output ONLY a valid JSON array. No commentary. OUTPUT FORMAT: [ { "type": "constraint_type", "scope": {"type": "global"} | {"type": "employee", "employee_id": "..."} | {"type": "role", "role": "..."}, "params": {...}, "hard": true|false, "weight": 1-100, "natural_language_summary": "..." } ]` try { const client = getAnthropicClient() const response = await client.messages.create({ model: 'claude-opus-4-5', max_tokens: 2048, system: SYSTEM_PROMPT, messages: [{ role: 'user', content: text }], }) const content = response.content[0] if (content.type !== 'text') throw new Error('Unexpected response type') // Extract JSON from response const jsonMatch = content.text.match(/\[[\s\S]*\]/) if (!jsonMatch) throw new Error('No JSON array found in response') const constraints: ConstraintJSON[] = JSON.parse(jsonMatch[0]) const ambiguities: string[] = [] // Validate basic structure for (const c of constraints) { if (!c.type || !c.scope || !c.params) { ambiguities.push(`Unvollständige Bedingung erkannt: ${JSON.stringify(c)}`) } } return { constraints, ambiguities } } catch (err) { console.error('Constraint parse error:', err) throw createError({ statusCode: 500, message: `KI-Fehler: ${String(err)}` }) } })