Merge pull request 'feat(learning): חיווט אוטונומי לכפתורי מסלול-הסופי (סקריפט-תזמור אחד לכל שלב)' (#161) from worktree-autonomous-final-pipeline into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 14s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 14s
This commit was merged in pull request #161.
This commit is contained in:
@@ -51,6 +51,8 @@
|
|||||||
| `goldset_independent_judge.py` | python | **INV-DM7 ולידציה** — שופט-תפקיד **עצמאי שני** ממודל אחר (DeepSeek API ישיר, OpenAI-compatible) ששובר את עיגון-ה-AI: מסווג rule_role **בעיוור** (בלי לראות תיוג-אדם או המלצת-claude) ומחשב מטריצת-הסכמה (deepseek↔אדם מול ai↔אדם) + ציר-גס (כלל-בר-הכללה מול application/obiter). **ממצא (2026-06-07):** ai↔אדם=100% (מעוגן), deepseek↔אדם=50% מדויק אך **92% גס** → תת-הסוג holding/interpretive/procedural עמום-מטבעו (לא לשער עליו); הציר-הגס אמין חוצה-מודלים. read-only על הזהב. `--model`/`--limit`/`--concurrency`. מפתח מ-`~/.hermes/profiles/deepseek/.env`. raw→`/tmp/goldset_judge_raw.json`. | ידני — ולידציית אמינות-תוויות |
|
| `goldset_independent_judge.py` | python | **INV-DM7 ולידציה** — שופט-תפקיד **עצמאי שני** ממודל אחר (DeepSeek API ישיר, OpenAI-compatible) ששובר את עיגון-ה-AI: מסווג rule_role **בעיוור** (בלי לראות תיוג-אדם או המלצת-claude) ומחשב מטריצת-הסכמה (deepseek↔אדם מול ai↔אדם) + ציר-גס (כלל-בר-הכללה מול application/obiter). **ממצא (2026-06-07):** ai↔אדם=100% (מעוגן), deepseek↔אדם=50% מדויק אך **92% גס** → תת-הסוג holding/interpretive/procedural עמום-מטבעו (לא לשער עליו); הציר-הגס אמין חוצה-מודלים. read-only על הזהב. `--model`/`--limit`/`--concurrency`. מפתח מ-`~/.hermes/profiles/deepseek/.env`. raw→`/tmp/goldset_judge_raw.json`. | ידני — ולידציית אמינות-תוויות |
|
||||||
| `halacha_panel_approve.py` | python | **פאנל-אישור הלכות (Trust-or-Escalate, dry-run).** 3 שופטים בלתי-תלויי-לינאז' (Opus/claude_session · DeepSeek · Gemini-2.5-flash) מצביעים על ה**ציר-הגס האמין** (92% חוצה-מודלים): נקיות→"הלכה לשמירה?"; nli_unsupported→"הציטוט תומך בכלל?" (שיפוט-מחדש); פגומות→re-extraction. רק ורדיקט מוסכם פועל אוטומטית, **פיצול מסלים ליו"ר** (INV-G10). `--apply` **מחווט** (clean: רוב 2/3; nli: פה-אחד-entailed מנקה flag) — הפיך, מגבה ל-`data/audit/` קודם. מפתחות: DeepSeek מ-`~/.hermes/...`, Gemini מ-`~/.env`. **חובה מקומי**. dry-run 2026-06-07: 197→103 אוטו (פה-אחד) / ~15 (רוב). | ידני / שלב-אימות-הלכות במסלול-הסופי |
|
| `halacha_panel_approve.py` | python | **פאנל-אישור הלכות (Trust-or-Escalate, dry-run).** 3 שופטים בלתי-תלויי-לינאז' (Opus/claude_session · DeepSeek · Gemini-2.5-flash) מצביעים על ה**ציר-הגס האמין** (92% חוצה-מודלים): נקיות→"הלכה לשמירה?"; nli_unsupported→"הציטוט תומך בכלל?" (שיפוט-מחדש); פגומות→re-extraction. רק ורדיקט מוסכם פועל אוטומטית, **פיצול מסלים ליו"ר** (INV-G10). `--apply` **מחווט** (clean: רוב 2/3; nli: פה-אחד-entailed מנקה flag) — הפיך, מגבה ל-`data/audit/` קודם. מפתחות: DeepSeek מ-`~/.hermes/...`, Gemini מ-`~/.env`. **חובה מקומי**. dry-run 2026-06-07: 197→103 אוטו (פה-אחד) / ~15 (רוב). | ידני / שלב-אימות-הלכות במסלול-הסופי |
|
||||||
| `style_lesson_panel.py` | python | **פאנל-סגנון דו-סוכני (למידה כפולה).** על-גבי דיסטילציית-ה-Opus (draft↔final ב-`draft_final_pairs.analysis`), שני שופטים בלתי-תלויים — DeepSeek + Gemini-2.5-flash — מצביעים לכל לקח על השאלה הגסה "האם זו הנחיית-סגנון מופשטת ובת-הכללה (INV-LRN5 — קול ולא מהות)?". הסכמה 2/2-keep → נכתב כ-`decision_lesson` (`source=panel:deepseek+gemini`); 2/2-drop → לא נכתב; פיצול/substance → מוסלם ליו"ר. `--apply` הפיך, מגבה ל-`data/audit/`. הטמעה ל-SKILL.md/lessons.md נשארת שער-יו"ר ידני (INV-G10). מפתחות כמו פאנל-ההלכות. **חובה מקומי**. `--case <num>` / `--pair-id <uuid>`. | שלב-למידה במסלול-הסופי |
|
| `style_lesson_panel.py` | python | **פאנל-סגנון דו-סוכני (למידה כפולה).** על-גבי דיסטילציית-ה-Opus (draft↔final ב-`draft_final_pairs.analysis`), שני שופטים בלתי-תלויים — DeepSeek + Gemini-2.5-flash — מצביעים לכל לקח על השאלה הגסה "האם זו הנחיית-סגנון מופשטת ובת-הכללה (INV-LRN5 — קול ולא מהות)?". הסכמה 2/2-keep → נכתב כ-`decision_lesson` (`source=panel:deepseek+gemini`); 2/2-drop → לא נכתב; פיצול/substance → מוסלם ליו"ר. `--apply` הפיך, מגבה ל-`data/audit/`. הטמעה ל-SKILL.md/lessons.md נשארת שער-יו"ר ידני (INV-G10). מפתחות כמו פאנל-ההלכות. **חובה מקומי**. `--case <num>` / `--pair-id <uuid>`. | שלב-למידה במסלול-הסופי |
|
||||||
|
| `final_learning_pipeline.py` | python | **תזמור שלב-הלמידה (פקודה אחת).** מופעל ע"י הרמס כשלוחצים "הרץ למידת-קול" במסלול-הסופי. דטרמיניסטי: (1) `ingest_final_version` עם נתיב-הסופי, (2) רישום לקורפוס-הסגנון (idempotent), (3) `style_lesson_panel --apply`. מקפל את הזרימה לפקודה אחת כדי שהסוכן לא ירכיב כמה קריאות (חסין). idempotent. **חובה מקומי**. `--case <num>`. | אוטו (כפתור run-learning) / ידני |
|
||||||
|
| `final_halacha_pipeline.py` | python | **תזמור שלב-אימות-ההלכות (פקודה אחת).** מופעל ע"י הרמס כשלוחצים "הרץ אימות-הלכות". דטרמיניסטי: (1) `extract_internal_citations(chair)`, (2) `corroboration.build_all()`, (3) `halacha_panel_approve --apply`. **חובה מקומי**. `--case <num>` / `--limit N` (תקרת תור). | אוטו (כפתור run-halacha) / ידני |
|
||||||
| `halacha_panel_audit.py` | python | **רשת-ביטחון לפאנל** (selective-prediction monitoring) — דוגם הלכות שאושרו ע"י הפאנל (`reviewer LIKE 'panel:%'`), מריץ עליהן **שוב** את הצבעת-ה-KEEP של 3 השופטים, ומציף כל מקרה שכעת נוטה DROP (false-keep פוטנציאלי). report-only כברירת-מחדל; `--flag` מחזיר את ה-flips ל-`pending_review` לסקירת-יו"ר. `--sample N`/`--seed`. בסיס 2026-06-07: 0/15. מיועד להרצה תקופתית (שבועי). מייבא שופטים מ-`halacha_panel_approve`. **חובה מקומי**. | תקופתי (שבועי) — ניטור |
|
| `halacha_panel_audit.py` | python | **רשת-ביטחון לפאנל** (selective-prediction monitoring) — דוגם הלכות שאושרו ע"י הפאנל (`reviewer LIKE 'panel:%'`), מריץ עליהן **שוב** את הצבעת-ה-KEEP של 3 השופטים, ומציף כל מקרה שכעת נוטה DROP (false-keep פוטנציאלי). report-only כברירת-מחדל; `--flag` מחזיר את ה-flips ל-`pending_review` לסקירת-יו"ר. `--sample N`/`--seed`. בסיס 2026-06-07: 0/15. מיועד להרצה תקופתית (שבועי). מייבא שופטים מ-`halacha_panel_approve`. **חובה מקומי**. | תקופתי (שבועי) — ניטור |
|
||||||
| `halacha_panel_calibrate.py` | python | **כיול מדיניות-ההצבעה של הפאנל** (Trust-or-Escalate, ICLR 2025). מריץ את שאלת-ה-KEEP של `halacha_panel_approve` על מדגם-הזהב ומודד מול `is_holding` (הציר-הגס) precision+coverage לכל מדיניות (unanimous/majority) + ספירת false-keep/false-drop. נותן את **אחוז-הטעות בפועל** לבחירת סף-סיכון α. מייבא שופטים מ-`halacha_panel_approve` (מקור-אמת יחיד). read-only, **חובה מקומי**. | ידני — לפני חיווט `--apply` |
|
| `halacha_panel_calibrate.py` | python | **כיול מדיניות-ההצבעה של הפאנל** (Trust-or-Escalate, ICLR 2025). מריץ את שאלת-ה-KEEP של `halacha_panel_approve` על מדגם-הזהב ומודד מול `is_holding` (הציר-הגס) precision+coverage לכל מדיניות (unanimous/majority) + ספירת false-keep/false-drop. נותן את **אחוז-הטעות בפועל** לבחירת סף-סיכון α. מייבא שופטים מ-`halacha_panel_approve` (מקור-אמת יחיד). read-only, **חובה מקומי**. | ידני — לפני חיווט `--apply` |
|
||||||
| `halacha_rule_role_backfill.py` | python | **INV-DM7** — backfill חד-פעמי: מסווג-מחדש את ההלכות הישנות (`rule_type IN ('binding','persuasive')` — ערכי-סמכות שנשמרו במסווה תפקיד לפני פיצול הצירים) לאחד מחמשת **תפקידי-הכלל** (holding/interpretive/procedural/application/obiter) דרך claude_session המקומי (אפס עלות). **לא נוגע בסמכות** (נגזרת מ-`precedent_level`). `--apply` (ברירת-מחדל dry-run) / `--limit N` / `--concurrency`. כותב backup CSV ל-`data/audit/` תחילה. fail-safe (פריט שנכשל → נשמר ערך ישן). **חובה מקומי** (claude_session). | ידני חד-פעמי אחרי deploy של פיצול-הסמכות |
|
| `halacha_rule_role_backfill.py` | python | **INV-DM7** — backfill חד-פעמי: מסווג-מחדש את ההלכות הישנות (`rule_type IN ('binding','persuasive')` — ערכי-סמכות שנשמרו במסווה תפקיד לפני פיצול הצירים) לאחד מחמשת **תפקידי-הכלל** (holding/interpretive/procedural/application/obiter) דרך claude_session המקומי (אפס עלות). **לא נוגע בסמכות** (נגזרת מ-`precedent_level`). `--apply` (ברירת-מחדל dry-run) / `--limit N` / `--concurrency`. כותב backup CSV ל-`data/audit/` תחילה. fail-safe (פריט שנכשל → נשמר ערך ישן). **חובה מקומי** (claude_session). | ידני חד-פעמי אחרי deploy של פיצול-הסמכות |
|
||||||
|
|||||||
87
scripts/final_halacha_pipeline.py
Normal file
87
scripts/final_halacha_pipeline.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""One-shot LOCAL pipeline for the 'run-halacha' button (halacha validation).
|
||||||
|
|
||||||
|
The /api/cases/{case}/final/run-halacha endpoint wakes the Hermes curator, which
|
||||||
|
runs THIS single deterministic command (the 3-judge panel uses local DeepSeek+Gemini
|
||||||
|
keys + the local claude CLI, so it can't run inside the container).
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
[1] extract_internal_citations(chair) → links the citation graph for the chair's
|
||||||
|
decisions (idempotent; ON CONFLICT DO NOTHING).
|
||||||
|
[2] corroboration_rebuild → builds the citation-treatment signal and applies the
|
||||||
|
corroborated→approved / overruled→pending policy (X11 Phase 2).
|
||||||
|
[3] halacha_panel_approve --apply → 3 judges (Opus+DeepSeek+Gemini); agreement
|
||||||
|
auto-approves/rejects (reversible, CSV-backed); splits/defects → chair (INV-G10).
|
||||||
|
|
||||||
|
NB: per-precedent halacha extraction for newly-cited precedents is NOT automated here
|
||||||
|
(it needs each cited precedent to be in the library with a known case_law_id) — the
|
||||||
|
chair drives that from /precedents when a missing precedent is added.
|
||||||
|
|
||||||
|
Local-only. Idempotent. The panel pass over the full pending queue can take minutes.
|
||||||
|
|
||||||
|
cd ~/legal-ai/mcp-server
|
||||||
|
.venv/bin/python ../scripts/final_halacha_pipeline.py --case 8126-03-25
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from argparse import Namespace
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
|
||||||
|
from legal_mcp.services import corroboration, db # noqa: E402
|
||||||
|
from legal_mcp.tools.citations import extract_internal_citations # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
async def main(args: argparse.Namespace) -> int:
|
||||||
|
case_number = args.case
|
||||||
|
case = await db.get_case_by_number(case_number)
|
||||||
|
if not case:
|
||||||
|
print(f"✗ תיק {case_number} לא נמצא")
|
||||||
|
return 1
|
||||||
|
chair = case.get("chair_name") or "דפנה תמיר"
|
||||||
|
|
||||||
|
# [1] citation graph
|
||||||
|
print(f"[1/3] extract_internal_citations (chair={chair})…", flush=True)
|
||||||
|
raw = await extract_internal_citations(chair_name=chair, limit=0)
|
||||||
|
try:
|
||||||
|
d = json.loads(raw).get("data", {})
|
||||||
|
print(f" ✓ extracted {d.get('extracted')} · linked {d.get('linked')} "
|
||||||
|
f"· new {d.get('new')}")
|
||||||
|
except Exception:
|
||||||
|
print(f" (citations returned: {str(raw)[:160]})")
|
||||||
|
|
||||||
|
# [2] corroboration signal + policy (whole corpus backfill) — skipped on dry-run
|
||||||
|
if args.dry_run:
|
||||||
|
print("[2/3] corroboration_rebuild — מדולג (dry-run)")
|
||||||
|
else:
|
||||||
|
print("[2/3] corroboration_rebuild (backfill)…", flush=True)
|
||||||
|
try:
|
||||||
|
cr = await corroboration.build_all()
|
||||||
|
print(f" ✓ {cr}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠ corroboration failed (non-fatal): {e}")
|
||||||
|
|
||||||
|
# [3] three-judge halacha panel
|
||||||
|
apply = not args.dry_run
|
||||||
|
print(f"[3/3] halacha_panel_approve {'--apply' if apply else '(dry-run)'} "
|
||||||
|
f"(Opus+DeepSeek+Gemini)…", flush=True)
|
||||||
|
import halacha_panel_approve as hpa
|
||||||
|
rc = await hpa.main(Namespace(limit=args.limit, concurrency=6, apply=apply))
|
||||||
|
print("\n✓ pipeline-אימות-הלכות הושלם" + (" (dry-run)" if args.dry_run else ""))
|
||||||
|
return rc or 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--case", required=True, help="case_number, e.g. 8126-03-25")
|
||||||
|
ap.add_argument("--limit", type=int, default=0,
|
||||||
|
help="cap pending halachot judged (0 = full queue)")
|
||||||
|
ap.add_argument("--dry-run", dest="dry_run", action="store_true",
|
||||||
|
help="citations only; skip corroboration writes; panel in dry-run")
|
||||||
|
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
||||||
140
scripts/final_learning_pipeline.py
Normal file
140
scripts/final_learning_pipeline.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""One-shot LOCAL pipeline for the 'run-learning' button (voice learning).
|
||||||
|
|
||||||
|
The container can't run the LLM steps (claude/DeepSeek/Gemini keys are local), so
|
||||||
|
the /api/cases/{case}/final/run-learning endpoint wakes the Hermes curator, which
|
||||||
|
runs THIS single deterministic command. Collapsing the flow into one script (rather
|
||||||
|
than asking the agent to assemble several tool calls) makes the autonomous path
|
||||||
|
reliable.
|
||||||
|
|
||||||
|
Steps:
|
||||||
|
[1] ingest_final_version(case, file_path) → Opus distils draft↔final into
|
||||||
|
draft_final_pairs.analysis (status→analyzed). INV-LRN5 separates style↔substance.
|
||||||
|
[2] enroll the final into style_corpus (idempotent) so lessons have a corpus_id.
|
||||||
|
[3] style_lesson_panel --apply → DeepSeek+Gemini vote per style lesson; 2/2-keep →
|
||||||
|
decision_lesson (source=panel:deepseek+gemini); split → chair (INV-G10).
|
||||||
|
|
||||||
|
The fold into SKILL.md / legal-decision-lessons.md stays a manual chair gate.
|
||||||
|
Local-only. Idempotent — safe to re-run.
|
||||||
|
|
||||||
|
cd ~/legal-ai/mcp-server
|
||||||
|
.venv/bin/python ../scripts/final_learning_pipeline.py --case 8126-03-25
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from argparse import Namespace
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# scripts/ is not a package — make style_lesson_panel importable.
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
|
||||||
|
from legal_mcp import config # noqa: E402
|
||||||
|
from legal_mcp.services import db # noqa: E402
|
||||||
|
from legal_mcp.tools.documents import document_upload_training # noqa: E402
|
||||||
|
from legal_mcp.tools.workflow import ingest_final_version # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_final_path(case_number: str) -> str | None:
|
||||||
|
"""The canonical final saved by /final/upload, with a graceful fallback."""
|
||||||
|
export_dir = config.find_case_dir(case_number) / "exports"
|
||||||
|
canonical = export_dir / f"סופי-{case_number}.docx"
|
||||||
|
if canonical.exists():
|
||||||
|
return str(canonical)
|
||||||
|
cands = sorted(export_dir.glob("סופי-*.docx"))
|
||||||
|
return str(cands[0]) if cands else None
|
||||||
|
|
||||||
|
|
||||||
|
async def _has_style_corpus(decision_number: str) -> bool:
|
||||||
|
pool = await db.get_pool()
|
||||||
|
async with pool.acquire() as conn:
|
||||||
|
row = await conn.fetchrow(
|
||||||
|
"SELECT 1 FROM style_corpus WHERE decision_number = $1 LIMIT 1",
|
||||||
|
decision_number,
|
||||||
|
)
|
||||||
|
return bool(row)
|
||||||
|
|
||||||
|
|
||||||
|
async def _latest_pair_status(case_id) -> str | None:
|
||||||
|
pool = await db.get_pool()
|
||||||
|
async with pool.acquire() as conn:
|
||||||
|
return await conn.fetchval(
|
||||||
|
"SELECT status FROM draft_final_pairs WHERE case_id = $1 "
|
||||||
|
"ORDER BY created_at DESC LIMIT 1",
|
||||||
|
case_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def main(args: argparse.Namespace) -> int:
|
||||||
|
case_number = args.case
|
||||||
|
case = await db.get_case_by_number(case_number)
|
||||||
|
if not case:
|
||||||
|
print(f"✗ תיק {case_number} לא נמצא")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
final_path = _resolve_final_path(case_number)
|
||||||
|
if not final_path:
|
||||||
|
print(f"✗ לא נמצא קובץ סופי ל-{case_number} (העלה דרך 'העלאת החלטה סופית של היו\"ר')")
|
||||||
|
return 1
|
||||||
|
print(f"final: {final_path}\n")
|
||||||
|
|
||||||
|
# [1] distillation (Opus) — skip if already analyzed (idempotent; --force to redo)
|
||||||
|
status = await _latest_pair_status(case["id"])
|
||||||
|
if status == "analyzed" and not args.force:
|
||||||
|
print(f"[1/3] ingest_final_version — דולג (הזוג כבר analyzed; --force לחידוש)")
|
||||||
|
else:
|
||||||
|
print("[1/3] ingest_final_version — דיסטילציית טיוטה↔סופי…", flush=True)
|
||||||
|
raw = await ingest_final_version(case_number, file_path=final_path)
|
||||||
|
try:
|
||||||
|
env = json.loads(raw)
|
||||||
|
if env.get("status") == "error":
|
||||||
|
print(f" ✗ {env.get('message')}")
|
||||||
|
return 1
|
||||||
|
d = env.get("data", {})
|
||||||
|
ds = d.get("diff_stats", {})
|
||||||
|
print(f" ✓ change {ds.get('change_percent')}% · lessons {d.get('lessons_count')} "
|
||||||
|
f"· new_expr {d.get('new_expressions')}")
|
||||||
|
except Exception:
|
||||||
|
print(f" (ingest returned: {raw[:200]})")
|
||||||
|
|
||||||
|
# [2] enroll into style_corpus (idempotent) — lessons need a corpus_id
|
||||||
|
print("[2/3] רישום לקורפוס-הסגנון (idempotent)…", flush=True)
|
||||||
|
if await _has_style_corpus(case_number):
|
||||||
|
print(" ✓ כבר רשום בקורפוס-הסגנון")
|
||||||
|
else:
|
||||||
|
r = await document_upload_training(
|
||||||
|
final_path,
|
||||||
|
decision_number=case_number,
|
||||||
|
title=f"החלטה סופית — {case.get('proceeding_type', '')} {case_number}".strip(),
|
||||||
|
practice_area=case.get("practice_area") or "appeals_committee",
|
||||||
|
appeal_subtype=case.get("appeal_subtype") or "",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
print(f" ✓ corpus_id {json.loads(r).get('data', {}).get('corpus_id')}")
|
||||||
|
except Exception:
|
||||||
|
print(f" (training upload returned: {r[:160]})")
|
||||||
|
|
||||||
|
# [3] two-judge style panel (DeepSeek + Gemini)
|
||||||
|
apply = not args.dry_run
|
||||||
|
print(f"[3/3] פאנל-סגנון דו-סוכני (DeepSeek+Gemini) {'--apply' if apply else '(dry-run)'}…",
|
||||||
|
flush=True)
|
||||||
|
import style_lesson_panel as slp
|
||||||
|
rc = await slp.main(Namespace(
|
||||||
|
case=case_number, pair_id=None, apply=apply, limit=0, concurrency=4,
|
||||||
|
))
|
||||||
|
print("\n✓ pipeline-למידה הושלם" + (" (dry-run)" if args.dry_run else ""))
|
||||||
|
return rc or 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument("--case", required=True, help="case_number, e.g. 8126-03-25")
|
||||||
|
ap.add_argument("--dry-run", dest="dry_run", action="store_true",
|
||||||
|
help="run the chain but the style panel in dry-run (no decision_lesson writes)")
|
||||||
|
ap.add_argument("--force", action="store_true",
|
||||||
|
help="re-run ingest_final_version even if the pair is already analyzed")
|
||||||
|
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
||||||
@@ -335,5 +335,6 @@ if __name__ == "__main__":
|
|||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
ap.add_argument("--limit", type=int, default=0)
|
ap.add_argument("--limit", type=int, default=0)
|
||||||
ap.add_argument("--concurrency", type=int, default=6)
|
ap.add_argument("--concurrency", type=int, default=6)
|
||||||
ap.add_argument("--apply", action="store_true", help="(not yet wired — dry-run only)")
|
ap.add_argument("--apply", action="store_true",
|
||||||
|
help="write the agreed verdicts (reversible, CSV-backed); default dry-run")
|
||||||
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
raise SystemExit(asyncio.run(main(ap.parse_args())))
|
||||||
|
|||||||
@@ -1056,27 +1056,25 @@ def _curator_task_brief(task: str, case_number: str, final_filename: str) -> tup
|
|||||||
task='learning' — draft↔final voice distillation + the 2-judge style panel.
|
task='learning' — draft↔final voice distillation + the 2-judge style panel.
|
||||||
task='halacha' — extract the halachot CITED in the final + corroboration + the
|
task='halacha' — extract the halachot CITED in the final + corroboration + the
|
||||||
3-judge halacha panel.
|
3-judge halacha panel.
|
||||||
The curator (Hermes) has Bash + MCP tools, so it both calls MCP tools and runs the
|
The curator (Hermes) runs ONE deterministic local pipeline script per task — the
|
||||||
local panel scripts. Panels write only reversible, CSV-backed proposals (INV-G10).
|
script chains the MCP-tool calls + panels internally, so the agent only has to run a
|
||||||
|
single command (reliable). Panels write only reversible, CSV-backed proposals (INV-G10).
|
||||||
"""
|
"""
|
||||||
if task == "halacha":
|
if task == "halacha":
|
||||||
title = f"[ערר {case_number}] אימות-הלכות — פאנל 3-סוכנים"
|
title = f"[ערר {case_number}] אימות-הלכות — פאנל 3-סוכנים"
|
||||||
description = (
|
description = (
|
||||||
f"אימות ההלכות שצוטטו בהחלטה הסופית של תיק {case_number} "
|
f"אימות ההלכות סביב ההחלטה הסופית של תיק {case_number} "
|
||||||
f"(`{final_filename}`).\n\n"
|
f"(`{final_filename}`).\n\n"
|
||||||
f"**שלב 1 — ציטוטים:** הרץ "
|
f"**הרץ פקודה אחת:**\n"
|
||||||
f"`mcp__legal-ai__extract_internal_citations(chair_name=\"דפנה תמיר\")` "
|
f"```\ncd /home/chaim/legal-ai/mcp-server && "
|
||||||
f"כדי למפות לאילו תקדימים ההחלטה מפנה.\n"
|
f".venv/bin/python ../scripts/final_halacha_pipeline.py --case {case_number}\n```\n"
|
||||||
f"**שלב 2 — חילוץ הלכות:** לכל תקדים מצוטט שקיים בספרייה הרץ "
|
f"הסקריפט מבצע דטרמיניסטית: (1) `extract_internal_citations` (גרף-ציטוטים), "
|
||||||
f"`mcp__legal-ai__precedent_extract_halachot(case_law_id=...)` (idempotent). "
|
f"(2) `corroboration_rebuild` (אות-תיקוף + מדיניות), (3) פאנל-הלכות תלת-סוכני "
|
||||||
f"תקדים מצוטט שחסר — `mcp__legal-ai__missing_precedent_create`.\n"
|
f"(Opus+DeepSeek+Gemini) `--apply` — הסכמה→approved/rejected (הפיך, מגובה ל-CSV), "
|
||||||
f"**שלב 3 — corroboration:** `mcp__legal-ai__corroboration_rebuild` לבניית "
|
f"פיצול→pending_review ליו\"ר. סמכות binding/persuasive נגזרת מ-precedent_level "
|
||||||
f"אות-התיקוף (treatment) ומדיניות.\n"
|
f"(INV-DM7), לא נקבעת בפאנל.\n"
|
||||||
f"**שלב 4 — פאנל-הלכות (אוטו-אישור + אסקלציה):** הרץ "
|
f"**לסיום:** כתוב comment בעברית עם סיכום הפלט (אושרו/נדחו/הוסלמו), סגור issue (done). "
|
||||||
f"`cd ~/legal-ai/mcp-server && .venv/bin/python ../scripts/halacha_panel_approve.py --apply`. "
|
f"תקדים מצוטט שחסר בספרייה — פתח `missing_precedent_create` והפנה ליו\"ר."
|
||||||
f"הסכמה 2/3+ → approved/rejected (הפיך, מגובה ל-CSV); פיצול → נשאר pending_review "
|
|
||||||
f"ליו\"ר. **אל תקבע סמכות binding/persuasive — היא נגזרת מ-precedent_level (INV-DM7).**\n"
|
|
||||||
f"**שלב 5:** כתוב comment בעברית עם סיכום (כמה אושרו/נדחו/הוסלמו), סגור issue (done)."
|
|
||||||
)
|
)
|
||||||
return title, description
|
return title, description
|
||||||
|
|
||||||
@@ -1085,21 +1083,18 @@ def _curator_task_brief(task: str, case_number: str, final_filename: str) -> tup
|
|||||||
description = (
|
description = (
|
||||||
f"דפנה סימנה את ההחלטה הסופית של תיק {case_number} כסופית.\n"
|
f"דפנה סימנה את ההחלטה הסופית של תיק {case_number} כסופית.\n"
|
||||||
f"קובץ סופי: `{final_filename}`\n\n"
|
f"קובץ סופי: `{final_filename}`\n\n"
|
||||||
f"**שלב 1 — דיסטילציה (חובה, draft↔final):** הרץ "
|
f"**הרץ פקודה אחת:**\n"
|
||||||
f"`mcp__legal-ai__ingest_final_version(case_number=\"{case_number}\")`. "
|
f"```\ncd /home/chaim/legal-ai/mcp-server && "
|
||||||
f"הוא משווה את הטיוטה (snapshot מפנקס-ההתאמה) לסופי, מסווג כל שינוי "
|
f".venv/bin/python ../scripts/final_learning_pipeline.py --case {case_number}\n```\n"
|
||||||
f"style_method מול substance (INV-LRN5), ושומר את ההצעה ב-draft_final_pairs "
|
f"הסקריפט מבצע דטרמיניסטית: (1) `ingest_final_version` — דיסטילציית טיוטה↔סופי "
|
||||||
f"(status→analyzed). **אל תקבע לקח לבד — זו הצעה לאישור.**\n"
|
f"(Opus), מסווג style_method מול substance (INV-LRN5), שומר ב-draft_final_pairs "
|
||||||
f"**שלב 2 — פאנל-סגנון דו-סוכני (DeepSeek+Gemini, אוטו-אישור + אסקלציה):** הרץ "
|
f"(status→analyzed); (2) רישום לקורפוס-הסגנון (idempotent); (3) פאנל-סגנון דו-סוכני "
|
||||||
f"`cd ~/legal-ai/mcp-server && .venv/bin/python ../scripts/style_lesson_panel.py "
|
f"(DeepSeek+Gemini) `--apply` — הסכמה 2/2→decision_lesson "
|
||||||
f"--case {case_number} --apply`. הסכמה 2/2 → נכתב כ-decision_lesson "
|
f"(source=panel:deepseek+gemini), פיצול→ליו\"ר. substance מדולג.\n"
|
||||||
f"(source=panel:deepseek+gemini); פיצול → מוסלם ליו\"ר. רק לקחי style_method "
|
f"**לסיום:** מתוך לקחי-הסגנון שאושרו, בחר 3-5 דפוסים שלא תועדו ב-skills/decision/"
|
||||||
f"נשקלים (substance מדולג, INV-LRN5).\n"
|
f"SKILL.md / docs/legal-decision-lessons.md / daphna-voice-fingerprint.md, כתוב "
|
||||||
f"**שלב 3 — הצעה:** מתוך לקחי-הסגנון שאושרו בפאנל, בחר 3-5 דפוסים שלא תועדו "
|
f"comment בעברית ניטרלי וממוספר, עדכן MEMORY.md, וסגור issue (done). הטמעה "
|
||||||
f"ב-skills/decision/SKILL.md / docs/legal-decision-lessons.md / "
|
f"ל-SKILL.md/lessons.md נשארת אישור-יו\"ר ידני (INV-G10)."
|
||||||
f"daphna-voice-fingerprint.md (אל תציע מה שכבר שם). כתוב comment בעברית, ניטרלי, ממוספר.\n"
|
|
||||||
f"**שלב 4:** עדכן MEMORY.md, סגור issue (status=done). הטמעה ל-SKILL.md/lessons.md "
|
|
||||||
f"נשארת אישור-יו\"ר ידני (INV-G10)."
|
|
||||||
)
|
)
|
||||||
return title, description
|
return title, description
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user