name: dicta-book-pipeline description: Use when the user asks to process a Dicta book — identify headings, validate hierarchy, OCR-compare against the original PDF, or run final QA before pushing to otzaria-library. Triggers: "תעבד ספר דיקטה", "בדיקה סופית", "השוואה מול PDF", "זיהוי כותרות". The skill orchestrates: HebrewBooks DB lookup → PDF download → page OCR → diff → header proposal → QA report.
Dicta Book Processing Pipeline
You are running a multi-stage pipeline that bridges Step 5.3 of dicta-automation-plan.md. The deterministic prefix (5.1, 5.2) is already implemented as CLIs. Your job is the AI-judgment passes: header identification, OCR comparison, and final QA.
שפת התגובה (חובה)
כל התגובות למשתמש בעברית בלבד. המשתמש אינו קורא אנגלית. אל תכתוב משפטים/הסברים באנגלית מול המשתמש (פלט גולמי של פקודות/קוד/שמות שדות מותר). זה חל על כל שלבי הפייפליין.
מוסכמות הפלט (חובה)
הקובץ נפתח בעורך .claude/skills/dicta-book-pipeline/scripts/עריכת ספר באוצריא.html, שעובד שורה‑אחר‑שורה. הקובץ תקין רק אם הוא עומד בכל הכללים הבאים. חלק מהכללים נאכפים ע"י validate-otzaria (ראה Stage 6), וחלקם דורשים בדיקת AI/סקירה עצמאית כי הם תלויים בהבנת תוכן. לעולם אל תניח שה‑CLI כיסה כלל שאינו מופיע בפלט שלו.
גבול חשוב: כותרות מול גוף הספר
הפייפליין נועד בעיקר לעבודת כותרות וקיטוע. מבחינת הספרייה, כותרות (<h1>–<h6>) הן שכבת ניווט מחוץ לגוף הספר, ולכן מותר להציע ולבצע בהן נרמול משמעותי: פתיחת קיצורים, הוספת מסכת, שינוי רמת כותרת, הוספת שם פרק קנוני, ותיקון ניסוח כותרת לצורה המקובלת באוצריא.
לעומת זאת, גוף הספר עצמו הוא טקסט מקור. אסור להוסיף בו תוכן, לשנות ניסוח, לפתוח קיצורים, לתקן לשון, או להחליף מילים על סמך השערה. בגוף הספר מותר לבצע רק פעולות מבניות/סימוניות הנדרשות לעריכה:
- קיטוע שורות: איחוד שברים או פיצול כמה הערות לשורות נפרדות, בלי לשנות את המילים.
- סימון דיבור־המתחיל ב‑
<b>...</b>, בלי לשנות את המילים. - העברת סמן מבני מתוך הגוף לכותרת כאשר ברור שהוא כותרת שהוחמצה, למשל
דף פה ע"א. - תיקון OCR/טקסט בגוף הספר רק אחרי השוואה מפורשת למקור ואישור אדם, כמפורט ב‑Stage 5.
- כותרת לבד בשורה — אסור טקסט אחרי
</hN>באותה שורה. - בלי
<big>בכותרות —<h2><big>מסכת קדושין</big></h2>⇒<h2>מסכת קדושין</h2>. שורות<big>דקורטיביות בודדות בראש קטע (<big>חדושי</big>) — להסיר; קולופון<big>תם ונשלם...</big>בסוף — להשאיר. - קובץ אחד לכל מסכת — פצל קובץ רב‑מסכתי,
<h1>נפרד לכל מסכת בתבנית<שם ספר> על <מסכת>. בלי גרשיים ("/”/’) ב‑<h1>. - כותרת
דףמנוקדת, בלי גרש — עמוד א =., עמוד ב =:(דף ט'⇒דף ט.,דף ח':⇒דף ח:).- כל פתיחת דף חדש = כותרת
<h>נפרדת, חובה. אסור שפתיחת דף תופיע כטקסט מודגש בתוך הגוף. סמן דף שהמחבר משבץ בתוך הקטע —<b>דפ"ה</b>,<b>דף פ"ה</b>,דף פה ע"א,ד' פהוכד' — הוא פתיחת דף, ויש להמיר אותו לכותרת בשורה נפרדת (<b>דפ"ה</b> ע"א בגמרא...⇒<h3>דף פה.</h3>בשורה אחת ואזבגמרא...בשורה הבאה). העמוד נקבע מהסמן (ע"א⇒.,ע"ב⇒:); אם אין סמן עמוד — ברירת מחדל עמוד א (.), אך הצלב מול ה‑DB/הקשר (Stage 6 ι).
- כל פתיחת דף חדש = כותרת
- קטע = שורה אחת, המסתיימת ב‑
:— כל הערה/דיבור הוא שורת body אחת שלמה. הגולמי של דיקטה לרוב שובר זאת בשתי צורות, ושתיהן מתוקנות לאותו יעד:- מילים בודדות בשורות נפרדות (
<b>אמר</b>/<b>המעתיק</b>...) ⇒ אַחֵד לשורה אחת. - כמה הערות דחוסות בשורה אחת, מופרדות ב‑
:⇒ פַּצֵּל לשורה לכל הערה. קטע מסתיים ב‑:(סוף עניין); קטע נושא־דיבור מסתיים ב‑.בתוך ההדגשה ואז:בסוף.
- מילים בודדות בשורות נפרדות (
- דיבור־המתחיל מודגש — תחילת כל קטע: עטוף את הלמה כולה (לא רק מילה אחת) ב‑
<b>. בפירושי תלמוד הלמה היא הציטוט מהגמרא, ומסתיימת בנקודה בתוך התג:<b>ורבא אמר משום שאין עדים מצויים לקיימו.</b> פירוש.... אַחֵד<b>עוקבים של למה/שם אחד (<b>אמר</b> <b>המעתיק</b>⇒<b>אמר המעתיק</b>); אל תאחד למות/מקורות נפרדים (<b>תוס'</b> <b>ד"ה ולא</b>נשארים שניים). - רמות יחסיות — דף = רמה אחת מתחת לפרק; פרק = רמה אחת מתחת למסכת/חלק. אם פרק=
<h2>אז דף=<h3>; אם פרק=<h3>אז דף=<h4>. כשהדף הוא יחידת המבנה העליונה (אין כותרת פרק/מסכת מעליו) — דף=<h2>. בלי מספר רמה גלובלי קבוע. - שם כותרת מנורמל — תוכן הכותרת עצמה נכתב בצורה הקנונית, לא כפי שהופיע בדפוס:
- הסר שם מחבר מכותרת — הוא כבר ב‑
<h1>(חדושי פרקא קמא דכתובות מהרי"ט⇒חדושי פרקא קמא דכתובות). - פתח קיצורים —
פ' האומר⇒פרק האומר. - הוסף
מסכתלכותרת מסכת כשחסר —<h2>ברכות</h2>⇒<h2>מסכת ברכות</h2>. - כותרת פרק בפורמט
<שם הפרק> - פרק <מספר>— שם הפרק המקובל בש"ס תחילה, מקף, ואז "פרק" + מספר. מספר חשוף ⇒ הוסף את שם הפרק הקנוני (פרק ח'בכתובות ⇒האשה שנפלו - פרק שמיני). אורדינל 1–10 במילים (שביעי/שמיני/עשירי), 11+ בגימטריה (יא/יב/יג). זו עבודת שיקול‑דעת ב‑Stage 4 (דורשת ידע בשמות פרקי הש"ס), לא נורמול דטרמיניסטי.
- הסר שם מחבר מכותרת — הוא כבר ב‑
- נרמול גרשיים —
''(גרש כפול) ⇒", גרשיים טיפוגרפיים”/“⇒". (נאכף ע"יclean-text.)
Inputs you need from the user
Ask for whatever is missing — but only ask once per conversation, and do not ask for things already saved in memory or already provided.
| Input | How to obtain |
|---|---|
| הנתיב לקובץ ה‑TXT של דיקטה (לא ערוך או חצי-ערוך) | שאל את המשתמש או קבל בהפעלה |
| נתיב ל‑DB של קטלוג HebrewBooks | ברירת מחדל: ~/Downloads/otzaria_latest/otzaria/otzar-HB_catalog.db. אפשר להעביר גם ב‑--db, או לשמור כ‑HB_CATALOG_DB ב‑.claude/skills/dicta-book-pipeline/.env.local |
נתיב ל‑seforim.db של אוצריא |
ברירת מחדל: ~/Downloads/otzaria_latest/otzaria/seforim.db. נדרש רק לבדיקות צולבות מול טקסט הש"ס ב‑Stage 6 |
| URL + API key של שרת ה‑OCR | env vars OCRWIN_URL, OCRWIN_API_KEY. אם חסרים — שאל וצור .claude/skills/dicta-book-pipeline/.env.local (gitignored) |
| שם משתמש + סיסמה ל‑otzaria.org | רק אם המשתמש מבקש להוריד ספר מהאתר (Stage 0). שאל את המשתמש בכל פעם. אסור לשמור/לכתוב לקובץ/לזכור — ראה Stage 0 |
זיכרון/קונפיגורציה מקומית ל‑DB
אם נתיב ברירת המחדל ל‑DB לא קיים והמשתמש מסר נתיב חלופי, שמור את הנתיב בזיכרון המקומי המתמיד של הסוכן אם זמין. אם אין זיכרון מתמיד זמין, כתוב אותו ל‑.claude/skills/dicta-book-pipeline/.env.local בשדות:
HB_CATALOG_DB=/path/to/otzar-HB_catalog.db
SEFORIM_DB=/path/to/seforim.db
הקובץ הזה gitignored ואינו מכיל סודות. בכל הרצה עתידית קרא קודם env vars, אחר כך .env.local, ורק בסוף את ברירות המחדל. העבר את הנתיב בפועל לכל סקריפט/שאילתת SQLite שצריכים אותו (search_hb.py --db ..., חיפוש ב‑seforim.db, וכד').
Stage 0 — הורדת ספר מאתר otzaria.org (אופציונלי)
הרץ שלב זה רק כשהמשתמש מבקש "תוריד ספר מהאתר" / מצביע על https://otzaria.org/library/admin/uploads. אם המשתמש כבר נתן נתיב לקובץ TXT — דלג ישר ל‑Stage 3.5/1.
אבטחה — חובה: עמוד ה‑uploads מוגן בהתחברות (next‑auth). בקש מהמשתמש שם משתמש + סיסמה בכל הפעלה (למשל ב‑
AskUserQuestion). לעולם אל תכתוב את הפרטים לקובץ, ל‑.env, ל‑memory, או לתוך ה‑SKILL הזה. השתמש בהם רק בתוך הבקשה הרצה. ה‑session cookie אפשר לשמור בקובץ זמני (/tmp/...jar.txt).
האתר הוא אפליקציית Next.js עם next‑auth, provider מסוג credentials, basePath /api/auth בשורש הדומיין. שדות ההתחברות: identifier (שם המשתמש) + password.
א. התחברות (פעם אחת)
JAR=/tmp/otz_jar.txt; rm -f "$JAR"
CSRF=$(curl -s -c "$JAR" "https://otzaria.org/api/auth/csrf" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['csrfToken'])")
curl -s -b "$JAR" -c "$JAR" \
--data-urlencode "csrfToken=$CSRF" \
--data-urlencode "identifier=<USERNAME>" \
--data-urlencode "password=<PASSWORD>" \
--data-urlencode "callbackUrl=https://otzaria.org/library/admin/uploads" \
--data-urlencode "json=true" \
"https://otzaria.org/api/auth/callback/credentials"
# אימות: צריך להחזיר user עם role:"admin"
curl -s -b "$JAR" "https://otzaria.org/api/auth/session"
אם ה‑session ריק ({}) — ההתחברות נכשלה (סיסמה שגויה / אין הרשאת admin). דווח למשתמש ועצור.
ב. רשימת ההעלאות + סינון
curl -s -b "$JAR" "https://otzaria.org/api/admin/uploads/list" -o /tmp/otz_list.json
מחזיר {"success":true,"uploads":[{id, bookName, originalFileName, uploadedBy, uploadedByEmail, uploadedAt, uploadType, status, bookStatus, editCopy, isOcr}, ...]}.
- מתויג דיקטה =
uploadType == "dicta"(אוoriginalFileNameמסתיים ב‑_dicta.txt). bookStatus— ערכי הסטטוס בשדה זה (כפי שמופיעים ב‑list בפועל):not_checked(לא נבדק),In_treatment(בטיפול),ready(מוכן),needs_attention(דורש טיפול),Website_editing(עריכת אתר),added_to_library(נוסף לספרייה / הוכנס לאוצריא). הפייפליין משתמש בשניים בלבד:In_treatmentו‑added_to_library(ראה שלב ו והנדבך ב‑Stage 7).- מיין לפי
uploadedAtיורד ל"החדש ביותר".
החל את כל הקריטריונים שהמשתמש ביקש (סוג דיקטה, סטטוס, וכו'). אם נשארו כמה מועמדים — הצג ב‑AskUserQuestion. אם המשתמש אמר "לא משנה איזה" — קח את החדש ביותר.
ג. הורדת הקובץ
curl -s -b "$JAR" "https://otzaria.org/api/download/<UPLOAD_ID>" \
-o "/tmp/dicta_pipeline/<book>/dicta.txt"
(הורדה מרובה: POST /api/download/batch עם {"uploadIds":[...]} → zip.)
ד. בדיקת המקור ב'לא ערוך'
לפני המעבר לפייפליין, אתר את המקור הגולמי של הספר תחת
DictaToOtzaria/לא ערוך/ספרים/אוצריא/<קטגוריה>/<שם>.txt (חיפוש ב‑list.txt או find).
הקובץ שהורד מ‑uploads עשוי להיות חצי‑ערוך (כותרות כבר זוהו), בעוד שהמקור הוא הייצוא הגולמי — הוא ישמש כ"רפרנס לפני‑ניקוי" ל‑Stage 5. סכם למשתמש את ההבדל (שורות, מספר כותרות, <big>).
ה. שינוי שם + העלאה ל‑stage (כדי שהמשתמש יראה את השינויים)
שם הקובץ ב‑uploads הוא מקווקו עם סיומת _dicta (תלמוד_בבלי_אחרונים_מלא_הרועים__חלק_ב_dicta.txt). שנה אותו לשם הקנוני כפי שהוא מופיע ב'לא ערוך' (שאותר בשלב ד) — השם הנקי עם רווחים, בלי _dicta, למשל מלא הרועים חלק ב.txt.
- העתק את הקובץ שהורד לשורש הפרויקט עם השם מ'לא ערוך'. הפקודות בשלב זה רצות מתיקיית שורש הפרויקט (cwd), לכן השתמש בנתיב יחסי — בלי נתיב מוחלט תלוי‑מכונה:
(שמור על הריווח המדויק של השם ב'לא ערוך', כולל רווח כפול אם קיים.)cp "/tmp/dicta_pipeline/<book>/dicta.txt" "./<שם מ'לא ערוך'>.txt" - העלה ל‑stage את הקובץ הגולמי מיד לאחר ההעתקה, לפני הרצת שאר הפייפליין:
כך ה‑staged הוא הגרסה הגולמית שהורדה, ועריכות הפייפליין שיבואו אחר כך יישארו לא‑staged — וכלgit add "<שם מ'לא ערוך'>.txt"git diffיראה למשתמש בדיוק מה הפייפליין שינה. - אל תעשה
git addשוב אחרי עריכות הפייפליין, אלא אם המשתמש ביקש זאת במפורש.
ו. סימון הסטטוס ל‑"בטיפול" (In_treatment) — מיד לאחר ההורדה
מיד אחרי שהקובץ הורד (וזוהה ה‑UPLOAD_ID), עדכן את הסטטוס שלו ב‑otzaria.org ל‑In_treatment ("בטיפול"), כדי לסמן שהספר נלקח לעיבוד. השתמש באותו $JAR מההתחברות (שלב א):
curl -s -X PUT "https://otzaria.org/api/admin/uploads/batch-update-book-status" \
-b "$JAR" -H "Content-Type: application/json" \
--data-raw '{"uploadIds":["<UPLOAD_ID>"],"bookStatus":"In_treatment"}'
- ה‑endpoint מקבל מערך
uploadIds, כך שאפשר לסמן כמה ספרים בבקשה אחת (["id1","id2"]). - אַמֵּת שהתשובה מציינת הצלחה. אם נכשל — דווח למשתמש, אך אפשר להמשיך בפייפליין (הסימון אינו חוסם עיבוד).
- אל תסמן
added_to_libraryבשלב הזה. המעבר ל‑"נוסף לספרייה" קורה רק בסוף, אחרי אישור המשתמש וקומיט (ראה Stage 7).
Stage 1 — Locate the book in HebrewBooks
מבנה ה‑DB (otzar-HB_catalog.db)
ה‑DB מכיל שתי טבלאות נפרדות:
| טבלה | תוכן | האם לחפש בה? |
|---|---|---|
hebrew_books |
קטלוג HebrewBooks (~60K ספרים) | כן — הספרי דיקטה כולם משם |
otzar_hahochma |
קטלוג אוצר החכמה (~163K ספרים) | לא — דיקטה לא משתמשים בו |
ספרי דיקטה מקורם אך ורק ב‑HebrewBooks. אסור לחפש ב‑otzar_hahochma. הסקריפט search_hb.py כבר מחויב לטבלה הנכונה — אל תיגע בו.
סכמת hebrew_books
id_book INTEGER PRIMARY KEY ← זה גם ה‑ID להורדת PDF (ראה Stage 2)
title TEXT
author TEXT ← בפורמט "משפחה - פרטי בן ..."
printing_place TEXT ← לדוגמה "לבוב", "ירושלים"
printing_year TEXT ← גימטריה ("תרכו"). הצג למשתמש את זה.
pub_date INTEGER ← שנה לועזית (1866). הצג בסוגריים אם מסייע.
pages INTEGER
tags TEXT ← מערך JSON (הסקריפט פורסר אותו לרשימה)
סדר הפעולות
- הסק את שם הספר מה‑TXT הנכנס: התוכן של
<h1>בשורה הראשונה. אם הוא מכיל "על מסכת X" / "על X", שווה לנסות גם בלי הסיומת — מחבר עשוי להופיע בקטלוג בצורה אחרת. - הרץ את החיפוש:
python3 .claude/skills/dicta-book-pipeline/scripts/search_hb.py --title "<book>" --json- הסקריפט מחזיר עד 25 תוצאות, ממוינות לפי דמיון (התאמה מדויקת → התחלה → הכלה → מילים משותפות).
- אם הסקריפט שגיאה "DB not found" — ורק אז שאל את המשתמש על נתיב חלופי, והעבר אותו ב‑
--db.
- אם יש מועמד יחיד עם score גבוה (כותרת זהה / starts-with) — המשך, אבל הצג למשתמש שורה אחת
[<id>] <title> — <author>, <place> <printing_year>ואשר לפני הורדה. - אם יש כמה מועמדים —
AskUserQuestionעם עד 4 אפשרויות. המידע שצריך להיות בכל אפשרות:- label: כותרת + מחבר (קצר)
- description: מקום + שנה (גם גימטריה וגם לועזית) + מספר עמודים + תגים
- השתמש ב‑pub_date (לועזית) לסינון מהיר אם המשתמש מבין יותר מ‑printing_year (גימטריה).
- אם אין מועמדים — דווח למשתמש. הצע:
- לחפש מילה אחת מהכותרת בלבד.
- אם המשתמש מכיר את המחבר — לחפש לפי שם המחבר (יש לתמוך בזה דרך
--titleכי הסקריפט מחפש בכותרת בלבד; להחיפוש לפי מחבר אפשר לבקש זמני להריץsqlite3ידני). - לבקש id ישירות מהמשתמש.
אל תנחש id. אם המשתמש סיפק id ישירות — דלג על שלבים 1‑5.
Stage 2 — Download the PDF
URL ההורדה: https://download.hebrewbooks.org/downloadhandler.ashx?req=<ID> — זהה לכל הספרים.
python3 .claude/skills/dicta-book-pipeline/scripts/download_pdf.py --id <ID> --out /tmp/dicta_pipeline/<book>/source.pdf
הסקריפט שומר ל‑cache; אם הקובץ קיים — לא יוריד שוב.
Stage 3 — Convert PDF to images and OCR
python3 .claude/skills/dicta-book-pipeline/scripts/pdf_to_pages.py --pdf <pdf> --out-dir /tmp/dicta_pipeline/<book>/pages --dpi 300
python3 .claude/skills/dicta-book-pipeline/scripts/ocr_batch.py --in-dir <pages> --out /tmp/dicta_pipeline/<book>/ocr.txt --concurrency 8
- ה‑OCR יוצר קובץ אחד שמרכז את כל העמודים, עם מפריד
\n\n=== PAGE N ===\n\n. - שרת ה‑OCR מקבל בקשה אחת לעמוד (multipart
file+ headerX-API-Key). הסקריפט שולח במקביל. - אם השרת לא זמין → דלג על שלבים 5 ו‑6 והודע למשתמש שאי אפשר להשוות מול המקור.
Stage 3.5 — Split & normalize (deterministic)
רוץ תמיד, גם כש‑OCR מדולג. הסקריפטים ב‑../EditingDictaBooks/edit_dicta_cli.py (יחסית לשורש הפרויקט — מאגר EditingDictaBooks הוא תיקייה אחות לפרויקט; תומכים --json, קוד יציאה 0/1/2). אם לא נמצא שם — שאל את המשתמש על המיקום.
- פצל לפי מסכת — אם יש כמה מסכתות (כמה
<h2>/ קולופוניםתם ונשלם מסכת X/סליק מסכת X), צור קובץ נפרד לכל מסכת עם<h1>משלו בתבנית<שם הספר> על <מסכת>. הצג את רשימת הקבצים המוצעת ואשר לפני יצירה. - נקה
<h1>— הסר גרשיים ("/”/’). - נקה טקסט —
clean-text --file <f>(נרמול גרשיים''→"ו‑”→", רווחים כפולים, שורות ריקות). - הסר
<big>מכותרות ומשורות דקורטיביות בודדות (לא קולופון סיום). - נרמל כותרות
דף—page-number --file <f> --style dot-colonו‑replace-page-bלהמרתעמוד ב/גרש ל‑./:. ⚠ שתי הפקודות פועלות רק כשהשורה אחרי כותרת ה‑דףעדיין נושאת סמןע"א/עמוד א; אם דיקטה כבר הסירה אותו, הכותרת נשארתדף יבחשופה והפקודה היא no‑op שקט. לכן אחרי ההרצה אמת שלא נותרה אף כותרתדףבלי./::grep -nE '<h[2-6]>דף [^.:<]*</h[2-6]>' <f>. לכל דף חשוף הסק את העמוד מהרצף — דף הוא ברירת‑מחדל עמוד א (.); אם כבר קייםדף N.לאותו מספר, החשוף הוא עמוד ב (:). זו עבודת היגיון‑רצף (קטגוריה ι ב‑Stage 6), לא נורמול דטרמיניסטי. - הדגש מילה ראשונה —
emphasize-first --file <f>(מוסיף<b>+ פיסוק סוף). הרחבת הלמה המלאה ומיזוג<b>עוקבים (מוסכמה §6) הם שיקול‑דעת ב‑Stage 4. - הפרד כותרות דבוקות לטקסט — שורה שמתחילה
<hולא מסתיימת ב‑>: כותרת בשורה אחת, טקסט בשורה הבאה. אתר:grep -nE '^<h[1-6]>' <f> | grep -vE '>[[:space:]]*$'.
סיכום למשתמש: לכמה קבצים פוצל, כמה כותרות נורמלו, כמה מילים ראשונות הודגשו, כמה כותרות הופרדו.
Stage 4 — Header proposal (LLM Pass A)
זה אתה. טען את prompts/headers.md, קרא את הקובץ הנכנס, והפק הצעות כותרות.
עקרונות מחייבים:
- אסור לערוך את הקובץ ישירות. הפק רק
headers.proposed.jsonבמבנה:[{"line": 142, "current": "...", "proposed": "<h3>דף ב.</h3>", "level": 3, "confidence": "high|medium|low"}] - בגוף הספר אל תוסיף תוכן שלא קיים ואל תשנה מילים. בכותרות בלבד מותר להציע נרמול קנוני לפי מוסכמות §8, ולציין ב‑
rationaleמה נורמל. - כותרת לבד בשורה — בלי טקסט אחרי
</hN>(מוסכמות §1). - רמות יחסיות (מוסכמות §6): קבע קודם את עומק הכותרת בעץ, ואז גזור רמה:
- שם הספר/המסכת →
<h1>(קיים, אל תיגע) - חלק / "מסכת X על הרי"ף" / "הקדמה" →
<h2> - פרק →
<h2>או<h3>(לפי האם קיים חלק<h2>מעליו) - דף → רמה אחת מתחת לפרק (
<h3>או<h4>) - סימן / סעיף → רמה אחת מתחת לדף
- שם הספר/המסכת →
- אסור לדלג רמה (h2→h4). אם חסרה כותרת ביניים — הצע להוסיף אותה (למשל
<h3>פרק האשה</h3>). - בש"ס: צמד
דף ב.ו‑דף ב:הם שתי כותרות נפרדות, לא אחת.
לאחר ההפקה — הצג למשתמש סיכום: כמה כותרות הוצעו, התפלגות רמות, וכמה ב‑confidence נמוך. שאל אם להחיל אוטומטית את ה‑high או רק לסקור הכל.
Stage 5 — OCR diff (LLM Pass D — only on flagged regions)
python3 .claude/skills/dicta-book-pipeline/scripts/diff_texts.py --dicta <dicta.txt> --ocr <ocr.txt> --out /tmp/dicta_pipeline/<book>/diff.json --threshold 0.85
הסקריפט מציג רק קטעים שבהם דמיון פאזי < threshold. עבור כל פער:
- הצג את 3 השורות לפני, השורה החשודה, 3 שורות אחרי — משלושת המקורות (Dicta / OCR / לפני‑ניקוי אם זמין).
- תפקידך כ‑LLM: לקבוע מי נכון. השתמש בשיקול דעת הנדסי:
- Dicta צודק ברוב המכריע. מטה‑prior: ~85% Dicta נכון.
- OCR צודק רק כאשר יש שגיאה ברורה ב‑Dicta (אות מוחלפת מסקנית — ד↔ר, ה↔ח — והעמוד ב‑OCR ברור).
- שניהם שגויים קורה. אם משהו לא מסתדר — סמן ב‑manual_review.
- הפק
corrections.proposed.jsonבאותו מבנה כמו headers.
חוק זהב: לעולם לא להחליף אוטומטית על בסיס OCR. רק להציע. אישור אנושי הוא דרישה.
Stage 6 — Final QA pass (LLM Pass B)
טען את prompts/qa.md. קרא את הקובץ במלואו (אחרי שלב 4).
הרץ קודם את הגייט הדטרמיניסטי על כל קובץ פלט:
python3 ../EditingDictaBooks/edit_dicta_cli.py validate-otzaria --file <path> --shas # יחסי לשורש הפרויקט; --shas לש"ס (עמוד ב כפול)
פקודה זו אוכפת את המוסכמות הדטרמיניסטיות שה‑CLI יודע לבדוק: תגים, כותרות עם טקסט נלווה, פיצול רב‑מסכתי, גרשיים ב‑<h1>, ו‑<big> בכותרת/דקורטיבי. היא אינה בודקת הבנת תוכן: האם הלמה מלאה, האם כל דיבור הסתיים כקטע עצמאי, האם קטע ארוך צריך פיצול, או האם כותרת פרק קנונית נכונה. קוד יציאה: 0 = עבר (ירוק), 1 = הפרת מוסכמה קשה. תקן כל בעיה "קשה" עד ש‑exit=0:
multi_masechta, h1_gershayim, big_in_heading, decorative_big, opening/closing_without_opening, heading_errors.
הממצאים תחת advisory_daf_format ו‑advisory_heading_sequence אינם מכשילים את exit, אך אינם רעש שמותר לדלג עליו — בספרי חידושים הם רועשים (דפים נדונים שלא לסדר), אך הם גם הרשת היחידה לטעויות מספור. כש‑OCR מדולג הם הופכים לבדיקה מחייבת (קטגוריה ι). הבחן בין דילוג לגיטימי (המחבר אינו דן בדף) לטעות אמיתית.
דרך העבודה המומלצת לכותרות: עבור על כל הכותרות ברצף, אבל אל תדווח כל דף ב. תקין כבעיה. צור לעצמך רשימת כותרות מסודרת (line, level, text), סמן חריגות ברמת סיכון, ודווח למשתמש קודם רק את החריגות: דף בלי ./:, כפילות חשודה, קפיצה חריגה, ירידת/עליית רמה לא הגיונית, כותרת שנראית body, או body שנראה ככותרת. אם כל הרצף תקין, כתוב בדו"ח שעברת על כל הכותרות ולא נמצאו חריגות.
רק אחרי exit=0 — עבור לקטגוריות שיקול הדעת:
- α — קטעים גדולים ללא כותרת (>800 מילים בין כותרות צמודות).
- β — כללים שאינם נאכפים ע"י ה‑CLI ויש לבדוק עצמאית: קטעי body שאינם שורה אחת, קטעים שאינם מסתיימים ב‑
:, דיבור־המתחיל קצר מדי/מודגש חלקית,<b>עוקבים שצריך לאחד, וכמה הערות שנדחסו באותה שורה. השתמש בחיפוש/סקריפטים אד-הוק רק כעזר; ההכרעה היא לפי קריאת ההקשר. - η —
דף ב,דף גכשורה רגילה (לא כותרת) — דגל לכל מופע. - ι — היגיון כותרות
דף(חובה כש‑OCR מדולג): (א) כותרתדףבלי./:— הסק עמוד מהרצף ותקן; (ב) מספר לא הגיוני — קפיצה אחורה חדה או בלבול אות גימטריה (דף כבפתח מסכת במקוםדף ב;ב↔כ,ג↔נ,ד↔ר) — הצלב מול תוכן הקטע ותקן; (ג) כפילות חשודה — אותהדף N:חוזרת רצוף — בדוק חלוקת דף לעומת טעות מספור; (ד) סמן דף בתוך הגוף —<b>דפ"ה</b>,דף פ"ה,דף פה ע"אוכד' המופיעים כטקסט ולא ככותרת — כל אחד הוא פתיחת דף שהוחמצה; המר לכותרת<h>(מוסכמה §4). אתר:grep -nE '<b>דף?פ?["׳]' <f>ו‑grep -nE 'דף [א-ת]+ ע"[אב]'.- ⚠ אזהרה — אל תמיר ראשי‑תיבות בעיוורון. הרבה
<b>ד..."...</b>הם ר"ת ולא דפים:<b>ד"ה</b>=דיבור המתחיל (הנפוץ ביותר!),<b>ד"ח</b>,<b>דת"ק</b>=דתנא קמא,<b>דא"ק</b>=דאמר קרא,<b>דבש"ע</b>=דבשולחן ערוך. המר רק כשהסמן מלווה ב‑ע"א/ע"ב, או שאחריו פתיחת דיון (בגמרא/מתני'/תוס'/רש"י) והמספר משתלב ברצף הדפים של המסכת. תמיד הרץ dry‑run והצג את המועמדים לפני המרה — בספר חידושים על תוס' רוב/כל ה‑<b>ד"ה</b>אינם דפים. - במקרי שיבוש בכותרות, זה עלול להיות טעות OCR פשוטה, אך עלול לרמז גם על השמטת OCR של קטע/עמוד שלם: יש לבחון בזהירות, ולפי ההקשר. (למשל
דף פדחוזר 4× ואז קפיצה ל‑דף פט— סימן שה‑OCR של הכותרות לדפים שבאמצע נשבר, לא טעות מספור בודדת.) - הצלב מול ה‑DB כדי לקבוע את האמת:
seforim.dbמכיל את טקסט הש"ס. קבע את הנתיב לפי env varSEFORIM_DB, אחר כך.env.local, אחר כך ברירת המחדל~/Downloads/otzaria_latest/otzaria/seforim.db. קח ביטוי/דיבור־המתחיל מצוטט מהקטע (תוס' ד"ה אתא,מתני' ...), חפש אותו ב‑DB, וקבל את הדף+עמוד האמיתי — ואז תקן את הכותרת (למשל הקטעתוס' ד"ה אתאנמצא בדף פח.;תוס' ד"ה א"הבדף פח:).
- ⚠ אזהרה — אל תמיר ראשי‑תיבות בעיוורון. הרבה
- θ — שורות עם תווים מוזרים/רצפים לא הגיוניים — חשוד ל‑OCR (רק אם OCR זמין).
הפק דו"ח סופי report.md עם הסעיפים: סיכום מנהל, רשימה מסודרת לפי רמת חומרה, והמלצה — האם הספר מוכן לדחיפה ל‑repo, או דורש בקרת אדם.
Stage 7 — Output and handoff
/tmp/dicta_pipeline/<book>/
├── source.pdf (Stage 2)
├── pages/ (Stage 3)
├── ocr.txt (Stage 3)
├── headers.proposed.json (Stage 4)
├── diff.json (Stage 5 — automated)
├── corrections.proposed.json (Stage 5 — your output)
└── report.md (Stage 6)
אסור לדחוף ל‑repo otzaria-library בלי אישור מפורש מהמשתמש. גם עם אישור — תמיד ב‑branch נפרד, לא ב‑main.
סימון הסטטוס ל‑"נוסף לספרייה" (added_to_library) — רק בסוף
המעבר הסופי קורה אך ורק אחרי ש‑שני התנאים התקיימו:
- המשתמש נתן אישור סופי לספר (סקר את ה‑
git diff/ הדו"ח ואישר), וגם - השינויים נכנסו לקומיט (commit).
רק אז עדכן את הסטטוס ב‑otzaria.org ל‑added_to_library ("הוכנס לאוצריא"), עם אותו endpoint משלב 0/ו (אם ה‑$JAR פג, התחבר מחדש לפי Stage 0/א):
curl -s -X PUT "https://otzaria.org/api/admin/uploads/batch-update-book-status" \
-b "$JAR" -H "Content-Type: application/json" \
--data-raw '{"uploadIds":["<UPLOAD_ID>"],"bookStatus":"added_to_library"}'
- אל תקפוץ מ‑
In_treatmentל‑added_to_libraryלפני אישור וקומיט. אם המשתמש אישר אך עוד לא קומטת — אל תסמן. - אַמֵּת הצלחה בתשובה ודווח למשתמש שהסטטוס עודכן ל‑"נוסף לספרייה".
Conventions
- כל קריאה לסקריפט: גם אם הוא משכתב קובץ — הראה למשתמש מה קרה, על איזה קובץ עבדת, וכמה שינויים. השתמש בפלט
--jsonשל הסקריפטים הקיימים. - אם משימה כלשהי נכשלה (DB לא נמצא, OCR לא מגיב) — דווח, אל תמשיך עם נתונים שגויים.
- שמור על הסדר: סטייג'ים 1‑3 הם תשתית; 4‑6 דורשים שיקול דעת. אם הסטייג' תשתית נכשל, סטייג' השיקול לא יבוצע.
- בכל שלב הצג למשתמש סיכום קצר (1‑2 משפטים): מה רץ, מה התוצאה, מה הצעד הבא. אל תהיה מילולי מדי.
Failure modes — what to do
| תרחיש | תגובה |
|---|---|
OCRWIN_URL לא מוגדר |
בקש מהמשתמש URL+key, שמור ל‑.env.local, המשך |
| ה‑PDF לא יורד (404, חיבור) | ייתכן ID שגוי. הצג את ה‑URL והתבקש שוב מהמשתמש |
pdftoppm לא מותקן |
אמור: brew install poppler |
| OCR מחזיר טקסט ריק לעמוד | רשום אזהרה, המשך לשאר העמודים, וציין בדו"ח |
| יותר מ‑1000 עמודים | אזהר את המשתמש לפני התחלה — זה ייקח זמן |
| המשתמש מבקש לקפוץ ישר ל‑Stage 6 | לגיטימי. אבל בלי OCR אין יכולת לזהות θ. הסבר. |
Scripts in this skill
- scripts/search_hb.py — sqlite חיפוש בכותרת, עם זיהוי סכמה דינמי
- scripts/download_pdf.py — הורדת PDF (cache + retry)
- scripts/pdf_to_pages.py — pdftoppm wrapper
- scripts/ocr_batch.py — שליחת עמודים במקביל ל‑OCR
- scripts/diff_texts.py — השוואה fuzzy
הסקריפטים תומכים ב‑--json להוצאת פלט שמיש לעיבוד שלך.
Prompts
- prompts/headers.md — להפעלה כשאתה ב‑Stage 4
- prompts/qa.md — להפעלה כשאתה ב‑Stage 6