diff --git a/scripts/SCRIPTS.md b/scripts/SCRIPTS.md index e12294b..c07f9fb 100644 --- a/scripts/SCRIPTS.md +++ b/scripts/SCRIPTS.md @@ -200,7 +200,7 @@ | `drain_digests.py` | python | ריקון תור ההעשרה של יומונים (X12): מעבד כל digest בסטטוס `pending` דרך `digest_library.enrich_digest` (חילוץ-LLM Sonnet + embedding + autolink). מקבילי (CONCURRENCY=3, env-tunable), idempotent. מוסיף `~/.local/bin` ל-PATH כדי שה-claude CLI יימצא תחת cron. בודק דגל `drain_controls('legal-digest-drain')` ב-startup → no-op כשכבוי מ-/operations. | דרך `legal-digest-drain.config.cjs` (pm2 cron) + ידני אחרי backfill. חלופת-MCP: `digest_process_pending` | | `legal-digest-drain.config.cjs` | pm2/js | **תזמון כל שעתיים של `drain_digests.py`** (cron `12 */2 * * *`, `DIGEST_DRAIN_CRON` לעקיפה; דקת-הצתה `:12` כדי לא לחלוק דקה עם metadata-drain `:00` — מונע deadlock של DDL-המיגרציה) — הועבר מ-crontab של המערכת ל-pm2 כדי שיופיע ויהיה שליט בדף `/operations` (הרץ-עכשיו/הפעל/כבה). `autorestart:false` (one-shot per tick). דורש claude CLI + `VOYAGE_API_KEY`. התקנה: `pm2 start scripts/legal-digest-drain.config.cjs && pm2 save`. | pm2 cron (host-side) | -| `renumber_cases.py` | python | **מיגרציה חד-פעמית (בוצעה 2026-06-12)** — תיקון 11 מספרי-תיקים לפורמט קנוני `NNNN-MM-YY` (הוספת ספרות-חודש; 1046-26→1024-02-26 תיקון-סידורי). רץ על ה-host (לא בקונטיינר): DB pool של האפליקציה + `mcli` (MinIO) + Gitea API + Paperclip DB. אטומי per-case עם גיבוי ל-`data/audit/` ואימות-אחרי. FK-ים על `cases.id` (UUID) לא נגעו; משכתב כל עמודה עם `cases/{old}/` (file_path **וגם** image_thumbnail_path שהוא storage-key בלי `/data`), מנרמל זהות חוצת-קורפוס (case_law/style_corpus/style_exemplars/citations — לא תוכן/full_text), מעביר מפתחי-MinIO ב-3 buckets (legal-immutable=WORM copy-only), משנה-שם repo ב-Gitea, ומעדכן שם-פרויקט ב-Paperclip. dry-run כברירת-מחדל; `--apply --tier clean\|archive`. **מיצוי — לא להריץ שוב** (ה-MAPPING היסטורי). | חד-פעמי — בוצע | +| `renumber_cases.py` | python | **מיגרציה חד-פעמית (בוצעה 2026-06-12)** — תיקון 11 מספרי-תיקים לפורמט קנוני `NNNN-MM-YY` (הוספת ספרות-חודש; 1046-26→1024-02-26 תיקון-סידורי). רץ על ה-host (לא בקונטיינר): DB pool של האפליקציה + `mcli` (MinIO) + Gitea API + Paperclip DB. אטומי per-case עם גיבוי ל-`data/audit/` ואימות-אחרי. FK-ים על `cases.id` (UUID) לא נגעו; משכתב כל עמודה עם `cases/{old}/` (file_path **וגם** image_thumbnail_path שהוא storage-key בלי `/data`), מנרמל זהות חוצת-קורפוס (case_law/style_corpus/style_exemplars/citations — לא תוכן/full_text), מעביר מפתחי-MinIO ב-3 buckets (legal-immutable=WORM copy-only), משנה-שם repo ב-Gitea, ומעדכן ב-Paperclip את **שם-הפרויקט + `plugin_state.legal-case-number` + כותרות-issues** (3 המשטחים ש-`get_case_issues` נשען עליהם — בלי (b)+(c) ה-issues נשארים על המספר הישן ו-run-learning/run-halacha מדלגים בשקט עם "no_issue"; תוקן 2026-06-28 אחרי שהפער התגלה ב-8137-11-24). dry-run כברירת-מחדל; `--apply --tier clean\|archive`. **מיצוי — לא להריץ שוב** (ה-MAPPING היסטורי); לתבנית-עתידית: ה-Paperclip-step המעודכן הוא הרפרנס. | חד-פעמי — בוצע | ## סקריפטים שנמחקו (git history בלבד) diff --git a/scripts/renumber_cases.py b/scripts/renumber_cases.py index e222932..c289c91 100644 --- a/scripts/renumber_cases.py +++ b/scripts/renumber_cases.py @@ -19,7 +19,13 @@ migrates atomically per case — is everything that embeds the number as *text*: 4. MinIO keys cases/{old}/… 3 buckets; cp→new then rm old. legal-immutable (WORM/object-lock) → copy-only, old object stays locked. 5. Gitea repo cases/{old} API PATCH name + local .git remote rewrite - 6. Paperclip project name replace(old→new) so case↔project lookup holds + 6. Paperclip case↔issue linkage replace(old→new) in THREE places, because the + legal-ai → Paperclip lookup (get_case_issues) keys on the case number as text: + • projects.name so case↔project lookup holds + • plugin_state.legal-case-number the authoritative issue linkage value_json + • issues.title the '[ערר {cn}] …' tag the title-path lookup uses + Without (b)+(c) the issues keep the OLD number and get_case_issues returns [], + so post-final actions (run-learning / run-halacha) silently skip ("no_issue"). Bare occurrences of the old number that are NOT inside a '/cases/{old}/' path (e.g. prose in notes, a citation) are *reported for review*, never auto-edited. @@ -273,10 +279,25 @@ async def inspect_paperclip(old: str) -> dict: try: c = await asyncpg.connect(PAPERCLIP_DSN, timeout=10) except Exception as e: - return {"reachable": False, "error": str(e)[:120], "projects": []} + return {"reachable": False, "error": str(e)[:120], + "projects": [], "linkage_rows": 0, "issue_titles": 0} try: rows = await c.fetch("SELECT id, name FROM projects WHERE name LIKE $1", f"%{old}%") - return {"reachable": True, "projects": [(str(r["id"]), r["name"]) for r in rows]} + # The two surfaces get_case_issues actually keys on: the legal-case-number + # plugin_state linkage and the '[ערר {cn}]' tag in issue titles. + linkage = await c.fetchval( + "SELECT count(*) FROM plugin_state " + "WHERE state_key = 'legal-case-number' AND value_json = to_jsonb($1::text)", + old, + ) + titles = await c.fetchval( + "SELECT count(*) FROM issues WHERE title LIKE $1", f"%{old}%") + return { + "reachable": True, + "projects": [(str(r["id"]), r["name"]) for r in rows], + "linkage_rows": linkage or 0, + "issue_titles": titles or 0, + } finally: await c.close() @@ -369,16 +390,31 @@ async def apply_case(conn, rec: dict, *, skip_minio: bool, skip_gitea: bool, except urllib.error.HTTPError as e: log(f" ✗ Gitea rename failed: HTTP {e.code} {e.read()[:160]!r}") - # 6. Paperclip project name - if not skip_paperclip and rec["paperclip"].get("reachable") and rec["paperclip"]["projects"]: + # 6. Paperclip case↔issue linkage — project name + legal-case-number value + + # issue titles. (b)+(c) are what get_case_issues keys on; without them the + # issues keep the old number and run-learning/run-halacha skip with "no_issue". + if not skip_paperclip and rec["paperclip"].get("reachable"): import asyncpg c = await asyncpg.connect(PAPERCLIP_DSN, timeout=10) try: + if rec["paperclip"]["projects"]: + res = await c.execute( + "UPDATE projects SET name = replace(name, $1, $2), updated_at = now() " + "WHERE name LIKE $3", + old, new, f"%{old}%", + ) + log(f" ✓ Paperclip projects: {res}") res = await c.execute( - "UPDATE projects SET name = replace(name, $1, $2), updated_at = now() WHERE name LIKE $3", + "UPDATE plugin_state SET value_json = to_jsonb($2::text) " + "WHERE state_key = 'legal-case-number' AND value_json = to_jsonb($1::text)", + old, new, + ) + log(f" ✓ Paperclip plugin_state (legal-case-number): {res}") + res = await c.execute( + "UPDATE issues SET title = replace(title, $1, $2) WHERE title LIKE $3", old, new, f"%{old}%", ) - log(f" ✓ Paperclip projects: {res}") + log(f" ✓ Paperclip issue titles: {res}") finally: await c.close() @@ -419,6 +455,8 @@ def print_inspection(rec: dict) -> None: log(f" pclip: {name}") if not pc["projects"]: log(" pclip: (no matching project)") + log(f" pclip: linkage rows={pc.get('linkage_rows', 0)} " + f"issue titles={pc.get('issue_titles', 0)} (→ rewritten to {rec['new']})") else: log(f" pclip: unreachable ({pc.get('error','')})") log(" DB path columns to rewrite:")