Files
confidence-engine/app/api/cases/update/route.js
T

145 lines
3.8 KiB
JavaScript

import { updateCase, reconsiderCompletedEpisode } from "@/lib/graph/orchestrator.js";
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
import { prepareCompletedEpisode } from "@/lib/graph/episode-preparation.js";
import { updateCaseEpisodeRequestSchema } from "@/lib/graph/schema.js";
function mapFailureStatus(result) {
switch (result?.stage) {
case "request_validation":
case "graph_validation":
return 400;
case "provider":
return 502;
case "proposal_validation":
case "proposal_compatibility":
case "application":
return 422;
case "result_validation":
return 500;
default:
return 500;
}
}
function buildFailureResponse(result) {
return {
success: false,
stage: result?.stage ?? "internal",
error: result?.error ?? "Update case failed",
validationErrors: result?.validationErrors,
graphValidationErrors: result?.graphValidationErrors,
proposalErrors: result?.proposalErrors,
providerErrors: result?.providerErrors,
errors: result?.errors,
diagnostics: result?.diagnostics,
};
}
export async function POST(request) {
try {
const body = await request.json();
const isEpisodeMode = body?.episodeMode === true;
if (isEpisodeMode) {
const parsed = updateCaseEpisodeRequestSchema.safeParse(body);
if (!parsed.success) {
return Response.json(
{
success: false,
stage: "request_validation",
error: "Invalid episode request",
validationErrors: parsed.error.issues,
},
{ status: 400 },
);
}
return await handleEpisodeMode(body.situationGraph, body);
}
const result = await updateCase(body, { applyProposal: true });
if (result.success) {
return Response.json(result, { status: 200 });
}
return Response.json(buildFailureResponse(result), {
status: mapFailureStatus(result),
});
} catch (error) {
if (error instanceof SyntaxError) {
return Response.json(
{
success: false,
stage: "request_validation",
error: "Invalid JSON request body",
},
{ status: 400 },
);
}
return Response.json(
{
success: false,
stage: "internal",
error: "Internal server error",
},
{ status: 500 },
);
}
}
/** Server-side completed-episode reconsideration flow. */
async function handleEpisodeMode(situationGraph, body) {
const prepared = prepareCompletedEpisode({
situationGraph,
targetNodeId: body.targetNodeId,
contributions: body.contributions ?? [],
findings: body.findings,
});
if (!prepared?.turns?.length && !prepared?.eligibleCanonicalFindings?.length) {
return Response.json(
{ success: false, stage: "preparation", error: "no_episodic_content" },
{ status: 400 },
);
}
const reasoning = await reconsiderCompletedEpisode(prepared);
if (!reasoning.success) {
return Response.json(
buildFailureResponse(reasoning),
{ status: mapFailureStatus(reasoning) },
);
}
const application = await applyValidatedProposal({
situationGraph,
proposal: reasoning.proposal,
evidenceContext: {
isCompletedEpisode: true,
episodeEvidence: prepared,
},
});
if (!application.success) {
return Response.json(
buildFailureResponse(application),
{ status: mapFailureStatus(application) },
);
}
const resolvedNodeIds = new Set(application.updatedSituationGraph.resolvedNodeIds ?? []);
resolvedNodeIds.add(body.targetNodeId);
const updatedSituationGraph = {
...application.updatedSituationGraph,
resolvedNodeIds: [...resolvedNodeIds],
};
return Response.json({
success: true,
updatedSituationGraph,
proposal: reasoning.proposal,
}, { status: 200 });
}