disclosure-bureau/web/app/api/graph/route.ts

66 lines
2.5 KiB
TypeScript
Raw Normal View History

/**
* /api/graph Public entity-graph endpoints (read-only).
*
* GET /api/graph?op=neighbors&class=people&id=j-edgar-hoover
* top entities co-mentioned with this one
*
* GET /api/graph?op=paths&from=PK_A&to=PK_B&max_hops=3
* shortest paths between two entities via shared chunks
*
* GET /api/graph?op=co_mention&a=PK_A&b=PK_B
* chunks where both entities appear together
*/
import { NextRequest } from "next/server";
import { findEntity, getNeighbors, findPaths, getCoMentionChunks } from "@/lib/retrieval/graph";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
function json(data: unknown, status = 200) {
return new Response(JSON.stringify(data), {
status,
headers: { "content-type": "application/json" },
});
}
export async function GET(req: NextRequest) {
const u = new URL(req.url);
const op = u.searchParams.get("op") ?? "neighbors";
try {
if (op === "neighbors") {
const cls = u.searchParams.get("class") ?? "";
const id = u.searchParams.get("id") ?? "";
if (!cls || !id) return json({ error: "class and id required" }, 400);
const ent = await findEntity(cls, id);
if (!ent) return json({ error: "entity_not_found", class: cls, id }, 404);
const filterClasses = u.searchParams.get("filter_classes")?.split(",").filter(Boolean);
const limit = Number(u.searchParams.get("limit") ?? 30);
const neighbors = await getNeighbors(ent.entity_pk, { limit, classes: filterClasses });
return json({ entity: ent, neighbors });
}
if (op === "paths") {
const from = Number(u.searchParams.get("from"));
const to = Number(u.searchParams.get("to"));
const maxHops = Math.min(Number(u.searchParams.get("max_hops") ?? 3), 4);
if (!from || !to) return json({ error: "from and to required" }, 400);
const paths = await findPaths(from, to, maxHops);
return json({ from, to, max_hops: maxHops, paths });
}
if (op === "co_mention") {
const a = Number(u.searchParams.get("a"));
const b = Number(u.searchParams.get("b"));
const limit = Math.min(Number(u.searchParams.get("limit") ?? 20), 100);
if (!a || !b) return json({ error: "a and b required" }, 400);
const chunks = await getCoMentionChunks(a, b, limit);
return json({ a, b, count: chunks.length, chunks });
}
return json({ error: "unknown_op", op }, 400);
} catch (e) {
return json({ error: "graph_unavailable", message: (e as Error).message }, 503);
}
}