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>