"""MCP tools for the External Precedent Library. This is distinct from: - ``precedents`` (case_precedents table) — chair-attached quotes scoped to a specific case section. Use ``precedent_search_library`` for that. - ``style_corpus`` (Daphna's prior decisions) — searched via ``search_decisions`` for style/voice. The precedent library is the **authoritative law** corpus: external court rulings and other appeals committees' decisions, with halachot extracted and reviewed by the chair. All halachot enter as ``pending_review`` and are invisible to search until the chair approves them — per project review policy. """ from __future__ import annotations import json from uuid import UUID from legal_mcp.services import db, precedent_library def _ok(payload) -> str: return json.dumps(payload, ensure_ascii=False, indent=2, default=str) def _err(msg: str) -> str: return json.dumps({"error": msg}, ensure_ascii=False) async def precedent_library_upload( file_path: str, citation: str, case_name: str = "", court: str = "", decision_date: str = "", source_type: str = "", precedent_level: str = "", practice_area: str = "", appeal_subtype: str = "", subject_tags: list[str] | None = None, is_binding: bool = True, headnote: str = "", summary: str = "", ) -> str: """העלאת פסיקה חיצונית לקורפוס הסמכותי + חילוץ הלכות אוטומטי. Args: file_path: נתיב מלא לקובץ PDF/DOCX/RTF/TXT/MD. citation: מראה המקום ("עע\\"מ 3975/22 ב. קרן-נכסים נ' ועדה מקומית"). case_name: שם קצר. court: ערכאה (עליון / מנהלי / ועדת ערר ארצית / ועדת ערר מחוזית). decision_date: ISO date (YYYY-MM-DD), אופציונלי. source_type: court_ruling / appeals_committee. precedent_level: עליון / מנהלי / ועדת_ערר_ארצית / ועדת_ערר_מחוזית. practice_area: rishuy_uvniya / betterment_levy / compensation_197. subject_tags: תגיות נושא (חניה, קווי_בניין, וכד'). Returns: JSON עם case_law_id, מספר chunks, מספר הלכות שנכנסו לתור אישור. """ if not citation.strip(): return _err("citation חובה") try: result = await precedent_library.ingest_precedent( file_path=file_path, citation=citation, case_name=case_name, court=court, decision_date=decision_date or None, source_type=source_type, precedent_level=precedent_level, practice_area=practice_area, appeal_subtype=appeal_subtype, subject_tags=subject_tags or [], is_binding=is_binding, headnote=headnote, summary=summary, ) except Exception as e: return _err(str(e)) return _ok(result) async def precedent_library_list( practice_area: str = "", court: str = "", precedent_level: str = "", source_type: str = "", search: str = "", limit: int = 100, ) -> str: """רשימה של פסיקה בקורפוס הסמכותי, עם פילטרים.""" rows = await precedent_library.list_precedents( practice_area=practice_area, court=court, precedent_level=precedent_level, source_type=source_type, search=search, limit=limit, ) return _ok(rows) async def precedent_library_get(case_law_id: str) -> str: """פסיקה ספציפית עם כל ההלכות שלה (כולל ממתינות לאישור).""" try: cid = UUID(case_law_id) except ValueError: return _err("case_law_id לא תקין") record = await precedent_library.get_precedent(cid) if not record: return _err("פסיקה לא נמצאה") return _ok(record) async def precedent_library_delete(case_law_id: str) -> str: """מחיקת פסיקה מהקורפוס. cascade: chunks + halachot.""" try: cid = UUID(case_law_id) except ValueError: return _err("case_law_id לא תקין") ok = await precedent_library.delete_precedent(cid) return _ok({"deleted": ok, "case_law_id": case_law_id}) async def precedent_extract_halachot(case_law_id: str) -> str: """הרצה מחדש של חילוץ ההלכות לפסיקה קיימת. הלכות קודמות נמחקות.""" try: cid = UUID(case_law_id) except ValueError: return _err("case_law_id לא תקין") try: result = await precedent_library.reextract_halachot(cid) except Exception as e: return _err(str(e)) return _ok(result) async def precedent_extract_metadata(case_law_id: str) -> str: """חילוץ מטא-דאטה (case_name קצר, summary, headnote, key_quote, subject_tags, appeal_subtype) מהטקסט. ממלא רק שדות ריקים — לא דורס מה שכבר הוזן.""" try: cid = UUID(case_law_id) except ValueError: return _err("case_law_id לא תקין") try: result = await precedent_library.reextract_metadata(cid) except Exception as e: return _err(str(e)) return _ok(result) async def search_precedent_library( query: str, practice_area: str = "", court: str = "", precedent_level: str = "", appeal_subtype: str = "", is_binding: bool | None = None, subject_tag: str = "", limit: int = 10, include_halachot: bool = True, ) -> str: """חיפוש סמנטי בקורפוס הפסיקה הסמכותית. מחזיר תוצאות מעורבות: הלכות (rule-level, מאושרות בלבד) + קטעי טקסט (passage-level). הלכות מקבלות boost קל בדירוג כי הן מזוקקות מראש. Args: query: שאילתת חיפוש בעברית. practice_area: rishuy_uvniya / betterment_levy / compensation_197. court: סינון לפי ערכאה (substring). precedent_level: עליון / מנהלי / ועדת_ערר_ארצית / ועדת_ערר_מחוזית. appeal_subtype: סינון לתת-סוג. is_binding: True/False (None = ללא סינון). subject_tag: סינון לפי תגית נושא (לדוגמה "מועד_קביעת_שומה"). limit: מספר תוצאות מקסימלי. include_halachot: האם לכלול הלכות (ברירת מחדל: כן). Returns: רשימה מדורגת. כל פריט הוא {"type": "halacha"|"passage", "score", ...}. """ if not query or len(query.strip()) < 2: return json.dumps([], ensure_ascii=False) results = await precedent_library.search_library( query=query.strip(), practice_area=practice_area, court=court, precedent_level=precedent_level, appeal_subtype=appeal_subtype, is_binding=is_binding, subject_tag=subject_tag, limit=limit, include_halachot=include_halachot, ) return _ok(results) async def halacha_review( halacha_id: str, status: str, reviewer: str = "דפנה", rule_statement: str = "", reasoning_summary: str = "", subject_tags: list[str] | None = None, practice_areas: list[str] | None = None, ) -> str: """אישור / דחייה / עריכה של הלכה שחולצה אוטומטית. Args: halacha_id: מזהה ההלכה. status: pending_review / approved / rejected / published. reviewer: שם המאשר (ברירת מחדל: דפנה). rule_statement: עריכת ניסוח הכלל (ריק = ללא שינוי). reasoning_summary: עריכת תמצית ההיגיון (ריק = ללא שינוי). subject_tags: עריכת תגיות (None = ללא שינוי). practice_areas: עריכת תחומים (None = ללא שינוי). """ if status not in {"pending_review", "approved", "rejected", "published"}: return _err( "status לא חוקי. ערכים תקינים: " "pending_review / approved / rejected / published" ) try: hid = UUID(halacha_id) except ValueError: return _err("halacha_id לא תקין") row = await db.update_halacha( halacha_id=hid, review_status=status, reviewer=reviewer, rule_statement=rule_statement or None, reasoning_summary=reasoning_summary or None, subject_tags=subject_tags, practice_areas=practice_areas, ) if row is None: return _err("הלכה לא נמצאה") return _ok(row) async def halachot_pending(limit: int = 100) -> str: """תור ההלכות הממתינות לאישור (review_status='pending_review').""" rows = await db.list_halachot(review_status="pending_review", limit=limit) return _ok(rows)