diff --git a/mcp-server/src/legal_mcp/services/block_writer.py b/mcp-server/src/legal_mcp/services/block_writer.py index 028a097..4de5853 100644 --- a/mcp-server/src/legal_mcp/services/block_writer.py +++ b/mcp-server/src/legal_mcp/services/block_writer.py @@ -192,16 +192,16 @@ BLOCK_PROMPTS = { ## כללים קריטיים: - **סנתז טענות דומות** — אל תרשום כל טענה בנפרד. קבץ טענות דומות לנושא אחד. למשל: כל הטענות על הודעות → סעיף אחד, כל הטענות על רכוש משותף → סעיף אחד. - גוף שלישי: "העוררים טוענים כי...", "הוועדה המקומית ציינה כי..." -- **מבנה קבוע עם 3 חלקים:** - 1. "טענות העוררים" — 8-12 סעיפים מקובצים לפי נושא - 2. "עמדת הוועדה המקומית" — 5-8 סעיפים - 3. "עמדת מבקשי ההיתר" (אם יש) — 5-10 סעיפים +- **מבנה: חלק (סעיף עם כותרת-משנה) לכל צד שמופיע בטענות שחולצו למטה** — לפי הכותרות שם (### ...). אל תמציא צדדים שאינם מופיעים ואל תשמיט צד שמופיע. + - "טענות העוררים" — 8-12 סעיפים מקובצים לפי נושא. + - **צד-משיב/מתנגד: כתוב חלק נפרד לכל כתב-תשובה** שמופיע (למשל "עמדת משיבות 2-3" ו-"עמדת משיבים 4-6" בנפרד) — 5-10 סעיפים לכל אחד. **אל תמזג משיבים שונים לסעיף אחד**, ואם עמדותיהם מנוגדות — שקף זאת. + - "עמדת הוועדה המקומית" / "עמדת מבקשי ההיתר" (אם מופיעים) — 5-8 סעיפים. - כותרת: "תמצית טענות הצדדים" - נאמנות למקור — לא להמציא טענות, אבל כן לאחד ולסכם טענות חוזרות - אין ניתוח, אין מסקנות, אין הערכה ("טענה חלשה/חזקה") - רק מכתבי טענות מקוריים (לא השלמות טיעון) - מספור רציף -- **יעד אורך: 800-1500 מילים** +- **יעד אורך: 800-1500 מילים** (יותר כשיש כמה כתבי-תשובה נפרדים — כל צד מקבל את מלוא ההתייחסות) ## טענות שחולצו (קבץ טענות דומות לנושאים): {claims_context} @@ -629,18 +629,44 @@ async def _build_claims_context(case_id: UUID) -> str: # Fallback to all non-block-zayin claims if no source claims exist. source_claims = [c for c in claims if c.get("source_document", "") != "block-zayin"] or claims - lines = [] - current_role = "" + # Group by (party_role, brief) so a multi-litigant side (respondent / + # permit_applicant) whose claims come from distinct pleadings reads as + # SEPARATE positions — e.g. "כתב תשובה משיבות 2-3" vs "משיבים 4-6" — instead + # of one merged "טענות המשיבים" (#224). The brief label is the source + # pleading, and which sides split is SPLIT_PARTIES — the SAME rule the + # argument aggregator uses, so block ז and the arguments panel agree (G2). + from legal_mcp.services.argument_aggregator import SPLIT_PARTIES + role_heb = {"appellant": "טענות העוררים", "respondent": "טענות המשיבים", "committee": "עמדת הוועדה המקומית", "permit_applicant": "עמדת מבקשי ההיתר"} - claim_num = 0 + role_order = ["appellant", "committee", "respondent", "permit_applicant"] + + groups: dict[tuple[str, str], list[dict]] = {} for c in source_claims: - if c["party_role"] != current_role: - current_role = c["party_role"] - lines.append(f"\n### {role_heb.get(current_role, current_role)}") - claim_num += 1 - lines.append(f"טענה #{claim_num}: {c['claim_text'][:400]}") - lines.append(f"\n**סה\"כ {claim_num} טענות. ענה על כל טענה מהותית; טענות [bundle] — אגד; טענות [skip] — ציון קצר בלבד.**") + role = c.get("party_role", "") or "" + brief = (c.get("source_document", "") or "").strip() if role in SPLIT_PARTIES else "" + groups.setdefault((role, brief), []).append(c) + + def _sort_key(k: tuple[str, str]) -> tuple[int, str]: + role, brief = k + idx = role_order.index(role) if role in role_order else len(role_order) + return (idx, brief) + + lines: list[str] = [] + claim_num = 0 + for role, brief in sorted(groups.keys(), key=_sort_key): + header = role_heb.get(role, role or "טענות נוספות") + if brief: + header = f"{header} — {brief}" + lines.append(f"\n### {header}") + for c in groups[(role, brief)]: + claim_num += 1 + lines.append(f"טענה #{claim_num}: {c['claim_text'][:400]}") + lines.append( + f"\n**סה\"כ {claim_num} טענות. שמור על ההפרדה בין הצדדים לעיל — " + f"לכל צד/כתב-תשובה עמדה נפרדת (אל תמזג משיבים שונים). ענה על כל טענה " + f"מהותית; טענות [bundle] — אגד; טענות [skip] — ציון קצר בלבד.**" + ) return "\n".join(lines)