Files
ims/app/api/incidents/ai/quality-check/route.ts
T
adminandClaude 7bf3b6a409 fix: rename deepseek model from deepseek-chat to deepseek-v4-pro
DeepSeek API deprecated deepseek-chat model. Returns error:
'the supported API model names are deepseek-v4-pro or deepseek-v4-flash'

Affected all 4 AI endpoints:
- risk-flags (90-day dashboard)
- triage-suggest
- rca-draft
- quality-check

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 10:17:26 +08:00

108 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<number>`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<ReturnType<typeof client.chat.completions.create>>
try {
res = await client.chat.completions.create({
model: 'deepseek-v4-pro',
max_tokens: 1024,
tools: [{
type: 'function',
function: {
name: 'assess_quality',
description: 'Assess HSE incident report description quality',
parameters: {
type: 'object',
properties: {
score: { type: 'number', description: '1-10 quality score' },
passes: { type: 'boolean', description: 'True when score is 6 or above' },
feedback: { type: 'string', description: 'One-sentence quality summary' },
suggestions: {
type: 'array',
items: { type: 'string' },
description: 'Up to 3 concrete suggestions to improve the description',
},
},
required: ['score', 'passes', 'feedback', 'suggestions'],
},
},
}],
tool_choice: { type: 'function', function: { name: 'assess_quality' } },
messages: [{
role: 'user',
content: `You are an HSE reporting assistant for a Malaysian 3PL warehouse. Assess this incident report description.
Incident type: ${body.incident_type}
Description: ${body.description}
Score 110 based on: specificity (location, time, persons involved), completeness (what happened + immediate actions), and clarity. Score 6 or above passes. If score is below 6, give up to 3 actionable suggestions.`,
}],
})
} catch {
return NextResponse.json({ error: 'AI service unavailable' }, { status: 503 })
}
const call = res.choices[0]?.message?.tool_calls?.[0]
if (!call || call.type !== 'function') return NextResponse.json({ error: 'AI assessment failed' }, { status: 500 })
let input: { score?: unknown; passes?: unknown; feedback?: unknown; suggestions?: unknown }
try { input = JSON.parse(call.function.arguments) }
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 })
}
// audit write uses session.sub as a pseudo record-id (no incident_id at this stage)
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)
}