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>
60 lines
1.8 KiB
TypeScript
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);
|
|
}
|
|
}
|