from __future__ import annotations from typing import Any, Literal, Optional from pydantic import BaseModel, Field RiskLevel = Literal["critical", "high", "medium", "low", "info"] ApplyStatus = Literal["applied", "not_applied", "error"] AnchorMode = Literal["paragraph", "exact"] class PlaybookSummary(BaseModel): id: str name: str description: str source_path: str updated_at: Optional[str] = None is_editable: bool = False class PlaybookDetail(PlaybookSummary): yaml_text: str class ParagraphText(BaseModel): id: str text: str native_para_id: str | None = None class BodyTextInvariant(BaseModel): paragraphs: list[ParagraphText] text_hash: str class ReviewFinding(BaseModel): id: str model_block_id: str = "" comment_key: str = "" risk_level: RiskLevel clause_type: str issue: str comment: str review_reasoning: dict target_text: str = "" anchor_mode: AnchorMode = "paragraph" start_paragraph_id: str end_paragraph_id: str start_anchor: str end_anchor: str before_anchor: str after_anchor: str confidence: float = Field(ge=0, le=1) stable_id: str = "" native_para_id: str | None = None text_hash: str = "" xml_path: str = "" op_type: Literal["COMMENT", "REWRITE_PARAGRAPH", "INSERT", "DELETE_PARAGRAPH"] = "COMMENT" new_text: Optional[str] = None insert_text: Optional[str] = None position: Optional[Literal["before", "after"]] = None rewrite_mode: Optional[Literal["paragraph", "span"]] = None old_span: Optional[str] = None new_span: Optional[str] = None bookmark_name: Optional[str] = None section_heading: Optional[str] = None class ReviewResponse(BaseModel): schema_version: Literal["word_review_v1"] = "word_review_v1" workflow_id: str findings: list[ReviewFinding] model: Optional[str] = None usage: Optional[dict[str, Any]] = None ResolveStrategy = Literal[ "exact", "whitespace_normalized", "anchor_normalized", "sentence_fallback", "paragraph_fallback", "editable_subspan", "paragraph_anchor", "not_resolved", ] class ApplyResult(BaseModel): id: str status: ApplyStatus message: str paragraph_id: Optional[str] = None match_count: int = 0 failure_code: Optional[str] = None model_block_id: Optional[str] = None writer_paragraph_id: Optional[str] = None native_para_id: Optional[str] = None stable_id: Optional[str] = None xml_path: Optional[str] = None comment_key: Optional[str] = None expected_text_hash: Optional[str] = None actual_text_hash: Optional[str] = None original_target_text: Optional[str] = None resolved_target_text: Optional[str] = None resolve_strategy: Optional[ResolveStrategy] = None resolve_confidence: Optional[float] = None class RunSummary(BaseModel): run_id: str created_at: str created_short: str document_name: str commented_docx_name: Optional[str] = None playbook_id: str playbook_name: str status: str finding_count: int applied_count: int model: Optional[str] = None class RunDetail(RunSummary): document_text: str playbook_text: str result: ReviewResponse apply_report: list[ApplyResult] usage: Optional[dict[str, Any]] = None commented_docx_available: bool = False class ChatMessage(BaseModel): id: str role: Literal["user", "assistant"] content: str created_at: str document_name: Optional[str] = None class ChatConversationSummary(BaseModel): conversation_id: str title: str created_at: str updated_at: str class ChatConversation(BaseModel): conversation_id: str title: str created_at: str updated_at: str messages: list[ChatMessage] = Field(default_factory=list) class ChatConversationDetail(ChatConversation): owner_username: str