fix(block-zayin): הפרדת-משיבים בבלוק ז' (טענות הצדדים) — השלמת #224 לשכבת-הכתיבה
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s

בלוק ז' של טיוטת-הביניים מיזג את כל המשיבים לסעיף אחד גנרי (או השמיטם) —
כי (א) _build_claims_context קיבץ רק לפי party_role, ו-(ב) ה-prompt נעל
"מבנה קבוע 3 חלקים: עוררים / ועדה מקומית / מבקשי-היתר" — בלי "משיבים" בכלל.
לכן בתיק 1043-02-26 המשיבים 2-3 ו-4-6 לא הופיעו כצדדים מובחנים, אף שהנתונים
(129 טענות-משיבים מפוצלות לפי כתב-תשובה) תקינים ב-claims. הפרדת-המשיבים של
#224 יושמה בצבירה+פאנל בלבד (PR#392) — לא בכותב-הבלוקים.

- `_build_claims_context` מקבץ לפי (party_role, brief) כאשר brief=source_document
  ל-SPLIT_PARTIES (respondent/permit_applicant) — **אותו כלל בדיוק כמו הצבירה**
  (import SPLIT_PARTIES מ-argument_aggregator, G2). התוצאה: סעיף נפרד לכל
  כתב-תשובה ("טענות המשיבים — כתב תשובה משיבות 2 3", "— משיבים 4 6"...).
- prompt בלוק ז': מבנה-צדדים דינמי לפי הכותרות בקונטקסט; הוראה מפורשת לא למזג
  משיבים שונים ולשקף עמדות-מנוגדות; יעד-אורך גמיש לכמה כתבי-תשובה.

משפיע גם על בלוק י' (אותו _build_claims_context) — הקשר מופרד. py_compile ירוק.
עבודה על MCP → דורש host reload + הרצה-מחדש כדי לראות בפועל.
Invariants: G2 (נגזרת-הפרדה יחידה משותפת עם הצבירה), G1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:46:32 +00:00
parent 66cddd20f0
commit 1da1585ca8

View File

@@ -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)