feat(mcp): FU-14 GAP-51 — איחוד אוצר-המילים של תוצאת-תיק (set_outcome SSoT)

הכרעת-יו"ר: קנוני = 3 תוצאות אמיתיות (rejection/partial_acceptance/full_acceptance);
betterment_levy יוצא מהיותו "תוצאה" ועובר ל-override לפי practice_area.
+ עקרון "אנגלית-ב-DB, עברית-ב-UI": מפת-תוויות SSoT אחת.

lessons.py:
- VALID_OUTCOMES = 3 (הוסר betterment_levy).
- OUTCOME_LABELS_HE (SSoT לתצוגה) + LEGACY_OUTCOME_MAP + canonical_outcome().
- PRACTICE_AREA_OVERRIDES["betterment_levy"] מרכז את כל ה-guidance שהיה מפתוח כ-outcome
  (golden_ratios/opening/summary/discussion/template).
- get_lessons_for_outcome(outcome, practice_area) + format_ratios_comment(..., practice_area)
  מחילים override + מנרמלים legacy.

block_writer.py: STRUCTURE_GUIDANCE קנוני + תווית מ-OUTCOME_LABELS_HE + override betterment.
workflow.set_outcome: קנוני 3 + מיפוי-legacy סלחני; תווית מ-SSoT.
drafting.py: טבלת יחסי-זהב + get_decision_template מודעי-practice_area (override).
web-ui case.ts: הסרת betterment_levy מ-expectedOutcomes (הוא practice_area).
server.py: docstrings קנוניים.

