export const dynamic = 'force-dynamic' import { NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth/get-session' import { withUser, asAdmin } from '@/lib/db/with-user' import { writeAuditLog } from '@/lib/db/audit' import { auditLog } from '@/lib/db/schema' import { eq, and, gte, sql } from 'drizzle-orm' import { createDeepSeekClient } from '@/lib/claude/client' import { getApiKey } from '@/lib/settings' export async function POST(request: NextRequest) { const session = await getSession() if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) // Rate limit const since = new Date(Date.now() - 60_000) const [rateRow] = await asAdmin(db => db.select({ cnt: sql`count(*)` }).from(auditLog) .where(and( eq(auditLog.changedBy, session.sub), eq(auditLog.action, 'ai_quality_check'), gte(auditLog.changedAt, since), )) ) if (Number(rateRow?.cnt ?? 0) > 0) return NextResponse.json({ error: 'Rate limited — please wait 60 seconds' }, { status: 429 }) const deepseekKey = await getApiKey('DEEPSEEK_API_KEY') const client = createDeepSeekClient(deepseekKey) let body: { description?: string; incident_type?: string } try { body = await request.json() } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }) } if (!body.description || !body.incident_type) { return NextResponse.json({ error: 'description and incident_type required' }, { status: 422 }) } let res: Awaited> try { res = await client.chat.completions.create({ model: 'deepseek-v4-pro', max_tokens: 1024, messages: [ { role: 'system', content: `You are an HSE reporting assistant for a Malaysian 3PL warehouse. You must respond with valid JSON only — no markdown, no explanation outside the JSON object. Output exactly this JSON structure: { "score": , "passes": = 6>, "feedback": "", "suggestions": ["= 6>"] } Scoring criteria: specificity (location, time, persons involved), completeness (what happened + immediate actions), clarity. Score 6 or above passes.`, }, { role: 'user', content: `Assess this incident report description. Respond with JSON only.\n\nIncident type: ${body.incident_type}\nDescription: ${body.description}`, }, ], }) } catch { return NextResponse.json({ error: 'AI service unavailable' }, { status: 503 }) } const raw = res.choices[0]?.message?.content if (!raw) return NextResponse.json({ error: 'AI returned empty response' }, { status: 500 }) const json = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim() let input: { score?: unknown; passes?: unknown; feedback?: unknown; suggestions?: unknown } try { input = JSON.parse(json) } catch { return NextResponse.json({ error: 'AI returned unexpected structure' }, { status: 500 }) } if ( typeof input.score !== 'number' || typeof input.passes !== 'boolean' || typeof input.feedback !== 'string' || !Array.isArray(input.suggestions) ) { return NextResponse.json({ error: 'AI returned unexpected structure' }, { status: 500 }) } await withUser(session.sub, async tx => { await writeAuditLog(tx, 'incidents', session.sub, 'ai_quality_check', { score: input.score, passes: input.passes, model: 'deepseek-v4-pro', }) }) return NextResponse.json(input) }