feat(graph): in-app corpus citation graph (/graph) — Phase 1 #113

Merged
chaim merged 1 commits from worktree-corpus-graph into main 2026-06-07 18:52:02 +00:00
Owner

מה ולמה

חיים שאל אם אפשר להשתמש ב‑Obsidian Graph View על מאגרי הקורפוס. ההכרעה: להטמיע גרף נייטיב ב‑web‑ui במקום לייצא ל‑vault חיצוני — ייצוא חיצוני יוצר עותק מקביל של הקורפוס שמתיישן (כשל‑השורש ש‑G2 בא לייבש). גרף נייטיב קורא את ה‑DB החי → אפס drift, ומחובר לדפים הקיימים.

התובנה: כל קשתות הגרף כבר קיימות בטבלאות — ה‑PR רק חושף אותן.

דף /graph (Phase 1)

  • נקודות: פסיקה (case_law) + נקודות‑נושא (subject_tags) + נקודות‑תחום (practice_area).
  • קשתות: cites (precedent_internal_citations) · same_chain (case_law_relations) · tagged/in_area (שיוך נושא/תחום).
  • גודל נקודה = ציטוטים נכנסיםGROUP BY על idx_pic_target (לא N+1).
  • Local Graph: לחיצה על נקודה → התמקדות בשכניה (BFS עומק 1‑2). פאנל‑צד → deep‑link ל‑/precedents/[id].
  • סינון: תחום / מקור / מינ׳ ציטוטים / חיפוש / toggles לסוגי נקודות.

Backend (read‑only — G2)

  • web/graph_api.py — מודלי Pydantic (CorpusGraph/GraphNode/GraphEdge → OpenAPI מפיק טיפוסים אמיתיים, UI2) + הרכבת‑SQL מעל ה‑pool המשותף. אין כתיבה (8× conn.fetch, 0× INSERT/UPDATE/DELETE).
  • web/app.pyGET /api/graph/corpus, GET /api/graph/node/{id}/neighborhood, שניהם עם response_model מפורש. practice_area מאומת מול ה‑enum הסגור (G5).

Frontend

  • react-force-graph-2d (canvas/d3‑force), נטען דרך next/dynamic ssr:false.
  • /graph page + כניסה לניווט · graph.ts (TanStack hooks) · filter/node panels · highlight של שכנים ב‑hover/selection · טיפול שגיאה מפורש (UI4).

