All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 6s
Documents the rules and decisions behind building DOCX files from דפנה's
decision template (טיוטת החלטה.dotx). The implementation lives in
mcp-server/src/legal_mcp/services/analysis_docx_exporter.py; this skill
captures the "why" so future improvements don't need to rediscover it.
Contents:
SKILL.md 5 critical rules, style mapping table,
export flow, line classification,
dash policy, placeholder handling,
troubleshooting, future TODOs
references/dotx-to-docx.md why python-docx can't open .dotx +
the conversion recipe
references/rtl-runs.md why <w:rtl/> is required on every run
(otherwise Hebrew falls back to
Times New Roman)
references/style-mapping.md XML dump of every template style,
with the Title-via-theme gotcha
references/line-classification.md the 7 regex categories in
_classify_line() with real examples
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
293 lines
12 KiB
Markdown
293 lines
12 KiB
Markdown
---
|
||
name: dafna-decision-template
|
||
description: >
|
||
ייצוא מסמכי DOCX עבור ועדת ערר לתכנון ובניה מחוז ירושלים (יו"ר עו"ד דפנה תמיר),
|
||
באמצעות שימוש בסגנונות המוגדרים בטמפלט Word של דפנה (`טיוטת החלטה.dotx`).
|
||
הסקיל מחיל את סגנונות הטמפלט (Normal/Heading 1/Heading 2/Quote/List Paragraph)
|
||
על תוכן שנכתב מתוכנתית — בלי להגדיר פונט/גודל/RTL/שוליים ידנית.
|
||
|
||
טריגרים: "ייצוא החלטה", "ייצוא ניתוח משפטי", "DOCX של דפנה",
|
||
"טמפלט החלטה", "סגנונות החלטה", "Word עם David", "מסמך ועדת ערר",
|
||
"הורדת החלטה", "פסקה ממוספרת בעברית", "(א) (ב) (ג)",
|
||
כל בקשה להוציא מסמך Word המבוסס על טמפלט דפנה.
|
||
---
|
||
|
||
# Dafna Decision Template — ייצוא DOCX מסגנונות טמפלט
|
||
|
||
## מה זה עושה
|
||
|
||
סקיל זה הוא **layer דק מעל `python-docx`** שמטעין את הטמפלט של דפנה
|
||
([skills/docx/decision_template.docx](../docx/decision_template.docx) —
|
||
מומר מ-`data/training/טיוטת החלטה.dotx`) וכותב תוכן חדש על בסיסו, **על ידי
|
||
שיוך שמות סגנונות בלבד** (`paragraph.style = "Heading 2"`). העיצוב —
|
||
פונט David, RTL, גדלים, הזחות, מספור אוטומטי — מגיע מה-`styles.xml`
|
||
של הטמפלט.
|
||
|
||
השירות המעשי נמצא ב-
|
||
[`mcp-server/src/legal_mcp/services/analysis_docx_exporter.py`](../../mcp-server/src/legal_mcp/services/analysis_docx_exporter.py).
|
||
הסקיל מתעד את **הכללים** שה-service מיישם, כך שניתן לשחזר/להרחיב אותם
|
||
בסקריפטים אחרים ללא צורך לגלות הכל מחדש.
|
||
|
||
---
|
||
|
||
## 🔴 קריטי — 5 עקרונות שחייבים לזכור
|
||
|
||
### 1. **לא להגדיר font/size/indent ידנית** — תמיד style
|
||
```python
|
||
# ❌ אל
|
||
run.font.name = "David"; run.font.size = Pt(13)
|
||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||
|
||
# ✅ כן
|
||
paragraph = doc.add_paragraph(style="Normal") # כל השאר מגיע מהטמפלט
|
||
```
|
||
|
||
### 2. **RTL חייב דגלים ב-paragraph וב-run — גם אם הסגנון כולל bidi**
|
||
```python
|
||
# ❌ אל — עברית תצא ב-Times New Roman כי Word לא יודע שזה complex-script
|
||
p = doc.add_paragraph(style="Normal")
|
||
p.add_run("טקסט בעברית")
|
||
|
||
# ✅ כן — מסמנים את ה-run כ-RTL
|
||
p = doc.add_paragraph(style="Normal")
|
||
_mark_paragraph_rtl(p)
|
||
run = p.add_run("טקסט בעברית")
|
||
_mark_run_rtl(run) # ← מוסיף <w:rtl/> ל-rPr
|
||
```
|
||
בלי הדגל הזה, Word נופל בחזרה ל-`ascii` font (Times New Roman) במקום
|
||
ל-`cs` font (David). זו הסיבה הנפוצה ביותר לתוצאה "עברית ב-Times New Roman".
|
||
|
||
ראה [references/rtl-runs.md](references/rtl-runs.md) לפרטים מלאים.
|
||
|
||
### 3. **`.dotx` לא נטען ישירות** — יש להמיר ל-`.docx` פעם אחת
|
||
python-docx פותח רק `.docx`. להמרה: `scripts/convert_decision_template.py`
|
||
(בשורש הפרויקט). יש להסיר:
|
||
- `word/glossary/*` parts
|
||
- Override entries שמצביעים אליהם ב-`[Content_Types].xml`
|
||
- Relationship `glossaryDocument` ב-`word/_rels/document.xml.rels`
|
||
- להחליף content-type מ-`...template.main+xml` ל-`...document.main+xml`
|
||
|
||
ראה [references/dotx-to-docx.md](references/dotx-to-docx.md).
|
||
|
||
### 4. **Title לא טוב ככותרת עברית** — השתמש ב-Heading 1
|
||
הסגנון `Title` בטמפלט מפנה ל-theme fonts (`majorFont`). ב-theme1.xml:
|
||
`majorFont.latin = "Aptos Display"`, `majorFont.cs = ""` (ריק).
|
||
לכן עברית תרונדר ב-Latin fallback.
|
||
|
||
`Heading 1` יורש cs="David" מ-`Normal` — השתמש בו לכותרת ראשית.
|
||
|
||
### 5. **מספור אוטומטי רק ב-`List Paragraph`** — decimal בלבד
|
||
`List Paragraph` (styleId `a0`) מקושר ל-`numId=1 → numFmt=decimal`.
|
||
כלומר Word יוסיף אוטומטית "1.", "2.", "3." לכל פסקה עם הסגנון הזה.
|
||
|
||
- שורות שמתחילות ב-`N.` → הסר את המספר מהטקסט, החל `List Paragraph`.
|
||
- שורות שמתחילות ב-`(א)` `(ב)` → השתמש ב-`List Paragraph` **עם הסרת `<w:numPr>`**,
|
||
כי המספור בעברית נכתב בעצמו על ידי המחבר.
|
||
- Bullets (`- `, `• `) → הסר את הסימן, השאר `Normal`.
|
||
|
||
ראה [references/style-mapping.md](references/style-mapping.md).
|
||
|
||
---
|
||
|
||
## הטמפלט — מיפוי סגנונות
|
||
|
||
מיפוי מלא של הסגנונות בטמפלט ל-content type. זה ה"חוזה" של הסקיל —
|
||
כל שינוי דרך השירות צריך להיצמד אליו.
|
||
|
||
| תוכן | style name | הערה |
|
||
|------|-----------|------|
|
||
| כותרת מסמך ("ניתוח משפטי וכתיבת עמדה בערר X") | `Heading 1` | יורש cs="David" |
|
||
| כותרת מקטע ראשי (רקע דיוני, פסיקה כללית, סוגיות להכרעה, מסקנות) | `Heading 2` | bold + underline |
|
||
| כותרת משנה בתוך סוגיה (טענה (claim), תשובה, ניתוח, נקודות פתוחות, …) | `Heading 2` | |
|
||
| שם subsection (טענת סף 1, סוגיה 2) | `Normal` + bold run | |
|
||
| פסקת רקע רגילה | `Normal` | David 13pt, justify, RTL |
|
||
| פסקת רשימה ממוספרת (1., 2., 3.) | `List Paragraph` | Word ימספר |
|
||
| פסקת רשימה בעברית ((א), (ב)) | `List Paragraph` + `_strip_numpr()` | המספור נשאר בטקסט |
|
||
| ציטוט פסיקה | `Quote` | bold + הזחה |
|
||
| citation / מקור | `Normal` + italic | |
|
||
|
||
ראה [references/style-mapping.md](references/style-mapping.md) לטבלה
|
||
מורחבת עם ה-XML של כל סגנון.
|
||
|
||
---
|
||
|
||
## תוכן המקור — `analysis-and-research.md`
|
||
|
||
הקובץ נכתב על ידי `legal-analyst` agent. מבנה מצופה:
|
||
|
||
```markdown
|
||
# ניתוח משפטי וכתיבת עמדה — ערר {case_number}
|
||
תאריך: DD.MM.YYYY
|
||
|
||
## רקע דיוני
|
||
{prose content}
|
||
|
||
## עובדות מוסכמות
|
||
1. ...
|
||
|
||
## עובדות שנויות במחלוקת
|
||
1. ...
|
||
|
||
## טענות סף
|
||
### טענה {n}: {title}
|
||
**עמדת המבקשת:** ...
|
||
**עמדת ועדת הערר:** [ימולא ע"י יו"ר הוועדה]
|
||
|
||
## סוגיות להכרעה
|
||
### סוגיה {n}: {title}
|
||
**ממצאים עובדתיים:**
|
||
...
|
||
**טענה (claim):**
|
||
העורר טוען כי:
|
||
(א) ...
|
||
(ב) ...
|
||
**ניתוח:**
|
||
...
|
||
- **נקודות פתוחות:**
|
||
1. ...
|
||
- **הערכה ראשונית:** {prose}
|
||
**עמדת ועדת הערר:** [ימולא ע"י יו"ר הוועדה]
|
||
|
||
## מסקנות
|
||
...
|
||
```
|
||
|
||
ה-parser (`research_md.py`) חולק את זה ל-`threshold_claims[]`, `issues[]`,
|
||
prose sections, ו-`conclusions`. חשוב: **שמות sections חייבים להכיל את
|
||
המילים המופתח** (`רקע דיוני`, `טענות סף`, …) — ה-parser עובד לפי matching של
|
||
keywords, לא לפי מספר ה-H2.
|
||
|
||
---
|
||
|
||
## זרימת הייצוא
|
||
|
||
סדר המקטעים ב-DOCX (גם אם סדר ה-H2 ב-MD שונה):
|
||
|
||
1. **כותרת** (Heading 1) + שורת תאריך
|
||
2. **רקע** (Heading 2) — `represented_party`, `procedural_background`,
|
||
`agreed_facts`, `disputed_facts` (אם קיימים)
|
||
3. **פסיקה כללית** (Heading 2) — `case_precedents` עם `section_id=NULL`
|
||
4. **טענות סף** (Heading 2) — לכל subsection: title, fields, chair_position, precedents
|
||
5. **סוגיות להכרעה** (Heading 2) — אותה חלוקה
|
||
6. **מסקנות** (Heading 2) — בסוף
|
||
|
||
הסיבה: בקריאה משפטית נכון להציג תחילה רקע ועובדות, ואז את הדיון. פסיקה
|
||
כללית מופיעה לפני הסוגיות כי היא רוחבית.
|
||
|
||
---
|
||
|
||
## עיבוד שורות — `_classify_line()`
|
||
|
||
ה-service מפרש כל שורה של content לאחת מ-6 קטגוריות:
|
||
|
||
| kind | דוגמה | מה קורה |
|
||
|------|--------|---------|
|
||
| `label_heading` | `**נקודות פתוחות:**` (שורה שלמה, כולל `- **X:**`) | Heading 2 |
|
||
| `label_heading` (plain) | `העורר טוען כי:` (שורה קצרה שמסתיימת ב-`:`) | Heading 2 |
|
||
| `inline_label` | `**שאלה עקרונית:** מה...` | Normal עם label bold inline |
|
||
| `numbered` | `1. הנספח אינו מחייב` | List Paragraph, המספר מוסר |
|
||
| `bullet` | `- nevo (קלאסי)...` | Normal, הסימן מוסר |
|
||
| `heb_letter` | `(א) הנספח אינו...` | List Paragraph + strip numPr |
|
||
| `plain` | רגיל | Normal |
|
||
|
||
בנוסף, **inline `**...**`** מעובד בכל ריצה דרך `_add_runs_with_inline_bold`
|
||
— כל `**word**` הופך ל-run נפרד עם `bold=True`.
|
||
|
||
ראה [references/line-classification.md](references/line-classification.md).
|
||
|
||
---
|
||
|
||
## מקפים — מדיניות
|
||
|
||
המשתמש (דפנה) ביקשה: **"לא רוצה מקפים בכלל"**. הסקיל מסיר:
|
||
|
||
- `—` (em-dash, U+2014)
|
||
- `–` (en-dash, U+2013)
|
||
|
||
מכל טקסט שהקוד כותב למסמך (גם תוכן מהמקור). מקפים רגילים (`-`)
|
||
נשמרים. הפונקציה: `_no_dash()`.
|
||
|
||
---
|
||
|
||
## שדות ריקים — placeholder
|
||
|
||
שדה `chair_position` (עמדת ועדת הערר) שמכיל אחד מהסימנים הריקים
|
||
(`[ימולא ע"י יו"ר הוועדה]`, `[טרם מולא]`, וכד') → מוחלף ב-
|
||
`[טרם מולאה עמדת ועדת הערר]` בסגנון italic. זה סימן ויזואלי ברור
|
||
שנשאר עדיין להשלים.
|
||
|
||
---
|
||
|
||
## שימוש — API
|
||
|
||
```python
|
||
from legal_mcp.services.analysis_docx_exporter import build_analysis_docx
|
||
|
||
path = await build_analysis_docx("8070-25")
|
||
# → data/cases/8070-25/exports/ניתוח-משפטי-v{N}.docx
|
||
```
|
||
|
||
Endpoint ציבורי: `GET /api/cases/{case_number}/research/analysis/export-docx`
|
||
|
||
---
|
||
|
||
## התמודדות עם בעיות נפוצות
|
||
|
||
### "עברית יוצאת ב-Times New Roman"
|
||
- חסר `<w:rtl/>` ב-run. הוסף `_mark_run_rtl(run)` אחרי כל `add_run`.
|
||
- בדוק: הסגנון שאתה משתמש בו יש בו `cs="David"` (או יורש מ-Normal שיש לו)?
|
||
|
||
### "המספור כפול: '1. (א) ...'"
|
||
- אתה משתמש ב-`List Paragraph` על שורה עם `(א)`. צריך `_strip_numpr(para)`.
|
||
|
||
### "כוכביות `**...**` מופיעות במסמך"
|
||
- הקפד להעביר תוכן דרך `_add_runs_with_inline_bold()`, לא `paragraph.add_run()`.
|
||
|
||
### "התבנית לא נטענת"
|
||
- הרץ מחדש `python scripts/convert_decision_template.py`. בדוק שה-docx
|
||
שנוצר פותחיb ב-Word ללא שגיאות.
|
||
|
||
### "המספור ברשימה השנייה ממשיך מהראשונה (4,5,6 במקום 1,2,3)"
|
||
- ידוע. הפתרון: להוסיף override של `numId` ברמת ה-paragraph הראשון של
|
||
הרשימה החדשה. עדיין לא מיושם — ראה "שיפורים עתידיים".
|
||
|
||
---
|
||
|
||
## שיפורים עתידיים (TODO)
|
||
|
||
- [ ] Reset של מספור List Paragraph בין רשימות נפרדות (לא רציף).
|
||
- [ ] תמיכה ב-`פיסקת רשימה - ללא מספור` (styleId `-`) לbullets.
|
||
- [ ] עיצוב מותאם לסוג הערר (1xxx/8xxx/9xxx) — כרגע אחיד.
|
||
- [ ] אפשרות להוריד רק מקטעים נבחרים (רק טענות סף, רק מסקנות, וכו').
|
||
|
||
---
|
||
|
||
## מבנה קבצים
|
||
|
||
```
|
||
skills/dafna-decision-template/
|
||
├── SKILL.md ← הקובץ הזה
|
||
└── references/
|
||
├── dotx-to-docx.md ← איך ממירים .dotx ל-.docx
|
||
├── rtl-runs.md ← למה `<w:rtl/>` חשוב בכל run
|
||
├── style-mapping.md ← מיפוי מלא של סגנונות הטמפלט
|
||
└── line-classification.md ← לוגיקת _classify_line()
|
||
|
||
mcp-server/src/legal_mcp/services/
|
||
└── analysis_docx_exporter.py ← המימוש המעשי
|
||
|
||
scripts/
|
||
└── convert_decision_template.py ← המרת dotx → docx (חד-פעמי)
|
||
|
||
skills/docx/
|
||
└── decision_template.docx ← הטמפלט המומר (artifact)
|
||
```
|
||
|
||
---
|
||
|
||
## היסטוריה
|
||
|
||
| גרסה | תאריך | שינוי |
|
||
|-------|-------|-------|
|
||
| v1.0 | 2026-04-16 | יצירת הסקיל. מיפוי ראשוני של סגנונות, תמיכה ב-RTL runs, inline bold, (א)(ב), Heading 1/2, מקטעי רקע. |
|