Clean up scripts/: archive 17, delete 5, add SCRIPTS.md registry
Active scripts (5): auto-sync-cases.sh, backup-db.sh, restore-db.sh, notify.py, bidi_table.py Archived (17): one-time migration/seeding scripts whose functionality is now in MCP server or web API. Moved to scripts/.archive/ Deleted (5): zero-value scripts (duplicates, hardcoded single-case, debug scripts) Added scripts/SCRIPTS.md — registry of all scripts with purpose, status, and what superseded them. CLAUDE.md updated with rule: any script change requires SCRIPTS.md update. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
257
scripts/.archive/validate-decision.py
Normal file
257
scripts/.archive/validate-decision.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a decision against block-schema rules.
|
||||
|
||||
Usage: python validate-decision.py <case_number>
|
||||
|
||||
Checks:
|
||||
1. Neutral background (block-vav) — no party quotes or value words
|
||||
2. Weight compliance — blocks within expected ranges
|
||||
3. Structural integrity — all required blocks present
|
||||
4. Claims coverage — every claim in block-zayin addressed in block-yod
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "mcp-server" / "src"))
|
||||
|
||||
from legal_mcp.services.db import get_pool, init_schema, close_pool
|
||||
|
||||
|
||||
# Value/judgment words that shouldn't appear in neutral background
|
||||
VALUE_WORDS = [
|
||||
"חריג", "חטא", "בעייתי", "מזעזע", "שערורייתי", "מגוחך",
|
||||
"נפשע", "פגום", "חמור", "מקומם", "בלתי סביר", "מופרז",
|
||||
"מגונה", "פסול", "נלוז", "מטריד",
|
||||
]
|
||||
|
||||
# Party quote indicators
|
||||
QUOTE_INDICATORS = [
|
||||
r"לטענת\s+(העוררי|המשיב|מבקשי)",
|
||||
r"לדברי\s+(העוררי|המשיב|מבקשי)",
|
||||
r"העורר\s+טוען",
|
||||
r"המשיבה\s+טוענת",
|
||||
r"לשיטת\s+(העוררי|המשיב)",
|
||||
]
|
||||
|
||||
# Expected weight ranges per block type (for רישוי appeals)
|
||||
WEIGHT_RANGES_LICENSING = {
|
||||
"block-he": (0.5, 5),
|
||||
"block-vav": (3, 40),
|
||||
"block-zayin": (13, 40),
|
||||
"block-chet": (0, 15),
|
||||
"block-tet": (0, 15),
|
||||
"block-yod": (30, 75),
|
||||
"block-yod-alef": (1, 10),
|
||||
"block-yod-bet": (0, 2),
|
||||
}
|
||||
|
||||
# Expected weight ranges for היטל השבחה
|
||||
WEIGHT_RANGES_LEVY = {
|
||||
"block-he": (0, 5),
|
||||
"block-vav": (2, 20),
|
||||
"block-zayin": (15, 40),
|
||||
"block-chet": (0, 25),
|
||||
"block-tet": (0, 15),
|
||||
"block-yod": (25, 75),
|
||||
"block-yod-alef": (1, 10),
|
||||
"block-yod-bet": (0, 3),
|
||||
}
|
||||
|
||||
|
||||
def check_neutral_background(content: str) -> list[str]:
|
||||
"""Check block-vav for neutrality violations."""
|
||||
issues = []
|
||||
if not content:
|
||||
return issues
|
||||
|
||||
lines = content.split("\n")
|
||||
for i, line in enumerate(lines):
|
||||
# Check value words
|
||||
for word in VALUE_WORDS:
|
||||
if word in line:
|
||||
issues.append(f"מילת שיפוט ברקע (שורה {i+1}): \"{word}\" — \"{line[:80]}...\"")
|
||||
|
||||
# Check party quotes
|
||||
for pattern in QUOTE_INDICATORS:
|
||||
if re.search(pattern, line):
|
||||
match = re.search(pattern, line).group()
|
||||
issues.append(f"ציטוט מצד ברקע (שורה {i+1}): \"{match}\" — \"{line[:80]}...\"")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_weight_compliance(blocks: list[dict], appeal_type: str) -> list[str]:
|
||||
"""Check block weights are within expected ranges."""
|
||||
issues = []
|
||||
ranges = WEIGHT_RANGES_LEVY if appeal_type == "levy" else WEIGHT_RANGES_LICENSING
|
||||
|
||||
total_words = sum(b["word_count"] for b in blocks)
|
||||
if total_words == 0:
|
||||
return ["אין תוכן בהחלטה"]
|
||||
|
||||
for block in blocks:
|
||||
bid = block["block_id"]
|
||||
if bid in ranges and block["word_count"] > 0:
|
||||
weight = block["word_count"] / total_words * 100
|
||||
low, high = ranges[bid]
|
||||
if weight < low:
|
||||
issues.append(f"בלוק {bid} ({block['title']}): משקל {weight:.1f}% — מתחת לטווח ({low}-{high}%)")
|
||||
elif weight > high:
|
||||
issues.append(f"בלוק {bid} ({block['title']}): משקל {weight:.1f}% — מעל לטווח ({low}-{high}%)")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_structural_integrity(blocks: list[dict]) -> list[str]:
|
||||
"""Check all required blocks are present."""
|
||||
issues = []
|
||||
required = ["block-he", "block-zayin", "block-yod"]
|
||||
block_ids = {b["block_id"] for b in blocks if b["word_count"] > 0}
|
||||
|
||||
for req in required:
|
||||
if req not in block_ids:
|
||||
issues.append(f"בלוק חובה חסר: {req}")
|
||||
|
||||
# Check discussion is the heaviest block
|
||||
yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)
|
||||
if yod:
|
||||
max_block = max((b for b in blocks if b["block_id"] not in ("block-alef", "block-bet", "block-gimel", "block-dalet")),
|
||||
key=lambda x: x["word_count"], default=None)
|
||||
if max_block and max_block["block_id"] != "block-yod":
|
||||
issues.append(f"בלוק הדיון (י) אינו הבלוק הגדול ביותר — {max_block['title']} ({max_block['word_count']} מילים) גדול יותר")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
def check_no_duplication(vav_content: str, yod_content: str) -> list[str]:
|
||||
"""Check block-yod doesn't repeat block-vav content."""
|
||||
issues = []
|
||||
if not vav_content or not yod_content:
|
||||
return issues
|
||||
|
||||
# Find sentences from background that appear verbatim in discussion
|
||||
vav_sentences = [s.strip() for s in re.split(r'[.!?]', vav_content) if len(s.strip()) > 30]
|
||||
for sent in vav_sentences:
|
||||
if sent in yod_content:
|
||||
issues.append(f"כפילות: משפט מהרקע חוזר בדיון — \"{sent[:60]}...\"")
|
||||
|
||||
return issues
|
||||
|
||||
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("שימוש: python validate-decision.py <מספר_תיק>")
|
||||
sys.exit(1)
|
||||
|
||||
case_number = sys.argv[1]
|
||||
await init_schema()
|
||||
pool = await get_pool()
|
||||
|
||||
async with pool.acquire() as conn:
|
||||
case = await conn.fetchrow(
|
||||
"SELECT * FROM cases WHERE case_number = $1", case_number
|
||||
)
|
||||
if not case:
|
||||
print(f"תיק {case_number} לא נמצא")
|
||||
sys.exit(1)
|
||||
|
||||
decision = await conn.fetchrow(
|
||||
"SELECT * FROM decisions WHERE case_id = $1",
|
||||
case["id"],
|
||||
)
|
||||
if not decision:
|
||||
print(f"אין החלטה לתיק {case_number}")
|
||||
sys.exit(1)
|
||||
|
||||
blocks = await conn.fetch(
|
||||
"""SELECT block_id, title, content, word_count, weight_percent
|
||||
FROM decision_blocks WHERE decision_id = $1
|
||||
ORDER BY block_index""",
|
||||
decision["id"],
|
||||
)
|
||||
blocks = [dict(b) for b in blocks]
|
||||
|
||||
claims_count = await conn.fetchval(
|
||||
"SELECT count(*) FROM claims WHERE case_id = $1", case["id"]
|
||||
)
|
||||
|
||||
await close_pool()
|
||||
|
||||
# Determine appeal type
|
||||
num = case_number.split("/")[0].split("+")[0].split("-")[0]
|
||||
if num.startswith("8"):
|
||||
appeal_type = "levy"
|
||||
appeal_type_heb = "היטל השבחה"
|
||||
elif num.startswith("9"):
|
||||
appeal_type = "compensation"
|
||||
appeal_type_heb = "פיצויים"
|
||||
else:
|
||||
appeal_type = "licensing"
|
||||
appeal_type_heb = "רישוי ובנייה"
|
||||
|
||||
print(f"{'='*60}")
|
||||
print(f"ולידציה: {case_number} — {case['title']}")
|
||||
print(f"סוג: {appeal_type_heb} | מילים: {decision['total_words']} | טענות: {claims_count}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
all_issues = []
|
||||
|
||||
# 1. Neutral background
|
||||
vav = next((b for b in blocks if b["block_id"] == "block-vav"), None)
|
||||
issues = check_neutral_background(vav["content"] if vav else "")
|
||||
if issues:
|
||||
print(f"\n❌ רקע ניטרלי — {len(issues)} בעיות:")
|
||||
for i in issues:
|
||||
print(f" • {i}")
|
||||
all_issues.extend(issues)
|
||||
else:
|
||||
print("\n✅ רקע ניטרלי — תקין")
|
||||
|
||||
# 2. Weight compliance
|
||||
issues = check_weight_compliance(blocks, appeal_type)
|
||||
if issues:
|
||||
print(f"\n⚠ משקלות — {len(issues)} חריגות:")
|
||||
for i in issues:
|
||||
print(f" • {i}")
|
||||
all_issues.extend(issues)
|
||||
else:
|
||||
print("\n✅ משקלות — בטווח")
|
||||
|
||||
# 3. Structural integrity
|
||||
issues = check_structural_integrity(blocks)
|
||||
if issues:
|
||||
print(f"\n❌ מבנה — {len(issues)} בעיות:")
|
||||
for i in issues:
|
||||
print(f" • {i}")
|
||||
all_issues.extend(issues)
|
||||
else:
|
||||
print("\n✅ מבנה — תקין")
|
||||
|
||||
# 4. No duplication
|
||||
yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)
|
||||
issues = check_no_duplication(
|
||||
vav["content"] if vav else "",
|
||||
yod["content"] if yod else "",
|
||||
)
|
||||
if issues:
|
||||
print(f"\n⚠ כפילויות — {len(issues)} נמצאו:")
|
||||
for i in issues:
|
||||
print(f" • {i}")
|
||||
all_issues.extend(issues)
|
||||
else:
|
||||
print("\n✅ ללא כפילויות — תקין")
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
if all_issues:
|
||||
print(f"סה\"כ: {len(all_issues)} בעיות נמצאו")
|
||||
else:
|
||||
print("✅ ההחלטה עומדת בכל הכללים")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user