Add pre-ruling interim draft (טיוטת ביניים) for appeals committee
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
Lets the chair generate a partial decision DOCX before the discussion-and-
ruling block is decided. Same template, skill and DOCX styling as the final
decision (David, RTL, bookmarks) — only the block selection and order differ:
רקע (ו) → תכניות+היתרים (ט) → טענות (ז) → הליכים (ח). The opening (ה),
ruling (י), summary (יא), and signatures (יב) are omitted.
- New appraiser_facts table + CRUD + conflict detection in db.py (V5 schema).
Conflict = same plan/permit identifier reported differently by 2+ appraisers.
- New appraiser_facts_extractor service: per-appraisal Claude extraction of
plans + permits with raw quotes and page numbers.
- block-tet prompt extended with a permits sub-section sourced from the
extracted facts, plus an explicit instruction to flag inter-appraiser
conflicts in neutral wording without resolving them (deferred to block-yod).
- block-chet prompt extended with a post-hearing materials context sourced
from documents.metadata.is_post_hearing.
- docx_exporter.export_decision now accepts mode='interim' which reorders
the blocks per the chair's mental model and writes
טיוטת-ביניים-v{N}.docx (versioned independently of regular drafts).
- 3 new MCP tools: extract_appraiser_facts, write_interim_draft,
export_interim_draft. write_interim_draft auto-runs extraction if the
appraiser_facts table is empty for the case.
This commit is contained in:
@@ -416,6 +416,130 @@ async def export_docx(case_number: str, output_path: str = "") -> str:
|
||||
}, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ── Interim draft (pre-ruling) ────────────────────────────────────
|
||||
|
||||
# Blocks written for the interim draft, in display order.
|
||||
# This is the same content the chair sees in the final decision (same template,
|
||||
# same skill, same prompts) — minus opening, ruling, summary, signatures.
|
||||
_INTERIM_BLOCKS = ["block-vav", "block-tet", "block-zayin", "block-chet"]
|
||||
|
||||
|
||||
async def extract_appraiser_facts(case_number: str) -> str:
|
||||
"""חילוץ תכניות והיתרים מכל השומות בתיק וזיהוי סתירות בין שמאים.
|
||||
|
||||
משמש כהכנה לטיוטת ביניים: בלוק ט (תכניות חלות) זקוק לעובדות מובנות
|
||||
כדי לפרט תת-פרק היתרים ולסמן סתירות בנוסח ניטרלי.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
"""
|
||||
from legal_mcp.services import appraiser_facts_extractor
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return json.dumps({"status": "error",
|
||||
"message": f"תיק {case_number} לא נמצא."},
|
||||
ensure_ascii=False, indent=2)
|
||||
case_id = UUID(case["id"])
|
||||
try:
|
||||
result = await appraiser_facts_extractor.extract_appraiser_facts(case_id)
|
||||
return json.dumps(result, default=str, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
return json.dumps({"status": "error", "message": str(e)},
|
||||
ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def write_interim_draft(case_number: str, instructions: str = "") -> str:
|
||||
"""כתיבת ארבעת הבלוקים לטיוטת ביניים: רקע (ו), תכניות+היתרים (ט),
|
||||
טענות הצדדים (ז), הליכים (ח). אם לא חולצו עובדות שמאיות עדיין —
|
||||
מריץ extract_appraiser_facts קודם כדי שבלוק ט יקבל פרק היתרים תקף.
|
||||
|
||||
הבלוקים נכתבים באותו skill, אותם prompts ואותו טמפלט כמו בטיוטה רגילה —
|
||||
הסדר משתנה רק בעת הייצוא ל-DOCX (ראה export_interim_draft).
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
instructions: הנחיות נוספות (לכל הבלוקים)
|
||||
"""
|
||||
from legal_mcp.services import appraiser_facts_extractor, block_writer
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return json.dumps({"status": "error",
|
||||
"message": f"תיק {case_number} לא נמצא."},
|
||||
ensure_ascii=False, indent=2)
|
||||
case_id = UUID(case["id"])
|
||||
|
||||
# Make sure appraiser facts exist before writing block-tet (which depends on them).
|
||||
facts = await db.list_appraiser_facts(case_id)
|
||||
facts_run: dict | None = None
|
||||
if not facts:
|
||||
try:
|
||||
facts_run = await appraiser_facts_extractor.extract_appraiser_facts(case_id)
|
||||
except Exception as e:
|
||||
facts_run = {"status": "error", "message": str(e)}
|
||||
|
||||
results = []
|
||||
for bid in _INTERIM_BLOCKS:
|
||||
try:
|
||||
r = await block_writer.write_and_store_block(case_id, bid, instructions)
|
||||
results.append({
|
||||
"block_id": bid,
|
||||
"title": r["title"],
|
||||
"word_count": r["word_count"],
|
||||
"status": "completed",
|
||||
})
|
||||
except Exception as e:
|
||||
results.append({
|
||||
"block_id": bid,
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
})
|
||||
|
||||
return json.dumps({
|
||||
"status": "completed",
|
||||
"blocks": results,
|
||||
"appraiser_facts_run": facts_run,
|
||||
"total_words": sum(r.get("word_count", 0) for r in results),
|
||||
"completed": sum(1 for r in results if r["status"] == "completed"),
|
||||
}, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def export_interim_draft(case_number: str, output_path: str = "") -> str:
|
||||
"""ייצוא טיוטת ביניים ל-DOCX — אותו עיצוב של טיוטה רגילה (David, RTL,
|
||||
bookmarks), אבל בסדר חדש: רקע → תכניות+היתרים → טענות → הליכים, ללא
|
||||
דיון/סיכום/חתימות. שם הקובץ: טיוטת-ביניים-v{N}.docx.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
output_path: נתיב לשמירה (אופציונלי)
|
||||
"""
|
||||
from legal_mcp.services import docx_exporter
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return json.dumps({"status": "error",
|
||||
"message": f"תיק {case_number} לא נמצא."},
|
||||
ensure_ascii=False, indent=2)
|
||||
case_id = UUID(case["id"])
|
||||
|
||||
try:
|
||||
path = await docx_exporter.export_decision(
|
||||
case_id, output_path or None, mode="interim",
|
||||
)
|
||||
await db.set_active_draft_path(case_id, path)
|
||||
return json.dumps({
|
||||
"status": "completed",
|
||||
"mode": "interim",
|
||||
"path": path,
|
||||
"active_draft_path": path,
|
||||
"message": f"טיוטת ביניים נוצרה: {path}",
|
||||
}, ensure_ascii=False, indent=2)
|
||||
except ValueError as e:
|
||||
return json.dumps({"status": "error", "message": str(e)},
|
||||
ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def apply_user_edit(case_number: str, edit_filename: str) -> str:
|
||||
"""רישום עריכה שהעלה המשתמש כמקור האמת החדש של התיק.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user