dicta-book-pipeline

star 15

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.

Otzaria By Otzaria schedule Updated 6/12/2026

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.
  1. כותרת לבד בשורה — אסור טקסט אחרי </hN> באותה שורה.
  2. בלי <big> בכותרות<h2><big>מסכת קדושין</big></h2><h2>מסכת קדושין</h2>. שורות <big> דקורטיביות בודדות בראש קטע (<big>חדושי</big>) — להסיר; קולופון <big>תם ונשלם...</big> בסוף — להשאיר.
  3. קובץ אחד לכל מסכת — פצל קובץ רב‑מסכתי, <h1> נפרד לכל מסכת בתבנית <שם ספר> על <מסכת>. בלי גרשיים ("//) ב‑<h1>.
  4. כותרת דף מנוקדת, בלי גרש — עמוד א = ., עמוד ב = : (דף ט'דף ט., דף ח':דף ח:).
    • כל פתיחת דף חדש = כותרת <h> נפרדת, חובה. אסור שפתיחת דף תופיע כטקסט מודגש בתוך הגוף. סמן דף שהמחבר משבץ בתוך הקטע — <b>דפ"ה</b>, <b>דף פ"ה</b>, דף פה ע"א, ד' פה וכד' — הוא פתיחת דף, ויש להמיר אותו לכותרת בשורה נפרדת (<b>דפ"ה</b> ע"א בגמרא...<h3>דף פה.</h3> בשורה אחת ואז בגמרא... בשורה הבאה). העמוד נקבע מהסמן (ע"א., ע"ב:); אם אין סמן עמוד — ברירת מחדל עמוד א (.), אך הצלב מול ה‑DB/הקשר (Stage 6 ι).
  5. קטע = שורה אחת, המסתיימת ב‑: — כל הערה/דיבור הוא שורת body אחת שלמה. הגולמי של דיקטה לרוב שובר זאת בשתי צורות, ושתיהן מתוקנות לאותו יעד:
    • מילים בודדות בשורות נפרדות (<b>אמר</b> / <b>המעתיק</b> ...) ⇒ אַחֵד לשורה אחת.
    • כמה הערות דחוסות בשורה אחת, מופרדות ב‑: פַּצֵּל לשורה לכל הערה. קטע מסתיים ב‑: (סוף עניין); קטע נושא־דיבור מסתיים ב‑. בתוך ההדגשה ואז : בסוף.
  6. דיבור־המתחיל מודגש — תחילת כל קטע: עטוף את הלמה כולה (לא רק מילה אחת) ב‑<b>. בפירושי תלמוד הלמה היא הציטוט מהגמרא, ומסתיימת בנקודה בתוך התג: <b>ורבא אמר משום שאין עדים מצויים לקיימו.</b> פירוש.... אַחֵד <b> עוקבים של למה/שם אחד (<b>אמר</b> <b>המעתיק</b><b>אמר המעתיק</b>); אל תאחד למות/מקורות נפרדים (<b>תוס'</b> <b>ד"ה ולא</b> נשארים שניים).
  7. רמות יחסיות — דף = רמה אחת מתחת לפרק; פרק = רמה אחת מתחת למסכת/חלק. אם פרק=<h2> אז דף=<h3>; אם פרק=<h3> אז דף=<h4>. כשהדף הוא יחידת המבנה העליונה (אין כותרת פרק/מסכת מעליו) — דף=<h2>. בלי מספר רמה גלובלי קבוע.
  8. שם כותרת מנורמל — תוכן הכותרת עצמה נכתב בצורה הקנונית, לא כפי שהופיע בדפוס:
    • הסר שם מחבר מכותרת — הוא כבר ב‑<h1> (חדושי פרקא קמא דכתובות מהרי"טחדושי פרקא קמא דכתובות).
    • פתח קיצוריםפ' האומרפרק האומר.
    • הוסף מסכת לכותרת מסכת כשחסר — <h2>ברכות</h2><h2>מסכת ברכות</h2>.
    • כותרת פרק בפורמט <שם הפרק> - פרק <מספר> — שם הפרק המקובל בש"ס תחילה, מקף, ואז "פרק" + מספר. מספר חשוף ⇒ הוסף את שם הפרק הקנוני (פרק ח' בכתובות ⇒ האשה שנפלו - פרק שמיני). אורדינל 1–10 במילים (שביעי/שמיני/עשירי), 11+ בגימטריה (יא/יב/יג). זו עבודת שיקול‑דעת ב‑Stage 4 (דורשת ידע בשמות פרקי הש"ס), לא נורמול דטרמיניסטי.
  9. נרמול גרשיים'' (גרש כפול) ⇒ ", גרשיים טיפוגרפיים /". (נאכף ע"י 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.

  1. העתק את הקובץ שהורד לשורש הפרויקט עם השם מ'לא ערוך'. הפקודות בשלב זה רצות מתיקיית שורש הפרויקט (cwd), לכן השתמש בנתיב יחסי — בלי נתיב מוחלט תלוי‑מכונה:
    cp "/tmp/dicta_pipeline/<book>/dicta.txt" "./<שם מ'לא ערוך'>.txt"
    
    (שמור על הריווח המדויק של השם ב'לא ערוך', כולל רווח כפול אם קיים.)
  2. העלה ל‑stage את הקובץ הגולמי מיד לאחר ההעתקה, לפני הרצת שאר הפייפליין:
    git add "<שם מ'לא ערוך'>.txt"
    
    כך ה‑staged הוא הגרסה הגולמית שהורדה, ועריכות הפייפליין שיבואו אחר כך יישארו לא‑staged — וכל git diff יראה למשתמש בדיוק מה הפייפליין שינה.
  3. אל תעשה 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 (הסקריפט פורסר אותו לרשימה)

סדר הפעולות

  1. הסק את שם הספר מה‑TXT הנכנס: התוכן של <h1> בשורה הראשונה. אם הוא מכיל "על מסכת X" / "על X", שווה לנסות גם בלי הסיומת — מחבר עשוי להופיע בקטלוג בצורה אחרת.
  2. הרץ את החיפוש:
    python3 .claude/skills/dicta-book-pipeline/scripts/search_hb.py --title "<book>" --json
    
    • הסקריפט מחזיר עד 25 תוצאות, ממוינות לפי דמיון (התאמה מדויקת → התחלה → הכלה → מילים משותפות).
    • אם הסקריפט שגיאה "DB not found" — ורק אז שאל את המשתמש על נתיב חלופי, והעבר אותו ב‑--db.
  3. אם יש מועמד יחיד עם score גבוה (כותרת זהה / starts-with) — המשך, אבל הצג למשתמש שורה אחת [<id>] <title> — <author>, <place> <printing_year> ואשר לפני הורדה.
  4. אם יש כמה מועמדיםAskUserQuestion עם עד 4 אפשרויות. המידע שצריך להיות בכל אפשרות:
    • label: כותרת + מחבר (קצר)
    • description: מקום + שנה (גם גימטריה וגם לועזית) + מספר עמודים + תגים
    • השתמש ב‑pub_date (לועזית) לסינון מהיר אם המשתמש מבין יותר מ‑printing_year (גימטריה).
  5. אם אין מועמדים — דווח למשתמש. הצע:
    • לחפש מילה אחת מהכותרת בלבד.
    • אם המשתמש מכיר את המחבר — לחפש לפי שם המחבר (יש לתמוך בזה דרך --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 + header X-API-Key). הסקריפט שולח במקביל.
  • אם השרת לא זמין → דלג על שלבים 5 ו‑6 והודע למשתמש שאי אפשר להשוות מול המקור.

Stage 3.5 — Split & normalize (deterministic)

רוץ תמיד, גם כש‑OCR מדולג. הסקריפטים ב‑../EditingDictaBooks/edit_dicta_cli.py (יחסית לשורש הפרויקט — מאגר EditingDictaBooks הוא תיקייה אחות לפרויקט; תומכים --json, קוד יציאה 0/1/2). אם לא נמצא שם — שאל את המשתמש על המיקום.

  1. פצל לפי מסכת — אם יש כמה מסכתות (כמה <h2> / קולופונים תם ונשלם מסכת X / סליק מסכת X), צור קובץ נפרד לכל מסכת עם <h1> משלו בתבנית <שם הספר> על <מסכת>. הצג את רשימת הקבצים המוצעת ואשר לפני יצירה.
  2. נקה <h1> — הסר גרשיים ("//).
  3. נקה טקסטclean-text --file <f> (נרמול גרשיים ''" ו‑", רווחים כפולים, שורות ריקות).
  4. הסר <big> מכותרות ומשורות דקורטיביות בודדות (לא קולופון סיום).
  5. נרמל כותרות דףpage-number --file <f> --style dot-colon ו‑replace-page-b להמרת עמוד ב/גרש ל‑./:. ⚠ שתי הפקודות פועלות רק כשהשורה אחרי כותרת ה‑דף עדיין נושאת סמן ע"א/עמוד א; אם דיקטה כבר הסירה אותו, הכותרת נשארת דף יב חשופה והפקודה היא no‑op שקט. לכן אחרי ההרצה אמת שלא נותרה אף כותרת דף בלי ./:: grep -nE '<h[2-6]>דף [^.:<]*</h[2-6]>' <f>. לכל דף חשוף הסק את העמוד מהרצף — דף הוא ברירת‑מחדל עמוד א (.); אם כבר קיים דף N. לאותו מספר, החשוף הוא עמוד ב (:). זו עבודת היגיון‑רצף (קטגוריה ι ב‑Stage 6), לא נורמול דטרמיניסטי.
  6. הדגש מילה ראשונהemphasize-first --file <f> (מוסיף <b> + פיסוק סוף). הרחבת הלמה המלאה ומיזוג <b> עוקבים (מוסכמה §6) הם שיקול‑דעת ב‑Stage 4.
  7. הפרד כותרות דבוקות לטקסט — שורה שמתחילה <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. עבור כל פער:

  1. הצג את 3 השורות לפני, השורה החשודה, 3 שורות אחרי — משלושת המקורות (Dicta / OCR / לפני‑ניקוי אם זמין).
  2. תפקידך כ‑LLM: לקבוע מי נכון. השתמש בשיקול דעת הנדסי:
    • Dicta צודק ברוב המכריע. מטה‑prior: ~85% Dicta נכון.
    • OCR צודק רק כאשר יש שגיאה ברורה ב‑Dicta (אות מוחלפת מסקנית — ד↔ר, ה↔ח — והעמוד ב‑OCR ברור).
    • שניהם שגויים קורה. אם משהו לא מסתדר — סמן ב‑manual_review.
  3. הפק 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 var SEFORIM_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) — רק בסוף

המעבר הסופי קורה אך ורק אחרי ש‑שני התנאים התקיימו:

  1. המשתמש נתן אישור סופי לספר (סקר את ה‑git diff / הדו"ח ואישר), וגם
  2. השינויים נכנסו לקומיט (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

הסקריפטים תומכים ב‑--json להוצאת פלט שמיש לעיבוד שלך.

Prompts

Install via CLI
npx skills add https://github.com/Otzaria/otzaria-library --skill dicta-book-pipeline
Repository Details
star Stars 15
call_split Forks 12
navigation Branch main
article Path SKILL.md
More from Creator