feat(graph): centrality + cluster analytics (corpus graph PR B)
The Obsidian "Graph Analysis" equivalent — surfaces influence and structure beyond raw citation count. Backend (new web/graph_metrics.py — pure, dependency-free, no DB → G2): - PageRank (power-iteration), betweenness (Brandes), community (deterministic label-propagation + connected-components fallback), computed in-memory over the precedent citation subgraph that build_corpus_graph already fetched. Normalized 0–1; community ints dense-ranked by size (stable colours). - GraphNode += pagerank/betweenness/community (None unless metrics=true). - build_corpus_graph + /api/graph/corpus gain metrics=false (default path unchanged). Validated on the live corpus: 147 nodes in 13ms. Frontend: - graph.ts: GraphNode metrics fields + metrics param. - graph-canvas: color-by (type | practice_area | precedent_level | community | recency) and size-by (in-degree | pagerank | betweenness) via colorForNode / radiusForNode; exported palettes. - graph-view: colorBy/sizeBy controls; metrics requested only when needed; global metrics overlaid onto neighborhood nodes by id (a node's PageRank shouldn't change when focused); a ranking panel (Tabs: המשפיעות / גשרים, click → focus); dynamic legend per color-by. - graph-filter-panel: "צביעה לפי" + "גודל נקודה לפי" Selects. web-ui build + lint pass. Invariants: G2 (metrics pure, no DB writes), UI2 (model grows on explicit Pydantic). api:types post-deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,9 @@ export type GraphNode = {
|
||||
court: string | null;
|
||||
date: string | null; // ISO date
|
||||
case_law_id: string | null;
|
||||
pagerank: number | null; // normalized 0–1, only when metrics requested
|
||||
betweenness: number | null; // normalized 0–1
|
||||
community: number | null; // dense cluster id, 0 = largest
|
||||
};
|
||||
|
||||
export type GraphFacets = {
|
||||
@@ -72,6 +75,7 @@ export type GraphFilters = {
|
||||
district?: string;
|
||||
year_from?: number;
|
||||
year_to?: number;
|
||||
metrics?: boolean;
|
||||
};
|
||||
|
||||
export const graphKeys = {
|
||||
@@ -95,6 +99,7 @@ function buildParams(f: GraphFilters): string {
|
||||
if (f.district) p.set("district", f.district);
|
||||
if (f.year_from) p.set("year_from", String(f.year_from));
|
||||
if (f.year_to) p.set("year_to", String(f.year_to));
|
||||
if (f.metrics) p.set("metrics", "true");
|
||||
return p.toString();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user