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>
This commit is contained in:
89
scripts/migrate_gap51_outcomes.py
Normal file
89
scripts/migrate_gap51_outcomes.py
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/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))
|
||||
Reference in New Issue
Block a user