""" 報告相關 Pydantic Schema """ from datetime import datetime, date from typing import Optional, Literal from pydantic import BaseModel, Field from app.schemas.user import PaginationResponse # ===== Article (簡化版) ===== class ArticleBrief(BaseModel): id: int title: str source_name: str url: str published_at: Optional[datetime] = None class Config: from_attributes = True class ArticleInReport(ArticleBrief): is_included: bool = True # ===== Report ===== class ReportBase(BaseModel): title: str = Field(..., max_length=200) class ReportUpdate(BaseModel): title: Optional[str] = Field(None, max_length=200) edited_summary: Optional[str] = None article_selections: Optional[list[dict]] = None # [{article_id: int, is_included: bool}] class GroupBrief(BaseModel): id: int name: str category: str class Config: from_attributes = True class ReportResponse(ReportBase): id: int report_date: date status: Literal["draft", "pending", "published", "delayed"] group: GroupBrief article_count: Optional[int] = 0 published_at: Optional[datetime] = None class Config: from_attributes = True class ReportDetailResponse(ReportResponse): ai_summary: Optional[str] = None edited_summary: Optional[str] = None articles: list[ArticleInReport] = [] is_favorited: Optional[bool] = False comment_count: Optional[int] = 0 created_at: datetime updated_at: datetime class ReportReviewResponse(ReportResponse): """專員審核用""" ai_summary: Optional[str] = None edited_summary: Optional[str] = None articles: list[ArticleInReport] = [] class ReportListResponse(BaseModel): data: list[ReportResponse] pagination: PaginationResponse class PublishResponse(BaseModel): published_at: datetime notifications_sent: int class RegenerateSummaryResponse(BaseModel): ai_summary: str # ===== Article Full ===== class ArticleSourceBrief(BaseModel): id: int name: str class Config: from_attributes = True class ArticleResponse(BaseModel): id: int title: str source: ArticleSourceBrief url: str published_at: Optional[datetime] = None crawled_at: datetime class Config: from_attributes = True class MatchedGroup(BaseModel): group_id: int group_name: str matched_keywords: list[str] class ArticleDetailResponse(ArticleResponse): content: Optional[str] = None summary: Optional[str] = None author: Optional[str] = None matched_groups: list[MatchedGroup] = [] class ArticleListResponse(BaseModel): data: list[ArticleResponse] pagination: PaginationResponse