Merge remote-tracking branch 'origin/main' into rebase-358
# Conflicts: # docs/spec/04-analysis-writing.md
This commit is contained in:
@@ -264,6 +264,11 @@ async def document_get_text(case_number: str, doc_title: str = "") -> str:
|
||||
async def document_list(case_number: str) -> str:
|
||||
"""רשימת מסמכים בתיק.
|
||||
|
||||
כל מסמך כולל `doc_type`, וכן את הסיווג הנגזר `is_primary` (bool) ו-
|
||||
`doc_category` ("primary"/"secondary") — מסמך-עיקרי (ערר/תשובה/התנגדות/
|
||||
פרוטוקול/שומה/החלטת-ועדה) מול משני. נגזר מ-`doc_type` (db.PRIMARY_DOC_TYPES),
|
||||
אינו נכתב ידנית.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
"""
|
||||
@@ -345,11 +350,15 @@ async def extract_claims(
|
||||
if not docs:
|
||||
return empty(f"אין מסמכים בתיק {case_number}.")
|
||||
|
||||
# Filter to claims documents (appeal, response) or specific doc
|
||||
# Filter to claims documents or a specific doc. protocol carries oral
|
||||
# arguments raised at the hearing — extracted with claim_type='protocol'
|
||||
# (claims_extractor._infer_claim_type) so analyze_protocol can compare them
|
||||
# against the written pleadings, while block ז (write_block_zayin) excludes
|
||||
# them to stay original-pleadings-only (INV-WR4).
|
||||
if doc_title:
|
||||
docs = [d for d in docs if doc_title.lower() in d["title"].lower()]
|
||||
else:
|
||||
docs = [d for d in docs if d["doc_type"] in ("appeal", "response", "objection")]
|
||||
docs = [d for d in docs if d["doc_type"] in ("appeal", "response", "objection", "protocol")]
|
||||
|
||||
if not docs:
|
||||
return empty("לא נמצאו כתבי טענות בתיק.")
|
||||
|
||||
@@ -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:
|
||||
"""כתיבת ארבעת הבלוקים לטיוטת ביניים: רקע (ו), תכניות+היתרים (ט),
|
||||
טענות הצדדים (ז), הליכים (ח). אם לא חולצו עובדות שמאיות עדיין —
|
||||
|
||||
Reference in New Issue
Block a user