feat(X13): auto-trigger court fetch from digests + drain tool

סוגר את הלולאה — יומון שמצביע על פס"ד בית-משפט שלא בקורפוס מזניק אחזור
אוטומטי, וקושר את היומון חזרה אחרי הקליטה (INV-DIG3 + INV-CF2).

- digest_library.try_autolink: בכשל-קישור, אם הציטוט מסווג כפס"ד-בימ"ש
  (supreme/admin) → _enqueue_court_fetch יוצר court_fetch_jobs(pending);
  ועדת-ערר (skip) לא מוזנק. never-raises (לא שובר קליטת-יומון).
- orchestrator.drain_pending(limit): מנקז pending/failed סדרתי (cooldown,
  INV-CF4), fetch+ingest לכל אחד; בהצלחה מקשר את היומון ל-case_law שנקלט.
- כלי-MCP court_fetch_drain + רישום ב-server.py.
- X13 spec: עודכן (הפער ב-INV-CF2 סומן כמתוקן).

נבדק מול ה-DB: עת"מ 46111-12-22 → job tier=admin pending digest-linked;
ערר 1110/20 → לא מוזנק. כלי מקומי בלבד (ingest = claude CLI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 20:04:12 +00:00
parent 635dc98492
commit f56309da5a
5 changed files with 94 additions and 3 deletions

View File

@@ -83,11 +83,40 @@ async def try_autolink(digest_id: UUID | str, underlying_citation: str) -> str |
logger.warning("digest try_autolink lookup failed for %r: %s", citation, e)
return None
if not match:
# Gap (INV-DIG3): the underlying ruling isn't in the corpus. If it's a
# court verdict (not ועדת-ערר), enqueue an X13 auto-fetch job so the gap
# is actionable instead of silently dropped (INV-CF2). Never raises.
await _enqueue_court_fetch(digest_id, citation)
return None
await db.link_digest_to_case_law(digest_id, match["id"])
return str(match["id"])
async def _enqueue_court_fetch(digest_id: UUID | str, citation: str) -> None:
"""Queue an X13 court-verdict fetch for an unlinked digest citation.
Court rulings (supreme/admin) → a ``court_fetch_jobs`` row drained later by
``court_fetch_drain``. ועדת-ערר (skip) is left alone — it needs Nevo and is
surfaced through the normal missing-precedent path, not auto-fetch.
"""
try:
from legal_mcp.services import court_citation
cit = court_citation.classify(citation)
if cit.tier not in ("supreme", "admin"):
return
await db.court_fetch_job_upsert(
case_number_norm=cit.case_number_norm,
citation_raw=citation,
tier=cit.tier,
court=cit.court_prefix,
digest_id=UUID(str(digest_id)),
)
logger.info("digest %s: enqueued court-fetch for %r (tier=%s)",
digest_id, citation, cit.tier)
except Exception as e: # never break digest ingest
logger.warning("digest court-fetch enqueue failed for %r: %s", citation, e)
# ── Container-safe creation (web upload) — no LLM, no embedding ──────
async def create_pending_digest(