Commit Graph

171 Commits

Author SHA1 Message Date
d4d2ab4d68 feat(arguments): פופאפ פרופוזיציות גולמיות בלחיצה על "מסתמך על N"
הקישור טיעון↔פרופוזיציות כבר נשמר ב-DB (legal_argument_propositions),
אך ה-UI הציג רק את המספר. מעשיר את get_legal_arguments באותו round-trip
(JOIN ל-claims) להחזיר supporting_propositions = {id, text, source_document},
ועוטף את שורת "מסתמך על N פרופוזיציות" ב-Popover שמציג את הטענות הגולמיות
verbatim עם מקור. שקיפות ועקיבוּת מהטיעון המאוגד חזרה לטענות-המקור.

- supporting_claims נשאר id-only (תאימות לאחור: מונה, צרכני MCP)
- supporting_propositions שדה חדש אופציונלי; fallback לטקסט סטטי כשחסר
- אין מסלול מקביל (G2) — העשרה של אותו endpoint; נרמול-במקור (G1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:51:09 +00:00
98c5feff25 feat(cases): תצוגת "פסיקה שצוטטה בהחלטה" בעמוד-התיק + שחזור חיווט-הרמס
UI שביקש חיים: בכניסה להחלטה רואים את הפסיקה שצוטטה בתוכה — מקושרת לספרייה
(קליק → /precedents/[id]) מול חסרה (סומנה אוטומטית להעלאה).

- web/app.py: GET /api/cases/{case}/citations — מהשורה internal_committee של
  ההחלטה ב-case_law → precedent_internal_citations: linked (join case_law) +
  missing (unresolved + האם flagged ב-missing_precedents).
- web-ui: lib/api/citations.ts (hook) + CitationsSection ב-drafts-panel
  (מוצג כשההחלטה בספרייה). מקושרת=ירוק/קליק, חסרה=ענבר "סומנה להעלאה".
- scripts/curator_apply_pipeline_branch.py: מקור-אמת לחיווט-הכפתורים של הרמס
  (ה-prompt חי רק ב-Paperclip DB). מקדים branch שמריץ את pipeline-ה-final
  ל-wake reason final_learning_*/final_halacha_* (HOME/DOTENV/DATA_DIR מוחלטים
  → מפתחות DeepSeek+Gemini + DATA_DIR נפתרים נכון). idempotent, שני הסוכנים.
  כבר הוחל ב-DB; הסקריפט לשחזור אחרי reset.

אומת: py_compile ✓ · tsc ✓ · החיווט אומת חי על 8126 (deepseek+gemini, dedup,
✓ pipeline הושלם). G2 (יכולת חסרה) · INV-LRN1/G10 נשמרים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:59:21 +00:00
55362bf5a1 feat(learning): כל החלטה שלנו נכנסת תמיד לספריית-הפסיקה + בדיקת-ציטוטים אוטומטית
סוגר את הפער שלולאת-צמיחת-הקורפוס (07-learning §1.3) הוגדרה אך לא חווטה: מסלול
/final/upload הכניס רק לקורפוס-הסגנון, וההכנסה ל-case_law הייתה best-effort
שקטה שנכשלה כש-chair_name ריק.

web/app.py — /api/cases/{case}/final/upload עכשיו, סינכרונית:
- קובע chair_name דטרמיניסטית (תיק → ברירת-מחדל-ועדה לפי prefix; לעולם לא ריק →
  אילוץ case_law_internal_chair_check תמיד מסופק). לא נשען על חילוץ-LLM —
  להחלטות שלנו היו"ר ידוע.
- מכניס את ההחלטה ל-case_law כ-internal_committee (תמיד, לא best-effort) →
  ברת-ציטוט בהחלטות עתידיות. מטה-דאטה נוסף מועשר אסינכרונית (Gemini).
- מחלץ את הציטוטים שההחלטה מצטטת (extract_internal_citations), ו**מסמן
  אוטומטית** כל ציטוט שאינו בספרייה כ-missing_precedent (open) — dedup מול קיימים.
- התוצאה מוחזרת ב-response (enrolled/linked/missing_flagged) — לא נבלעת בשקט.

הציטוטים-המקושרים מזינים את לולאת-ה-corroboration (X11) — תוקן הניתוק שבו
החלטות שלנו לא היו ב-case_law ולכן לא חיזקו הלכות.

web-ui — toast מציג "נוספה לספרייה · N ציטוטים · M חסרים סומנו".
ספ: 07-learning §0.6 עודכן. אומת ידנית על בל"מ 8126-03-25 (15 קושרו / 6 סומנו).

Invariants: INV-LRN4, X11; G2 (יכולת חסרה, לא מקבילה); feedback_silent_swallow
(כשל-הכנסה צף, לא נבלע); DM7 (סמכות נגזרת).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:27:55 +00:00
9ae49f0f70 Merge pull request 'feat(learning): מסלול נקי להעלאת החלטה סופית + פאנל-סגנון דו-סוכני (DeepSeek+Gemini)' (#158) from worktree-final-upload-pipeline into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 42s
2026-06-08 09:04:16 +00:00
f79c46a352 feat(learning): מסלול נקי להעלאת החלטה סופית + פאנל-סגנון דו-סוכני (DeepSeek+Gemini)
מוסיף מסלול ייעודי לקליטת ההחלטה החתומה של היו"ר, ומפעיל אותו דרך שני
שלבים אוטומטיים מדורגים עם פאנלי-סוכנים (אוטו-אישור + אסקלציה ליו"ר).

Backend (web/):
- POST /api/cases/{case}/final/upload — קליטת final חיצוני: שמירה קנונית
  (סופי-{case}.docx + עותק קורפוס-סגנון תחת case_number מלא כדי שבל"מ לא
  יתנגש עם ערר באותו מספר), פתיחת draft_final_pairs (final_received). לא נוגע
  ב-active_draft ולא מריץ retrofit (נבדל מ-exports/upload ו-mark-final → לא G2).
- POST .../final/run-learning + .../final/run-halacha — שלבים מדורגים שמעירים
  worker מקומי (claude/DeepSeek/Gemini מקומיים בלבד) דרך הרחבת
  wake_curator_for_final עם param task=learning|halacha.

פאנל-סגנון חדש (scripts/style_lesson_panel.py): שני שופטים (DeepSeek+Gemini)
על-גבי דיסטילציית-ה-Opus; הסכמה 2/2-keep → decision_lesson
(source=panel:deepseek+gemini); substance מדולג (INV-LRN5); הפיך + גיבוי CSV.
פאנל-הלכות: docstring/SCRIPTS.md עודכנו (--apply מחווט).

Frontend (web-ui/): כפתור "העלאת החלטה סופית של היו"ר" + שני כפתורים מדורגים
"הרץ למידת-קול"/"הרץ אימות-הלכות" ב-drafts-panel; כל התוויות בעברית
(badge מקור-לקח: "פאנל: דיפסיק+גמיני", "הרמס (סקירה)"...).

Spec: docs/spec/07-learning.md §0.6. Invariants: INV-LRN1/LRN4/LRN5, G10
(שער-יו"ר ידני להטמעה ל-SKILL.md/lessons.md — הפאנלים יוצרים הצעות בלבד);
G2 (מסלול-סופי הוא יכולת חסרה, לא מסלול-מקביל).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:03:26 +00:00
638eef6803 feat(ops): /operations — מוני-תור אחידים, "מה רץ עכשיו", וניהול-תהליכים
הדף הציג את התורים באופן לא-אחיד (by_status גולמי), בלי הבחנה בין "ממתין"
(בקלוג: status=pending) ל"בתור" (התור הפעיל: requested_at IS NOT NULL), בלי
הצגת הפריט שרץ כרגע, ובלי שום שליטה בתהליכים.

מה נוסף:
1. כרטיסי-תור אחידים — בתור / ממתין(בקלוג) / בעיבוד / הושלם / נכשל + "רץ עכשיו"
   (citation/case_number של הפריט בעיבוד) לכל drain (אחזור-פסיקה, מטא-דאטה,
   הלכות, יומונים). שערי-אנוש (אישור-הלכות, פסיקה-חסרה) נשארים מוני-סטטוס.
2. פאנל ניהול-תהליכים בסגנון "שירותי Windows":
   - דמון (court-fetch-service/xvfb/chat/reaper): הפעל-מחדש / עצור / הפעל.
   - cron drain: "הרץ עכשיו" (pm2 restart) + מתג הפעל/כבה תזמון.
3. כל תגי-הסטטוס מתורגמים לעברית.

מנגנון:
- הפעל/כבה תזמון = דגל ב-DB (טבלה drain_controls). pm2 cron_restart מחיה תהליך
  שעוצר ב-stop, לכן ה"כיבוי" האמין הוא דגל שכל drain בודק ב-startup (no-op מיידי
  כשכבוי). הקונטיינר כותב/קורא ישירות מ-DB.
- הרץ-עכשיו + restart/stop/start = proxy ל-pm2 דרך endpoint חדש בגשר-המארח
  (court_fetch_service /pm2/control), מאובטח Bearer + whitelist ל-legal-* בלבד.
- יומונים: drain_digests הועבר מ-crontab ל-pm2 (legal-digest-drain.config.cjs)
  כדי שיופיע ויהיה שליט כמו כל drain. drain_halacha_queue.py הובא לבקרת-גרסאות.

Invariants: מקיים G2 (הרחבת /operations + הגשר הקיים, לא מסלול מקביל) ו-G1
(drain_controls = מקור-אמת יחיד לכיבוי, נורמליזציה במקור ולא תיקון-בקריאה).
אין בליעת שגיאות שקטה (הגשר מחזיר {ok,error}; המוטציות מציגות toast).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:57:23 +00:00
5745d36bb4 feat(digests-ui): publication filter + 'מאמר'/source badges for bulletins
משלים את #154 בצד-לקוח:
- פילטר "מקור" בדף /digests (כל המקורות / כל יום / עו"ד על נדל"ן) — backend:
  list_digests + /api/digests מקבלים publication.
- DigestCard: תג "מאמר" ל-digest_kind='article', ו-chip מקור לפרסום שאינו 'כל יום'.

build (webpack) עובר, lint נקי. digests = hand-written types (אין api:types).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:14:23 +00:00
54948eb8ab feat(nav): הסרת דף מדגם-זהב (goldset) מה-UI
הכיול החד-פעמי של ולידטורי חילוץ-ההלכות (#81.8) הסתיים — הוסר מה-UI:
- web-ui/src/app/goldset/page.tsx (הדף)
- web-ui/src/components/goldset/goldset-panel.tsx (הרכיב)
- web-ui/src/lib/api/goldset.ts (מודול ה-API)
- הקישור "מדגם-זהב" מתפריט "פסיקה" + השטחת התת-כותרת "ניתוח וכיול"
  (נותר רק "מפת הקורפוס" → רשימה שטוחה)
- ניקוי אזכורי gold-set מהערות approvals/page.tsx ו-chair.ts

ה-backend נשאר במכוון: טבלת halacha_goldset, ה-endpoints (/api/goldset*)
ופונקציות ה-DB משמשים את סקריפטי ה-eval/benchmark ומחזיקים נתוני-תיוג
אנושיים — אין מחיקת DB ואין שבירת סקריפטים. /api/chair/pending ממילא לא
כלל goldset, אז אין קישור שבור במרכז-האישורים.

Invariants: G2 (הסרת יכולת-UF מיותרת ללא יצירת מסלול מקביל).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:32:48 +00:00
6ec67d1a11 Merge pull request 'feat(ops): דף /operations — כל מה שרץ ברקע' (#149) from worktree-ops-dashboard into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
2026-06-08 07:29:19 +00:00
34d80a39e5 feat(ops): /operations dashboard — everything running in the background
A single live page for all the background work that downloads/analyses, so the
chair can see what's running instead of guessing.

- court_fetch_service: GET /pm2 (unauthenticated, host-only) → trimmed pm2 jlist
  for the legal-* services (status, restarts, mem, cron schedule).
- FastAPI GET /api/operations: aggregates the DB-backed pipelines (court_fetch
  jobs, metadata + halacha extraction queues, halacha review gate,
  missing_precedents, digests, recent court ingests) and proxies the host /pm2
  over the docker bridge (graceful if the host service is down).
- web-ui /operations page (+ src/lib/api/operations.ts hook, nav entry under
  admin): services grid (with Hebrew labels + schedules) + pipeline cards +
  recent-fetch / recent-ingest lists. Auto-refreshes every 5s.

tsc --noEmit clean; pm2 status carries nothing sensitive and the bind
(10.0.1.1) is host/container-only, so /pm2 needs no secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:28:41 +00:00
83d30365c9 feat(nav): קיבוץ הניווט העליון בתפריטים נפתחים (פסיקה/סגנון)
שורת הניווט הצטמצמה מ-11 קישורים ישירים ל-4 קישורי-עבודה
(בית · מרכז אישורים · הערות יו״ר · ארכיון) + 2 תפריטים נפתחים:

- "פסיקה ▾": ספריית פסיקה · יומונים · פסיקה חסרה · ‎—ניתוח וכיול—‎ ·
  מפת הקורפוס · מדגם-זהב
- "סגנון ▾": אימון סגנון · מתודולוגיה

מפת-הקורפוס, מדגם-זהב ומתודולוגיה הורדו-בדרגה מהשורה הראשית לתוך
התפריטים (לפי בקשת היו"ר) — אך כל ה-routes נשמרים, אין שינוי URL.

trigger התפריט מקבל הדגשה + קו-זהב תחתון כשאחד מילדיו פעיל;
badge "פסיקה חסרה" מוצג גם על trigger "פסיקה" וגם בתוך הפריט.

Invariants: מקיים G2 (איחוד מסלולי-ניווט, ללא יצירת מסלול מקביל —
כל הדפים נותרים נגישים, deep-links נשמרים).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:19:32 +00:00
d895062b4c feat(digests-ui): digest_kind badge — mark announcement issues in /digests
משלים את #141 בצד-לקוח: שדה digest_kind ב-Digest type (hand-written), ותג
"עדכון" ב-DigestCard לגיליונות announcement (לא-הכרעות). decision = ברירת-מחדל
ללא תג. זורם דרך /api/digests (digest_kind כבר ב-_DIGEST_COLS).

build (webpack) עובר, lint נקי בקבצי digests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 06:06:53 +00:00
7fb5134580 chore(web-ui): regenerate api types from prod OpenAPI
`npm run api:types` — brings the generated src/lib/api/types.ts up to date
with the live FastAPI schema (UI1: types derive from the OpenAPI SSoT). The
file had drifted; this regen captures the corpus-graph endpoints/models
(/api/graph/corpus, /api/graph/facets, /api/graph/node/{id}/neighborhood;
CorpusGraph / GraphNode / GraphFacets) plus accumulated changes from other
merged work. web-ui build passes against the regenerated types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 05:15:43 +00:00
ef21cb93e5 feat(graph): halacha (rule) layer (corpus graph — closes Phase 2)
Enables the previously-disabled "הלכות" toggle. Each approved/published halacha
of a displayed precedent becomes a hal:<id> node linked to its parent
precedent (extracted_from); two cross-rule edges when both endpoints are in
view: corroborates (a later ruling cites the rule —
halacha_citation_corroboration) and equivalent (same principle from another
committee — equivalent_halachot). Node size = corroboration in-degree.

Backend (web/graph_api.py — read-only, G2):
- _halacha_nodes_and_edges(): halachot WHERE case_law_id in view AND
  review_status IN (approved, published), LIMIT 600; rule_type carried in the
  source_kind slot, rule_statement in note. Wired into both build functions
  (gated via node_types). Metrics still exclude halacha edges (only cites/
  precedent-typed feed PageRank). Validated: 185 halachot on the top-30
  precedents; 20 corroboration + 5 equivalent edges in the corpus.

Frontend:
- graph.ts: GraphEdgeType += extracted_from.
- graph-filter-panel: "הלכות" toggle enabled (was disabled "שלב ב׳").
- graph-canvas: amber halacha nodes; edge colours — extracted_from (faint
  amber), corroborates (amber), equivalent (violet).
- graph-node-panel: halacha branch — אזכורים + סוג כלל + rule text; "open in
  library" deep-links to the parent precedent.
- graph-view: halacha added to node + edge legends.

web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2 (no model change —
reuses note/source_kind/case_law_id slots).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 05:13:09 +00:00
5f1b96ccaf feat(graph): navigation & UX — deep-link, depth, PNG, rich panel (PR D)
Final corpus-graph PR. Connects the graph to the chair's workflow and rounds
out the Obsidian-grade interactions.

Backend (web/graph_api.py): neighborhood depth cap 2 → 3 (still bounded by
NODE_CAP_MAX).

Frontend:
- URL deep-link: /graph?focus=cl:<id> is read on mount and written on focus
  change (router.replace, scroll:false). GraphView wrapped in <Suspense> per
  Next 16's useSearchParams requirement.
- "הצג בגרף" button on the precedent detail page → /graph?focus=cl:<id>.
- Depth slider (1–3) in the focused overlay → useNodeNeighborhood(id, depth).
- Export PNG: grabs the rendered <canvas> from the area ref → toDataURL →
  download; failures surface a toast (UI4).
- Rich node panel: precedent nodes fetch headnote/summary via the existing
  usePrecedent hook (Skeleton while pending, error surfaced — UI4).
- Edge-type legend (ציטוט / נושא-תחום / יומון) added under the node legend.

Deferred (noted for a later pass): expand-in-place merge, search→camera-center.

web-ui build + lint pass. Invariants: G2 (depth change is read-only), UI4
(PNG + detail errors surfaced, not swallowed). api:types post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 04:56:01 +00:00
fc5d69902f feat(graph): daily-digest (יומון) discovery layer (corpus graph PR E)
Chaim's idea: surface the downloaded "כל יום" digests in the graph. Each digest
COVERS the ruling it analyses — a corpus precedent when we have it (16), or a
synthesized gap node from its underlying_citation when we don't (269). So the
digest layer doubles as a discovery signal: it makes visible that the daily
feed overwhelmingly covers rulings NOT yet in the corpus.

Backend (web/graph_api.py — read-only, G2):
- "digest" added to VALID_NODE_TYPES (off by default).
- _digest_nodes_and_edges(): dig:<id> nodes from completed digests, `covers`
  edge → cl:precedent (linked_case_law_id in view) or → gap:<underlying_citation>
  (synthesized, deduped against the gap layer — real in-degree wins). Carries
  concept_tag (label), headline_holding (note), underlying_court/date.
- _add_digests() appends the layer with gap dedup. Wired into both build
  functions. GraphNode += note, digest_id. Gated via node_types (no app.py
  change). Validated: 16 covers→precedent, 269 covers→gap.

Frontend:
- graph.ts: GraphNodeType += "digest"; GraphEdgeType += "covers"; node fields.
- graph-filter-panel: toggle "יומונים (כל יום)" (off by default).
- graph-canvas: digest = teal node (r=4); `covers` edges teal.
- graph-node-panel: digest branch — concept + holding + court/date + link to
  /digests.

web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2. api:types post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:31:04 +00:00
9a126f7c36 feat(graph): research-gap (ghost) nodes (corpus graph PR C)
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>
2026-06-07 21:21:53 +00:00
2fbc0cd3c2 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>
2026-06-07 21:04:47 +00:00
8258f09228 feat(graph): metadata filters + facets (corpus graph PR A)
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>
2026-06-07 20:52:13 +00:00
f3e99a14ca fix(graph): stop corpus-graph labels overlapping
Labels piled on top of each other (esp. in the neighborhood view) for two
reasons, both fixed in graph-canvas.tsx:

1. Font grew as you zoomed OUT (size was divided by sqrt(globalScale) and had
   a +6 floor), so at overview zoom labels became huge and collided. Now the
   label font is a ~constant SCREEN size (fontPx / globalScale).

2. Every node drew its label at once. Now labels are zoom-gated: at overview
   zoom only the active node, the 3 practice-area hubs, and the most-cited
   precedents (size>=4) are labeled; topic hubs appear at >=1.05 and the rest
   at >=1.5 — by which point there is pixel room between nodes.

Also: a white halo (strokeText) behind each label for legibility over edges
and nearby nodes, and stronger d3 forces (charge -220, link distance 60) so
nodes spread out and labels have more room.

web-ui build passes; /graph in the route table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:07:27 +00:00
9315ba4dfe Merge pull request 'feat(graph): in-app corpus citation graph (/graph) — Phase 1' (#113) from worktree-corpus-graph into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m44s
2026-06-07 18:52:01 +00:00
c80e4ce8ff feat(graph): in-app corpus citation graph (/graph) — Phase 1
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>
2026-06-07 18:50:56 +00:00
f3740fef68 Merge pull request 'fix(halacha): split authority (derived) from rule_role — stop source-conflation (INV-DM7)' (#112) from worktree-halacha-authority-split into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m32s
2026-06-07 18:19:43 +00:00
2e33cac043 fix(halacha): split authority (derived) from rule_role — stop source-conflation (INV-DM7)
The extractor classified rule_type by SOURCE bindingness (higher-court→binding,
committee→persuasive) instead of by rule KIND. The gold-set proved it: 'binding'
appeared on 19/19 external rulings & 0 committees; 'persuasive' on 13/13
committees & 0 external — only 58% agreement with the human role tags. The two
axes (authority vs rule role) were crammed into one enum.

This splits them per INV-DM7:
- authority (binding/persuasive) — DERIVED from case_law.precedent_level
  (עליון/מנהלי→binding, ועדת_ערר_מחוזית→persuasive), never stored, never
  LLM-guessed. New helper halacha_quality.derive_authority; surfaced read-only
  in list_halachot / goldset_list / search results.
- rule_type — now the rule ROLE only: holding/interpretive/procedural/
  application/obiter. Both extractor prompts unified to this vocabulary;
  _coerce_halacha no longer defaults rule_type from the source; legacy
  binding→holding / persuasive→interpretive fold for safety.

UI: authority shown as a separate read-only badge (gold=מחייב / muted=משכנע)
across the review queue, precedent detail, and gold-set; the gold-set role
selector drops binding/persuasive and adds מהותי (holding).

Migration: scripts/halacha_rule_role_backfill.py re-classifies the 276 pre-split
binding/persuasive rows into a genuine role via local claude_session (run after
deploy). Gold-set correct_type/ai_correct_type 'binding'→'holding' via SQL.

Sources (≥3, per research-decision policy): OASIS LegalRuleML v1.0
(appliesAuthority/Strength as metadata orthogonal to rule logic) · SemEval-2023
Task 6 LegalEval (rhetorical roles by function, authority kept separate) ·
Bluebook signals (weight-of-authority is a separate dimension).

Invariants: ESTABLISHES INV-DM7. Upholds G1 (normalize at source — extractor
classifies role, system derives authority) and G2 (single source of truth —
authority derived, not a parallel stored field). Tests: 211 pass + new
derive_authority/coerce coverage. web-ui build + tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:18:41 +00:00
06281996ca feat(digests): Phase 2 — API endpoints + /digests UI (X12)
משטחי-משתמש לקורפוס היומונים: endpoints ב-FastAPI + דף UI נפרד /digests
(לדפדוף, חיפוש, העלאה, וקישור לפסק המקורי). היומון נשאר מקור-משני המצביע
על הפסק — אינו מצוטט בהחלטה (INV-DIG1) ואינו מחלץ הלכות (INV-DIG2).

Backend (container-safe + local split):
- digest_library: פוצל ל-create_pending_digest (CONTAINER-SAFE: stage+
  extract_text+create row 'pending', בלי LLM) ↔ enrich_digest/
  process_pending_digests (local: LLM+embed+autolink). ingest_digest מאחד.
- db.list_pending_digests; MCP digest_process_pending (tool+server) — חלופה
  ל-batch script לריקון התור.
- web/app.py: 10 endpoints /api/digests/* (upload/list/search/queue-pending/
  get/patch/delete/link/relink/unlink). upload=INSERT-only pending (ה-LLM רץ
  מקומית — claude_session local-only). כולם מחזירים dict בדפוס precedent.

Frontend (Next 16, ללא api:types — hooks עם טיפוסים hand-written כמו
precedent-library.ts):
- lib/api/digests.ts — hooks (useDigests/useDigestSearch/useDigestPending/
  useUploadDigest/useLink/Relink/Unlink/Delete/Update).
- דף /digests נפרד (לא כרטיסייה ב-/precedents — לשמור גבול סמכותי/משני,
  INV-DIG1): טאבים יומונים/חיפוש + DigestCard (badge קישור-לפסק) +
  DigestUploadDialog + pending badge. nav + header-context.

אומת: backend round-trip מלא (create_pending→list_pending→process_pending→
search→restore); web-ui מתקמפל (webpack/tsc נקי, route /digests נוצר).
הערה: build דיפולטי (turbopack) נכשל ב-worktree עקב symlink ל-node_modules —
ב-CI/Docker (node_modules אמיתי) עובד; אומת עם --webpack.

Invariants: מקיים INV-DIG1/2 (upload לא מחלץ הלכות, UI מציג "מצביע לא
מצוטט"), INV-DIG3 (link/relink/queue). G4 (אין בליעה — שגיאות→toast/HTTP),
G2 (מסלול נפרד, לא מקביל). X6 (חוזה UI↔API — endpoints בדפוס precedent;
hooks hand-written כמו שאר ה-domain modules).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:11:05 +00:00
90f3c472b5 fix(goldset): single view-mode filter — can't get stuck hiding untagged
The old independent toggles had a trap: clicking "אי-הסכמות AI" set a filter,
and once all disagreements were resolved the toggle button disappeared
(rendered only when count>0) while the filter stayed ON — so the list showed
zero items and the untagged ones were unreachable.

Replaced hideTagged + disagreeOnly with one mutually-exclusive segmented
control: הכל / לא תויגו / תויגו / ⚠ אי-הסכמות, each with a live count and always
visible. No stuck state; "לא תויגו" makes the remaining work obvious.

Verified: tsc --noEmit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:47:53 +00:00
0e35060d3d feat(goldset): AI second-opinion per item (QA aid) — compare vs human tag
The chair wanted an independent recommendation beside each tag, to reconsider
his own judgments. Adds a NON-ground-truth AI second-opinion:

- schema: halacha_goldset.ai_is_holding / ai_correct_type / ai_rationale /
  ai_generated_at (additive).
- db.goldset_set_ai_recommendation + goldset_list now returns the ai_* fields.
- scripts/goldset_ai_recommend.py — local claude_session judges is_holding +
  type + a one-line rationale per item, INDEPENDENTLY (own legal rubric).
  Independent of the rule-based validators #81.8 measures → no circularity.
  Never auto-applied; QA aid only.
- web-ui: each card shows "🤖 המלצת AI: הלכה/לא · type" + rationale and an
  agreement/disagreement chip vs the human tag (amber on disagree); a
  "⚠ אי-הסכמות AI (N)" filter to review only the conflicts.

Methodology note kept explicit: the human stays the ground truth; the AI is a
prompt to reconsider, not to copy.

Verified: tsc --noEmit 0; generator stores recs and flags disagreements with
existing human tags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:24:35 +00:00
7e7de485a4 fix(goldset): score panel open by default + sparse-negatives hint
The validator score panel was collapsed by default, so taggers thought nothing
was happening. Now open by default, with a caption explaining the metrics
measure "not-a-holding" detection and become meaningful as more "לא הלכה" items
are tagged (showing the current negative count while it's small).

Verified: tsc --noEmit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:11:49 +00:00
632fe73857 feat(goldset): separate court rulings from committee decisions in tagging
Tagging is easier one source-type at a time. goldset_list now returns
case_law.source_type; the page adds:
- a filter (הכל / פסקי דין / ועדת ערר) with live counts,
- a group-sort so even in "הכל" all court rulings come first, then all
  committee decisions,
- a per-card source badge (פסק-דין / ועדת ערר).

Verified: tsc --noEmit 0; source_type splits the live batch 58 court / 92 committee.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:55:06 +00:00
a07622659c fix(goldset): order rule-type help table to match the buttons
TYPE_HELP popover now follows the same order as the type buttons:
מחייבת · פרשני · יישום · אמרת-אגב · פרוצדורלי · משכנע.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:45:30 +00:00
5aa3d4ed99 feat(goldset): soft consistency warning between is_holding and type
"לא הלכה" + "מחייבת" (or any holding-type) is a logical contradiction — binding
means it IS the holding. Likewise "הלכה" + application/obiter. The three controls
are independent, so the combo was clickable with no signal.

Adds a non-blocking amber warning under the type buttons when is_holding and
correct_type contradict (holding ↔ binding/interpretive/procedural/persuasive;
not-holding ↔ application/obiter). Soft by design — flags the inconsistency for
the tagger to fix without forcing, leaving room for genuine edge cases.

Verified: tsc --noEmit exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:40:05 +00:00
27911c5beb fix(goldset): "tagged" = all 3 answers set + add rule-type help popover
Two UX fixes on the gold-set tagging page:

1. isTagged now requires is_holding AND correct_type AND quote_complete — not
   just is_holding. Previously, in "hide tagged" mode the card vanished the
   instant is_holding was clicked, so the type and quote-complete answers could
   never be set. The progress counter / "תויג" badge now reflect full tagging.

2. An info (ℹ) icon next to "הסוג הנכון" opens a popover explaining the six
   rule types (definition + the deciding test + an example each), so the tagger
   has the criteria in front of them while tagging.

Verified: tsc --noEmit exits 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:26:52 +00:00
ac279220c4 feat(goldset): interactive gold-set tagging page (#81.7/#81.8)
Replaces the CSV-edit workflow with an in-app tagging page so the chair/Dafna
can label the extraction-quality gold-set by clicking, and see validator
precision/recall live.

Schema (V29): halacha_goldset — a stratified, human-tagged evaluation batch
(is_holding / correct_type / quote_complete, NULL until tagged).

db.py:
- goldset_create_sample (stratified round-robin over case×rule_type, idempotent),
- goldset_list (items + halacha content + the machine's own labels),
- goldset_tag (partial — one field at a time for keyboard tagging),
- goldset_score (ports the script's P/R/F1: each validator scored as a
  not-a-holding detector against the human tags — the #81.8 input).

API: GET /api/goldset, POST /api/goldset/sample, GET /api/goldset/score,
PATCH /api/goldset/{id}.

web-ui:
- lib/api/goldset.ts (hooks),
- components/goldset/goldset-panel.tsx — card-per-item, keyboard-first
  (J/K nav, H/N holding, C/X quote), progress bar, hide-tagged toggle, and a
  collapsible live score table,
- app/goldset/page.tsx + nav link "מדגם-זהב" under ידע ולמידה.

Methodology guard kept explicit in UI + docstrings: tags are HUMAN ground truth,
no AI pre-fill (circular bias). Populated a 150-item stratified batch.

Verified: backend create/list/tag/score against the live DB; tsc --noEmit 0;
py_compile ok. (Local Turbopack build blocked by worktree symlink — CI builds clean.)

Invariants: G1 (eval set modeled at source in its own table); G2 (reuses the same
halacha_quality validators the extractor runs — no parallel scoring logic).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:52:05 +00:00
b7b44f4453 feat(halacha): equivalent-halacha (parallel-authority) links across precedents
Cross-precedent recurrence of a principle is real but is NOT citation
corroboration (X11) — the 5 candidate pairs have ZERO citations between their
precedents. Recording them in halacha_citation_corroboration would fabricate
citation data and inflate corroboration_count. This adds a proper, separate
halacha-level link for parallel authority.

Schema (V28): equivalent_halachot — symmetric (halacha_a < halacha_b, CHECK +
UNIQUE), non-citation, cross-precedent-only. ON DELETE CASCADE.

db.py:
- link_equivalent_halachot (idempotent; rejects same-id and SAME-precedent pairs
  — parallel authority is cross-precedent by definition), unlink, and
  list_equivalent_for_halacha.
- list_halachot gains include_equivalents → _annotate_equivalents attaches an
  `equivalents` list (both directions) per row.

API: include_equivalents on GET /api/halachot; GET/POST/DELETE
/api/halachot/{id}/equivalents for the chair to view/link/unlink manually.

scripts/halacha_batch_reconcile.py: --link records found cross-precedent pairs
as equivalent_halachot (non-destructive, idempotent).

web-ui: Halacha.equivalents type; the clean review queue fetches
include_equivalents; the review card shows a gold "עיקרון מקביל ב-N" badge + an
expandable list (case + rule + similarity) labeled "אסמכתה מקבילה — לא ציטוט".

Populated the 5 reviewed pairs (chair decision: keep all + link as parallel
authority). Verified: 5 rows; the 1023-20 hub annotates 3 of its halachot with
equivalents; tsc --noEmit exits 0.

Invariants: G1 (model recurrence at source in its own table, not by abusing the
citator); G2 (no parallel path — extends list_halachot); citator integrity
preserved (corroboration stays citation-only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:29:46 +00:00
25e0662ead Merge pull request 'feat(halacha-triage UI): wire gating + near-duplicate cluster cards (#84.2)' (#98) from worktree-task84.2-ui-clustering into main
Some checks failed
Build & Deploy / build-and-deploy (push) Has been cancelled
2026-06-06 21:02:09 +00:00
12313774a1 feat(halacha-triage UI): wire gating + near-duplicate cluster cards (#84.2)
Completes #84 — surfaces the backend gating/prioritization (#84.1/#84.3, PR
#93) in the chair's review UI and adds near-duplicate clustering (#84.2).

Backend
- db.list_halachot gains `cluster` (#84.2): annotates each row with cluster_id +
  cluster_size by unioning same-precedent halachot within HALACHA_CLUSTER_COSINE
  (0.90, new config). Display-only — never merges/deletes. Pairwise is confined
  to the returned set (cheap).
- GET /api/halachot exposes the `cluster` query param (default off).

Frontend (web-ui)
- Halacha type gains optional cluster_id / cluster_size (hand-written module; no
  api:types regen needed — halachot aren't typed off the generated schema).
- useHalachotPending(opts): the default "clean" queue now fetches
  exclude_low_quality + order_by_priority + cluster; needsFix:true returns the
  flagged 'needs extraction fix' bucket (filtered client-side).
- HalachaReviewPanel: a "תור נקי / דורש תיקון-חילוץ" toggle (#84.1); near-dup
  clusters collapse into ONE card showing "+N וריאנטים" with an expandable list,
  and approve/reject/defer on a clustered card applies to all variants via the
  batch endpoint (#84.2 + #84.4). Counts show true halacha totals (pendingTotal).
  New flag labels added (application / near_duplicate / nevo_preamble_leak).

Verified:
- backend: list_halachot(cluster=True) on the live queue — algorithm correct
  (groups related same-precedent rules at 0.78; none at the production 0.90
  because dedup #82 already removed near-dups — the desired state).
- frontend: `tsc --noEmit` exits 0 (type-clean); no new lint errors (the one
  lint error is pre-existing in training/learning-panel.tsx from #94). Local
  Turbopack build can't run on the worktree node_modules symlink — CI builds in
  a clean checkout.

Invariants: G1 (gate/cluster at source in SQL, not post-hoc); G2 (same
list_halachot path); §6 (flagged items routed to a visible bucket, not dropped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 21:01:30 +00:00
afc1548bca chore(style-acq T11): regen API types (learning + methodology endpoints)
npm run api:types — מסנכרן types.ts המחולל עם ה-endpoints החדשים
(/api/learning/pairs, style-distance, promote). הקוד משתמש בטיפוסים ידניים
(learning.ts) אז זה היגיינה לעתיד, לא תלות. סוגר את T11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:44:41 +00:00
f20a3a09fd feat(style-acq T14): שער-יו"ר לאישור הצעות-curator → הטמעה לפרופיל
סוגר את הלולאה מקצה-לקצה (INV-G10/LRN1): ה-curator מציע (status=analyzed),
היו"ר מאשרת, והלקחים נכתבים לערוצים שהכותב צורך (T15) — אין auto-commit.

- db.get_draft_final_pair(id) — שורת-פנקס מלאה כולל analysis.
- app.py: GET /api/learning/pairs/{id} (חושף רק changes מסוג style_method —
  INV-LRN5) + POST .../promote (לקחים→discussion_rules['universal'],
  ביטויים→transition_phrases['universal'] דרך merge ל-appeal_type_rules;
  status→lessons_folded). _append_methodology_override משותף.
- web-ui: usePairDetail/usePromoteLearning + ProposalReview (בחירת לקחים/
  ביטויים לאימוץ) בטאב "למידה" עבור pairs במצב analyzed.

INV-G10 (שער-יו"ר) · INV-LRN1 (אין auto-commit) · INV-LRN5 (טוהר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:17:56 +00:00
ee76455a9a feat(style-acq T6+T13): פנקס-התאמה + מדד מרחק-סגנון ב-UI
ה"איך מנהלים/רואים את הלמידה": טאב "למידה" ב-/training.

- app.py: GET /api/learning/pairs (פנקס-ההתאמה — כל ההחלטות + סטטוס draft↔final,
  INV-LRN4) + GET /api/learning/style-distance/{case} (מדד T7).
- web-ui: learning.ts hooks + LearningPanel (טבלת פנקס; לחיצה על תיק →
  מדד מרחק-הסגנון: שינוי draft→final, סטיית יחסי-זהב, אנטי-דפוסים) + טאב ב-/training.

מכסה גם את T6 (רשימת כל ההחלטות הנסגרות מול הסופי). ללא endpoint-schema חדש
לטיפוסים מחוללים (טיפוסים ידניים). G9, INV-LRN4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:13:10 +00:00
e4fbda6c1f feat(style-acq T12): /methodology — קטגוריות ביטויי-מעבר + אנטי-דפוסים
מרחיב את עורך-הפרופיל ב-/methodology עם 2 קטגוריות נוספות שהכותב (T15)
והמדד (T7) צורכים — כך שהיו"ר עורכת אותן והעריכה זורמת לכתיבה:

- app.py: _METHODOLOGY_DEFAULTS += transition_phrases (מקובץ לפי תוצאה) +
  anti_patterns (מ-lessons.ANTI_PATTERNS). דרך ה-CRUD הגנרי הקיים (appeal_type_rules).
- block_writer (T15 loop): קורא overrides גם ל-transition_phrases + anti_patterns.
- web-ui: GenericMethodologyPanel (עורך key→JSON) + 2 טאבים ב-/methodology.

voice_invariants (doc) — נדחה (לא key-value). G11, INV-LRN4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:08:44 +00:00
a3ef9e5e34 fix(ui): ברירת-מחדל של ספריית הפסיקה — החלטות ועדות ערר ראשונות
מתג-המקטעים נפתח כעת על "החלטות ועדות ערר" (הקורפוס המרכזי של היו"ר)
במקום "פסיקת בתי משפט".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 17:26:49 +00:00
6bf19bd0d7 feat(ui): אינדיקטור התקדמות לחילוץ מטא-דאטה + מתג-מקטעים בספריית הפסיקה
שתי בעיות UX בדף /precedents:

1. חילוץ מטא-דאטה לא נתן שום אינדיקציה שהוא רץ. בניגוד לחילוץ טקסט/הלכות
   (extraction_status / halacha_extraction_status) למטא-דאטה היתה רק חותמת-זמן
   metadata_extraction_requested_at — אין מצב "processing", לכן StatusPill לא
   הציג כלום. נוספה עמודת metadata_extraction_status ('pending'|'processing'|
   'completed'|'failed') במתכונת העמודות הקיימות, וה-worker
   (process_pending_extractions + reextract_metadata) מעדכן אותה: processing
   בתחילת פריט, completed בסיום (מנקה גם את החותמת), pending בכשל (לריטריי).
   ה-UI מציג תג "מחלץ מטא-דאטה" + באנר מונה-אצווה עם אחוז התקדמות (high-water-mark
   של עומק-התור) שמתעדכן אוטומטית דרך ה-polling הקיים (5ש').

2. שתי טבלאות מוערמות (בתי משפט / ועדות ערר) חייבו גלילה ארוכה. הוחלפו במתג-
   מקטעים — טבלה אחת בכל פעם, עם שמירה על העמודות הייעודיות לכל סוג.

Invariants: G2 (מרחיב מנגנון-סטטוס קיים, לא מסלול מקביל), INV-TOOL4/GAP-45
(המשך חשיפת תור-החילוץ הסמוי). אין נגיעה בתוכן משפטי (G11).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:21:41 +00:00
701efab726 feat(mcp): FU-14 GAP-51 — איחוד אוצר-המילים של תוצאת-תיק (set_outcome SSoT)
הכרעת-יו"ר: קנוני = 3 תוצאות אמיתיות (rejection/partial_acceptance/full_acceptance);
betterment_levy יוצא מהיותו "תוצאה" ועובר ל-override לפי practice_area.
+ עקרון "אנגלית-ב-DB, עברית-ב-UI": מפת-תוויות SSoT אחת.

lessons.py:
- VALID_OUTCOMES = 3 (הוסר betterment_levy).
- OUTCOME_LABELS_HE (SSoT לתצוגה) + LEGACY_OUTCOME_MAP + canonical_outcome().
- PRACTICE_AREA_OVERRIDES["betterment_levy"] מרכז את כל ה-guidance שהיה מפתוח כ-outcome
  (golden_ratios/opening/summary/discussion/template).
- get_lessons_for_outcome(outcome, practice_area) + format_ratios_comment(..., practice_area)
  מחילים override + מנרמלים legacy.

block_writer.py: STRUCTURE_GUIDANCE קנוני + תווית מ-OUTCOME_LABELS_HE + override betterment.
workflow.set_outcome: קנוני 3 + מיפוי-legacy סלחני; תווית מ-SSoT.
drafting.py: טבלת יחסי-זהב + get_decision_template מודעי-practice_area (override).
web-ui case.ts: הסרת betterment_levy מ-expectedOutcomes (הוא practice_area).
server.py: docstrings קנוניים.

מיגרציה: migrate_gap51_outcomes.py — 9 שורות נורמלו (rejected→rejection וכו'),
גיבוי ב-data/audit/. הקוד canonicalize בקריאה ⇒ backward-compatible גם בלי מיגרציה.

אומת: py_compile (5 קבצים) + בדיקות-יחידה offline (override/legacy/labels) + אימות-DB.
עודכנו X9 §3 + gap-audit (GAP-51 ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:34:49 +00:00
1f1a025509 fix(lint): תיקון 10 שגיאות ESLint + ניקוי directives מיותרים
10 שגיאות (כולן קיימות-מראש, לא מהפיצ'רים האחרונים):
- react/no-unescaped-entities (3): legal-arguments-panel, precedent-edit-sheet
  — escaping של מרכאות ב-JSX (&ldquo;/&quot;)
- react-hooks/set-state-in-effect (6): documents-panel, chair-editor,
  content-checklists, discussion-rules, golden-ratios, documents.ts
  — disable-comment לדפוסי sync/reset לגיטימיים (false-positive ידוע)
- React Compiler reassign (1): subject-donut — refactor לחישוב prefix-sums
  ללא mutable accumulator

ניקוי: הסרת 5 eslint-disable directives מיותרים (halacha-review-panel,
precedent-upload-sheet). תוצאה: 0 errors (היה 10), 24→ warnings (היה 29).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:31:31 +00:00
4174217179 feat(feedback): סימון "יושם" מפעיל CEO לקיפול הלקח לקובץ הנכון
סוגר את לולאת פידבק-יו"ר→ידע-סוכנים. עד כה resolve רק עדכן את ה-DB; עכשיו
לחיצה ב-/feedback מעירה את ה-CEO שמקפל את הלקח לקובץ לפי הקטגוריה.

- paperclip_client.py: wake_ceo_for_feedback_fold() — יוצר issue ב-Paperclip
  עם הלקח + rubric ניתוב (style→SKILL.md, wrong_structure→block-schema,
  אחר→lessons.md), מעיר CEO. משכפל את דפוס wake_for_precedent_extraction
- db.py: get_chair_feedback(id) — שליפת הערה בודדת עם case_number/appeal_type
- app.py: resolve endpoint מקבל fold (ברירת מחדל true); BackgroundTask
  fire-and-forget; guard — רק עם lesson_extracted. מחזיר fold_queued
- legal-ceo.md: dispatch ל-feedback_fold_ + סעיף "קיפול הערת יו"ר" עם rubric
- frontend: useResolveFeedback מקבל fold; /feedback שולח fold=true עם toast;
  drafts-panel שולח fold=false (bookkeeping per-case, בלי קיפול כפול)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 13:08:41 +00:00
e3e3da09e5 feat(feedback): דף מרכזי /feedback להערות יו"ר + תיקון קישורי מרכז אישורים
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 37s
- דף /feedback חדש: מאגד את כל הערות chair_feedback מכל התיקים, סינון
  טרם-יושמו/הכל + לפי קטגוריה, כפתור "סמן כיושם" לכל הערה
- מרכז אישורים: כרטיס "הערות יו"ר" קישר ל-/ (חסר תועלת) → עכשיו /feedback
- מרכז אישורים: כרטיס "תיקים שנכשלו ב-QA" — כל תיק במדגם קליקבילי לדף
  התיק, והכרטיס מקשר ישירות לתיק כשיש רק אחד
- ApprovalSample.href אופציונלי; פריטי מדגם נהפכים ל-Link כשיש href
- ניווט: הוספת "הערות יו"ר" לקבוצת work ב-app-shell

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:38:04 +00:00
59ff4e31cf feat(halacha): כפתורי אישור/דחייה/שחזור inline ברכיב "הלכות שחולצו"
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 38s
ExtractedHalachotSection היה read-only — הוסף כפתורי פעולה לכל הלכה לפי
review_status: נדחתה → אשר/שחזר לתור · מאושרת → בטל אישור/דחה ·
ממתינה → אשר/דחה. משתמש ב-useUpdateHalacha שמרענן את detail query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:27:48 +00:00
68a77c11b6 feat(upload): חסימת כפילות בהעלאת פסיקה + banner עם אפשרויות
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m38s
- בקאנד: GET לפני ה-async task — אם citation כבר קיים כ-external_upload מחזיר 409
- DB: get_external_case_law_by_citation — lookup לפי citation + source_kind
- פרונט: banner אדום עם פרטי הרשומה הקיימת ושני כפתורות:
  • "הפעל חילוץ מחדש" — request-halachot ל-ID הקיים וסגירת הטופס
  • "מחק את הרשומה" — DELETE עם confirm, ניקוי conflict לאחר מכן

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:11:33 +00:00
c83d0162ca feat(halacha): טאבים נדחו/אושרו + שחזור הלכה + הסרת placeholders עם שמות
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
- מוסיף טאב "נדחו" לדף האישורים: הלכות שנדחו מופיעות עם כפתורי "אשר" (ישירות) ו-"שחזר לתור"
- מוסיף טאב "אושרו": הלכות שאושרו עם "בטל אישור" ו-"דחה"
- ספירה צבועה על כל טאב (זהב/אדום/כחול)
- מוסיף useHalachotByStatus hook ב-API
- מסיר placeholders עם שמות ("דפנה תמיר") משדות יו"ר

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 12:07:49 +00:00
f5926506fe chore(types): regenerate OpenAPI types after decision-blocks endpoints
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 39s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 09:42:05 +00:00