docs(principles): move research into docs/precedent-corpus-redesign/ (README + research-full) (#153)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
83
mcp-server/src/legal_mcp/services/principle_gold.py
Normal file
83
mcp-server/src/legal_mcp/services/principle_gold.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Principle-level gold matching (importance layer #153, component 1).
|
||||
|
||||
Flags the SPECIFIC principle a chair relied on — not the whole precedent. A chair
|
||||
cites a precedent for ONE holding; we embed the citation's ``match_context`` (or a
|
||||
digest's ``headline_holding``) and cosine-match it to the principles extracted from
|
||||
that precedent. The best match ≥ threshold gets the flag:
|
||||
|
||||
• OUR_CHAIR (דפנה) cited it → ``gold_chair`` (tier-1, protective in the cull)
|
||||
• another committee chair cited it → ``chair_cited`` (tier-2, weight not protection)
|
||||
• a digest highlights it → ``gold_digest`` (tier-1)
|
||||
|
||||
Embedding-only — NO LLM, so it is cheap and re-runnable (the periodic refresh job).
|
||||
Tuned toward recall (HALACHA_GOLD_MATCH_THRESHOLD): missing a chair-relied principle
|
||||
(buried by the cull) is worse than over-protecting one.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from uuid import UUID
|
||||
|
||||
from legal_mcp import config
|
||||
from legal_mcp.services import db, embeddings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def _embed_all(texts: list[str]) -> list[list[float]]:
|
||||
"""Embed in Voyage-sized batches (embed_texts already chunks at 128)."""
|
||||
return await embeddings.embed_texts(texts, input_type="query") if texts else []
|
||||
|
||||
|
||||
async def match_all(threshold: float | None = None, apply: bool = False) -> dict:
|
||||
"""Run gold matching over all chair citations + digests.
|
||||
|
||||
Returns a stats dict; when ``apply`` writes the flags (idempotent — clears
|
||||
prior flags first). Dry-run computes everything but writes nothing.
|
||||
"""
|
||||
threshold = config.HALACHA_GOLD_MATCH_THRESHOLD if threshold is None else threshold
|
||||
if apply:
|
||||
cleared = await db.reset_principle_gold()
|
||||
logger.info("principle_gold: cleared %d prior flags before re-match", cleared)
|
||||
|
||||
citations = await db.gold_chair_citations()
|
||||
digests = await db.gold_digest_holdings()
|
||||
|
||||
cit_vecs = await _embed_all([c["match_context"] for c in citations])
|
||||
dig_vecs = await _embed_all([d["holding_text"] for d in digests])
|
||||
|
||||
stats = {"threshold": threshold, "applied": apply,
|
||||
"chair_citations": len(citations), "digests": len(digests),
|
||||
"gold_chair": 0, "chair_cited": 0, "gold_digest": 0,
|
||||
"chair_no_match": 0, "digest_no_match": 0, "samples": []}
|
||||
|
||||
async def _flag(case_law_id, vec, *, gold_chair=False, gold_digest=False,
|
||||
chair_cited=False, label=""):
|
||||
match = await db.nearest_original_principle(case_law_id, list(vec))
|
||||
if match is None or match[1] < threshold:
|
||||
return None
|
||||
hid, sim = match
|
||||
if apply:
|
||||
await db.set_principle_gold(
|
||||
UUID(hid), gold_chair=gold_chair, gold_digest=gold_digest,
|
||||
chair_cited=chair_cited, score=sim)
|
||||
if len(stats["samples"]) < 12:
|
||||
stats["samples"].append({"label": label, "halacha_id": hid, "sim": round(sim, 3)})
|
||||
return hid
|
||||
|
||||
for c, vec in zip(citations, cit_vecs):
|
||||
cid = c["cited_case_law_id"]
|
||||
if c["is_our_chair"]:
|
||||
hid = await _flag(cid, vec, gold_chair=True, label="chair:דפנה")
|
||||
stats["gold_chair" if hid else "chair_no_match"] += 1
|
||||
else:
|
||||
hid = await _flag(cid, vec, chair_cited=True, label=f"chair:{c['citing_chair']}")
|
||||
stats["chair_cited" if hid else "chair_no_match"] += 1
|
||||
|
||||
for d, vec in zip(digests, dig_vecs):
|
||||
hid = await _flag(d["linked_case_law_id"], vec, gold_digest=True, label="digest")
|
||||
stats["gold_digest" if hid else "digest_no_match"] += 1
|
||||
|
||||
if apply:
|
||||
stats["coverage"] = await db.gold_coverage_stats()
|
||||
return stats
|
||||
Reference in New Issue
Block a user