import { z } from "zod"; import { NextResponse } from "next/server"; import { requireAdminApiUser } from "@/lib/auth/admin"; import { callOpenAiJsonSchema } from "@/lib/ai/openai"; import { storeAiSuggestionFromEnvelope } from "@/lib/ai/suggestions"; import { getM7DatasetForUser } from "@/lib/compliance/server"; import type { M7Dataset } from "@/lib/compliance/types"; const M7_PROMPT_VERSION = "m7_playbook_v1"; const M7PlaybookSchema = z.object({ predictedIncidents: z .array( z.object({ title: z.string().min(8).max(220), likelihood: z.enum(["alta", "media", "baja"]), impact: z.enum(["alto", "medio", "bajo"]), timeHorizon: z.string().min(4).max(80), }), ) .max(12), priorityOrder: z.array(z.string().min(8).max(220)).max(12), preventiveActions: z .array( z.object({ action: z.string().min(8).max(400), ownerSuggestion: z.string().min(3).max(120), targetDate: z.string().min(4).max(40), }), ) .max(20), escalationAdvice: z.array(z.string().min(8).max(420)).max(8), confidence: z.enum(["low", "medium", "high"]), }); const M7PlaybookJsonSchema = { type: "object", additionalProperties: false, required: ["predictedIncidents", "priorityOrder", "preventiveActions", "escalationAdvice", "confidence"], properties: { predictedIncidents: { type: "array", items: { type: "object", additionalProperties: false, required: ["title", "likelihood", "impact", "timeHorizon"], properties: { title: { type: "string" }, likelihood: { type: "string", enum: ["alta", "media", "baja"] }, impact: { type: "string", enum: ["alto", "medio", "bajo"] }, timeHorizon: { type: "string" }, }, }, }, priorityOrder: { type: "array", items: { type: "string" }, }, preventiveActions: { type: "array", items: { type: "object", additionalProperties: false, required: ["action", "ownerSuggestion", "targetDate"], properties: { action: { type: "string" }, ownerSuggestion: { type: "string" }, targetDate: { type: "string" }, }, }, }, escalationAdvice: { type: "array", items: { type: "string" }, }, confidence: { type: "string", enum: ["low", "medium", "high"] }, }, } as const; function parseDataset(value: unknown): M7Dataset | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value as M7Dataset; } export async function POST(request: Request) { const user = await requireAdminApiUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const body = (await request.json().catch(() => ({}))) as Record; const providedDataset = parseDataset(body.dataset); const dataset = providedDataset ?? (await getM7DatasetForUser(user.id)); const condensedDataset = { generatedAt: dataset.generatedAt, kpis: dataset.kpis, m3States: dataset.m3States, deadlines: dataset.tabs.plazos.slice(0, 25), alerts: dataset.tabs.alertas.slice(0, 40), checklist: dataset.tabs.checklist.slice(0, 25), }; const systemPrompt = [ "Eres un especialista en cumplimiento para contratacion publica en Mexico.", "Construye un playbook preventivo con enfoque operativo para los proximos 30 dias.", "No cambies severidades ni estados existentes: solo sugiere acciones.", "Responde solo JSON valido en espanol.", ].join(" "); const userPrompt = [ "Dataset actual de M7:", JSON.stringify(condensedDataset), "", "Genera:", "- predictedIncidents: incidentes probables (sin inventar datos externos).", "- priorityOrder: orden de atencion recomendado.", "- preventiveActions: acciones con ownerSuggestion y targetDate.", "- escalationAdvice: criterios breves para escalar a legal/direccion.", ].join("\n"); const envelope = await callOpenAiJsonSchema({ promptVersion: M7_PROMPT_VERSION, systemPrompt, userPrompt, outputSchema: M7PlaybookSchema, schemaName: "m7_playbook", jsonSchema: M7PlaybookJsonSchema as unknown as Record, model: process.env.OPENAI_M7_MODEL?.trim() || undefined, }); const payload = envelope.data ?? ({ predictedIncidents: [], priorityOrder: [], preventiveActions: [], escalationAdvice: [], confidence: envelope.confidence ?? "low", } satisfies z.infer); const persisted = await storeAiSuggestionFromEnvelope({ userId: user.id, moduleKey: "M7", featureKey: "compliance_playbook", subjectType: "m7_dataset", subjectId: dataset.generatedAt, inputForHash: condensedDataset, envelope, responsePayload: payload, }); return NextResponse.json({ ok: true, ...payload, suggestionId: persisted.suggestionId, meta: { engine: envelope.engine, model: envelope.model, usage: envelope.usage, warnings: envelope.warnings, confidence: envelope.confidence, }, }); }