feat(citation-verify): backend for the "אימות פסיקה" panel — per-argument support + verify gate (#154)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 12s

Backend half of the citation-verification tab (frontend follows in a separate PR):

- Schema V44: case_precedents gains argument_id (the legal argument it supports),
  case_law_id (the corpus ruling — for cited_by + dedup), verified (the INV-AH gate
  the writer respects) + verified_at. All nullable; chair_note already existed.
- db: create_case_precedent(+argument_id/case_law_id/verified),
  set_case_precedent_verified(id, verified, chair_note), list_case_precedents(+cols).
- services/case_citation_verification.build_view(case): per legal_argument →
  in-corpus suggestions (search_library per issue) each with the cited_by authority
  breakdown (db.citation_authority, X11) + merged attach/verify state + per-issue
  radar (case_digest_radar grouped by matched issue). Pure read/assembly; reuses the
  one corpus search + one authority query + one radar (G2).
- endpoints: GET /api/cases/{n}/citation-verification (the view) and
  POST .../verify (upsert verify/un-verify + chair note).

Validated on 1044-03-26: 14 arguments, all with support; 317/10 surfaces with
אומץ×11/אובחן×1; radar leads attributed per issue. Auto-approval untouched (chair
gate, INV-G10). Verify defaults to false — nothing is authoritative until the chair
marks it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 18:08:21 +00:00
parent 33c10e4147
commit 5ede8a9653
3 changed files with 243 additions and 5 deletions

View File

@@ -3155,6 +3155,62 @@ async def api_precedent_list(case_number: str):
return envelope_unwrap(parsed)
# ── Citation-verification panel (X11 Phase 2 / #154) — "אימות פסיקה" tab ──
@app.get("/api/cases/{case_number}/citation-verification")
async def api_citation_verification(case_number: str):
"""Per legal-argument: supporting corpus precedents (with cited_by authority),
their verify state + chair note, and per-issue radar (unlinked digests). Powers
the compose 'אימות פסיקה' tab — the chair verifies before the writer cites."""
from legal_mcp.services import case_citation_verification as ccv
view = await ccv.build_view(case_number)
if view.get("status") == "case_not_found":
raise HTTPException(404, f"תיק {case_number} לא נמצא")
return view
class CitationVerifyRequest(BaseModel):
argument_id: str
case_law_id: str = ""
quote: str = ""
citation: str = ""
chair_note: str | None = None
verified: bool = True
attached_id: str = "" # set when the precedent row already exists
@app.post("/api/cases/{case_number}/citation-verification/verify")
async def api_citation_verify(case_number: str, req: CitationVerifyRequest):
"""Verify / un-verify a precedent for a specific argument (the INV-AH gate the
writer respects). Upsert: PATCH an existing attachment, else attach a new one
linked to the argument + corpus ruling and mark it."""
case = await db.get_case_by_number(case_number)
if not case:
raise HTTPException(404, f"תיק {case_number} לא נמצא")
try:
if req.attached_id:
row = await db.set_case_precedent_verified(
UUID(req.attached_id), req.verified, req.chair_note)
if not row:
raise HTTPException(404, "שיוך-פסיקה לא נמצא")
return row
if not req.quote.strip() or not req.citation.strip():
raise HTTPException(400, "quote ו-citation חובה לצירוף חדש")
cid = case["id"]
row = await db.create_case_precedent(
case_id=UUID(cid) if isinstance(cid, str) else cid,
quote=req.quote, citation=req.citation,
chair_note=req.chair_note or "",
argument_id=UUID(req.argument_id) if req.argument_id else None,
case_law_id=UUID(req.case_law_id) if req.case_law_id else None,
verified=req.verified,
)
return row
except ValueError:
raise HTTPException(400, "מזהה לא תקין")
@app.delete("/api/precedents/{precedent_id}")
async def api_precedent_delete(precedent_id: str):
"""Delete a precedent attachment. The archived PDF (if any) stays