"""Compare Google Vision extractions vs existing MDs, then benchmark voyage-law-2.""" import json import time from pathlib import Path import voyageai API_KEY = "pa-qbfhBDxW0tVtgzr_abMyw_AJO2gli9w3nnqyHuQOW-e" client = voyageai.Client(api_key=API_KEY) MODEL = "voyage-law-2" DOCS_DIR = Path("/home/chaim/legal-ai/data/cases/1130-25/documents") GOOGLE_DIR = DOCS_DIR / "extracted" # Map new (Google Vision) files to existing MDs PAIRS = [ ("מרק קובר-כתב ערר.md", "2025-08-14-כתב-ערר-קובר.md"), ("תשובה לערר מטעם המשיבים.md", "2025-09-01-כתב-תשובה-ליבמן-לערר.md"), ("תשובת הועדה המרחבית לערר.md", "2025-09-02-כתב-תשובה-ועדת-הראל-לערר.md"), ("תשובת המשיב-יצחק מטמון.md", "2025-10-22-כתב-ערר-מטמון.md"), ("השלמת טיעון מטעם משיבים 2-3.md", "2025-12-23-השלמת-טיעון-ליבמן.md"), ("תשובה מטעם העורר להשלמת טיעון.md", "2025-12-08-תגובת-קובר-לבקשת-השלמת-טיעון.md"), ("בקשה להשלמת טיעון ממשיבים 2-3.md", "2025-12-03-בקשה-להשלמת-טיעון-ליבמן.md"), ("השלמת טיעון מטעם הוועדה המקומית.md", "2026-02-04-השלמת-טיעון-ועדת-הראל.md"), ("תגובת העורר לתשובת ועדת הראל להשלמת הטיעון ערר.md", "2026-02-10-תגובת-קובר-להשלמת-טיעון-הראל.md"), ("כתב תשובה-השלמת טיעון מטעם המשיב יצחק מטמון.md", "2026-02-12-כתב-תשובה-השלמת-טיעון-מטמון.md"), ("בקשת העורר לדחיית השלמת הטיעון במלואה.md", "2026-01-13-תגובת-קובר-לדחיית-השלמת-טיעון.md"), ("1130-25-החלטה לתיקון פרוטוקול.md", "2025-11-27-החלטה-לתיקון-פרוטוקול.md"), ("החלטת ביניים 1130-25.md", "2025-12-31-החלטת-ביניים.md"), ("1130-25-פרוטוקול ועדת ערר והחלטה.md", "2025-10-27-פרוטוקול-דיון-ועדת-ערר.md"), ("פרוטוקול ועדה מקומית לדיון בתכנית 152-1257682.md", "2025-07-23-פרוטוקול-ועדה-מקומית-הראל.md"), ] QUERIES = [ "מהי הטענה המרכזית של העוררים בנוגע לחניה?", "מה עמדת הוועדה המקומית לגבי התכנית?", "האם יש פגיעה בזכויות הבנייה של השכנים?", "מהם התנאים שנקבעו בהיתר הבנייה?", "האם התכנית עומדת בתקן החניה?", "מה טענות המשיבים לגבי הגובה והצפיפות?", "האם נערך שימוע כדין לפני מתן ההחלטה?", "מהם הנימוקים לאישור התכנית על ידי הוועדה המקומית?", ] def cosine_sim(a, b): dot = sum(x * y for x, y in zip(a, b)) na = sum(x * x for x in a) ** 0.5 nb = sum(x * x for x in b) ** 0.5 return dot / (na * nb) if na and nb else 0.0 def chunk_text(text, chunk_size=600, overlap=100): words = text.split() chunks = [] i = 0 while i < len(words): chunks.append(" ".join(words[i:i + chunk_size])) i += chunk_size - overlap return chunks def word_overlap(a, b): wa, wb = set(a.split()), set(b.split()) if not wa or not wb: return 0.0 return len(wa & wb) / max(len(wa), len(wb)) def main(): # ── Part 1: Document comparison ── print("=" * 70) print("PART 1: DOCUMENT COMPARISON (Google Vision vs Existing)") print("=" * 70) comparison_results = [] all_new_chunks = [] all_old_chunks = [] for new_name, old_name in PAIRS: new_path = GOOGLE_DIR / new_name old_path = DOCS_DIR / old_name if not new_path.exists(): continue if not old_path.exists(): print(f" SKIP (no existing): {old_name}") continue new_text = new_path.read_text(encoding="utf-8") old_text = old_path.read_text(encoding="utf-8") new_words = len(new_text.split()) old_words = len(old_text.split()) overlap = word_overlap(new_text, old_text) short_name = old_name[:40] diff = new_words - old_words diff_pct = (diff / old_words * 100) if old_words else 0 comparison_results.append({ "name": short_name, "old_words": old_words, "new_words": new_words, "diff": diff, "diff_pct": diff_pct, "overlap": overlap, }) # Chunk for embedding new_chunks = chunk_text(new_text) old_chunks = chunk_text(old_text) for i, c in enumerate(new_chunks): all_new_chunks.append((short_name, i, c)) for i, c in enumerate(old_chunks): all_old_chunks.append((short_name, i, c)) print(f"\n{'Document':<42} {'Old':>6} {'New':>6} {'Diff':>8} {'Overlap':>8}") print("-" * 72) for r in comparison_results: print(f" {r['name']:<40} {r['old_words']:>6} {r['new_words']:>6} {r['diff']:>+7} ({r['diff_pct']:>+.0f}%) {r['overlap']:>7.0%}") # ── Part 2: Embedding benchmark ── print(f"\n{'=' * 70}") print("PART 2: VOYAGE-LAW-2 EMBEDDING BENCHMARK") print(f"{'=' * 70}") new_texts = [c[2] for c in all_new_chunks] old_texts = [c[2] for c in all_old_chunks] print(f"\nNew chunks: {len(new_texts)}, Old chunks: {len(old_texts)}") def embed_batched(texts, label): BATCH = 20 all_embs = [] total_tokens = 0 t0 = time.time() for i in range(0, len(texts), BATCH): batch = texts[i:i+BATCH] result = client.embed(batch, model=MODEL, input_type="document") all_embs.extend(result.embeddings) total_tokens += result.total_tokens elapsed = time.time() - t0 print(f" {label}: {len(texts)} chunks, {total_tokens:,} tokens, {elapsed:.1f}s") return all_embs, total_tokens, elapsed # Embed new print("Embedding NEW (Google Vision) chunks...") new_embs, new_tokens, new_time = embed_batched(new_texts, "NEW") # Embed old print("Embedding OLD (existing) chunks...") old_embs, old_tokens, old_time = embed_batched(old_texts, "OLD") # Embed queries print(f"Embedding {len(QUERIES)} queries...") q_result = client.embed(QUERIES, model=MODEL, input_type="query") q_embs = q_result.embeddings # Search and compare print(f"\n{'=' * 70}") print("PART 3: SEARCH QUALITY COMPARISON") print(f"{'=' * 70}") for qi, query in enumerate(QUERIES): # Score against new new_scores = [(cosine_sim(q_embs[qi], e), all_new_chunks[i][0], all_new_chunks[i][2][:60]) for i, e in enumerate(new_embs)] new_scores.sort(reverse=True) # Score against old old_scores = [(cosine_sim(q_embs[qi], e), all_old_chunks[i][0], all_old_chunks[i][2][:60]) for i, e in enumerate(old_embs)] old_scores.sort(reverse=True) print(f"\nQ{qi+1}: {query}") print(f" {'NEW top-1':>10}: [{new_scores[0][0]:.4f}] {new_scores[0][1]}") print(f" {'OLD top-1':>10}: [{old_scores[0][0]:.4f}] {old_scores[0][1]}") if new_scores[0][0] > old_scores[0][0]: print(f" >> NEW better by {new_scores[0][0] - old_scores[0][0]:.4f}") else: print(f" >> OLD better by {old_scores[0][0] - new_scores[0][0]:.4f}") # Summary new_avg = sum(max(cosine_sim(q_embs[qi], e) for e in new_embs) for qi in range(len(QUERIES))) / len(QUERIES) old_avg = sum(max(cosine_sim(q_embs[qi], e) for e in old_embs) for qi in range(len(QUERIES))) / len(QUERIES) print(f"\n{'=' * 70}") print("SUMMARY") print(f"{'=' * 70}") print(f" {'Metric':<30} {'Old (existing)':>15} {'New (Google Vision)':>20}") print(f" {'-' * 65}") print(f" {'Total chunks':<30} {len(old_texts):>15} {len(new_texts):>20}") print(f" {'Total tokens':<30} {old_tokens:>15,} {new_tokens:>20,}") print(f" {'Embed time':<30} {old_time:>14.1f}s {new_time:>19.1f}s") print(f" {'Avg top-1 score':<30} {old_avg:>15.4f} {new_avg:>20.4f}") print(f" {'Score difference':<30} {'':>15} {new_avg - old_avg:>+20.4f}") est_cost = (new_tokens + old_tokens) / 1_000_000 * 0.12 print(f"\n Embedding cost: ${est_cost:.3f}") if __name__ == "__main__": main()