מיגרציה: migrate_gap51_outcomes.py — 9 שורות נורמלו (rejected→rejection וכו'),
גיבוי ב-data/audit/. הקוד canonicalize בקריאה ⇒ backward-compatible גם בלי מיגרציה.

אומת: py_compile (5 קבצים) + בדיקות-יחידה offline (override/legacy/labels) + אימות-DB.
עודכנו X9 §3 + gap-audit (GAP-51 ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 15:34:49 +00:00
parent d3f1d04915
commit 701efab726
10 changed files with 267 additions and 73 deletions

View File

@@ -15,9 +15,11 @@ from legal_mcp.services.lessons import (
GOLDEN_RATIOS,
OPENING_STRATEGIES,
PARAGRAPH_LENGTHS,
PRACTICE_AREA_OVERRIDES,
SUMMARY_STRATEGIES,
TRANSITION_PHRASES,
VALID_OUTCOMES,
canonical_outcome,
format_ratios_comment,
get_lessons_for_outcome,
)
@@ -156,6 +158,15 @@ async def get_style_guide() -> str:
f"| {r['discussion'][0]}-{r['discussion'][1]}% "
f"| {r['summary'][0]}-{r['summary'][1]}% |\n"
)
# GAP-51: betterment_levy is a practice_area override (applied on top of the outcome), not an outcome.
_bl = PRACTICE_AREA_OVERRIDES["betterment_levy"]["golden_ratios"]
result += (
f"| {outcome_labels['betterment_levy']} (override לפי תחום) "
f"| {_bl['background'][0]}-{_bl['background'][1]}% "
f"| {_bl['claims'][0]}-{_bl['claims'][1]}% "
f"| {_bl['discussion'][0]}-{_bl['discussion'][1]}% "
f"| {_bl['summary'][0]}-{_bl['summary'][1]}% |\n"
)
result += "\n"
# Opening and summary strategies
@@ -167,6 +178,13 @@ async def get_style_guide() -> str:
result += f"- **פתיחה:** {opening['description']} ({opening['paragraphs'][0]}-{opening['paragraphs'][1]} פסקאות)\n"
result += f"- **סיכום ({summary['heading']}):** {summary['description']}\n\n"
# GAP-51: betterment_levy override (practice_area, applied over the outcome)
_bo = PRACTICE_AREA_OVERRIDES["betterment_levy"]
_op, _sm = _bo["opening_strategy"], _bo["summary_strategy"]
result += f"### {outcome_labels['betterment_levy']} (override לפי תחום)\n"
result += f"- **פתיחה:** {_op['description']} ({_op['paragraphs'][0]}-{_op['paragraphs'][1]} פסקאות)\n"
result += f"- **סיכום ({_sm['heading']}):** {_sm['description']}\n\n"
return result
@@ -319,7 +337,9 @@ async def get_decision_template(case_number: str) -> str:
if not case:
return f"תיק {case_number} לא נמצא."
expected_outcome = case.get("expected_outcome", "")
# GAP-51: canonicalize outcome + apply betterment_levy practice_area override.
expected_outcome = canonical_outcome(case.get("expected_outcome", ""))
practice_area = case.get("practice_area", "")
format_args = dict(
case_number=case["case_number"],
@@ -332,19 +352,24 @@ async def get_decision_template(case_number: str) -> str:
# 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")
# Add ratio comments (practice_area-aware)
format_args["ratios_background"] = format_ratios_comment(expected_outcome, "background", practice_area)
format_args["ratios_claims"] = format_ratios_comment(expected_outcome, "claims", practice_area)
format_args["ratios_discussion"] = format_ratios_comment(expected_outcome, "discussion", practice_area)
format_args["ratios_summary"] = format_ratios_comment(expected_outcome, "summary", practice_area)
template = DECISION_TEMPLATES[expected_outcome].format(**format_args)
# betterment_levy practice_area supplies its own template; else use the outcome's.
override = PRACTICE_AREA_OVERRIDES.get(practice_area, {})
template_src = override.get("decision_template") or DECISION_TEMPLATES[expected_outcome]
template = template_src.format(**format_args)
# Add guidance header
opening = OPENING_STRATEGIES[expected_outcome]
summary = SUMMARY_STRATEGIES[expected_outcome]
# Add guidance header (override-aware via get_lessons_for_outcome)
lessons_o = get_lessons_for_outcome(expected_outcome, practice_area)
opening = lessons_o["opening_strategy"]
summary = lessons_o["summary_strategy"]
header = (
f"<!-- תבנית מותאמת ל: {expected_outcome} -->\n"
f"<!-- תבנית מותאמת ל: {expected_outcome}"
f"{' / ' + practice_area if practice_area in PRACTICE_AREA_OVERRIDES else ''} -->\n"
f"<!-- פתיחת דיון: {opening['description']} -->\n"
f"<!-- סיכום: {summary['description']} -->\n\n"
)

View File

@@ -7,6 +7,11 @@ import logging
from uuid import UUID
from legal_mcp.services import db
from legal_mcp.services.lessons import (
OUTCOME_LABELS_HE,
VALID_OUTCOMES,
canonical_outcome,
)
logger = logging.getLogger(__name__)
@@ -151,7 +156,8 @@ async def set_outcome(
Args:
case_number: מספר תיק הערר
outcome: תוצאה — rejected (דחייה), accepted (קבלה), partial (קבלה חלקית)
outcome: תוצאה — rejection (דחייה) / partial_acceptance (קבלה חלקית) /
full_acceptance (קבלה מלאה). ערכי-legacy (rejected/accepted/partial) ממופים אוטומטית.
reasoning: נימוק (אופציונלי). אם ריק — מפעיל סיעור מוחות.
"""
from legal_mcp.services import brainstorm
@@ -160,9 +166,10 @@ async def set_outcome(
if not case:
return f"תיק {case_number} לא נמצא."
valid_outcomes = ("rejected", "accepted", "partial")
if outcome not in valid_outcomes:
return f"תוצאה לא תקינה. אפשרויות: {', '.join(valid_outcomes)}"
# GAP-51: accept legacy vocabulary (rejected/accepted/partial), store canonical.
outcome = canonical_outcome(outcome)
if outcome not in VALID_OUTCOMES:
return f"תוצאה לא תקינה. אפשרויות: {', '.join(VALID_OUTCOMES)}"
case_id = UUID(case["id"])
@@ -187,7 +194,7 @@ async def set_outcome(
# Update case status
await db.update_case(case_id, status="in_progress", expected_outcome=outcome)
outcome_hebrew = {"rejected": "דחייה", "accepted": "קבלה", "partial": "קבלה חלקית"}.get(outcome, outcome)
outcome_hebrew = OUTCOME_LABELS_HE.get(outcome, outcome)
result = {
"decision_id": decision["id"],