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>
This commit is contained in:
2026-06-08 11:59:21 +00:00
parent 2c4287fd3d
commit 98c5feff25
5 changed files with 273 additions and 0 deletions

View File

@@ -3222,6 +3222,65 @@ async def api_get_active_draft(case_number: str):
}
@app.get("/api/cases/{case_number}/citations")
async def api_case_citations(case_number: str):
"""Precedents CITED inside this case's signed decision — split into those linked to
the precedent library and those still missing from it (flagged for upload).
Powers the case-page "פסיקה שצוטטה בהחלטה" panel. Source: the decision's row in
case_law (source_kind='internal_committee') → precedent_internal_citations."""
case = await db.get_case_by_number(case_number)
if not case:
raise HTTPException(404, f"תיק {case_number} לא נמצא")
pool = await db.get_pool()
async with pool.acquire() as conn:
law_id = await conn.fetchval(
"SELECT id FROM case_law WHERE case_number = $1 "
"AND source_kind = 'internal_committee' ORDER BY created_at DESC LIMIT 1",
case_number,
)
if not law_id:
# decision not yet in the library (e.g. final not uploaded)
return {"in_library": False, "linked": [], "missing": []}
linked = await conn.fetch(
"SELECT pic.cited_case_number, cl.id AS cited_id, cl.case_name, cl.court, "
" cl.precedent_level "
"FROM precedent_internal_citations pic "
"JOIN case_law cl ON cl.id = pic.cited_case_law_id "
"WHERE pic.source_case_law_id = $1 "
"ORDER BY pic.cited_case_number",
law_id,
)
missing = await conn.fetch(
"SELECT DISTINCT pic.cited_case_number, "
" EXISTS(SELECT 1 FROM missing_precedents mp "
" WHERE mp.citation = pic.cited_case_number AND mp.status = 'open') AS flagged "
"FROM precedent_internal_citations pic "
"WHERE pic.source_case_law_id = $1 AND pic.cited_case_law_id IS NULL "
" AND pic.cited_case_number <> '' "
"ORDER BY pic.cited_case_number",
law_id,
)
return {
"in_library": True,
"case_law_id": str(law_id),
"linked": [
{
"citation": r["cited_case_number"],
"cited_id": str(r["cited_id"]),
"case_name": r["case_name"] or "",
"court": r["court"] or "",
"precedent_level": r["precedent_level"] or "",
}
for r in linked
],
"missing": [
{"citation": r["cited_case_number"], "flagged": r["flagged"]}
for r in missing
],
}
@app.post("/api/cases/{case_number}/exports/{filename}/mark-final")
async def api_mark_final(case_number: str, filename: str):
"""Mark an export as the final version — copies to training corpus."""