מאגר חדש ליומוני "כל יום" (עפר טויסטר) כשכבת-גילוי מעל קורפוסי-הפסיקה:
מקור-משני המצביע על פסק הדין המקורי, נקלט לטבלה נפרדת `digests`, נחפש
סמנטית, ומקושר לפסק המקורי בספריית הפסיקה — אך לעולם אינו מצוטט בהחלטה
ואינו מחלץ הלכות.
Phase 0 (spec):
- docs/spec/X12-digests-radar.md — INV-DIG1 (מצביע לא מצוטט) /
INV-DIG2 (מסלול-קליטה נפרד, לא מקביל — מקיים G2) / INV-DIG3 (קישור-לפסק
הוא הגשר; חוסר-קישור = פער גלוי). עדכון אינדקס 00/03/README.
Phase 1 (MVP):
- SCHEMA_V30: טבלת `digests` (HNSW על embedding — לא ivfflat, להימנע מ-recall
cliff בקורפוס קטן/צומח) + GIN/FTS + UNIQUE חלקי ל-idempotent.
- services/digest_metadata_extractor.py — חילוץ-LLM (claude_session local-only,
ייבוא lazy): תג-מושג, כותרת-הלכה, מראה-מקום, שני-תאריכים מובחנים, תגיות.
- services/digest_library.py — מסלול קצר עצמאי (INV-DIG2): extract→hash→LLM→
embedding יחיד→autolink. לא משתמש ב-ingest.ingest_document.
- tools/digests.py + רישום 7 כלים ב-server.py (digest_upload/list/get/link/
relink/delete + search_digests).
- scripts/ingest_digests_batch.py — קליטה ידנית מ-data/digests/incoming.
- legal-researcher.md: שלב 2ב.0 (סריקת-radar לפני אימות) + סעיף-דוח ט +
3 כלים ב-frontmatter. HEARTBEAT §8: ניתוב יומון→digest_upload.
אומת end-to-end: 4 יומונים נקלטו (מטא-דאטה מדויק), חיפוש סמנטי מדרג נכון
("היטל השבחה"→5160, "תמא 38"→5158), link/relink/autolink/revert + מעטפת-MCP.
Invariants: מוסיף INV-DIG1/2/3 (X12). מקיים G2 (bounded context נפרד, לא
מסלול מקביל), G3 (idempotent upsert), G4 (אין בליעה שקטה — פער-קישור מוצף),
G9 (עקיבוּת — היומון מצביע על מקור עקיב). נוגע G7 (RRF) — נדחה, חיפוש
סמנטי-בלבד בשלב 1 (FTS index מוכן).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
17 KiB
03 — אחזור (Retrieval: Corpora · Hybrid/RRF · Attribution · Eval)
קובץ-תחום זה כפוף ל-חוקת המערכת ומגדיר את שכבת-האחזור הקנונית (TARGET) — שלושת הקורפוסים, כלי-החיפוש המכוונים לכל אחד, מנגנון ה-hybrid (dense + lexical) ומיזוג ה-RRF, עקיבוּת-המקור והרמוניית-המדידה. הוא אוכף את G4 (חוזה-שלמות לפני "ניתן-לחיפוש"), G5 (הפרדת-קורפוס בכל query), G6 (re-index), G7 (מיזוג RRF), G8 (eval) ו- G9 (עקיבוּת-מקור).
TARGET, לא תיאור-מצב. כל מקום שבו הקוד בפועל סוטה מהיעד מתועד כ-audit-finding (§5), תסמין לתיקון — לא התנהגות תקינה. כל טענה על הקוד מצוטטת
file:line.
כשל-השורש שהקובץ מייבש: 3 קורפוסים שחולקים תשתית-אחזור אחת, אך הפרדת-הקורפוס נאכפת רק על חלק ממסלולי-ה-query — כך שפריט מקורפוס אחד דולף לתוצאה של חיפוש בקורפוס אחר (cross-corpus leak).
1. שלושת הקורפוסים וכלי-החיפוש
| קורפוס | טבלת-אחסון | source_kind |
כלי-MCP מכוון | אימות file:line |
|---|---|---|---|---|
| מסמכי-תיק + קורפוס-סגנון דפנה | document_chunks |
— (מובחן ב-case_id/practice_area) |
search_decisions · search_case_documents · find_similar_cases |
search.py:15,91,145 → hybrid_search.py:41 (search_documents_hybrid) → db.search_similar (hybrid_search.py:56) |
| פסיקה חיצונית סמכותית | case_law + precedent_chunks/halachot |
external_upload |
search_precedent_library |
search.py→precedent_library.py:235 → search_library → hybrid_search.py:89,101 (source_kind="external_upload") |
| החלטות ועדות-ערר (פנימי) | case_law + precedent_chunks/halachot |
internal_committee |
search_internal_decisions |
search.py:228 → internal_decisions.py:395,411-418 (source_kind="internal_committee") → hybrid_search.py:89 |
הבחנת-שם קריטית (לא קורפוס רביעי): precedent_search_library (server.py:160) הוא כלי שונה —
מחפש בציטוטים שהיו"ר צירפה ידנית לתיקים (case_precedents), לא בקורפוס הפסיקה הסמכותית.
search_precedent_library (server.py:280) הוא הכלי לקורפוס החיצוני. אל תבלבל ביניהם.
הקורפוס החיצוני והפנימי חולקים טבלה אחת (case_law), מובחנים ב-source_kind בלבד
(02-data-model §2א). שניהם רצים דרך אותן פונקציות-DB
(search_precedent_library_semantic/_lexical) — לכן הפרדת-הקורפוס היא תנאי-סינון בתוך אותה שאילתה,
ושם נולדת ההפרה ב-§5.
שכבת-גילוי — יומונים, לא קורפוס-ציטוט. מעל 3 הקורפוסים יושבת שכבת-radar נפרדת: יומונים (סיכומי עפר-טויסטר), בטבלה פיזית נפרדת
digestsעם כליsearch_digests. היומון הוא מקור משני המצביע על הפסק המקורי — אינו קורפוס-ציטוט רביעי, אינו עקיב-בפלט (INV-RET5), ואינו נוגע ב-case_law/document_chunks. ההפרדה כאן פיזית (טבלה נפרדת), לא תנאי-סינון — ולכן INV-RET1 מתקיים טריוויאלית. מלא ב- X12-digests-radar.md (INV-DIG1–DIG3).
2. עיצוב ה-hybrid retrieval
לכל קורפוס שני retrievers הטרוגניים המאוחים ב-RRF, ולא בסכום-ציונים — ראה INV-RET3:
- Dense (semantic) — דמיון-קוסינוס מול
embedding vector(1024)(voyage). פסיקה:search_precedent_library_semantic(db.py:3143); מסמכי-תיק:db.search_similar. - Lexical (BM25-style) —
ts_rank_cdמולcontent_tsv/rule_tsv/meta_tsv(Postgres FTS). פסיקה:search_precedent_library_lexical(db.py:3366). מופעל כש-BM25_HYBRID_ENABLED(hybrid_search.py:139). - מיזוג sem+lex —
_merge_sem_lex(hybrid_search.py:240-308), נוסחתrrf_score = 1/(k+sem_rank) + 1/(k+lex_rank)(hybrid_search.py:256). - שכבת-multimodal (אופציונלית) — כש-
MULTIMODAL_ENABLED, עמודי-תמונה (voyage-multimodal-3) מאוחים לטקסט ב-RRF נפרד:_merge(hybrid_search.py:311-389),text_weight/(k+rank) + img_weight/(k+rank)(hybrid_search.py:356-357). - Diversity cap (MMR-style) —
_diversify_by_case_law(hybrid_search.py:196-225): לכל היותרmax_per_case_lawhits לכלcase_law_id, כדי שפסק-דין יחיד לא ישתלט על הרשימה.
למה RRF ולא סכום משוקלל: קוסינוס (~0.4–0.7) ו-
ts_rank_cd(~0.001–0.5, תלוי-אורך-שאילתה) חיים בסקיילים שונים — סכום משוקלל היה נותן לצד אחד להשתלט במקרה. RRF מאחד לפי דירוג, ולכן עמיד להבדלי-סקייל (hybrid_search.py:248-252,319-323). תואם feedback קיים (RRF, לא weighted-sum).
3. Invariants של התחום
INV-RET1: הפרדת-קורפוס נאכפת ב-100% ממסלולי-ה-query
כלל: הפרדת 3 הקורפוסים נאכפת בכל מסלול-אחזור — גם בסינון ה-chunks וגם בסינון ההלכות.
אין פריט מקורפוס אחד שמופיע בתוצאת חיפוש שכוון לקורפוס אחר. כל ענף-SQL (semantic/lexical,
chunks/halachot) נושא את אותו תנאי-source_kind.
מקורות: Pinecone — Implement multitenancy (metadata-filter isolation per tenant) · RAG
attribution (Lewis et al., 2020, NeurIPS — pinned non-leaking provenance) · ISO 8000 (Data
quality / conformance) | סטטוס: verified
אכיפה: תנאי-source_kind בכל ענף-SQL בשכבת-החיפוש; בדיקת-בריאות שמריצה שאילתת-ביקורת
(חיפוש מכוון-קורפוס שמחזיר פריט בעל source_kind זר = כשל). אוכף את
G5.
הפרה ידועה: משימה #56 — halacha_filters אינם כוללים cl.source_kind ב-
search_precedent_library_semantic (db.py:3168, ענף ה-halacha; לעומת chunk_filters שכן —
db.py:3169) וב-search_precedent_library_lexical (db.py:3401 מול db.py:3402). שני
ה-halacha_sql עושים JOIN case_law cl בלי לסנן source_kind (db.py:3236-3238, db.py:3475-3477)
→ הלכות מהקורפוס הפנימי דולפות לתוצאות החיפוש בקורפוס החיצוני ולהפך → ממצא ל-audit.
INV-RET2: אין החזרה/אינדוקס בלי metadata מלא + locator פתיר
כלל: פריט אינו מוחזר מ-search (ואינו נחשף לאחזור) אלא אם שדות-החובה שלו מולאו
(G4) ובידו locator פתיר למקור
(case_law_id/document_id + מזהה-עמוד/chunk). רשומה ללא metadata לא-ריק או ללא chunk עם
embedding מסומנת searchable=false ולא מוחזרת (02-data-model INV-DM1).
מקורות: Pinecone (metadata filtering — completeness לפני שליפה) · RAG attribution (Lewis et
al., 2020) · ISO 8000 (completeness) | סטטוס: verified
אכיפה: חוזה-שלמות בנקודת-הקליטה (02-data-model §2)
- סינון בשכבת-החיפוש (
embedding IS NOT NULL,db.py:3239,3271;length(trim(content))>=50,db.py:3274) + בדיקת-בריאות שחושפת backlog. אוכף את G5. הפרה ידועה: ערן סופר 8046/24 — נקלטה בלי metadata (headnote/summary/tags ריקים), היעדר תיזמון חילוץ-מטא-דאטה במסלול הפנימי (01-ingest INV-ING3), אך ללא דגל-searchableמפורש שימנע את חשיפתה לאחזור → ממצא ל-audit.
INV-RET3: מיזוג retrievers הטרוגניים ב-RRF בלבד
כלל: מיזוג תוצאות בין retrievers שונים (semantic↔lexical, text↔image) נעשה אך ורק
לפי דירוג (Reciprocal Rank Fusion) — לעולם לא סכום/ממוצע ציונים גולמיים, שכן ציונים בסקיילים
שונים אינם בני-השוואה ישירה.
מקורות: Elastic — Reciprocal Rank Fusion · Weaviate — Hybrid Search Explained · Manning,
Raghavan & Schütze, Introduction to Information Retrieval (CUP, 2008) | סטטוס: verified
אכיפה: מיזוג sem+lex ב-_merge_sem_lex (hybrid_search.py:240-308, נוסחה ב-:256) ומיזוג
text+image ב-_merge (hybrid_search.py:311-389, נוסחה ב-:356-357), שניהם עם
k = MULTIMODAL_RRF_K. אוכף את G7.
מצב: כבר ממומש (codify, לא gap) — הקוד הקיים מיישם RRF נכון בשני המיזוגים. ה-invariant
מקבע את ההתנהגות הקיימת כחוזה. הפרה ידועה: —
INV-RET4: איכות-אחזור נמדדת ב-eval harness עומד (precision + recall)
כלל: איכות-האחזור נמדדת אמפירית — precision ו-recall — מול סט-שאילתות מתויג קבוע
(labeled query set) ב-eval harness עומד. כל שינוי בשכבת-האחזור (משקלי-RRF, k, סף-chunk, embedder)
מלווה במדידה לפני/אחרי; אין כוונון "לפי תחושה".
מקורות: Manning, Raghavan & Schütze, Introduction to Information Retrieval (CUP, 2008 — fixed
relevance judgments, precision/recall) · RAG evaluation literature (Lewis et al., 2020 ואחריו) ·
Elastic — relevance evaluation guidance | סטטוס: verified
אכיפה: eval harness עם gold-set מתויג + בדיקת-בריאות תקופתית; שער-CI על שינוי שכבת-האחזור.
אוכף את G8.
הפרה ידועה (GAP): אין כיום eval harness ולא gold-set — קיים רק telemetry.log_search_bg
(search.py:62,118,190,271; precedent_library.py:280) שמתעד שאילתות בפועל, אך אינו מודד
precision/recall מול תיוג (תצפית, לא הערכה). היעד: harness שמריץ סט קבוע ומחזיר metrics →
ממצא ל-audit.
INV-RET5: כל span מוחזר עקיב למקורו
כלל: כל קטע מוחזר נושא עקיבוּת-מקור מלאה — מזהה-מסמך/פסק-דין (case_law_id/document_id/
case_number) ו-locator בתוכו (page_number / chunk_id / supporting_quote להלכה). פלט
ללא ייחוס פתיר אינו תקין; היו"ר חייבת לאמת כל ציטוט מול מקורו.
מקורות: Council of Europe / CEPEJ — European Ethical Charter on AI in judicial systems
(2018, traceability) · RAG attribution (Lewis et al., 2020) · ISO 15489-1:2016 (records
authenticity/integrity) | סטטוס: verified
אכיפה: כל פורמטר-תוצאה כולל מזהה + locator: search.py:77-86 (case_number/page/section),
_format_internal_row (search.py:322-343: case_number/case_name/court + content/page או
rule/quote להלכה). עקיבוּת מלאה מפורטת ב-X5-audit-provenance.md. אוכף את
G9.
הפרה ידועה: —
4. re-index ושינוי-תוכן (G6)
האחזור מסתמך על embeddings מסונכרנים מול התוכן. ה-tsvectors (content_tsv/rule_tsv/meta_tsv)
הם GENERATED ALWAYS … STORED (db.py:778,782,1086) ולכן מתעדכנים אוטומטית; אך ה-embedding vector(1024) אינו generated — הוא תלוי-טריגר-חיצוני, נקודת-drift אפשרית
(02-data-model INV-DM3). שינוי-תוכן חייב להפעיל
re-embed; בדיקת-בריאות מגלה embeddings מיושנים. אוכף את
G6.
5. מצב קיים מול יעד — audit-findings
ההבדלים בין הקוד בפועל ל-TARGET. אלו תסמינים, לא התנהגויות תקינות. כל פריט אומת מול הקוד.
- דליפת-הלכות חוצת-קורפוס (משימה #56).
halacha_filtersנפתחים רק עםreview_status(db.py:3168,db.py:3401) ואינם מוסיפיםcl.source_kind, בעודchunk_filtersכן (db.py:3169,db.py:3402). שני ה-halacha_sqlעושיםJOIN case_lawבלי סינון (db.py:3236-3242,db.py:3463-3482). תסמין: חיפוש בקורפוס החיצוני (search_precedent_library,source_kind="external_upload") יכול להחזיר הלכה שמקורה בהחלטת-ועדה פנימית — ולהפך עבורsearch_internal_decisions(source_kind="internal_committee",internal_decisions.py:418). יעד:halacha_filtersיתחילו ב-cl.source_kind = '{source_kind}'בדיוק כמוchunk_filters(INV-RET1). - אין eval harness — מדידת-איכות לא קיימת. רק
telemetry.log_search_bgמתעד שאילתות (search.py:62,118,190,271); אין gold-set מתויג ואין precision/recall. יעד: harness עומד (INV-RET4). search_decisionsמתעד אזהרה כשאיןpractice_areaאך לא חוסם. ללא פילטר-תחום החיפוש עלול לערבב תחומים משפטיים (search.py:45-49,172-176—logger.warning, ממשיך). יעד: הפרדה לפי תחום נאכפת, לא מומלצת בלבד — תואם את עקרון ההפרדה ב-G5.embeddingאינוGENERATED(בניגוד ל-tsvector). נקודת-drift אפשרית בין תוכן ל-embedding אחרי עדכון (§4; תואם 02-data-model). יעד: טריגר re-embed מובטח + health-check.
6. הפניות-אחיות
- 00-constitution.md — invariants גלובליים (G4–G9) + כללי-הנדסה.
- 01-ingest.md — חוזה-הקליטה שמייצר את ה-chunks/embeddings שהאחזור שולף.
- 02-data-model.md — חוזה-השלמות (searchable) + re-index שהאחזור מסנן לפיהם.
- 05-qa-review.md — שער-הלכה הידני (
review_status) שמגדיר אילו הלכות searchable. - X5-audit-provenance.md — עקיבוּת-מקור מלאה של כל span מוחזר (בסיס ל-INV-RET5).
- X12-digests-radar.md — שכבת-הגילוי (יומונים) שמעל הקורפוסים — מצביעה, לא מצוטטת.