feat(analysis): דגל "לא-נותח" + ניתוח-מחדש מאחד + בדיקת-השפעה (#201)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 11s

WS2b של עיצוב-מחדש זרימת-העבודה. מאפשר ניתוח כתב-הערר לבד ואז איחוד
מסמך-עיקרי שנוסף מאוחר, בלי force-delete גורף, עם diff ליו"ר.

- SCHEMA_V49: documents.claims_extracted_at + claims_extraction_status
  (אירוע-חילוץ per-מסמך, לא נגזר מ-doc_type), + אינדקס חלקי
  idx_documents_claims_pending. idempotent (ADD COLUMN IF NOT EXISTS).
  V48 שמור ל-#357.
- claims_extractor חותם את המסמך אחרי שמירת/אי-מציאת טענות.
- db.primary_docs_not_analyzed (is_primary V47 + claims_extracted_at IS NULL)
  + mark_document_claims_extracted; _row_to_doc חושף claims_analyzed.
- reanalyze_claims (כלי-MCP): snapshot→חילוץ-מאחד רק למסמכים חדשים/לא-נותחו
  (store_claims מחליף per-source ⇒ טענות אחרות נשמרות)→aggregate force=True
  (מסלול קנוני, מוחק רק legal_arguments)→_impact_diff before↔after ליו"ר.
- workflow_status חושף primary_docs_not_analyzed + next-step.
- ספ: 02-data-model §2ג (דגל לא-נותח), 04-analysis-writing §1.3.
- 5 בדיקות-יחידה ל-_impact_diff/_snapshot (פונקציות טהורות).

Invariants:
- G1 (נרמול-במקור): claims_extracted_at נחתם בנקודת-החילוץ, claims_analyzed
  נגזר ממנו — לא תיקון-בקריאה.
- G2 (מסלול קנוני יחיד / merge-not-fork): reanalyze מרחיב את מסלול
  claims_extractor+argument_aggregator הקיים, לא forks; האיחוד דרך
  store_claims per-source; aggregate force=True מוחק רק נגזר (legal_arguments).
- INV-DM (מודל-נתונים): דגל-אירוע per-מסמך, אינדקס חלקי, מקור-אמת יחיד.
- G10 (שער-אנושי): בדיקת-ההשפעה מוצגת ליו"ר, לא מוחלת אוטומטית.
- INV-TOOL idempotency: SCHEMA_V49 idempotent; ניתוח-מחדש חוזר על מסמך
  כבר-נותח הוא refresh נקי (store_methods replace per-source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:14:54 +00:00
parent 99574f0eba
commit dfb2ffe7ce
8 changed files with 375 additions and 11 deletions

View File

@@ -360,6 +360,10 @@ async def extract_and_store_claims(
claims = await extract_claims_with_ai(text, doc_type, party_hint)
if not claims:
# Stamp the document as analysed even with zero claims, so it is not
# perpetually flagged "not-analysed" (#201 — the not-analysed flag is
# about whether extraction *ran*, not whether it found anything).
await db.mark_document_claims_extracted(document_id, status="no_claims")
return {"status": "no_claims", "total": 0, "source": source_name}
# Determine claim_type from document type and title
@@ -368,6 +372,10 @@ async def extract_and_store_claims(
c["claim_type"] = claim_type
stored = await db.store_claims(case_id, claims, source_document=source_name)
# Mark this document analysed (WS2 / #201). store_claims already replaced
# this document's prior claims by source_document — so re-running is a clean
# per-document refresh that does NOT touch other documents' claims (the merge).
await db.mark_document_claims_extracted(document_id, status="completed")
# Summarize by role
role_counts: dict[str, int] = {}