Add full decision writing pipeline: classify, extract, brainstorm, write, QA, export
New services (11 files): - classifier.py: auto doc-type classification + party identification (Claude Haiku) - claims_extractor.py: claim extraction from pleadings (Claude Sonnet + regex) - references_extractor.py: plan/case-law/legislation detection (regex) - brainstorm.py: direction generation with 2-3 options (Claude Sonnet) - block_writer.py: 12-block decision writer (template + Claude Sonnet/Opus) - docx_exporter.py: DOCX export with David font, RTL, headings - qa_validator.py: 6 QA checks with export blocking on critical failure - learning_loop.py: draft vs final comparison + lesson extraction - metrics.py: KPIs dashboard per case and global - audit.py: action audit log - cli.py: standalone CLI with 11 commands Updated pipeline: extract → classify → chunk → embed → store → extract_references New MCP tools: 29 total (was 16) New DB tables: audit_log, decisions CRUD, claims CRUD Config: Infisical support, external service allowlist Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""MCP tools for workflow status tracking."""
|
||||
"""MCP tools for workflow: status, outcome, brainstorming, direction."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -95,6 +95,25 @@ def _suggest_next_steps(case: dict, docs: list, has_draft: bool) -> list[str]:
|
||||
return steps
|
||||
|
||||
|
||||
async def get_metrics(case_number: str = "") -> str:
|
||||
"""מדדי הצלחה — KPIs לתיק ספציפי או דשבורד כולל.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק (אם ריק — דשבורד כולל)
|
||||
"""
|
||||
from legal_mcp.services import metrics
|
||||
|
||||
if case_number:
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
result = await metrics.get_case_metrics(UUID(case["id"]))
|
||||
else:
|
||||
result = await metrics.get_dashboard()
|
||||
|
||||
return json.dumps(result, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def processing_status() -> str:
|
||||
"""סטטוס כללי - מספר תיקים, מסמכים ממתינים לעיבוד."""
|
||||
pool = await db.get_pool()
|
||||
@@ -116,3 +135,186 @@ async def processing_status() -> str:
|
||||
"style_corpus_entries": corpus_count,
|
||||
"style_patterns": pattern_count,
|
||||
}, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ── Outcome & Brainstorming ───────────────────────────────────────
|
||||
|
||||
async def set_outcome(
|
||||
case_number: str,
|
||||
outcome: str,
|
||||
reasoning: str = "",
|
||||
) -> str:
|
||||
"""הזנת תוצאה לתיק ערר. יוצר רשומת החלטה ומפעיל סיעור מוחות אם אין נימוק.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
outcome: תוצאה — rejected (דחייה), accepted (קבלה), partial (קבלה חלקית)
|
||||
reasoning: נימוק (אופציונלי). אם ריק — מפעיל סיעור מוחות.
|
||||
"""
|
||||
from legal_mcp.services import brainstorm
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
valid_outcomes = ("rejected", "accepted", "partial")
|
||||
if outcome not in valid_outcomes:
|
||||
return f"תוצאה לא תקינה. אפשרויות: {', '.join(valid_outcomes)}"
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
|
||||
# Create or update decision
|
||||
existing = await db.get_decision_by_case(case_id)
|
||||
if existing:
|
||||
await db.update_decision(
|
||||
UUID(existing["id"]),
|
||||
outcome=outcome,
|
||||
outcome_summary=reasoning[:200] if reasoning else "",
|
||||
outcome_reasoning=reasoning,
|
||||
)
|
||||
decision = await db.get_decision(UUID(existing["id"]))
|
||||
else:
|
||||
decision = await db.create_decision(
|
||||
case_id=case_id,
|
||||
outcome=outcome,
|
||||
outcome_summary=reasoning[:200] if reasoning else "",
|
||||
outcome_reasoning=reasoning,
|
||||
)
|
||||
|
||||
# Update case status
|
||||
await db.update_case(case_id, status="in_progress", expected_outcome=outcome)
|
||||
|
||||
outcome_hebrew = {"rejected": "דחייה", "accepted": "קבלה", "partial": "קבלה חלקית"}.get(outcome, outcome)
|
||||
|
||||
result = {
|
||||
"decision_id": decision["id"],
|
||||
"outcome": outcome,
|
||||
"outcome_hebrew": outcome_hebrew,
|
||||
"reasoning": reasoning,
|
||||
"has_reasoning": bool(reasoning),
|
||||
}
|
||||
|
||||
if not reasoning:
|
||||
result["message"] = "לא סופק נימוק. הפעל /brainstorm כדי לקבל כיוונים אפשריים."
|
||||
result["next_step"] = "brainstorm"
|
||||
else:
|
||||
result["message"] = f"תוצאה נשמרה: {outcome_hebrew}. ניתן להתחיל כתיבת טיוטה."
|
||||
result["next_step"] = "draft"
|
||||
|
||||
return json.dumps(result, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def brainstorm_directions(
|
||||
case_number: str,
|
||||
) -> str:
|
||||
"""סיעור מוחות — הצגת טענות מרכזיות והצעת 2-3 כיוונים אפשריים לנימוק.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
"""
|
||||
from legal_mcp.services import brainstorm
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
|
||||
# Get existing decision for outcome
|
||||
decision = await db.get_decision_by_case(case_id)
|
||||
if not decision:
|
||||
return "לא הוזנה תוצאה לתיק. הפעל set_outcome קודם."
|
||||
|
||||
outcome = decision.get("outcome", "")
|
||||
reasoning = decision.get("outcome_reasoning", "")
|
||||
|
||||
directions = await brainstorm.generate_directions(case_id, outcome, reasoning)
|
||||
|
||||
# Save brainstorm results to decision
|
||||
await db.update_decision(
|
||||
UUID(decision["id"]),
|
||||
direction_doc={"brainstorm": directions, "approved": False},
|
||||
)
|
||||
|
||||
return json.dumps(directions, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def approve_direction(
|
||||
case_number: str,
|
||||
direction_index: int = 0,
|
||||
additional_notes: str = "",
|
||||
) -> str:
|
||||
"""אישור כיוון — יוצר מסמך כיוון מאושר. לא ניתן להתחיל כתיבת דיון בלי כיוון מאושר.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
direction_index: מספר הכיוון שנבחר (0 = ראשון)
|
||||
additional_notes: הערות נוספות
|
||||
"""
|
||||
from legal_mcp.services import brainstorm
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
decision = await db.get_decision_by_case(case_id)
|
||||
if not decision:
|
||||
return "לא הוזנה תוצאה לתיק."
|
||||
|
||||
direction_data = decision.get("direction_doc") or {}
|
||||
brainstorm_result = direction_data.get("brainstorm", {})
|
||||
|
||||
if not brainstorm_result.get("directions"):
|
||||
return "לא בוצע סיעור מוחות. הפעל brainstorm_directions קודם."
|
||||
|
||||
direction_doc = brainstorm.build_direction_doc(
|
||||
outcome=decision.get("outcome", ""),
|
||||
reasoning=decision.get("outcome_reasoning", ""),
|
||||
directions_result=brainstorm_result,
|
||||
selected_direction=direction_index,
|
||||
additional_notes=additional_notes,
|
||||
)
|
||||
|
||||
await db.update_decision(UUID(decision["id"]), direction_doc=direction_doc)
|
||||
|
||||
return json.dumps({
|
||||
"status": "approved",
|
||||
"message": "כיוון אושר. ניתן להתחיל כתיבת טיוטה.",
|
||||
"direction": direction_doc,
|
||||
}, default=str, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def ingest_final_version(
|
||||
case_number: str,
|
||||
file_path: str = "",
|
||||
final_text: str = "",
|
||||
) -> str:
|
||||
"""קליטת גרסה סופית (שדפנה חתמה). משווה לטיוטה ומחלצת לקחים.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
file_path: נתיב לקובץ הגרסה הסופית (PDF/DOCX)
|
||||
final_text: טקסט ישיר (אם אין קובץ)
|
||||
"""
|
||||
from legal_mcp.services import learning_loop
|
||||
|
||||
case = await db.get_case_by_number(case_number)
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
|
||||
# Extract text from file if provided
|
||||
if file_path and not final_text:
|
||||
from legal_mcp.services import extractor
|
||||
final_text, _ = await extractor.extract_text(file_path)
|
||||
|
||||
if not final_text:
|
||||
return "לא סופק טקסט — יש לספק file_path או final_text."
|
||||
|
||||
try:
|
||||
result = await learning_loop.process_final_version(case_id, final_text)
|
||||
return json.dumps(result, default=str, ensure_ascii=False, indent=2)
|
||||
except ValueError as e:
|
||||
return json.dumps({"status": "error", "message": str(e)}, ensure_ascii=False, indent=2)
|
||||
|
||||
Reference in New Issue
Block a user