disclosure-bureau/web/app/api/relations/route.ts
Luiz Gustavo a7e9dce6d2 rebuild entity layer from Sonnet-vision reextract pipeline
Add reextract pipeline (scripts/reextract/) that rebuilds doc-level entity
JSON from Sonnet-vision chunks via Opus, replacing the noisy per-page
extraction. Add synthesize scripts to regenerate wiki/entities from the 116
_reextract.json (30), aggregate missing page.md from chunks (31), and reprocess
805 pages the doc-rebuilder agent dropped on context overflow (32). Add
maintain scripts 43-56 for chunk-page sync, dedup, generic-entity marking, and
typed relation extraction.

Web: wire relations API + entity-relations component; entity/timeline/doc
pages consume the rebuilt layer.

Note: raw/, processing/, wiki/ remain gitignored (bulk data managed
separately); the 116 reextract JSONs and 7,798 rebuilt entity files live on
disk only. The 27 curated anchor events under wiki/entities/events/ are
preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 12:20:24 -03:00

60 lines
1.8 KiB
TypeScript

/**
* /api/relations — read typed relations for an entity.
*
* GET /api/relations?class=person&id=j-edgar-hoover
* → Returns relations where this entity is source OR target,
* grouped by relation_type and direction.
*/
import { NextRequest } from "next/server";
import { pgQuery } from "@/lib/retrieval/db";
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" },
});
}
interface Relation {
source_class: string;
source_id: string;
relation_type: string;
target_class: string;
target_id: string;
evidence_ref: string | null;
confidence: string;
}
export async function GET(req: NextRequest) {
const u = new URL(req.url);
const cls = u.searchParams.get("class") ?? "";
const id = u.searchParams.get("id") ?? "";
if (!cls || !id) return json({ error: "class and id required" }, 400);
try {
const outgoing = await pgQuery<Relation>(
`SELECT source_class, source_id, relation_type, target_class, target_id,
evidence_ref, confidence
FROM public.relations
WHERE source_class = $1 AND source_id = $2
ORDER BY confidence DESC, relation_type, target_class, target_id
LIMIT 200`,
[cls, id],
);
const incoming = await pgQuery<Relation>(
`SELECT source_class, source_id, relation_type, target_class, target_id,
evidence_ref, confidence
FROM public.relations
WHERE target_class = $1 AND target_id = $2
ORDER BY confidence DESC, relation_type, source_class, source_id
LIMIT 200`,
[cls, id],
);
return json({ outgoing, incoming });
} catch (e) {
return json({ error: "db_unavailable", message: (e as Error).message }, 503);
}
}