Add outcome-aware drafting, lessons system, and improved style analysis
- Add expected_outcome field to cases (rejection/partial/full/betterment_levy) - New lessons.py module with golden ratios, templates, and drafting guidance per outcome type - Style analyzer now uses Opus with full decision text (no truncation), with multi-pass fallback for large corpora - Drafting tool provides outcome-specific templates, section guidance, and ratio comments - Improved JSON extraction with bracket-matching fallback Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,21 @@ import json
|
||||
from uuid import UUID
|
||||
|
||||
from legal_mcp.services import db, embeddings
|
||||
from legal_mcp.services.lessons import (
|
||||
CITATION_GUIDANCE,
|
||||
DECISION_TEMPLATES,
|
||||
DISCUSSION_RULES,
|
||||
GOLDEN_RATIOS,
|
||||
OPENING_STRATEGIES,
|
||||
PARAGRAPH_LENGTHS,
|
||||
SUMMARY_STRATEGIES,
|
||||
TRANSITION_PHRASES,
|
||||
VALID_OUTCOMES,
|
||||
format_ratios_comment,
|
||||
get_lessons_for_outcome,
|
||||
)
|
||||
|
||||
|
||||
# Fallback template for cases without expected_outcome
|
||||
DECISION_TEMPLATE = """# החלטה
|
||||
|
||||
## בפני: דפנה תמיר, יו"ר ועדת הערר מחוז ירושלים
|
||||
@@ -51,38 +64,106 @@ DECISION_TEMPLATE = """# החלטה
|
||||
|
||||
|
||||
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"
|
||||
|
||||
# Part 1: DB-sourced patterns (from analyze_style)
|
||||
if patterns:
|
||||
type_names = {
|
||||
"opening_formula": "נוסחאות פתיחה",
|
||||
"transition": "ביטויי מעבר",
|
||||
"citation_style": "סגנון ציטוט",
|
||||
"analysis_structure": "מבנה ניתוח",
|
||||
"closing_formula": "נוסחאות סיום",
|
||||
"characteristic_phrase": "ביטויים אופייניים",
|
||||
"argument_flow": "זרימת טיעון",
|
||||
"evidence_handling": "התייחסות לראיות",
|
||||
}
|
||||
|
||||
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"],
|
||||
})
|
||||
|
||||
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"
|
||||
else:
|
||||
result += "_לא נמצאו דפוסים מקורפוס. יש להעלות החלטות ולהריץ /style-report._\n\n"
|
||||
|
||||
# Part 2: Lessons-based guidance
|
||||
result += "---\n\n# לקחים מעשיים\n\n"
|
||||
|
||||
# Universal discussion rules
|
||||
result += "## כללי דיון אוניברסליים\n\n"
|
||||
for rule in DISCUSSION_RULES["universal"]:
|
||||
result += f"- {rule}\n"
|
||||
result += "\n"
|
||||
|
||||
# Citation technique
|
||||
result += "## טכניקת ציטוט\n\n"
|
||||
result += f"{CITATION_GUIDANCE}\n\n"
|
||||
|
||||
# Transition phrases
|
||||
result += "## ביטויי מעבר (מהשוואת טיוטות)\n\n"
|
||||
for p in TRANSITION_PHRASES:
|
||||
ctx = p["context"]
|
||||
outcome_note = f" [בעיקר ב-{p['outcome']}]" if p["outcome"] else ""
|
||||
result += f"- **{p['phrase']}** — {ctx}{outcome_note}\n"
|
||||
result += "\n"
|
||||
|
||||
# Paragraph lengths
|
||||
result += "## אורכי פסקאות מומלצים\n\n"
|
||||
result += "| סוג | מילים |\n|-----|------|\n"
|
||||
labels = {
|
||||
"claims": "טענות",
|
||||
"discussion_regular": "דיון רגיל",
|
||||
"discussion_with_citation": "דיון + ציטוט",
|
||||
"discussion_average": "ממוצע דיון",
|
||||
}
|
||||
for key, (lo, hi) in PARAGRAPH_LENGTHS.items():
|
||||
result += f"| {labels.get(key, key)} | {lo}-{hi} |\n"
|
||||
result += "\n"
|
||||
|
||||
# Golden ratios
|
||||
result += "## יחסי הזהב (אחוזי סעיפים מסך ההחלטה)\n\n"
|
||||
result += "| סוג ערר | רקע | טענות | דיון | סיכום |\n"
|
||||
result += "|---------|------|-------|------|-------|\n"
|
||||
outcome_labels = {
|
||||
"rejection": "רישוי נדחה",
|
||||
"full_acceptance": "רישוי מתקבל",
|
||||
"partial_acceptance": "רישוי קבלה חלקית",
|
||||
"betterment_levy": "היטל השבחה",
|
||||
}
|
||||
for outcome in VALID_OUTCOMES:
|
||||
r = GOLDEN_RATIOS[outcome]
|
||||
result += (
|
||||
f"| {outcome_labels[outcome]} "
|
||||
f"| {r['background'][0]}-{r['background'][1]}% "
|
||||
f"| {r['claims'][0]}-{r['claims'][1]}% "
|
||||
f"| {r['discussion'][0]}-{r['discussion'][1]}% "
|
||||
f"| {r['summary'][0]}-{r['summary'][1]}% |\n"
|
||||
)
|
||||
result += "\n"
|
||||
|
||||
# Opening and summary strategies
|
||||
result += "## אסטרטגיות פתיחה וסיכום לפי תוצאה\n\n"
|
||||
for outcome in VALID_OUTCOMES:
|
||||
opening = OPENING_STRATEGIES[outcome]
|
||||
summary = SUMMARY_STRATEGIES[outcome]
|
||||
result += f"### {outcome_labels[outcome]}\n"
|
||||
result += f"- **פתיחה:** {opening['description']} ({opening['paragraphs'][0]}-{opening['paragraphs'][1]} פסקאות)\n"
|
||||
result += f"- **סיכום ({summary['heading']}):** {summary['description']}\n\n"
|
||||
|
||||
return result
|
||||
|
||||
@@ -104,6 +185,7 @@ async def draft_section(
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
case_id = UUID(case["id"])
|
||||
expected_outcome = case.get("expected_outcome", "")
|
||||
|
||||
# 1. Get relevant chunks from case documents
|
||||
section_query = {
|
||||
@@ -139,6 +221,7 @@ async def draft_section(
|
||||
"respondents": case["respondents"],
|
||||
"subject": case["subject"],
|
||||
"property_address": case["property_address"],
|
||||
"expected_outcome": expected_outcome,
|
||||
},
|
||||
"section": section,
|
||||
"instructions": instructions,
|
||||
@@ -167,11 +250,37 @@ async def draft_section(
|
||||
],
|
||||
}
|
||||
|
||||
# 4. Add outcome-aware drafting guidance
|
||||
if expected_outcome and expected_outcome in VALID_OUTCOMES:
|
||||
lessons = get_lessons_for_outcome(expected_outcome)
|
||||
guidance: dict = {
|
||||
"outcome": expected_outcome,
|
||||
"golden_ratios": lessons["golden_ratios"],
|
||||
"citation_guidance": lessons["citation_guidance"],
|
||||
}
|
||||
|
||||
# Section-specific guidance
|
||||
if section == "legal_analysis":
|
||||
guidance["discussion_rules"] = lessons["discussion_rules"]
|
||||
guidance["opening_strategy"] = lessons["opening_strategy"]
|
||||
guidance["transition_phrases"] = lessons["transition_phrases"]
|
||||
guidance["paragraph_lengths"] = lessons["paragraph_lengths"]
|
||||
elif section in ("conclusion", "ruling"):
|
||||
guidance["summary_strategy"] = lessons["summary_strategy"]
|
||||
elif section == "facts":
|
||||
guidance["target_ratio"] = lessons["golden_ratios"].get("background", "")
|
||||
guidance["paragraph_lengths"] = {"claims": lessons["paragraph_lengths"].get("claims", "")}
|
||||
elif section in ("appellant_claims", "respondent_claims"):
|
||||
guidance["target_ratio"] = lessons["golden_ratios"].get("claims", "")
|
||||
guidance["paragraph_lengths"] = {"claims": lessons["paragraph_lengths"].get("claims", "")}
|
||||
|
||||
context["drafting_guidance"] = guidance
|
||||
|
||||
return json.dumps(context, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
async def get_decision_template(case_number: str) -> str:
|
||||
"""קבלת תבנית מבנית להחלטה מלאה עם פרטי התיק.
|
||||
"""קבלת תבנית מבנית להחלטה מלאה עם פרטי התיק, מותאמת לסוג התוצאה הצפויה.
|
||||
|
||||
Args:
|
||||
case_number: מספר תיק הערר
|
||||
@@ -182,7 +291,9 @@ async def get_decision_template(case_number: str) -> str:
|
||||
if not case:
|
||||
return f"תיק {case_number} לא נמצא."
|
||||
|
||||
template = DECISION_TEMPLATE.format(
|
||||
expected_outcome = case.get("expected_outcome", "")
|
||||
|
||||
format_args = dict(
|
||||
case_number=case["case_number"],
|
||||
subject=case["subject"],
|
||||
appellants=", ".join(case.get("appellants", [])),
|
||||
@@ -191,7 +302,34 @@ async def get_decision_template(case_number: str) -> str:
|
||||
date=date.today().strftime("%d.%m.%Y"),
|
||||
)
|
||||
|
||||
return template
|
||||
# Use outcome-specific template if available
|
||||
if expected_outcome and expected_outcome in DECISION_TEMPLATES:
|
||||
# Add ratio comments
|
||||
format_args["ratios_background"] = format_ratios_comment(expected_outcome, "background")
|
||||
format_args["ratios_claims"] = format_ratios_comment(expected_outcome, "claims")
|
||||
format_args["ratios_discussion"] = format_ratios_comment(expected_outcome, "discussion")
|
||||
format_args["ratios_summary"] = format_ratios_comment(expected_outcome, "summary")
|
||||
|
||||
template = DECISION_TEMPLATES[expected_outcome].format(**format_args)
|
||||
|
||||
# Add guidance header
|
||||
opening = OPENING_STRATEGIES[expected_outcome]
|
||||
summary = SUMMARY_STRATEGIES[expected_outcome]
|
||||
header = (
|
||||
f"<!-- תבנית מותאמת ל: {expected_outcome} -->\n"
|
||||
f"<!-- פתיחת דיון: {opening['description']} -->\n"
|
||||
f"<!-- סיכום: {summary['description']} -->\n\n"
|
||||
)
|
||||
return header + template
|
||||
else:
|
||||
# Fallback to generic template
|
||||
template = DECISION_TEMPLATE.format(**format_args)
|
||||
if not expected_outcome:
|
||||
template = (
|
||||
"<!-- לא הוגדרה תוצאה צפויה. הגדר expected_outcome בתיק לקבלת תבנית מותאמת. -->\n"
|
||||
f"<!-- ערכים אפשריים: {', '.join(VALID_OUTCOMES)} -->\n\n"
|
||||
) + template
|
||||
return template
|
||||
|
||||
|
||||
async def analyze_style() -> str:
|
||||
|
||||
Reference in New Issue
Block a user