Ezer Mishpati - AI legal decision drafting system with: - MCP server (FastMCP) with document processing pipeline - Web upload interface (FastAPI) for file upload and classification - pgvector-based semantic search - Hebrew legal document chunking and embedding
203 lines
6.0 KiB
Python
203 lines
6.0 KiB
Python
"""MCP tools for decision drafting support."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from uuid import UUID
|
||
|
||
from legal_mcp.services import db, embeddings
|
||
|
||
|
||
DECISION_TEMPLATE = """# החלטה
|
||
|
||
## בפני: דפנה תמיר, יו"ר ועדת הערר מחוז ירושלים
|
||
|
||
**ערר מספר:** {case_number}
|
||
**נושא:** {subject}
|
||
**העוררים:** {appellants}
|
||
**המשיבים:** {respondents}
|
||
**כתובת הנכס:** {property_address}
|
||
|
||
---
|
||
|
||
## א. רקע עובדתי
|
||
|
||
[תיאור הרקע העובדתי של הערר]
|
||
|
||
## ב. טענות העוררים
|
||
|
||
[סיכום טענות העוררים]
|
||
|
||
## ג. טענות המשיבים
|
||
|
||
[סיכום טענות המשיבים]
|
||
|
||
## ד. דיון והכרעה
|
||
|
||
[ניתוח משפטי]
|
||
|
||
## ה. מסקנה
|
||
|
||
[מסקנת הוועדה]
|
||
|
||
## ו. החלטה
|
||
|
||
[ההחלטה הסופית]
|
||
|
||
---
|
||
ניתנה היום, {date}
|
||
דפנה תמיר, יו"ר ועדת הערר
|
||
"""
|
||
|
||
|
||
async def get_style_guide() -> str:
|
||
"""שליפת דפוסי הסגנון של דפנה - נוסחאות, ביטויים אופייניים ומבנה."""
|
||
patterns = await db.get_style_patterns()
|
||
|
||
if not patterns:
|
||
return "לא נמצאו דפוסי סגנון. יש להעלות החלטות קודמות ולהריץ ניתוח סגנון (/style-report)."
|
||
|
||
grouped: dict[str, list] = {}
|
||
for p in patterns:
|
||
pt = p["pattern_type"]
|
||
if pt not in grouped:
|
||
grouped[pt] = []
|
||
grouped[pt].append({
|
||
"text": p["pattern_text"],
|
||
"context": p["context"],
|
||
"frequency": p["frequency"],
|
||
})
|
||
|
||
type_names = {
|
||
"opening_formula": "נוסחאות פתיחה",
|
||
"transition": "ביטויי מעבר",
|
||
"citation_style": "סגנון ציטוט",
|
||
"analysis_structure": "מבנה ניתוח",
|
||
"closing_formula": "נוסחאות סיום",
|
||
"characteristic_phrase": "ביטויים אופייניים",
|
||
}
|
||
|
||
result = "# מדריך סגנון - דפנה תמיר\n\n"
|
||
for ptype, items in grouped.items():
|
||
result += f"## {type_names.get(ptype, ptype)}\n\n"
|
||
for item in items:
|
||
result += f"- **{item['text']}** ({item['context']}, תדירות: {item['frequency']})\n"
|
||
result += "\n"
|
||
|
||
return result
|
||
|
||
|
||
async def draft_section(
|
||
case_number: str,
|
||
section: str,
|
||
instructions: str = "",
|
||
) -> str:
|
||
"""הרכבת הקשר מלא לניסוח סעיף בהחלטה - כולל עובדות מהמסמכים, תקדימים רלוונטיים ודפוסי סגנון.
|
||
|
||
Args:
|
||
case_number: מספר תיק הערר
|
||
section: סוג הסעיף (facts, appellant_claims, respondent_claims, legal_analysis, conclusion, ruling)
|
||
instructions: הנחיות נוספות לניסוח
|
||
"""
|
||
case = await db.get_case_by_number(case_number)
|
||
if not case:
|
||
return f"תיק {case_number} לא נמצא."
|
||
|
||
case_id = UUID(case["id"])
|
||
|
||
# 1. Get relevant chunks from case documents
|
||
section_query = {
|
||
"facts": "רקע עובדתי של התיק",
|
||
"appellant_claims": "טענות העוררים",
|
||
"respondent_claims": "טענות המשיבים",
|
||
"legal_analysis": "ניתוח משפטי ודיון",
|
||
"conclusion": "מסקנות",
|
||
"ruling": "החלטה",
|
||
}.get(section, section)
|
||
|
||
query_emb = await embeddings.embed_query(section_query)
|
||
case_chunks = await db.search_similar(
|
||
query_embedding=query_emb, limit=10, case_id=case_id
|
||
)
|
||
|
||
# 2. Get similar sections from precedents
|
||
precedent_chunks = await db.search_similar(
|
||
query_embedding=query_emb, limit=5, section_type=section
|
||
)
|
||
# Filter out chunks from the same case
|
||
precedent_chunks = [c for c in precedent_chunks if str(c["case_id"]) != case["id"]]
|
||
|
||
# 3. Get style patterns
|
||
style_patterns = await db.get_style_patterns()
|
||
|
||
# Build context
|
||
context = {
|
||
"case": {
|
||
"case_number": case["case_number"],
|
||
"title": case["title"],
|
||
"appellants": case["appellants"],
|
||
"respondents": case["respondents"],
|
||
"subject": case["subject"],
|
||
"property_address": case["property_address"],
|
||
},
|
||
"section": section,
|
||
"instructions": instructions,
|
||
"case_documents": [
|
||
{
|
||
"document": c["document_title"],
|
||
"section_type": c["section_type"],
|
||
"content": c["content"],
|
||
}
|
||
for c in case_chunks
|
||
],
|
||
"precedents": [
|
||
{
|
||
"case_number": c["case_number"],
|
||
"document": c["document_title"],
|
||
"content": c["content"][:500],
|
||
}
|
||
for c in precedent_chunks[:3]
|
||
],
|
||
"style_patterns": [
|
||
{
|
||
"type": p["pattern_type"],
|
||
"text": p["pattern_text"],
|
||
}
|
||
for p in style_patterns[:15]
|
||
],
|
||
}
|
||
|
||
return json.dumps(context, ensure_ascii=False, indent=2)
|
||
|
||
|
||
async def get_decision_template(case_number: str) -> str:
|
||
"""קבלת תבנית מבנית להחלטה מלאה עם פרטי התיק.
|
||
|
||
Args:
|
||
case_number: מספר תיק הערר
|
||
"""
|
||
from datetime import date
|
||
|
||
case = await db.get_case_by_number(case_number)
|
||
if not case:
|
||
return f"תיק {case_number} לא נמצא."
|
||
|
||
template = DECISION_TEMPLATE.format(
|
||
case_number=case["case_number"],
|
||
subject=case["subject"],
|
||
appellants=", ".join(case.get("appellants", [])),
|
||
respondents=", ".join(case.get("respondents", [])),
|
||
property_address=case.get("property_address", ""),
|
||
date=date.today().strftime("%d.%m.%Y"),
|
||
)
|
||
|
||
return template
|
||
|
||
|
||
async def analyze_style() -> str:
|
||
"""הרצת ניתוח סגנון על קורפוס ההחלטות של דפנה. מחלץ דפוסי כתיבה ושומר אותם."""
|
||
from legal_mcp.services.style_analyzer import analyze_corpus
|
||
|
||
result = await analyze_corpus()
|
||
return json.dumps(result, ensure_ascii=False, indent=2)
|