Turns the graph into a gap-finder: the 247 unresolved internal citations
(a corpus precedent cites a ruling NOT in the corpus) collapse to 230 distinct
"gap" nodes — each sized by how many corpus precedents cite it, i.e. the
most-wanted missing precedent.
Backend (web/graph_api.py — read-only, G2):
- "gap" added to VALID_NODE_TYPES (NOT default → off unless requested).
- New _gap_nodes_and_edges(): gap:<normalized citation> nodes from
precedent_internal_citations WHERE cited_case_law_id IS NULL, sized by global
in-degree; cites edges only from precedents present in the view (dangling-edge
invariant holds). Best-effort enrichment from missing_precedents via exact
normalized-citation match → gap_status + missing_precedent_id. Validated:
230 gaps, top ע"א 3213/97 (cited 5×), 230/230 matched to missing_precedents.
- GraphNode += gap_status, missing_precedent_id. Metrics correctly exclude gap
edges (target not a precedent). No app.py change (gated via node_types).
Frontend:
- graph.ts: GraphNodeType += "gap"; node fields.
- graph-filter-panel: toggle "חוסרי מחקר (פסיקה חסרה)" (off by default).
- graph-canvas: gaps render as faint hollow dashed circles, never recoloured
by color-by; sized by citation count.
- graph-node-panel: gap branch — "מצוטטת ע״י N פסיקות" + status badge + link
to /missing-precedents.
web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2 (model grows on
explicit Pydantic). api:types post-deploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds legal-metadata filtering and the payload to color by it (foundation for
the color-by selector in the analytics PR).
Backend (web/graph_api.py, web/app.py) — read-only, G2:
- GraphNode += court, date (ISO) — precedents carry them for filter/color-by.
- build_corpus_graph += server-side WHERE filters (G5): court, precedent_level,
chair, district, year_from, year_to (EXTRACT(YEAR FROM date)). Neighborhood
query also selects court/date.
- New GET /api/graph/facets (response_model GraphFacets, UI2) → distinct
courts/levels/chairs/districts so the UI doesn't hardcode Hebrew strings.
Frontend:
- graph.ts: GraphNode += court/date; GraphFilters += the six params;
buildParams; useGraphFacets() hook.
- graph-filter-panel: an "advanced" Accordion with court/precedent_level/chair/
district Selects (from facets) + year-from/year-to Selects.
- graph-view: new controls wired into filters; facets fetched and passed down.
Verified read-only against the live DB (precedent_level=עליון&year_from=2015
filters correctly; facets populated: 36 courts / 3 levels / 19 chairs / 4
districts). web-ui build + lint pass.
Invariants: G2 (SELECT-only via db.get_pool), G5 (filters server-side),
UI2 (explicit response_models). api:types to be regenerated post-deploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Native, Obsidian-graph-view-like network of the precedent corpus, rendered
in web-ui from a read-only projection of the live DB. Replaces the idea of
exporting to an external Obsidian vault (which would be a parallel, drifting
copy of the corpus — the exact root cause G2 forbids).
The graph edges already existed in the data model; this only surfaces them:
nodes = precedents (case_law) + synthesized topic/practice-area hubs;
edges = cites (precedent_internal_citations) + same_chain (case_law_relations)
+ tagged/in_area (subject_tags / practice_area membership). Node size =
incoming-citation count (index-backed GROUP BY on idx_pic_target). Click a
node → local-graph neighborhood focus; panel deep-links to /precedents/[id].
Backend (read-only, SELECT only — G2):
- web/graph_api.py — Pydantic models (CorpusGraph/GraphNode/GraphEdge, so
OpenAPI emits real types — UI2) + SQL assembly over the shared db.get_pool().
- web/app.py — GET /api/graph/corpus, GET /api/graph/node/{id}/neighborhood,
both with explicit response_model. practice_area validated against the
closed enum (G5); both endpoints write nothing.
Frontend:
- react-force-graph-2d (canvas/d3-force), loaded via next/dynamic ssr:false.
- /graph page + nav entry; graph.ts TanStack hooks; filter panel (practice_area
/ source / min-citations / search / node-type toggles), node detail panel,
hover+selection neighborhood highlight. Explicit error handling (UI4).
Not a retrieval path (03-retrieval): returns graph topology, never ranked
search results. Halacha nodes + corroboration/equivalence edges are Phase 2,
already gated behind the node_types param (no contract change needed).
SQL validated read-only against the live DB (142 precedents, 85 resolved
citations, JSONB tag expansion, ANY(uuid[]) edge + BFS queries). web-ui lint
+ build pass; /graph in the route table.
Invariants: keeps G2 (single source of truth — live projection, no parallel
store), G5 (corpus separation filtered server-side), UI2 (response models),
UI4 (no swallowed UI errors).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>