refactor(digests): single source of truth — drop processed/ folder state (X12)
ה-DB (`digests`) הוא מקור-האמת היחיד למצב-קליטה. ingest_digests_batch.py העביר קבצים incoming→processed/ — state מבוסס-תיקיות מקביל ל-DB (הפרת-G2 קטנה). - הוסר ה-move ל-processed/ + import shutil + PROCESSED. הסקריפט מסתמך על dedup ב-content_hash (ingest_digest מחזיר 'exists' לקיימים) → הרצה חוזרת בטוחה. - תיקיות (incoming/) = staging בלבד, לא state. - X12 INV-DIG2: תועד מקור-אמת-יחיד + ההפרה-שתוקנה (processed/). - SCRIPTS.md עודכן. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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). כשהפסק המקורי בקורפוס —
|
||||
|
||||
@@ -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 בלבד)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user