feat(corroboration): treatment classifier + polarity (INV-COR2, X11)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 18:54:50 +00:00
parent ca31932a5f
commit 09eec6a906
2 changed files with 69 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
"""X11 citation corroboration — classify treatment, match to halacha, aggregate.
Phase 1: builds the SIGNAL only (no approval changes). See docs/spec/X11.
All LLM calls go through the local claude_session bridge (Opus 4.8 @ xhigh),
same architectural rule as the other extractors (local MCP only).
"""
from __future__ import annotations
import logging
from legal_mcp import config
from legal_mcp.config import parse_llm_json
from legal_mcp.services import claude_session
logger = logging.getLogger(__name__)
TREATMENT_POSITIVE = {"followed", "explained"}
TREATMENT_NEGATIVE = {"distinguished", "criticized", "questioned", "overruled"}
TREATMENT_NEUTRAL = {"mentioned"}
_VALID_TREATMENT = TREATMENT_POSITIVE | TREATMENT_NEGATIVE | TREATMENT_NEUTRAL
def is_positive(t: str) -> bool: return t in TREATMENT_POSITIVE
def is_negative(t: str) -> bool: return t in TREATMENT_NEGATIVE
def _coerce_treatment(raw: dict) -> str:
t = str((raw or {}).get("treatment", "")).strip().lower()
return t if t in _VALID_TREATMENT else "mentioned"
_TREATMENT_PROMPT = """אתה משפטן בכיר. נתון ציטוט של פסק/החלטה קודמים בתוך החלטה מאוחרת.
סווג כיצד ההחלטה המאוחרת **מטפלת** בתקדים המצוטט, לפי אחת מהקטגוריות:
- followed — אימצה והחילה את ההלכה.
- explained — הסבירה/הזכירה בלי לחלוק.
- distinguished — אבחנה (קבעה שלא חל בנסיבות).
- criticized — מתחה ביקורת בלי לבטל.
- questioned — הטילה ספק.
- overruled — דחתה/ביטלה את ההלכה.
- mentioned — אזכור-אגב בלי טיפול.
החזר JSON בלבד: {"treatment": "<קטגוריה>"}.
"""
async def classify_treatment(cited_citation: str, context: str) -> str:
"""Return one treatment label for how `context` treats `cited_citation`."""
user = f"תקדים מצוטט: {cited_citation}\n\n--- ההקשר המצטט ---\n{context}\n--- סוף ---"
try:
result = await claude_session.query_json(
user, system=_TREATMENT_PROMPT,
model=config.HALACHA_EXTRACT_MODEL or None,
effort=config.HALACHA_EXTRACT_EFFORT or None,
)
except Exception as e:
logger.warning("classify_treatment failed: %s", e)
return "mentioned"
return _coerce_treatment(result if isinstance(result, dict) else {})

View File

@@ -0,0 +1,17 @@
from __future__ import annotations
import pytest
from legal_mcp.services import corroboration as cor
@pytest.mark.parametrize("raw,expected", [
({"treatment": "followed"}, "followed"),
({"treatment": "OVERRULED"}, "overruled"), # case-insensitive
({"treatment": "bananas"}, "mentioned"), # unknown -> neutral default
({}, "mentioned"), # missing -> neutral default
])
def test_coerce_treatment(raw, expected):
assert cor._coerce_treatment(raw) == expected
def test_treatment_polarity():
assert cor.is_positive("followed") and cor.is_positive("explained")
assert cor.is_negative("distinguished") and cor.is_negative("overruled")
assert not cor.is_positive("mentioned") and not cor.is_negative("mentioned")