#!/usr/bin/env python3 """T1 — אכלוס style_exemplars מקורפוס דפנה (style_corpus + internal_committee). מפצל כל החלטה של דפנה לסעיפים (chunker._split_into_sections), ומכל סעיף לפסקאות, מטמיע (Voyage) ושומר ב-style_exemplars עם section/outcome/practice_area — כדי שהכותב יוכל לאחזר פסקאות-בלוק אמיתיות של דפנה בזמן כתיבה (T2/T3). מקור-סגנון בלבד (INV-LRN5) — לא מהות. אידמפוטנטי: מנקה לכל decision לפני הכנסה. שימוש: python3 scripts/backfill_style_exemplars.py # dry-run (סופר בלבד) python3 scripts/backfill_style_exemplars.py --apply # מטמיע ושומר דורש POSTGRES_URL + מפתח Voyage בסביבה (כמו שאר ה-MCP). """ from __future__ import annotations import argparse import asyncio import logging from legal_mcp.services import db from legal_mcp.services import style_exemplars as sx logging.basicConfig(level=logging.INFO, format="%(message)s") log = logging.getLogger("backfill_exemplars") # Section mapping + paragraph splitting now live in the shared service # (legal_mcp.services.style_exemplars) so the backfill and the live # final-enrollment path use ONE extraction implementation (G2). async def _gather_sources() -> list[dict]: """All of Dafna's decisions: style_corpus + internal_committee (chair דפנה).""" pool = await db.get_pool() rows: list[dict] = [] async with pool.acquire() as conn: sc = await conn.fetch( "SELECT decision_number, full_text, outcome, practice_area " "FROM style_corpus WHERE full_text <> ''" ) for r in sc: rows.append({ "decision_number": r["decision_number"] or "", "source": "style_corpus", "full_text": r["full_text"], "outcome": r["outcome"] or "", "practice_area": r["practice_area"] or "", }) ic = await conn.fetch( "SELECT case_number, full_text, practice_area FROM case_law " "WHERE source_kind = 'internal_committee' AND coalesce(chair_name,'') LIKE '%דפנה%' " "AND coalesce(full_text,'') <> ''" ) for r in ic: rows.append({ "decision_number": r["case_number"] or "", "source": "internal_committee", "full_text": r["full_text"], "outcome": "", "practice_area": r["practice_area"] or "", }) return rows async def main(apply: bool) -> None: sources = await _gather_sources() log.info("מקורות: %d החלטות של דפנה (style_corpus + internal_committee)", len(sources)) total_paras = 0 for src in sources: n = len(sx.units_for(src["full_text"])) if not n: continue total_paras += n log.info(" %-14s %-16s → %d פסקאות", src["source"], src["decision_number"], n) if not apply: continue await sx.extract_and_store( decision_number=src["decision_number"], source=src["source"], full_text=src["full_text"], practice_area=src["practice_area"], outcome=src["outcome"], ) if apply: cov = await db.count_style_exemplars() log.info("הושלם. style_exemplars: %s", cov) else: log.info("dry-run: %d פסקאות יוטמעו. הרץ --apply לביצוע.", total_paras) if __name__ == "__main__": ap = argparse.ArgumentParser() ap.add_argument("--apply", action="store_true", help="embed + insert (default: dry-run)") args = ap.parse_args() asyncio.run(main(args.apply))