Merge remote-tracking branch 'origin/main' into rebase-358
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

# Conflicts:
#	docs/spec/04-analysis-writing.md
This commit is contained in:
2026-06-30 12:16:41 +00:00
15 changed files with 966 additions and 61 deletions

View File

@@ -516,9 +516,18 @@ async def export_docx(case_number: str, output_path: str = "") -> str:
# ── Interim draft (pre-ruling) ────────────────────────────────────
# Blocks written for the interim draft, in display order.
# Blocks written for the interim draft, in a FIXED order.
# This is the same content the chair sees in the final decision (same template,
# same skill, same prompts) — minus opening, ruling, summary, signatures.
# same skill, same prompts, same single canonical write path — block_writer) —
# minus ruling, summary, signatures.
#
# Determinism (#204 / WS5): the interim draft is STRUCTURALLY deterministic.
# Each block is generated by block_writer.write_block, which now pins the model
# (Opus 4.8) and a per-block reasoning effort, and uses fixed structural prompts
# (e.g. block-he's opening is always "לפנינו ערר…"). The display order on export
# is fixed separately by docx_exporter._INTERIM_BLOCK_ORDER. There is no parallel
# generation path (G2): write_interim_draft is a thin orchestrator over the same
# write_and_store_block used by the full decision.
_INTERIM_BLOCKS = ["block-he", "block-vav", "block-tet", "block-zayin", "block-chet"]
@@ -571,6 +580,73 @@ async def get_appraiser_facts(case_number: str) -> str:
return err(str(e))
async def analyze_protocol(case_number: str) -> str:
"""ניתוח השוואתי של פרוטוקול-דיון מול כתבי-הטענות (WS4 / #203).
מזהה אילו טענות **ירדו** (נזנחו בדיון), אילו **חוזקו**, ואילו **עלו חדשות**,
מחדד את השאלות המשפטיות, ומחלץ את נתוני הכותרת (א–ד) מהפרוטוקול. התוצאה נכנסת
ל"ידע-התיק" (טבלת protocol_analysis), זמינה לסוכני הניתוח והכתיבה; תאריך-הדיון
מוזן חזרה לעמודה הקנונית cases.hearing_date.
דורש פרוטוקול מתויג doc_type='protocol' + טיעונים מאוגדים
(aggregate_claims_to_arguments). רץ עם Claude מקומי (Opus 4.8, effort=high);
re-run מחליף את הניתוח הקודם לאותו פרוטוקול (idempotent).
Args:
case_number: מספר תיק הערר
"""
from legal_mcp.services import protocol_analyzer
case = await db.get_case_by_number(case_number)
if not case:
return err(f"תיק {case_number} לא נמצא.")
case_id = UUID(case["id"])
try:
result = await protocol_analyzer.analyze_protocol(case_id)
await audit.log_action_safe(
"analyze_protocol", case_id=case_id,
details={"status": result.get("status"), "total": result.get("total", 0)},
)
return ok(result)
except Exception as e:
return err(str(e))
async def get_protocol_analysis(case_number: str, change_type: str = "") -> str:
"""קריאת ניתוח-הפרוטוקול שכבר חולץ — ללא הרצת ניתוח-מחדש (INV-TOOL4).
ה-get המקביל ל-analyze_protocol: מחזיר את רשומות ידע-התיק השמורות
(ירדה/חוזקה/חדשה + שאלה-מחודדת + ציטוט-מבסס), בלי קריאת-LLM יקרה.
מחזיר רשימה ריקה אם הניתוח טרם רץ (status=ok, count=0) — לא שגיאה.
Args:
case_number: מספר תיק הערר
change_type: סינון (dropped/strengthened/newly_raised). ריק = הכל.
"""
from legal_mcp.services import protocol_analyzer
case = await db.get_case_by_number(case_number)
if not case:
return err(f"תיק {case_number} לא נמצא.")
if change_type and change_type not in protocol_analyzer.VALID_CHANGE_TYPES:
return err(
f"change_type לא תקין: {change_type}",
data={"allowed": sorted(protocol_analyzer.VALID_CHANGE_TYPES)},
)
case_id = UUID(case["id"])
try:
rows = await db.list_protocol_analysis(
case_id, change_type=change_type or None,
)
return ok({
"case_number": case_number,
"count": len(rows),
"analysis": rows,
})
except Exception as e:
return err(str(e))
async def write_interim_draft(case_number: str, instructions: str = "") -> str:
"""כתיבת ארבעת הבלוקים לטיוטת ביניים: רקע (ו), תכניות+היתרים (ט),
טענות הצדדים (ז), הליכים (ח). אם לא חולצו עובדות שמאיות עדיין —