אימות

  • SQL אומת read‑only מול ה‑DB החי: 142 פסיקות, 332 ציטוטים פנימיים (85 פתורים), הרחבת JSONB tags, שאילתות ANY(uuid[]) + BFS. הצומת המצוטט ביותר (עע"מ 317/10) — 7 ציטוטים נכנסים = 7 שכנים בעומק 1. ✓
  • npm run lint + npm run build עוברים; /graph בטבלת ה‑routes. ✓
  • py_compile על graph_api.py + app.py. ✓

Invariants

Invariant איך מקוים
G2 מקור‑אמת יחיד projection חי, ללא store מקביל; קורא דרך db.get_pool() בלבד
G5 הפרדת‑קורפוס practice_area/source מסוננים בשרת ב‑WHERE
UI2 response model CorpusGraph מפורש בשני ה‑endpoints
UI4 ללא בליעת‑שגיאה כרטיס שגיאה + retry בגרף
03‑retrieval מחזיר טופולוגיה, לא תוצאות חיפוש מדורגות — אינו מסלול אחזור מקביל

Phase 2 (לא ב‑PR זה)

נקודות‑הלכה + קשתות corroborates/equivalent, כבר מגודרות מאחורי node_types (ללא שינוי חוזה). זו הגרנולריות שחיים יחליט עליה דרך ה‑toggle אחרי שיראה.

לאחר מיזוג + deploy: npm run api:types יחליף את הטיפוסים שהוצהרו ידנית ב‑graph.ts בטיפוסים המיוצרים (UI1).

🤖 Generated with Claude Code

## מה ולמה חיים שאל אם אפשר להשתמש ב‑Obsidian Graph View על מאגרי הקורפוס. ההכרעה: **להטמיע גרף נייטיב ב‑web‑ui** במקום לייצא ל‑vault חיצוני — ייצוא חיצוני יוצר **עותק מקביל של הקורפוס שמתיישן** (כשל‑השורש ש‑G2 בא לייבש). גרף נייטיב קורא את ה‑DB החי → אפס drift, ומחובר לדפים הקיימים. התובנה: **כל קשתות הגרף כבר קיימות בטבלאות** — ה‑PR רק חושף אותן. ## דף `/graph` (Phase 1) - **נקודות:** פסיקה (`case_law`) + נקודות‑נושא (`subject_tags`) + נקודות‑תחום (`practice_area`). - **קשתות:** `cites` (`precedent_internal_citations`) · `same_chain` (`case_law_relations`) · `tagged`/`in_area` (שיוך נושא/תחום). - **גודל נקודה = ציטוטים נכנסים** — `GROUP BY` על `idx_pic_target` (לא N+1). - **Local Graph:** לחיצה על נקודה → התמקדות בשכניה (BFS עומק 1‑2). פאנל‑צד → deep‑link ל‑`/precedents/[id]`. - **סינון:** תחום / מקור / מינ׳ ציטוטים / חיפוש / toggles לסוגי נקודות. ## Backend (read‑only — G2) - `web/graph_api.py` — מודלי Pydantic (`CorpusGraph`/`GraphNode`/`GraphEdge` → OpenAPI מפיק טיפוסים אמיתיים, UI2) + הרכבת‑SQL מעל ה‑pool המשותף. **אין כתיבה** (8× `conn.fetch`, 0× INSERT/UPDATE/DELETE). - `web/app.py` — `GET /api/graph/corpus`, `GET /api/graph/node/{id}/neighborhood`, שניהם עם `response_model` מפורש. `practice_area` מאומת מול ה‑enum הסגור (G5). ## Frontend - `react-force-graph-2d` (canvas/d3‑force), נטען דרך `next/dynamic` `ssr:false`. - `/graph` page + כניסה לניווט · `graph.ts` (TanStack hooks) · filter/node panels · highlight של שכנים ב‑hover/selection · טיפול שגיאה מפורש (UI4). ## אימות - **SQL אומת read‑only מול ה‑DB החי:** 142 פסיקות, 332 ציטוטים פנימיים (85 פתורים), הרחבת JSONB tags, שאילתות `ANY(uuid[])` + BFS. הצומת המצוטט ביותר (עע"מ 317/10) — 7 ציטוטים נכנסים = 7 שכנים בעומק 1. ✓ - `npm run lint` + `npm run build` עוברים; `/graph` בטבלת ה‑routes. ✓ - `py_compile` על `graph_api.py` + `app.py`. ✓ ## Invariants | Invariant | איך מקוים | |---|---| | **G2** מקור‑אמת יחיד | projection חי, ללא store מקביל; קורא דרך `db.get_pool()` בלבד | | **G5** הפרדת‑קורפוס | `practice_area`/`source` מסוננים בשרת ב‑WHERE | | **UI2** response model | `CorpusGraph` מפורש בשני ה‑endpoints | | **UI4** ללא בליעת‑שגיאה | כרטיס שגיאה + retry בגרף | | **03‑retrieval** | מחזיר טופולוגיה, לא תוצאות חיפוש מדורגות — אינו מסלול אחזור מקביל | ## Phase 2 (לא ב‑PR זה) נקודות‑הלכה + קשתות `corroborates`/`equivalent`, כבר מגודרות מאחורי `node_types` (ללא שינוי חוזה). זו הגרנולריות שחיים יחליט עליה דרך ה‑toggle אחרי שיראה. > לאחר מיזוג + deploy: `npm run api:types` יחליף את הטיפוסים שהוצהרו ידנית ב‑`graph.ts` בטיפוסים המיוצרים (UI1). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chaim added 1 commit 2026-06-07 18:51:55 +00:00
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>
chaim merged commit 9315ba4dfe into main 2026-06-07 18:52:02 +00:00
chaim deleted branch worktree-corpus-graph 2026-06-07 18:52:02 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ezer-mishpati/legal-ai#113