Commit Graph

43 Commits

Author SHA1 Message Date
f15cc896d6 feat(claims): אכלוס claims.party_name במקור + backfill — סגירת השדה-המת (#224)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 32s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
עד כה claims.party_name נשאר ריק והצבירה/הכתיבה גזרו את הפרדת-המשיבים
מ-source_document בזמן-ריצה (שדה-מת). עכשיו הוא מאוכלס במקור ונקרא ע"י הצרכנים.

- claims_extractor.extract_and_store_claims: מחתים party_name=source_document
  ל-SPLIT_PARTIES (respondent/permit_applicant); single-voice נשאר ''. אותו
  כלל SPLIT_PARTIES של הצבירה (G2, import לא-מעגלי).
- SCHEMA_V52: backfill idempotent ל-claims קיימים (רק split + party_name ריק).
- argument_aggregator + block_writer._build_claims_context: קוראים
  claims.party_name, עם fallback ל-source_document ל-claims מדור-קודם. התנהגות
  זהה (party_name==source_document) — אפס-רגרסיה, אך השדה כעת מקור-האמת (G1).

py_compile + טעינת-שרשרת-ייבוא נקיים. סוגר את הפתוח האחרון של #224.
Invariants: G1 (אכלוס במקור, לא גזירה-בקריאה), G2 (כלל-פיצול יחיד).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:10:50 +00:00
0fa2281ada fix(block-yod): התייחסות פרטנית לעמדות משיבים מרובים + זיהוי עמדות-מנוגדות (#224)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
השלמת #224 לבלוק י' (דיון והכרעה): הקשר-הטענות כבר מופרד לפי כתב-תשובה
(PR#401, אותו _build_claims_context), אך ה-prompt של block-yod לא הנחה
להתייחס לכל משיב לגופו. נוסף כלל "צדדים מרובים": התייחס לעמדת כל משיב/כתב-תשובה
בנפרד (משיבות 2-3 מול משיבים 4-6), אל תמזג ל"טענות המשיבים" גורפות, וכשעמדות
מנוגדות — ציין והכרע בנימוק.

שינוי-prompt בלבד; משפיע על ניסוח בלוק י' בהחלטות עם כמה משיבים. py_compile ירוק.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 13:19:37 +00:00
1da1585ca8 fix(block-zayin): הפרדת-משיבים בבלוק ז' (טענות הצדדים) — השלמת #224 לשכבת-הכתיבה
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
בלוק ז' של טיוטת-הביניים מיזג את כל המשיבים לסעיף אחד גנרי (או השמיטם) —
כי (א) _build_claims_context קיבץ רק לפי party_role, ו-(ב) ה-prompt נעל
"מבנה קבוע 3 חלקים: עוררים / ועדה מקומית / מבקשי-היתר" — בלי "משיבים" בכלל.
לכן בתיק 1043-02-26 המשיבים 2-3 ו-4-6 לא הופיעו כצדדים מובחנים, אף שהנתונים
(129 טענות-משיבים מפוצלות לפי כתב-תשובה) תקינים ב-claims. הפרדת-המשיבים של
#224 יושמה בצבירה+פאנל בלבד (PR#392) — לא בכותב-הבלוקים.

- `_build_claims_context` מקבץ לפי (party_role, brief) כאשר brief=source_document
  ל-SPLIT_PARTIES (respondent/permit_applicant) — **אותו כלל בדיוק כמו הצבירה**
  (import SPLIT_PARTIES מ-argument_aggregator, G2). התוצאה: סעיף נפרד לכל
  כתב-תשובה ("טענות המשיבים — כתב תשובה משיבות 2 3", "— משיבים 4 6"...).
- prompt בלוק ז': מבנה-צדדים דינמי לפי הכותרות בקונטקסט; הוראה מפורשת לא למזג
  משיבים שונים ולשקף עמדות-מנוגדות; יעד-אורך גמיש לכמה כתבי-תשובה.

משפיע גם על בלוק י' (אותו _build_claims_context) — הקשר מופרד. py_compile ירוק.
עבודה על MCP → דורש host reload + הרצה-מחדש כדי לראות בפועל.
Invariants: G2 (נגזרת-הפרדה יחידה משותפת עם הצבירה), G1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:46:32 +00:00
2b68d6cbeb fix(block-writer): רתימת חלון-1M של Opus 4.8 לבלוק-י (#216)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
הגבול _MAX_PROMPT_CHARS=400_000 היה שומר מלאכותי שתאם את קיר ה-200K-טוקן
של בניית-Opus הסטנדרטית (~400K תווים עברית @~2 תווים/טוקן) — לא גבול-מודל.
Opus 4.8 מציע חלון 1M-טוקן במחיר רגיל; `claude -p` חושף אותו ישירות כ-model-id
`claude-opus-4-8[1m]` (אומת CLI 2.1.196, בלי beta-header). בלוק-י (דיון), שנושא
את התיק המלא כ-source-context (805K/891K תווים), נחסם רק ע"י השומר המיושן.

שינויים:
- block_writer: GENERATION_MODEL_1M + סף אסקלציה _CTX_1M_THRESHOLD_CHARS=350_000.
  פרומפט > הסף → `[1m]`; אחרת הבנייה הסטנדרטית (בלוקים קטנים נשארים זולים/מהירים).
- _MAX_PROMPT_CHARS 400_000 → 1_500_000 (~750K טוקנים; מרווח מתחת ל-1M) כשומר-קשיח
  סופי גם על בניית-ה-1M; הודעת-השגיאה מפנה לצמצום-source-context.
- model_used (provenance) משקף את הבנייה בפועל (200K/[1m]).
- claude_session: אזהרת-גודל מעודכנת (350K + רמז ל-[1m]).
- ספ 04-analysis-writing §1.3: תיעוד האסקלציה כעידון של אותו pin (לא מסלול מקביל, G2).

Invariants: מקיים G2 (אותו מודל-נעוץ, חלון רחב — לא מסלול מקביל), G1, INV-WR*;
לא בולע שגיאות (שומר-קשיח סופי נשאר). xhigh לבלוק-י ללא שינוי.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:38:00 +00:00
88b7dd1004 feat(blocks): block-vav effort medium→low (empirical calibration #208)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Set block-vav (בלוק ו', רקע/background) generation effort from medium to
low in BLOCK_CONFIG. The #208 empirical effort calibration
(scripts/calibrate_effort.py, report
data/eval/effort-calibration-20260630T155054Z.md, n=7 finals) found that
for block-vav, low effort lands closest to chair Dafna Tamir's finalized
text — winning on BOTH the composite distance metric (low 0.4454 vs
medium 0.4749 vs high 0.5381 vs xhigh 0.5413) AND the cleaner style
signal anti_pattern_total (low 1.57 vs medium 2.29 vs high 4.14). Chair
(chaim) approved adopting low for block-vav.

This is the only block with a clean/confident calibration signal —
block-zayin and block-yod-alef signals were ambiguous/tied and are left
unchanged. No other block effort is touched.

Invariants: G8 (effort empirically calibrated, not by-feel);
claude_session-local-only (block_writer runs via the local MCP/claude_local
generation path). Host-side generation parameter — after merge the host
tree needs `git pull` to take effect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 18:40:26 +00:00
eb0653fb80 Merge pull request 'feat(calibration): כיול-אמפירי model×effort מול הסופיים (#208)' (#360) from worktree-agent-a5a22be0318670871 into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-30 12:15:46 +00:00
f935f166a9 feat(calibration): כיול-אמפירי model×effort מול הסופיים (#208)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
A/B harness שמכייל את ה-effort הנעוץ per-בלוק (#204) מול הסופיים של דפנה:
מייצר מחדש כל בלוק דרך מסלול-הייצור (write_block(effort_override=…) →
claude_session.query → claude -p, Opus 4.8, מקומי-בלבד) ומודד מול הסקשן
המתאים בסופי דרך style_distance.block_distance_to_final (change_percent,
anti_pattern_total, golden-ratio deviation, composite distance). ממליץ
per-בלוק על ה-effort הקרוב-ביותר לסופי.

- scripts/calibrate_effort.py — ההארנס (מודל eval_retrieval.py): --self-test
  (offline, מוכיח מדידה+המלצה, אפס DB/CLI) · --dry-run · --efforts/--blocks/
  --case/--repeats. דוח data/eval/effort-calibration-<ts>.{json,md} עם
  גודל-מדגם בולט — עדות-כיוון, לא רגרסיה (מעט סופיים-עלויים).
- style_distance.py — block_distance_to_final + split_final_by_section
  (מקור-מדידה יחיד, G2; reuse compute_diff_stats/count_anti_patterns/chunker).
- block_writer.py — write_block(effort_override=) להזרקת effort per-קריאה
  בלי לדרוס את ברירות-המחדל הנעוצות; רושם את ה-effort האפקטיבי.
- scripts/SCRIPTS.md — ערך חדש.

Invariants: G8 (eval-harness — מדידה אמפירית, לא הנחה) · G2 (reuse של
style_distance/learning_loop — אין מסלול-מדד מקביל) · claude_session
local-only (reference_claude_generation_path) · INV-LRN4/5 (השוואה מול
הסופי, מדידת-סגנון; אין מהות-תיק נגררת). אומת: --self-test 14/14 PASS.
הריצה החיה host-only (claude CLI) — לא ניתנת-להרצה ב-worktree/קונטיינר.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:13:43 +00:00
8d704e6e40 Merge remote-tracking branch 'origin/main' into rebase-357
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
# Conflicts:
#	mcp-server/src/legal_mcp/services/db.py
2026-06-30 12:09:49 +00:00
24e3e2fe80 feat(analysis): analyze_protocol — comparative hearing-protocol analysis (WS4 #203)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
מנתח פרוטוקול-דיון מול כתבי-הטענות: אילו טענות ירדו/חוזקו/עלו-חדשות + חידוד
שאלות משפטיות, ומחלץ נתוני כותרת (א–ד). התוצאה נכנסת לידע-התיק.

- protocol_analyzer.py — ניתוח השוואתי דרך claude_session (Opus 4.8, effort=high,
  מקומי בלבד); שער anti-hallucination (quote-or-retract) ב-_normalize_change;
  חילוץ א–ד + הזנת hearing_date חזרה ל-cases (לא דורס קלט-יו"ר).
- claims_extractor: protocol → claim_type='protocol'; extract_claims קולט protocol.
- block_writer._build_claims_context מחריג claim_type='protocol' מבלוק ז (INV-WR4).
- db: טבלת protocol_analysis (V47, idempotent per-doc) + replace/list helpers.
- tools/drafting + server: analyze_protocol + get_protocol_analysis (extract/get).
- ספ 04-analysis-writing §1.3 + 15 בדיקות.

Invariants: G1 (נרמול-במקור: hearing_date לעמודה הקנונית), G2 (ידע-תיק נגזר
ממקור-האמת protocol+legal_arguments, לא מסלול מקביל), INV-TOOL3 (idempotent
per document_id), INV-TOOL4 (extract/get symmetry), INV-AH (quote-or-retract),
INV-WR4 (בלוק ז = טענות-כתב מקוריות בלבד), claude_session local-only, G12 (נקי
מ-Paperclip — leak-guard עובר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:21:13 +00:00
aad2de9205 feat(drafting): טיוטת-ביניים דטרמיניסטית — נעיצת Opus 4.8 + effort per-בלוק (#204)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
מנגנון-הייצור (claude_session→claude -p) לא העביר model/effort: BLOCK_CONFIG
הגדיר temp+model per-בלוק שהיו מטא-דאטה-מת (block_writer.py:447 העביר רק
prompt/timeout/tools). הייצור רץ על מודל ברירת-המחדל של ה-CLI עם פרומפטים-חופשיים
→ מקור אי-העקביות בטיוטת-הביניים.

תיקון (עקביות-מבנית, WS5):
- נעיצת model="claude-opus-4-8" (GENERATION_MODEL) לכל בלוקי-ה-AI + effort per-בלוק
  שמועבר בפועל דרך claude_session.query → `claude -p --model --effort`.
  מפת-effort: ה=medium · ו=medium · ז=high · ח=medium · ט=high · יא=high · י=xhigh.
- ניסוח-פתיחה קבוע לבלוק-ה (תמיד "לפנינו ערר…", סוף ל-OR "עניינה של החלטה זו").
- מבנה-פנימי קבוע לבלוק-ה; סדר-בלוקים בייצוא כבר קבוע (_INTERIM_BLOCK_ORDER).
- הסרת שדות temp/model המתים מ-BLOCK_CONFIG; "temp" deprecated (Opus 4.7/4.8 דוחים
  temperature→400). decision_blocks.temperature נשמר=0 לתאימות-עמודה בלבד.
- timeout נגזר מ-effort (לא מפיצול sonnet/opus שכבר לא קיים).
- עדכון endpoint reference /api/settings/mcp/blocks + blocks-tab + McpBlock לחשוף effort.
- ספ: docs/block-schema.md §3 (effort מחליף temperature), docs/spec/06-export.md
  (דטרמיניזם-מבני של טיוטת-הביניים).

G2 (מקור-אמת יחיד) — אין מסלול-ייצור מקביל: write_interim_draft מתזמר את אותו
write_and_store_block→write_block של ההחלטה המלאה.
claude_session-local-only — הייצור נשאר דרך גשר-ה-CLI, לא SDK/קונטיינר.
INV-EX1 — סדר-ייצוא דטרמיניסטי נשמר; G11 — עקרונות-הכתיבה ל-12 הבלוקים נשמרים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:18:56 +00:00
a6fb632221 feat(learning): raise writer lessons cap 15 → 60 (config.WRITER_LESSONS_LIMIT) — closes #157
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
עכשיו שהלקחים מנוכי-כפילות (סינתזה #158) ומאושרי-יו"ר, התקרה 15 חתכה בשקט הנחיות-סגנון
מובחנות ו-vetted (רישוי/סגנון 29>15). מעלים ל-60 (env-tunable WRITER_LESSONS_LIMIT) —
מכסה כל shard נוכחי עם מרווח; הסינתזה שומרת על אי-רדודנטיות בצמיחה. ה-WARN על חיתוך נשאר
רשת-הביטחון (חוקה §6) ומפנה לסינתזה/העלאת-התקרה. block_writer צורך דרך config.

Invariants: INV-LRN1/G10 (עדיין רק approved זורם; superseded מוחרג), G2. closes #157 בפועל.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 22:50:55 +00:00
638dbf9b9f feat(learning): feed corpus-measured section ratios to the writer (P2 #7, structural half)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Golden ratios were measured from the corpus (style_distance.measure_corpus_ratios)
but never reached the writer — it used only the hardcoded STRUCTURE_GUIDANCE text.
So the structural target was frozen, and drafts kept deviating (e.g. discussion
block 10% vs a 40-47% target) with no corrective signal.

_structure_guidance_with_corpus(outcome, practice_area) now appends the
corpus-measured section ratios for the block's outcome to the structure guidance,
at both writer entry points (write_block + get_block_context) via one shared helper
(G2). Because measure_corpus_ratios reads style_corpus — which grows with every
enrolled final (see exemplar-growth, #343) — the writer's structural target now
reflects Dafna's ACTUAL current distribution and improves as the corpus grows.
Deterministic (no LLM); best-effort.

This is the STRUCTURAL half of the voice-profile-refresh (#7). The remaining half —
LLM-regenerating the abstract voice-fingerprint PROSE from the corpus (chair-gated)
— is a distinct larger initiative, deliberately deferred rather than rushed.

Invariants: G1 (the writer gets the live measured target, not a frozen constant),
G2 (one structure-guidance builder shared by both entry points; reuses the existing
measure_corpus_ratios — no parallel measurement), INV-LRN4 (the corpus the loop
grows now feeds the writer's structural target).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 22:10:39 +00:00
d7201736f2 fix(writer): always feed canonical anti-patterns to the writer
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The learning loop measured anti-patterns but never corrected them. style_distance
detects markdown headers / bullet lists / mid-paragraph mini-lists (from the
canonical lessons.ANTI_PATTERNS), but the writer only received anti-pattern
guidance if a chair `anti_patterns` override existed in appeal_type_rules — and
none does. So _build_style_context's `if ov:` branch injected nothing, the writer
was never told to avoid them, and drafts kept emitting them (8137: 28 hits, the
worst, newest — anti-patterns were trending UP, not down).

Anti-patterns are structural invariants of Dafna's voice (continuous legal
narrative — no markdown, no bullets), not overridable preferences. So inject the
canonical ANTI_PATTERNS notes ALWAYS, from the same list style_distance measures
against (single source of truth), with any chair additions layered on top. This
closes the measure-but-don't-correct gap: the next draft should show the markdown/
bullet anti-patterns drop, and Path A (style_distance_history) will confirm it.

Invariants: G1 (correct at source — the writer, not a post-hoc stripper), G2
(one canonical anti-pattern list shared by detection and instruction — no parallel
list), INV-LRN4 (closes the feedback half of the draft↔final loop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:24:24 +00:00
4be9cf8543 feat(plans): מרשם-תכניות קנוני (V38) + נוסח-ציטוט אחיד דטרמיניסטי לבלוק ט
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
מוסיף ישות קנונית לתכניות בניין-עיר (תב"ע) שחוזרות בין תיקים — SSOT לזהות+תוקף
(פרסום למתן תוקף ברשומות + מס' ילקוט-הפרסומים) + משפט-ייעוד — במקום גזירה-מחדש
מהשומות בכל תיק. בלוק ט מצטט את התוקף בנוסח אחיד דטרמיניסטי (format_plan_citation),
כך שתאריך-פרסום/מס'-ילקוט לעולם לא מהוזים ע"י ה-LLM.

- DB: טבלת plans (V38) + CRUD + _normalize_plan_number (G1) + format_plan_citation;
  upsert idempotent (G3) עם כלל-מיזוג: תוקף מאושר לא נדרס — סתירה נרשמת ב-discrepancies
  (G10 / אין בליעה שקטה).
- services/plans_extractor.py: חילוץ עובדתי (claude CLI מקומי) → pending_review.
- block_writer.py: _build_plans_registry_context מזריק משפטי-ציטוט מאושרים בלבד לבלוק ט;
  תכניות חסרות/לא-מאושרות מסומנות במפורש (לא נבלעות).
- tools/plans.py + server.py: extract_plans / plan_get / plan_search / plan_list /
  plan_upsert / plan_review (שער-יו"ר G10), עם extract/get-symmetry (X9).
- scripts/backfill_plans_registry.py: ייבוא מקורפוס-ההחלטות (טיוטות + סופיי-דפנה).
- docs: block-schema (בלוק ט), SKILL, spec 02-data-model + 04.

Invariants: G1/INV-DM2/X1 (מזהה מנורמל בכתיבה) · G2/INV-DM6 (מקור-אמת יחיד, appraiser_facts
ללא שינוי) · G3 (upsert) · INV-DM4/G9 (provenance) · INV-DM5/G10 (review_status) ·
INV-AH (ציטוט דטרמיניסטי) · G5 (lookup לא קורפוס) · G11/block-schema (נוסח-הציטוט) · X9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:46:26 +00:00
0d8cc31a2b feat(storage): seal INV-STG1 write path — 15 dual-write seals + CI leak-guard + tripwire
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
אחרי ה-cutover ל-s3-only, אודיט מצא 15 אתרי-כתיבת-בלוב שעוקפים את storage.py (uploads/
finalize/exports/training/research-backup/precedents/bulletins/draft) — קובץ ינחת
בתיקיות-הישנות אך **לא** ב-MinIO → יאבד בניקוי, לא מוגש, לא מגובה. ה-pipeline (ingest/
extract) עדיין קורא לפי file_path מהדיסק, אז ביטול-מוחלט של כתיבה-לדיסק דורש read-wiring
מלא (Phase 2, משימה נפרדת). תיקון בטוח עכשיו = **dual-write seal**.

- storage.py: `mirror`/`mirror_file` (+ sync) — best-effort persist ל-S3 כשה-backend
  s3/dual (no-op ב-filesystem; כשל S3 נרשם, לא שובר request — DualBackend philosophy).
- web/app.py: helpers `_seal_blob`/`_seal_blob_file` + 14 אתרים אטומים (storage.mirror
  אחרי כתיבת-הדיסק; הדיסק נשאר ל-pipeline). block_writer.py: draft אטום (async).
- **CI leak-guard** (test_storage_write_leak_guard): נכשל על כל כתיבת-בלוב-לדיסק
  (write_bytes/write_text/shutil.copy*/open(wb)) ב-web/+services ללא מרקר `# noqa: STG1`.
  כל ה-benign (fallbacks/tmp/staging/git-metadata/flag/state) מסומנים עם נימוק. storage.py
  מוחרג (הוא המימוש).
- **tripwire** (scripts/storage_leak_tripwire.py): ניטור-ריצה — בלובים בדיסק שלא ב-MinIO
  (json-key match, bucket per-file). אומת חי: 0 דליפות.

invariants: INV-STG1 (כל I/O דרך storage / ממורר אליו) · INV-STG6 · feedback_silent_swallow
(mirror רושם warning, לא bare-except). Phase 2 (read-wire ה-pipeline → להפיל את עותק-הדיסק)
= follow-up. tests: 4 mirror + 1 leak-guard + 6 serve_blob + 18 storage קיימות עוברות.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:57:12 +00:00
64db643e6d fix(writer): disable tools on block_writer + style_analyzer claude_session calls
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
המשך ל-#182 — שני האתרים שנותרו עם query()‎ ליצירת-טקסט/ניתוח, ששמרו על
ברירת-המחדל של ה-CLI (כל הכלים פעילים) ולכן חשופים לאותו error_max_turns:
המודל פולט stop_reason:"tool_use", מפיל את --max-turns 1, ומאלץ retry יקר.

- block_writer.py:413 — כתיבת פרוזת בלוק (Opus/Sonnet). יצירת-טקסט טהורה,
  אף פעם לא צריך כלי.
- style_analyzer.py:166/183/196 — single/multi-pass + synthesis; הפלט מפוענח
  כ-JSON (_parse_and_store_patterns/_extract_json). text→JSON טהור.

מיישר את שני האחרונים לאותו מסלול קנוני (claude_session.query(tools="")).
עכשיו כל קריאות ה-LLM שאינן צריכות כלים מעבירות tools="".

Invariants: מקיים INV-G2 (מסלול קנוני יחיד; סימטריה). אין בליעה שקטה (§6).
ללא שינוי-ספ.

בדיקות: py_compile נקי; 18 בדיקות (block/style/writer) עוברות.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:03:37 +00:00
a571ad535b fix(#88+#87): סנכרון DB↔file אוטומטי + claims_coverage מבחין כתב-ערר מתכתובת
#88 (DB↔file, lessons #35): drafts/decision.md דרסה את עצמה רק ב-save_block_content;
renumber_all_blocks + נתיבי store_block אחרים השאירו את הקובץ stale → QA נכשל
פעמיים על אותה בעיה (CMPA-62). תיקון: _update_draft_file הפך ל-hook אוטומטי
(מקבל decision_id, מאתר case פנימית) שנקרא מ-store_block (כל persist) ומ-
renumber_all_blocks. legal-qa ממילא קורא מ-DB → שני הצדדים זהים תמיד.

#87 (claims_coverage, 1033-25): טענות מתכתובת (claim_type='reply' — תגובה/
השלמת-טיעון) סומנו "לא נענו" כ-false-positive. תיקון: check_claims_coverage
דורש מענה רק לטענות כתב-הערר (claim_type='claim', appellant); reply/תכתובת
מוחרגות. בקבלה מלאה הסף מוקל (0.2→0.4) כי העורר זכה במלואו.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:54:31 +00:00
e4fbda6c1f feat(style-acq T12): /methodology — קטגוריות ביטויי-מעבר + אנטי-דפוסים
מרחיב את עורך-הפרופיל ב-/methodology עם 2 קטגוריות נוספות שהכותב (T15)
והמדד (T7) צורכים — כך שהיו"ר עורכת אותן והעריכה זורמת לכתיבה:

- app.py: _METHODOLOGY_DEFAULTS += transition_phrases (מקובץ לפי תוצאה) +
  anti_patterns (מ-lessons.ANTI_PATTERNS). דרך ה-CRUD הגנרי הקיים (appeal_type_rules).
- block_writer (T15 loop): קורא overrides גם ל-transition_phrases + anti_patterns.
- web-ui: GenericMethodologyPanel (עורך key→JSON) + 2 טאבים ב-/methodology.

voice_invariants (doc) — נדחה (לא key-value). G11, INV-LRN4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:08:44 +00:00
2e20e27e17 feat(style-acq T1-T3): קורפוס-דוגמאות של דפנה לכותב (style_exemplars)
ממלא את ערוץ-הדוגמאות (B) של מערכת רכישת-הסגנון: הכותב מאחזר פסקאות-בלוק
אמיתיות של דפנה בזמן כתיבה, ממוקדות section+outcome+practice_area.

T1 — תשתית + backfill:
- SCHEMA_V27: טבלת style_exemplars (purpose-built — בלי תיקים מזויפים בשרשרת
  decision_paragraphs). decision_number/source/section/outcome/practice_area+embedding.
- db: insert/delete/search_style_exemplars + count_style_exemplars.
- scripts/backfill_style_exemplars.py: מפצל קורפוס דפנה (style_corpus +
  internal_committee) לסעיפים→פסקאות, embed, שמירה. אידמפוטנטי, dry-run/apply.

T2 — אחזור ממוקד:
- search_style_exemplars(section, outcome, practice_area) — section=hard filter,
  outcome/practice_area=soft. block_writer._build_precedents_context ממפה
  block→section ומאחזר (ראשי), לצד הנתיב הישן (משלים).

T3 — contrastive/adapt:
- הדוגמאות מתויגות "מבנה/קול בלבד — התאם, אל תעתיק תוכן"; פסקה מלאה (1100 תווים).

INV-LRN5 (טוהר — סגנון בלבד). G11. הרצת backfill --apply בנפרד.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 18:10:01 +00:00
b9bdca0572 feat(style-acq T15): הכותב צורך את כל הלמידה (/methodology overrides + /training lessons) + תיקון-מספור
עונה ל"להתחשב במה שכבר למדנו": הכותב התעלם מעריכות היו"ר ב-/methodology
(נשמרו ב-appeal_type_rules אך block_writer קרא רק קבועי lessons.py) ומ-
decision_lessons של /training. עכשיו הכל מגיע לכתיבה.

- db.get_methodology_overrides(category) — overrides של היו"ר (יחסי-זהב,
  כללי-דיון, צ׳קליסטים) מ-appeal_type_rules (כמו merge של ה-API).
- db.get_recent_decision_lessons(limit, practice_area) — לקחי /training.
- _build_style_context(practice_area): מוסיף סעיף " למידה מצטברת — גובר
  על ברירת-מחדל" עם שניהם, אחרי voice-fingerprint (T0). שני ה-callers מעבירים
  practice_area. עובד יחד עם הלולאה (T4/T5) שתזין לאותן טבלאות.

תיקון-מספור (חלק מ-T9, דחוף כי T0 הזריק את הטעות): voice-fingerprint §3.1
תוקן — ההחלטה ממוספרת תמיד (מספור-אוטומטי ב-Word); "ללא מספור" היה
ארטיפקט-חילוץ. האנטי-דפוס האמיתי: רשימת-מיני בתוך פסקה + מספרים ידניים.

INV-LRN4 (הזרמת למידה) · INV-LRN5 (טוהר). G11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:36:32 +00:00
8a3bcd3ffc feat(style-acq T0): הזרקת פרופיל-הקול לכותב + מדיניות-העתקה + הפרדת דוגמאות↔פסיקה
הלוֹבר הראשי של מערכת רכישת-הסגנון. block_writer עבר היום מ"העתקה +
ערבוב-מהות" ל"הכללת-סגנון + הפרדה":

- _build_style_context: טוען את daphna-voice-fingerprint.md (פרופיל-הקול
  המופשט — המנגנון המרכזי) + מדיניות-העתקה מפורשת לפי סוג-תוכן
  (נוסחה→מותר, ניתוח→הכלל, מהות מתיק אחר→אסור). INV-LRN5.
- _build_precedents_context: פוצל לשני זרמים נפרדים —
  daphna_style_exemplars (איך דפנה כותבת) מול case_law_citations (מהות לציטוט).
- block-yod prompt: שני סעיפים מסומנים במקום "פסיקה רלוונטית (צטט מכאן)"
  שערבב סגנון ומהות; הדוגמאות-סגנוניות מתויגות "מבנה/קול בלבד".

INV: G11 (סגנון דפנה), INV-LRN5 (טוהר-הקול). חלק מתוכנית style-acquisition.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 16:20:24 +00:00
701efab726 feat(mcp): FU-14 GAP-51 — איחוד אוצר-המילים של תוצאת-תיק (set_outcome SSoT)
הכרעת-יו"ר: קנוני = 3 תוצאות אמיתיות (rejection/partial_acceptance/full_acceptance);
betterment_levy יוצא מהיותו "תוצאה" ועובר ל-override לפי practice_area.
+ עקרון "אנגלית-ב-DB, עברית-ב-UI": מפת-תוויות SSoT אחת.

lessons.py:
- VALID_OUTCOMES = 3 (הוסר betterment_levy).
- OUTCOME_LABELS_HE (SSoT לתצוגה) + LEGACY_OUTCOME_MAP + canonical_outcome().
- PRACTICE_AREA_OVERRIDES["betterment_levy"] מרכז את כל ה-guidance שהיה מפתוח כ-outcome
  (golden_ratios/opening/summary/discussion/template).
- get_lessons_for_outcome(outcome, practice_area) + format_ratios_comment(..., practice_area)
  מחילים override + מנרמלים legacy.

block_writer.py: STRUCTURE_GUIDANCE קנוני + תווית מ-OUTCOME_LABELS_HE + override betterment.
workflow.set_outcome: קנוני 3 + מיפוי-legacy סלחני; תווית מ-SSoT.
drafting.py: טבלת יחסי-זהב + get_decision_template מודעי-practice_area (override).
web-ui case.ts: הסרת betterment_levy מ-expectedOutcomes (הוא practice_area).
server.py: docstrings קנוניים.

מיגרציה: migrate_gap51_outcomes.py — 9 שורות נורמלו (rejected→rejection וכו'),
גיבוי ב-data/audit/. הקוד canonicalize בקריאה ⇒ backward-compatible גם בלי מיגרציה.

אומת: py_compile (5 קבצים) + בדיקות-יחידה offline (override/legacy/labels) + אימות-DB.
עודכנו X9 §3 + gap-audit (GAP-51 ).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 15:34:49 +00:00
9bfb912bdf fix(audit): _collect_block_sources mirrors None-doc-types (provenance accuracy, FU-7 review)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:40:42 +00:00
769f5020eb feat(audit): block→source provenance via write_block audit event (GAP-19, FU-7)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 21:33:36 +00:00
a9cd8aeb12 fix: prevent write_interim_draft context overflow (465K → ≤300K chars)
Two bugs caused all 5 interim blocks to fail with "Claude CLI failed
(exit 1): unknown error":

1. source_context was embedded BOTH inside the prompt template (via
   {source_context}) AND prepended again in write_block — doubling every
   block's context size (232K chars × 2 = 465K chars).

2. _build_source_context loaded all 9 case documents for every block
   regardless of relevance.

Fixes:
- Remove the duplicate source_context prepend in write_block; the
  template already contains it via {source_context}
- Add per-block document filtering (_BLOCK_DOC_TYPES): block-he/zayin →
  empty, block-chet → protocol only, block-tet → appraisals only
- Add 400K char guard before calling claude -p with a descriptive error
  (vs opaque "exit 1: unknown error")
- Add prompt-size warning and size info in claude_session error messages

Result: block-he 0 chars, block-zayin 0 chars, block-vav ~172K,
block-chet ~45K, block-tet ~300K (all under 400K limit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:49:47 +00:00
28f49defff LLM session: async, 30min timeout, semantic chunking + parallel
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
The claude_session bridge had two structural defects that made any
non-trivial document extraction unreliable:

  1. subprocess.run() blocks the asyncio event loop in the MCP server
     for the full duration of every LLM call (60-180s typical).
  2. The 120-second timeout was below the cold-cache cost of any
     document over ~12K Hebrew characters. Three back-to-back timeouts
     on case 8174-24 dropped 43 appellant claims on the floor.

Phase 1 of the remediation plan — keeps claude_session as the engine
(no Anthropic API switch) and restructures around it:

claude_session.py
  • query / query_json are now async — asyncio.create_subprocess_exec
    instead of subprocess.run, so MCP server can serve other coroutines
    while a call is in flight.
  • DEFAULT_TIMEOUT 120 → 1800 (30 min). High enough that no realistic
    document hits it; bounded so a runaway never zombifies forever.
  • LONG_TIMEOUT 300 → 3600 for opus block writing on full case context.
  • TimeoutError now actually kills the subprocess (asyncio.wait_for
    cancellation alone leaves the child running).

claims_extractor.py
  • _split_by_sections: chunks at numbered sections / Hebrew letter
    headings / "פרק" markers / markdown ##, falls back to paragraph
    breaks, then to hard splits. Targets 12K chars per chunk — small
    enough that each chunk reliably finishes inside the timeout.
  • _extract_chunk: per-chunk retry (1 attempt by default) with
    structured logging on failure. Failed chunks no longer crash the
    overall extraction; they're skipped with a partial-result warning.
  • extract_claims_with_ai now runs chunks in parallel via
    asyncio.gather bounded by a semaphore (CHUNK_CONCURRENCY=3).
    For a 25K-char appeal: was sequential 150-300s, now ~70-90s.

Updated all 9 callers (claims, appraiser facts, block writer, qa
validator, brainstorm, learning loop, style analyzer × 3) to await
the now-async API.

The one-shot scripts/extract_claims_8174.py used to recover 43
appellant claims on case 8174-24 has been moved to .archive/ — phase 1
makes it obsolete. SCRIPTS.md updated.

Phase 2 (background-task wrapper around LLM-bound MCP tools, persistent
llm_tasks table, SSE progress) is the structural follow-up — separate PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 14:21:35 +00:00
c536ed0e63 Edit document doc_type and appraiser side from the case UI
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
Until now changing a document's doc_type required a manual SQL update.
Adds an inline editor on the document badge so the chair can retag
without leaving the case page, and threads an appraiser_side tag
(committee / appellant / deciding) through the appraisal pipeline so
betterment-levy cases — which usually have 2-3 appraisers — render
conflicts with the deciding appraiser's view marked as governing.

Backend
- New appraiser_facts.appraiser_side column (V5.1) populated from
  documents.metadata.appraiser_side at extraction time.
- extract_appraiser_facts now returns status='sides_missing' with the
  list of untagged appraisals instead of running with empty side
  labels — chair must tag every appraisal first via the UI.
- Conflict detection orders entries committee → appellant → deciding so
  the deciding appraiser appears last; block-tet's prompt instructs the
  writer to phrase the deciding appraiser's view as the governing
  factual finding ("ואולם, השמאי המכריע קבע...").
- New PATCH /api/cases/{n}/documents/{doc_id} (Pydantic model with
  whitelist validation) and matching document_update MCP tool. Both
  merge appraiser_side into metadata JSONB instead of touching the
  schema.

UI
- New shared doc-types module exports the canonical 11 doc_type
  options plus the 3 appraiser-side options; both upload-sheet and
  the document badge now read from it instead of duplicating Hebrew
  labels.
- New DocumentTypeEditor renders a Popover off the doc-type Badge
  with two Selects. The save button stays disabled while doc_type is
  appraisal but no side has been picked, mirroring the backend
  enforcement so the user finds out before triggering extraction.
- usePatchDocument React-Query mutation invalidates the case detail
  on success so the badge updates without a manual refresh.
2026-04-19 06:26:51 +00:00
c619c22a51 Add pre-ruling interim draft (טיוטת ביניים) for appeals committee
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
Lets the chair generate a partial decision DOCX before the discussion-and-
ruling block is decided. Same template, skill and DOCX styling as the final
decision (David, RTL, bookmarks) — only the block selection and order differ:
רקע (ו) → תכניות+היתרים (ט) → טענות (ז) → הליכים (ח). The opening (ה),
ruling (י), summary (יא), and signatures (יב) are omitted.

- New appraiser_facts table + CRUD + conflict detection in db.py (V5 schema).
  Conflict = same plan/permit identifier reported differently by 2+ appraisers.
- New appraiser_facts_extractor service: per-appraisal Claude extraction of
  plans + permits with raw quotes and page numbers.
- block-tet prompt extended with a permits sub-section sourced from the
  extracted facts, plus an explicit instruction to flag inter-appraiser
  conflicts in neutral wording without resolving them (deferred to block-yod).
- block-chet prompt extended with a post-hearing materials context sourced
  from documents.metadata.is_post_hearing.
- docx_exporter.export_decision now accepts mode='interim' which reorders
  the blocks per the chair's mental model and writes
  טיוטת-ביניים-v{N}.docx (versioned independently of regular drafts).
- 3 new MCP tools: extract_appraiser_facts, write_interim_draft,
  export_interim_draft. write_interim_draft auto-runs extraction if the
  appraiser_facts table is empty for the case.
2026-04-18 13:28:04 +00:00
3da4d73498 Upgrade agents to Claude Opus 4.7
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
- legal-analyst: opus 4.6 → opus 4.7
- legal-proofreader: opus 4.6 → opus 4.7
- legal-writer: sonnet 4.6 → opus 4.7 (complex block writing benefits from stronger model)
- block_writer MODEL_MAP: updated opus ID to 4.7

Opus 4.7 brings: high-res images (2576px), better file-based memory,
improved DOCX generation, and task budgets for agentic loops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 16:10:56 +00:00
be9fa9e712 Add decision-writing methodology based on FJC, Garner, Posner sources
"בית ספר להחלטות" Phase 2 — the system now has formal analytical
methodology for building quasi-judicial decisions, separate from
Dafna's writing style (SKILL.md) and content checklists.

What was done:
- Downloaded 5 authoritative sources (~341K words): FJC Judicial
  Writing Manual (1991+2020), Garner Legal Writing in Plain English,
  Posner How Judges Think, Scalia/Garner Making Your Case
- Extracted principles from all sources into intermediate docs
- Synthesized into docs/decision-methodology.md (3,400 words,
  12 sections, 10 guiding principles)
- Integrated methodology into block-yod prompt via {methodology_guidance}
- Restructured legal-writer agent workflow to follow analytical stages
- Made "answer all claims" flexible (bundle/skip via chair_directions)
- Added methodology compliance check (#7) to legal-qa agent
- Updated all knowledge files (CLAUDE.md, SKILL.md, lessons, corpus)

Three-layer architecture:
1. Methodology (decision-methodology.md) — universal, how to think
2. Content checklists (lessons.py) — specific per appeal subtype
3. Style (SKILL.md) — Dafna's personal writing patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 23:29:16 +00:00
0fef20e272 Add content checklists for block-yod and chair feedback system
Addresses Dafna's observation that licensing decisions lack comprehensive
planning discussion. Systematic corpus analysis of all 24 training decisions
revealed the system learned writing style but not substantive content.

Changes:
- Corpus analysis of all 24 decisions (docs/corpus-analysis.md)
- 5 content checklists by appeal subtype injected into block-yod prompt
- chair_feedback DB table + API endpoints + MCP tools
- Feedback management page in Next.js UI (/feedback)
- Navigation updated with "הערות יו״ר" link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:58:28 +00:00
4df2040a40 Fix: save_block_content now writes draft file + writer must update status
Two issues that caused QA agent to fail:
1. save_block_content saved to DB only — now also rebuilds drafts/decision.md
2. legal-writer.md now has explicit mandatory step: case_update(status="drafted")

Without these, workflow_status reports has_draft=false and QA can't run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:25:53 +00:00
bacb330a2a Replace all Anthropic API calls with Claude Code session (claude -p)
New module claude_session.py provides query() and query_json() that
run prompts via `claude -p` CLI — uses the claude.ai session, zero API cost.

Converted 6 services:
- claims_extractor.py: extract_claims_with_ai
- brainstorm.py: brainstorm_directions
- block_writer.py: write_block (was streaming+thinking, now simple)
- qa_validator.py: claims_coverage check
- style_analyzer.py: 3 API calls (single pass, multi pass, synthesis)
- learning_loop.py: extract_lessons

Only extractor.py still uses Anthropic API (for PDF OCR with Vision).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:14:08 +00:00
9d0a73a1dc Add context-only mode: Claude Code writes blocks, no API needed
New architecture: MCP provides context, Claude Code writes.

New functions:
- get_block_context(case_id, block_id) → returns full context package
  (prompt, source docs, claims, direction, precedents, style guide)
  WITHOUT calling Anthropic API
- save_block_content(case_id, block_id, content) → saves block to DB

New MCP tools: get_block_context, save_block_content

The old write_block (API-based) still works as fallback.
The new flow uses Claude Code's own model (Opus 4.6, 1M context)
which has no separate API billing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:18:25 +00:00
7033d2d3ee Embed full style guide in block prompts for Dafna's voice
_build_style_context rewritten from 10-line summary to comprehensive
style guide including:
- Tone rules per appeal type (warm for licensing, cold for levy)
- 15 mandatory expressions ("כידוע", "ברי כי", "אין בידנו לקבל")
- Discussion structure rules (continuous prose, conclusion first)
- Per-party phrasing templates (appellants, committee, permit applicants)
- DB patterns grouped by type (phrases, transitions, openings, closings)

This addresses the main quality gap: style rated 2/5 because the output
was "dry and overly formal" vs Dafna's "direct and clear" voice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:12:09 +00:00
7d1dc73112 Fix max_tokens to 16K for Opus (API limit is 32K, need room for thinking)
block-yod max_tokens reduced from 32K to 16K — the API returned
"max_tokens: 32768 > 32000" error. With thinking enabled, the actual
limit for output is lower. 16K is sufficient for discussion blocks.

Also: extractor.py now supports .md files (was missing, blocked
Beit HaKerem upload).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 16:00:49 +00:00
e24e24dac5 Maximize context and output per Anthropic best practices
Per official Anthropic documentation (April 2026):

Output tokens increased to match model capabilities:
- block-yod (discussion): 8K → 32K (Opus supports 128K)
- block-zayin (claims): 4K → 16K
- block-vav (background): 4K → 16K
- claims_extractor: 4K → 8K (fixes truncated JSON)
- qa_validator: 4K → 8K

Source documents sent in full (not truncated):
- Was: 3000 chars per doc, 15K total
- Now: full document text, no truncation
- Reduces hallucinations: "extract word-for-word quotes first"

Prompt structure follows long-context tips:
- Source documents placed FIRST (top of prompt)
- Instructions and query placed LAST
- "Queries at the end improve quality by up to 30%"

Extended thinking uses adaptive mode for Opus 4.6.
Streaming enabled for all requests > 21K tokens.

Unified JSON parsing via parse_llm_json() helper in config.py.
Applied to: classifier, claims_extractor, brainstorm, qa_validator,
learning_loop (5 files).

Also: extractor.py now supports .md files.

Sources:
- https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
- https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/long-context-tips
- https://docs.anthropic.com/en/docs/minimizing-hallucinations
- https://docs.anthropic.com/en/docs/about-claude/models/overview

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:17:43 +00:00
bed9d5c7e9 Improve block-zayin: synthesize claims by topic + fix markdown JSON parsing
block_writer: Rewrote block-zayin prompt to require synthesis by topic
instead of listing each claim separately. Now produces 3 organized
sections (appellants 8, committee 6, permit applicants 3+) instead
of 40 scattered paragraphs. Target: 800-1500 words.

claims_extractor: Fix markdown code block stripping (same bug as
qa_validator had). Enables parsing claims from Claude responses
wrapped in ```json blocks.

Tested on Hecht: block-zayin from 40 paragraphs/1049 words to
17 organized paragraphs/1039 words. Structure now matches Dafna's
original (3 parties, grouped by topic).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:54:42 +00:00
e438740ab4 Add renumber_all_blocks + fix sequential_numbering check for bold format
block_writer: new renumber_all_blocks() function that renumbers all
paragraphs across all blocks sequentially (1, 2, 3...). Handles both
plain "N." and bold "**N.**" formats. Added missing 'import re'.

qa_validator: sequential_numbering check now matches bold-formatted
numbers (**N.**) in addition to plain (N.).

Tested on Hecht: renumbered 115 paragraphs across 7 blocks, QA 6/6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:30:31 +00:00
7781987c3a Fix precedents search + auto-update case parties
block_writer: _build_precedents_context now searches both
paragraph_embeddings (other decisions by Dafna) and case_law_embeddings
(precedent case law). Previously only searched document_chunks which
had no cross-case data. Now returns ~2400 chars from 3 other decisions.

processor: Step 1.6 auto-updates case appellants/respondents from
classifier results when they're empty. Prevents blank party fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:59:33 +00:00
018b5936a1 Fix claims handling: filter block-zayin duplicates, improve QA matching
block_writer: _build_claims_context now filters out block-zayin claims
(from final decision) and uses only claims from original pleadings.
Reduces noise from 78 to 48 real claims for Hecht case.

qa_validator: claims_coverage check rewritten:
- Filter block-zayin claims (same reason)
- Keyword-based matching instead of 3-word phrase matching
- 25% keyword overlap threshold (was: any 3-word match)
- Allow up to 20% uncovered claims before failing
- Check both block-yod and block-zayin for coverage

Result: Hecht case QA goes from 4/6 to 6/6, 47/48 claims covered (98%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:32:29 +00:00
570f745823 Improve block-yod prompt: require minimum length, numbered claims, precedent citations
- Add minimum word count guidance (2000-4000 words)
- Number each claim in claims_context for explicit tracking
- Require 3-5 case law citations minimum
- Fix max_tokens > budget_tokens for extended thinking
- Use streaming for opus+thinking requests (>10min timeout)

Tested on Hecht case: block-yod improved from 1039 to 1927 words.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:28:12 +00:00
d9e5ef0f46 Add full decision writing pipeline: classify, extract, brainstorm, write, QA, export
New services (11 files):
- classifier.py: auto doc-type classification + party identification (Claude Haiku)
- claims_extractor.py: claim extraction from pleadings (Claude Sonnet + regex)
- references_extractor.py: plan/case-law/legislation detection (regex)
- brainstorm.py: direction generation with 2-3 options (Claude Sonnet)
- block_writer.py: 12-block decision writer (template + Claude Sonnet/Opus)
- docx_exporter.py: DOCX export with David font, RTL, headings
- qa_validator.py: 6 QA checks with export blocking on critical failure
- learning_loop.py: draft vs final comparison + lesson extraction
- metrics.py: KPIs dashboard per case and global
- audit.py: action audit log
- cli.py: standalone CLI with 11 commands

Updated pipeline: extract → classify → chunk → embed → store → extract_references
New MCP tools: 29 total (was 16)
New DB tables: audit_log, decisions CRUD, claims CRUD
Config: Infisical support, external service allowlist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 10:21:47 +00:00