# app/schemas.py from pydantic import BaseModel from typing import List, Optional, Dict, Any from datetime import datetime # ---------- Project ---------- class ProjectBase(BaseModel): name: Optional[str] = None class ProjectCreate(ProjectBase): pdf_filename: str class ProjectResponse(ProjectBase): id: int pdf_filename: str status: str created_at: datetime completed_at: Optional[datetime] = None error_message: Optional[str] = None class Config: from_attributes = True class ProjectDetail(ProjectResponse): pages: List["PageResponse"] = [] issues: List["IssueResponse"] = [] class Config: from_attributes = True # ---------- Page ---------- class PageBase(BaseModel): page_number: int width: Optional[int] = None height: Optional[int] = None class PageResponse(PageBase): id: int png_path: Optional[str] = None vlm_description: Optional[str] = None ocr_data: Optional[Dict[str, Any]] = None issue_count: int = 0 class Config: from_attributes = True # ---------- Issue ---------- class IssueBase(BaseModel): issue_type: str severity: str message: str page_number: Optional[int] = None class IssueCreate(IssueBase): bbox_x1: Optional[float] = None bbox_y1: Optional[float] = None bbox_x2: Optional[float] = None bbox_y2: Optional[float] = None dimension_text: Optional[str] = None confidence: Optional[float] = None extra_data: Optional[Dict[str, Any]] = None class IssueResponse(IssueBase): id: int project_id: int bbox_x1: Optional[float] = None bbox_y1: Optional[float] = None bbox_x2: Optional[float] = None bbox_y2: Optional[float] = None dimension_text: Optional[str] = None confidence: Optional[float] = None source: Optional[str] = None created_at: datetime feedback: Optional["FeedbackResponse"] = None page_id: Optional[int] = None class Config: from_attributes = True # ---------- Feedback ---------- class FeedbackCreate(BaseModel): issue_id: int is_true_positive: Optional[bool] = None comment: Optional[str] = None action_taken: Optional[str] = None # fixed / ignored / not_sure class FeedbackResponse(BaseModel): id: int issue_id: int is_true_positive: Optional[bool] = None comment: Optional[str] = None action_taken: Optional[str] = None created_at: datetime class Config: from_attributes = True # ---------- Stats ---------- class StatsResponse(BaseModel): total_projects: int total_issues: int issues_by_type: Dict[str, int] feedback_stats: Dict[str, int] # true_positive, false_positive, unreviewed accuracy_estimate: Optional[float] = None # Доля правильных срабатываний # ---------- Training Data ---------- class TrainingSample(BaseModel): """Один пример для обучения ML-модели.""" issue_id: int issue_type: str severity: str message: str bbox: Dict[str, float] # x1, y1, x2, y2 dimension_text: Optional[str] = None confidence: Optional[float] = None page_number: int is_true_positive: Optional[bool] = None # Для обучения детектора (YOLO) image_path: Optional[str] = None # Путь к PNG страницы crop_path: Optional[str] = None # Вырезанный фрагмент bbox label: Optional[str] = None # "good_dimension" / "bad_placement" / etc. class TrainingDataExport(BaseModel): total_samples: int labeled_samples: int # Есть is_true_positive samples: List[TrainingSample] export_format: str # json / yolo / csv class Config: from_attributes = True