feat(principles): retroactive cull (Phase C) + source-derived terminology (Phase D, #152)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s

Phase C — scripts/cull_principles.py: re-adjudicates every existing 'original'
principle with the SAME panel regime (panel_keep_score → classify → apply_cap),
reversible (CSV backup + rejected canonical recoverable), usage-throttled.
panel_extraction.panel_keep_score + apply_cap (shared, G2). Dry-run on 3
decisions: 37→15 survive.

Phase D — services/principles.py: source-derived label הלכה (binding court) /
כלל פרשני (committee) / עיקרון (persuasive); umbrella עקרונות משפטיים. Wired into
canonical_halacha_get/list (principle_class+principle_label). UI string changes
deferred to the Claude Design gate. spec INV-LRN7; SCRIPTS.md; 7 new tests; 428 green.

Phase E needs no new code — synthesis already targets pending_synthesis, which the
cull leaves only on survivors (rejected canonicals → 'rejected').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-19 11:14:59 +00:00
parent 6b2fd562ae
commit 4ca907b97f
9 changed files with 426 additions and 14 deletions

View File

@@ -14,7 +14,7 @@ import asyncpg
from pgvector.asyncpg import register_vector
from legal_mcp import config
from legal_mcp.services import court_citation, halacha_quality
from legal_mcp.services import court_citation, halacha_quality, principles
logger = logging.getLogger(__name__)
@@ -6156,7 +6156,8 @@ async def get_canonical_halacha(canonical_id: "UUID") -> "dict | None":
"SELECT ch.id::text, ch.canonical_statement, ch.rule_type, "
" ch.practice_areas, ch.subject_tags, ch.review_status, "
" ch.instance_count, ch.created_at, ch.updated_at, "
" cl.case_number AS first_established_case "
" cl.case_number AS first_established_case, "
" cl.source_kind, cl.is_binding "
"FROM canonical_halachot ch "
"LEFT JOIN case_law cl ON cl.id = ch.first_established_in "
"WHERE ch.id = $1",
@@ -6172,10 +6173,12 @@ async def get_canonical_halacha(canonical_id: "UUID") -> "dict | None":
"WHERE h.canonical_id = $1 ORDER BY h.instance_type, cl.case_number",
canonical_id,
)
return {
**dict(row),
"instances": [dict(i) for i in instances],
}
out = dict(row)
# #152: source-derived class + Hebrew label (הלכה / כלל פרשני / עיקרון).
out["principle_class"] = principles.principle_class(out.get("source_kind"), out.get("is_binding"))
out["principle_label"] = principles.label_for_class(out["principle_class"])
out["instances"] = [dict(i) for i in instances]
return out
async def list_canonical_halachot(
@@ -6190,24 +6193,33 @@ async def list_canonical_halachot(
params: list = []
idx = 1
if practice_area:
conditions.append(f"${ idx} = ANY(practice_areas)")
conditions.append(f"${ idx} = ANY(ch.practice_areas)")
params.append(practice_area)
idx += 1
if review_status:
conditions.append(f"review_status = ${idx}")
conditions.append(f"ch.review_status = ${idx}")
params.append(review_status)
idx += 1
params += [limit, offset]
rows = await pool.fetch(
f"SELECT id::text, canonical_statement, rule_type, practice_areas, "
f" subject_tags, review_status, instance_count, created_at, updated_at "
f"FROM canonical_halachot "
f"SELECT ch.id::text, ch.canonical_statement, ch.rule_type, ch.practice_areas, "
f" ch.subject_tags, ch.review_status, ch.instance_count, "
f" ch.created_at, ch.updated_at, cl.source_kind, cl.is_binding "
f"FROM canonical_halachot ch "
f"LEFT JOIN case_law cl ON cl.id = ch.first_established_in "
f"WHERE {' AND '.join(conditions)} "
f"ORDER BY instance_count DESC, created_at DESC "
f"ORDER BY ch.instance_count DESC, ch.created_at DESC "
f"LIMIT ${idx} OFFSET ${idx + 1}",
*params,
)
return [dict(r) for r in rows]
out = []
for r in rows:
d = dict(r)
cls = principles.principle_class(d.pop("source_kind", None), d.pop("is_binding", None))
d["principle_class"] = cls
d["principle_label"] = principles.label_for_class(cls)
out.append(d)
return out
async def update_canonical_statement(