feat(corroboration): aggregator — distinct positive + negative-flag (INV-COR4, X11)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 19:00:16 +00:00
parent dbc176ae66
commit 33f955e372
2 changed files with 37 additions and 0 deletions

View File

@@ -33,6 +33,23 @@ def accept_match(best: tuple[str, float] | None, floor: float = config.HALACHA_C
return halacha_id if sim >= floor else None
def aggregate(links: list[dict], min_cites: int = config.HALACHA_CORROBORATION_MIN_CITES) -> dict:
"""Aggregate per-halacha corroboration (INV-COR4/COR2).
links: [{"source_id": str, "treatment": str}, ...] already matched to ONE halacha.
positive_sources = count of DISTINCT source_id whose treatment is positive.
has_negative = any negative treatment present.
corroborated = positive_sources >= min_cites AND not has_negative.
"""
positive = {l["source_id"] for l in links if is_positive(l["treatment"])}
has_negative = any(is_negative(l["treatment"]) for l in links)
return {
"positive_sources": len(positive),
"has_negative": has_negative,
"corroborated": len(positive) >= min_cites and not has_negative,
}
_TREATMENT_PROMPT = """אתה משפטן בכיר. נתון ציטוט של פסק/החלטה קודמים בתוך החלטה מאוחרת.
סווג כיצד ההחלטה המאוחרת **מטפלת** בתקדים המצוטט, לפי אחת מהקטגוריות:
- followed — אימצה והחילה את ההלכה.

View File

@@ -24,3 +24,23 @@ def test_match_rejects_below_threshold():
def test_match_rejects_empty():
assert cor.accept_match(None, floor=0.50) is None
def _link(src, treatment):
return {"source_id": src, "treatment": treatment}
def test_aggregate_counts_distinct_positive():
links = [_link("d1","followed"), _link("d1","explained"), _link("d2","followed")]
agg = cor.aggregate(links, min_cites=2)
assert agg["positive_sources"] == 2 # d1 counted once (INV-COR4 independence)
assert agg["has_negative"] is False
assert agg["corroborated"] is True
def test_aggregate_negative_blocks():
links = [_link("d1","followed"), _link("d2","followed"), _link("d3","distinguished")]
agg = cor.aggregate(links, min_cites=2)
assert agg["has_negative"] is True
assert agg["corroborated"] is False # any negative -> not corroborated (INV-COR2)
def test_aggregate_below_threshold():
agg = cor.aggregate([_link("d1","followed")], min_cites=2)
assert agg["corroborated"] is False # single source insufficient (INV-COR4)