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>
This commit is contained in:
2026-04-03 10:21:47 +00:00
parent df7cc4f5a5
commit d9e5ef0f46
21 changed files with 3957 additions and 14 deletions

View File

@@ -140,6 +140,36 @@ async def document_list(case_number: str) -> str:
return await documents.document_list(case_number)
# Claims extraction
@mcp.tool()
async def extract_claims(
case_number: str,
doc_title: str = "",
party_hint: str = "",
) -> str:
"""חילוץ טענות מכתב טענות בתיק. מחלץ טענות לפי צד ושומר ב-DB."""
return await documents.extract_claims(case_number, doc_title, party_hint)
@mcp.tool()
async def get_claims(
case_number: str,
party_role: str = "",
) -> str:
"""שליפת טענות שחולצו לתיק. party_role: appellant/respondent/committee/permit_applicant (ריק=הכל)."""
return await documents.get_claims(case_number, party_role)
# References
@mcp.tool()
async def extract_references(
case_number: str,
doc_title: str = "",
) -> str:
"""זיהוי תכניות, פסיקה וחקיקה מתוך מסמכי תיק. מזהה ומקשר להפניות קיימות ב-DB."""
return await documents.extract_references(case_number, doc_title)
# Search
@mcp.tool()
async def search_decisions(
@@ -193,12 +223,44 @@ async def get_decision_template(case_number: str) -> str:
return await drafting.get_decision_template(case_number)
@mcp.tool()
async def validate_decision(case_number: str) -> str:
"""בדיקת QA — 6 בדיקות איכות על ההחלטה. אם בדיקה קריטית נכשלת — ייצוא חסום."""
return await drafting.validate_decision(case_number)
@mcp.tool()
async def export_docx(case_number: str, output_path: str = "") -> str:
"""ייצוא החלטה לקובץ DOCX מעוצב — גופן David, RTL, כותרות, מספור סעיפים."""
return await drafting.export_docx(case_number, output_path)
@mcp.tool()
async def analyze_style() -> str:
"""ניתוח סגנון על קורפוס ההחלטות של דפנה. מחלץ ושומר דפוסי כתיבה."""
return await drafting.analyze_style()
@mcp.tool()
async def write_block(
case_number: str,
block_id: str,
instructions: str = "",
) -> str:
"""כתיבת בלוק יחיד בהחלטה: block-alef עד block-yod-bet. שומר ב-DB."""
return await drafting.write_block(case_number, block_id, instructions)
@mcp.tool()
async def write_all_blocks(
case_number: str,
start_from: str = "block-alef",
instructions: str = "",
) -> str:
"""כתיבת כל הבלוקים בהחלטה, בלוק אחרי בלוק. שומר כל בלוק מיד."""
return await drafting.write_all_blocks(case_number, start_from, instructions)
# Workflow
@mcp.tool()
async def workflow_status(case_number: str) -> str:
@@ -206,12 +268,55 @@ async def workflow_status(case_number: str) -> str:
return await workflow.workflow_status(case_number)
@mcp.tool()
async def get_metrics(case_number: str = "") -> str:
"""מדדי הצלחה — KPIs לתיק ספציפי או דשבורד כולל. ריק = דשבורד."""
return await workflow.get_metrics(case_number)
@mcp.tool()
async def processing_status() -> str:
"""סטטוס כללי - מספר תיקים, מסמכים, chunks."""
return await workflow.processing_status()
# Outcome & Brainstorming
@mcp.tool()
async def set_outcome(
case_number: str,
outcome: str,
reasoning: str = "",
) -> str:
"""הזנת תוצאה לתיק: rejected (דחייה), accepted (קבלה), partial (קבלה חלקית). אם אין נימוק — מפעיל סיעור מוחות."""
return await workflow.set_outcome(case_number, outcome, reasoning)
@mcp.tool()
async def brainstorm_directions(case_number: str) -> str:
"""סיעור מוחות — הצגת טענות מרכזיות והצעת 2-3 כיוונים אפשריים לנימוק ההחלטה."""
return await workflow.brainstorm_directions(case_number)
@mcp.tool()
async def approve_direction(
case_number: str,
direction_index: int = 0,
additional_notes: str = "",
) -> str:
"""אישור כיוון — יוצר מסמך כיוון מאושר. חובה לפני כתיבת דיון (בלוק י)."""
return await workflow.approve_direction(case_number, direction_index, additional_notes)
@mcp.tool()
async def ingest_final_version(
case_number: str,
file_path: str = "",
final_text: str = "",
) -> str:
"""קליטת גרסה סופית שדפנה חתמה — השוואה לטיוטה וחילוץ לקחים לשיפור עתידי."""
return await workflow.ingest_final_version(case_number, file_path, final_text)
def main():
mcp.run(transport="stdio")