Files
legal-ai/skills/dafna-decision-template/references/line-classification.md
Chaim bfec8bdaa3
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 6s
Add dafna-decision-template skill — knowledge for template-based DOCX export
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>
2026-04-16 18:57:57 +00:00

116 lines
4.6 KiB
Markdown

# סיווג שורות — `_classify_line()`
כל שורה של content מה-MD עוברת דרך `_classify_line()` שמחזירה
`(kind, clean_text)`. הקטגוריה מכתיבה איזה סגנון Word יוחל.
## טבלת הקטגוריות
| kind | regex | clean_text | נמפה ל-style |
|------|--------|-----------|--------------|
| `label_heading` | `^\s*\*\*([^\n*]+?):\*\*\s*$` | `"$1"` | `Heading 2` |
| `label_heading` (plain) | `^\s*([^\n:]{2,40}):\s*$` | `"$1"` | `Heading 2` |
| `inline_label` | `^\s*\*\*([^\n*]+?):\*\*\s+(.+)$` | `"$1\x00$2"` | `Normal` + bold label + value |
| `numbered` | `^\s*(\d+)[.)]\s+(.+)$` | `"$2"` | `List Paragraph` |
| `bullet` | `^\s*[\-\u2022\*\u25CF\u25E6]\s+(.+)$` | `"$1"` | `Normal` (marker stripped) |
| `heb_letter` | `^\s*\([א-ת]\)\s+` | **full line** (marker kept) | `List Paragraph` + `_strip_numpr()` |
| `plain` | fallback | line | `Normal` |
## סדר הבדיקות (חשוב!)
```
1. STANDALONE_LABEL_RE (**X:**)
2. INLINE_LABEL_RE (**X:** value)
3. NUMBERED_LINE_RE (1. X)
4. BULLET_LINE_RE (- X) + re-check inside:
4a. STANDALONE inside → label_heading
4b. INLINE inside → inline_label
5. HEB_LETTER_LINE_RE ((א) X)
6. PLAIN_LABEL_RE (X:) — last because it's broad
7. plain
```
## דוגמאות מהשטח
### input: `- **נקודות פתוחות:**`
- `BULLET_LINE_RE` תופס → `inner = "**נקודות פתוחות:**"`
- `STANDALONE_LABEL_RE` על ה-inner → `label_heading`, text = `"נקודות פתוחות"`
- יוצא כ-Heading 2.
### input: `- **נקודות פתוחות:** האם המקדם...`
- `BULLET_LINE_RE` תופס → inner = `"**נקודות פתוחות:** האם..."`
- `INLINE_LABEL_RE` על ה-inner → `inline_label`
- יוצא כ-Normal עם label "נקודות פתוחות:" bold + value רגיל.
### input: `1. **שאלה עקרונית:** האם נספח...`
- `NUMBERED_LINE_RE` תופס → `"**שאלה עקרונית:** האם נספח..."`
- יוצא כ-List Paragraph. ה-`**...**` בתוכו יעובד על ידי
`_add_runs_with_inline_bold()` (bold inline run).
### input: `(א) נספח הבינוי של תכנית...`
- `HEB_LETTER_LINE_RE` תופס
- יוצא כ-List Paragraph עם `_strip_numpr()` — כי המחבר כבר כתב "(א)".
### input: `העורר טוען כי:`
- לא תואם regex ספציפי
- `PLAIN_LABEL_RE` תופס (23 תווים, מסתיים ב-`:`)
- יוצא כ-Heading 2.
### input: `פסקה ארוכה: עם עוד תוכן ופסיק, וסוגיה מורכבת.`
- `PLAIN_LABEL_RE` לא תופס (יותר מ-40 תווים לפני `:`)
- נשאר `plain` → Normal.
## למה הגבלת `PLAIN_LABEL_RE` ל-`{2,40}`
בלי הגבלה, כל פסקה עם `:` במקום כלשהו הייתה הופכת ל-Heading 2. דוגמה
שצריך למנוע:
```
טענה חשובה כאן: היא שהוועדה שגתה בכל אופן.
```
אין כאן כוונה לכותרת — `:` הוא חלק ממשפט. ההגבלה ל-40 תווים מסננת את
רוב המקרים האלה כי רוב headings אמיתיים הם קצרים.
40 תווים זה ניחוש — אפשר לכוון אם מגלים false positives/negatives.
## inline bold — `_add_runs_with_inline_bold()`
אחרי סיווג, הטקסט עדיין יכול להכיל `**word**` באמצע. הפונקציה מחלקת
את המחרוזת ל-runs מתחלפים:
```
"העורר טוען **שהתוצאה** שגויה"
→ [
Run("העורר טוען ", bold=None),
Run("שהתוצאה", bold=True),
Run(" שגויה", bold=None),
]
```
כל run מסומן RTL בנפרד. יוצא ב-Word עם הדגש המקומי בלבד על המילה
`שהתוצאה`.
## השלמת התמונה
```
md content
↓ splitlines()
for line in lines:
↓ _classify_line(line)
→ (kind, clean_text)
↓ _emit_content_line(doc, line)
→ paragraph with chosen style
↓ _add_runs_with_inline_bold(paragraph, clean_text)
→ runs with inline **bold** rendered
↓ _mark_run_rtl / _mark_paragraph_rtl
→ Hebrew renders in David (cs slot)
```
## הוספת קטגוריה חדשה
אם יש דפוס שלא מזוהה ורצוי למפות אותו:
1. הוסף regex constant (למעלה בקובץ, אחרי הקיימים).
2. הוסף branch ב-`_classify_line()` לפי הסדר הנכון (ספציפי לפני כללי).
3. הוסף branch ב-`_emit_content_line()` עם הסגנון המתאים.
4. הוסף test case ב-references/line-classification.md (כאן).
5. הרץ על תיק מייצג (למשל 8070-25) וראה שהתוצאה נכונה.