diff --git a/docs/spec/X12-digests-radar.md b/docs/spec/X12-digests-radar.md index 23d1afe..055f709 100644 --- a/docs/spec/X12-digests-radar.md +++ b/docs/spec/X12-digests-radar.md @@ -113,8 +113,10 @@ Fowler — Bounded Context / Canonical Data Model | סטטוס: verified **אכיפה:** טבלה פיזית נפרדת `digests`; `ingest_digest` עושה reuse לשירותים אטומיים בלבד (`extractor.extract_text`, `embeddings.embed_texts`) ולא ל-`ingest.ingest_document`; ביקורת- ארכיטקטורה. אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) -+ כלל-הנדסה "סימטריה" (§6). -**הפרה ידועה:** — (תת-מערכת חדשה) ++ כלל-הנדסה "סימטריה" (§6). **מקור-אמת יחיד:** מצב-הקליטה נשמר אך-ורק בטבלת `digests` (סטטוס + +`content_hash` ל-idempotency); תיקיות-קבצים (`incoming/`) הן staging בלבד, **לא** state. +**הפרה ידועה (תוקנה 2026-06-07):** `ingest_digests_batch.py` העביר קבצים ל-`data/digests/processed/` +— state מבוסס-תיקיות מקביל ל-DB. הוסר; הסקריפט מסתמך על dedup ב-content_hash (G2). ### INV-DIG3: קישור-לפסק-המקורי הוא הגשר — חוסר-קישור הוא פער גלוי **כלל:** לכל `digest` שדה `linked_case_law_id` (FK ל-`case_law`, nullable). כשהפסק המקורי בקורפוס — diff --git a/scripts/SCRIPTS.md b/scripts/SCRIPTS.md index 76e0d63..8a0c3b7 100644 --- a/scripts/SCRIPTS.md +++ b/scripts/SCRIPTS.md @@ -88,7 +88,7 @@ | `run_curator_sonnet_rerun.sh` | A/B test #3 (2026-05-05) — ריצה חוזרת של Sonnet 4.5 על אותו CMP-78. תוצאה: 12:52 דק׳ (לעומת 20:13 בריצה המקורית — כי בלי לולאת interaction.json). זיהה תוצאה שגויה ("דחייה") **בעקביות עם הריצה המקורית** — Sonnet עקבי-בטעות, DeepSeek אקראי. | בדיקה חד-פעמית — לא להריץ שוב | | `ingest_incoming_batch.py` | python | קליטת batch של החלטות ועדת ערר מ-`data/precedents/incoming/` דרך המסלול הקנוני (`ingest_internal_decision`) + חילוץ מטא-דאטה לכל תיק (המסלול הפנימי לא מתזמן metadata — INV-ING3). רצף (לא מקבילי, להימנע מעומס CLI). רשימת `DECISIONS` נערכת ידנית לכל batch. config מ-`~/.env`. תומך תהליך [[project_precedent_incoming_workflow]]. | ידני, per-batch (חלופה ל-MCP `internal_decision_upload` כש-batch גדול) | | `drain_halacha_queue.py` | python | ריקון תור חילוץ ההלכות (`process_pending_extractions kind='halacha'`) ב-batches של 4 עד שהתור ריק (2 סבבים ריקים). משמש אחרי `ingest_incoming_batch.py`. | ידני אחרי batch (חלופה ל-MCP `precedent_process_pending`) | -| `ingest_digests_batch.py` | python | קליטת batch של יומוני "כל יום" מ-`data/digests/incoming/` דרך המסלול העצמאי של קורפוס-הגילוי (`digest_library.ingest_digest`) — חילוץ-LLM (תג-מושג, כותרת-הלכה, מראה-מקום, שני-תאריכים), embedding יחיד, ו-autolink לפסק המקורי (X12/INV-DIG3). רצף (לא מקבילי). מזהה-יומון+תאריך נגזרים משם-הקובץ; העלון החודשי מדולג. קבצים מועברים ל-`processed/`. config מ-`~/.env`. | ידני, per-batch (חלופה ל-MCP `digest_upload`) | +| `ingest_digests_batch.py` | python | קליטת batch של יומוני "כל יום" מ-`data/digests/incoming/` דרך המסלול העצמאי של קורפוס-הגילוי (`digest_library.ingest_digest`) — חילוץ-LLM (תג-מושג, כותרת-הלכה, מראה-מקום, שני-תאריכים), embedding יחיד, ו-autolink לפסק המקורי (X12/INV-DIG3). רצף (לא מקבילי). מזהה-יומון+תאריך נגזרים משם-הקובץ; העלון החודשי מדולג. **לא מעביר קבצים** — ה-DB (content_hash) הוא מקור-האמת היחיד; הרצה חוזרת מדלגת על קיימים (`exists`). config מ-`~/.env`. | ידני, per-batch (חלופה ל-MCP `digest_upload`) | ## סקריפטים שנמחקו (git history בלבד) diff --git a/scripts/ingest_digests_batch.py b/scripts/ingest_digests_batch.py index 993c851..b7605e4 100644 --- a/scripts/ingest_digests_batch.py +++ b/scripts/ingest_digests_batch.py @@ -15,6 +15,13 @@ halacha pipeline and is never cited in a decision (INV-DIG1/2). After this run, relink unmatched digests once the originals are uploaded, or surface them via missing_precedent_create. +SINGLE SOURCE OF TRUTH: the `digests` table (DB) is the ONLY authority for what +has been ingested. This script does NOT move files between folders — re-running +is safe because ``ingest_digest`` dedups on content_hash (already-ingested → +returns ``exists``). Files left in ``incoming/`` are simply re-checked and +skipped. (Earlier versions moved files to a ``processed/`` folder; that created +a second, divergent state and was removed.) + Yomon number + issue date are parsed from the filename ("יומון 5158 - 31.5.26.pdf") as hints; the LLM also extracts them from the body and the explicit hint wins. The monthly bulletin (e.g. "201 יוני.pdf") is @@ -28,7 +35,6 @@ Config (POSTGRES_URL, VOYAGE_API_KEY, ANTHROPIC_API_KEY) auto-loads from ~/.env. import asyncio import os import re -import shutil import sys import traceback from pathlib import Path @@ -39,7 +45,6 @@ from legal_mcp import config # noqa: E402 from legal_mcp.services import digest_library as svc # noqa: E402 INCOMING = Path(config.DATA_DIR) / "digests" / "incoming" -PROCESSED = Path(config.DATA_DIR) / "digests" / "processed" # Matches "יומון 5158 - 31.5.26" → ("5158", "31.5.26") _NAME_RE = re.compile(r"יומון\s*(\d+)\s*-\s*(\d{1,2})\.(\d{1,2})\.(\d{2,4})") @@ -78,7 +83,6 @@ async def main(argv: list[str]) -> None: if not files: print(f"No yomon PDFs found in {INCOMING}", flush=True) return - PROCESSED.mkdir(parents=True, exist_ok=True) results = [] for idx, fp in enumerate(files): @@ -108,11 +112,8 @@ async def main(argv: list[str]) -> None: f"{link} | {out.get('underlying_citation')}", flush=True, ) - # Move to processed/ so re-runs are clean (idempotent anyway). - try: - shutil.move(str(fp), str(PROCESSED / fp.name)) - except Exception as e: - print(f" (could not move {fp.name}: {e})", flush=True) + # No folder move — the DB (content_hash) is the single source of + # truth. Re-running re-checks incoming/ and skips already-ingested. except Exception as e: rec["error"] = f"{type(e).__name__}: {e}" print(f"✗ {fp.name}: {e}", flush=True)