Files
legal-ai/mcp-server/src/legal_mcp/models.py
Chaim 44f6915f0c
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
feat(status): single source of truth for the case-status model + canonicalize intermediates
Fixes the case where the H1 chip, the pipeline stepper and the manual-changer
dropdown disagreed (e.g. 1043-02-26 on analyst_verified): the status sat between
the canonical 10 and a legacy bucket, so each surface read a different list.

Root fix — ONE authoritative status model + canonicalize the real intermediate
states (chair-approved):

- New mcp-server/.../case_status_model.py — the single registry: ordered
  StatusDef list (key/label/description/phase/selectable/terminal/on_enter),
  the 5 phases, STATUS_ORDER, phase_of/label_of, and a drift assertion that the
  CaseStatus enum matches it. `on_enter` is the seam for a status to *do*
  something on entry, declared next to its definition (no dispatcher wired yet).
- models.CaseStatus enum: analyst_verified + research_complete promoted to
  first-class canonical statuses (the agents set them — they belong in the
  model, not a legacy fallback).
- tools/cases.py: the forward-only STATUS_ORDER guard now derives from the
  registry instead of an inline list.
- GET /api/status-model exposes the model so the frontend mirror can be
  generated against it.
- web-ui case-status.ts mirrors it: the two intermediates moved from the
  legacy map into CASE_STATUSES / PHASES(thinking) / STATUS_LABELS /
  STATUS_DESCRIPTIONS; status-badge icons+tones extended. So the chip (status),
  stepper (its phase) and dropdown (now includes it) all agree.

Invariants: G2 — collapses the scattered status definitions (enum, inline
STATUS_ORDER, two frontend label maps) onto one backend authority + a documented
frontend mirror; no parallel status list remains. Agent-prompt alignment +
backfill follow in a separate change. py_compile + tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 19:31:21 +00:00

117 lines
3.7 KiB
Python

"""Pydantic models for cases, documents, and related entities."""
from __future__ import annotations
import enum
from datetime import date, datetime
from uuid import UUID
from pydantic import BaseModel, Field
# Core case lifecycle. The canonical key set, order, labels, phases and (future)
# per-status actions all live in ONE place — legal_mcp/case_status_model.py —
# which asserts this enum matches it. The frontend mirror is
# web-ui/src/lib/api/case-status.ts (generated against GET /api/status-model).
# The analyst/research intermediate states are first-class canonical statuses
# (the agents set them) — not legacy.
class CaseStatus(str, enum.Enum):
NEW = "new"
PROCESSING = "processing"
DOCUMENTS_READY = "documents_ready"
ANALYST_VERIFIED = "analyst_verified"
RESEARCH_COMPLETE = "research_complete"
OUTCOME_SET = "outcome_set"
DIRECTION_APPROVED = "direction_approved"
QA_REVIEW = "qa_review"
DRAFTED = "drafted"
EXPORTED = "exported"
REVIEWED = "reviewed"
FINAL = "final"
class DocType(str, enum.Enum):
APPEAL = "appeal" # כתב ערר
RESPONSE = "response" # תשובה / כתב תשובה
PROTOCOL = "protocol" # פרוטוקול דיון
PLAN = "plan" # תכנית (תב"ע)
PERMIT = "permit" # היתר בנייה
COURT_DECISION = "court_decision" # פסק דין / החלטת בית משפט
DECISION = "decision" # החלטת ועדה
APPRAISAL = "appraisal" # שומה / חוות דעת שמאית
OBJECTION = "objection" # התנגדות
EXHIBIT = "exhibit" # נספח / מסמך תומך
REFERENCE = "reference" # מסמך עזר אחר
class SectionType(str, enum.Enum):
INTRO = "intro"
FACTS = "facts"
APPELLANT_CLAIMS = "appellant_claims"
RESPONDENT_CLAIMS = "respondent_claims"
LEGAL_ANALYSIS = "legal_analysis"
CONCLUSION = "conclusion"
RULING = "ruling"
OTHER = "other"
class ExtractionStatus(str, enum.Enum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
FAILED = "failed"
class CaseCreate(BaseModel):
case_number: str = Field(description="מספר תיק הערר (לדוגמה: 123-24)")
title: str = Field(description="כותרת קצרה של הערר")
appellants: list[str] = Field(default_factory=list, description="שמות העוררים")
respondents: list[str] = Field(default_factory=list, description="שמות המשיבים")
subject: str = Field(default="", description="נושא הערר")
property_address: str = Field(default="", description="כתובת הנכס")
permit_number: str = Field(default="", description="מספר היתר")
committee_type: str = Field(default="ועדה מקומית", description="סוג הוועדה")
hearing_date: date | None = Field(default=None, description="תאריך דיון")
notes: str = Field(default="", description="הערות")
class CaseInfo(BaseModel):
id: UUID
case_number: str
title: str
appellants: list[str]
respondents: list[str]
subject: str
property_address: str
permit_number: str
committee_type: str
status: CaseStatus
hearing_date: date | None
decision_date: date | None
tags: list[str]
notes: str
created_at: datetime
updated_at: datetime
class DocumentInfo(BaseModel):
id: UUID
case_id: UUID
doc_type: DocType
title: str
file_path: str
extraction_status: ExtractionStatus
page_count: int | None
created_at: datetime
class SearchResult(BaseModel):
chunk_content: str
score: float
case_number: str
document_title: str
section_type: str
page_number: int | None
document_id: UUID
case_id: UUID