Files
legal-ai/scripts/migrate_gap51_outcomes.py
Chaim 701efab726 feat(mcp): FU-14 GAP-51 — איחוד אוצר-המילים של תוצאת-תיק (set_outcome SSoT)
הכרעת-יו"ר: קנוני = 3 תוצאות אמיתיות (rejection/partial_acceptance/full_acceptance);
betterment_levy יוצא מהיותו "תוצאה" ועובר ל-override לפי practice_area.
+ עקרון "אנגלית-ב-DB, עברית-ב-UI": מפת-תוויות SSoT אחת.

lessons.py:
- VALID_OUTCOMES = 3 (הוסר betterment_levy).
- OUTCOME_LABELS_HE (SSoT לתצוגה) + LEGACY_OUTCOME_MAP + canonical_outcome().
- PRACTICE_AREA_OVERRIDES["betterment_levy"] מרכז את כל ה-guidance שהיה מפתוח כ-outcome
  (golden_ratios/opening/summary/discussion/template).
- get_lessons_for_outcome(outcome, practice_area) + format_ratios_comment(..., practice_area)
  מחילים override + מנרמלים legacy.

block_writer.py: STRUCTURE_GUIDANCE קנוני + תווית מ-OUTCOME_LABELS_HE + override betterment.
workflow.set_outcome: קנוני 3 + מיפוי-legacy סלחני; תווית מ-SSoT.
drafting.py: טבלת יחסי-זהב + get_decision_template מודעי-practice_area (override).
web-ui case.ts: הסרת betterment_levy מ-expectedOutcomes (הוא practice_area).
server.py: docstrings קנוניים.

מיגרציה: migrate_gap51_outcomes.py — 9 שורות נורמלו (rejected→rejection וכו'),
גיבוי ב-data/audit/. הקוד canonicalize בקריאה ⇒ backward-compatible גם בלי מיגרציה.

אומת: py_compile (5 קבצים) + בדיקות-יחידה offline (override/legacy/labels) + אימות-DB.
עודכנו X9 §3 + gap-audit (GAP-51 ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:34:49 +00:00

90 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""GAP-51 — נרמול ערכי outcome לאוצר-המילים הקנוני (FU-14).
ממפה את אוצר-המילים הישן של `set_outcome` לקנוני בשתי עמודות:
decisions.outcome ו- cases.expected_outcome
rejected → rejection
accepted → full_acceptance
partial → partial_acceptance
דטרמיניסטי וקטן (~9 שורות). `betterment_levy` אינו קיים בנתונים ואינו ממופה
(הוא practice_area, לא outcome). הקוד כבר canonicalize בקריאה, אז המיגרציה היא
לניקיון בלבד — לא חוסמת.
שימוש:
python3 scripts/migrate_gap51_outcomes.py # dry-run (ברירת מחדל)
python3 scripts/migrate_gap51_outcomes.py --apply # גיבוי ואז עדכון
דורש POSTGRES_URL / DATABASE_URL בסביבה. נוגע רק ב-cases/decisions — לא בטבלאות
החילוץ (case_law/halachot), כך שבטוח להריץ במקביל לחילוץ פעיל.
"""
from __future__ import annotations
import argparse
import asyncio
import csv
import os
from datetime import datetime, timezone
from pathlib import Path
import asyncpg
MAP = {"rejected": "rejection", "accepted": "full_acceptance", "partial": "partial_acceptance"}
TARGETS = [("decisions", "outcome"), ("cases", "expected_outcome")]
AUDIT_DIR = Path(__file__).resolve().parent.parent / "data" / "audit"
def _db_url() -> str:
url = os.environ.get("POSTGRES_URL") or os.environ.get("DATABASE_URL", "")
if not url:
raise SystemExit("POSTGRES_URL / DATABASE_URL not set")
return url
async def main(apply: bool) -> None:
conn = await asyncpg.connect(_db_url())
try:
affected = []
for table, col in TARGETS:
rows = await conn.fetch(
f"SELECT id, {col} AS val FROM {table} WHERE {col} = ANY($1::text[])",
list(MAP.keys()),
)
for r in rows:
affected.append((table, col, str(r["id"]), r["val"], MAP[r["val"]]))
print(f"GAP-51 outcome migration — {len(affected)} שורות מושפעות:")
for t, c, rid, old, new in affected:
print(f" {t}.{c} {rid} {old}{new}")
if not affected:
print("אין מה לנרמל — כל הערכים כבר קנוניים.")
return
if not apply:
print("\n(dry-run — להחלה הוסף --apply)")
return
ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
AUDIT_DIR.mkdir(parents=True, exist_ok=True)
backup = AUDIT_DIR / f"gap51-outcome-backup-{ts}.csv"
with backup.open("w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["table", "column", "id", "old_value", "new_value"])
w.writerows(affected)
print(f"\nגיבוי נכתב: {backup}")
async with conn.transaction():
for table, col in TARGETS:
for old, new in MAP.items():
await conn.execute(
f"UPDATE {table} SET {col} = $1 WHERE {col} = $2", new, old,
)
print(f"הוחלו {len(affected)} עדכונים בהצלחה.")
finally:
await conn.close()
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--apply", action="store_true", help="execute (default: dry-run)")
asyncio.run(main(ap.parse_args().apply))