""" Dashboard API Schemas 대시보드 API의 요청/응답 Pydantic 스키마를 정의합니다. YouTube Analytics API 데이터를 프론트엔드에 전달하기 위한 모델입니다. 사용 예시: from app.dashboard.schemas import DashboardResponse, ContentMetric # 라우터에서 response_model로 사용 @router.get("/dashboard/stats", response_model=DashboardResponse) async def get_dashboard_stats(): ... """ from datetime import datetime from typing import Any, Literal, Optional from pydantic import BaseModel, ConfigDict, Field def to_camel(string: str) -> str: """snake_case를 camelCase로 변환 Args: string: snake_case 문자열 (예: "label_en") Returns: camelCase 문자열 (예: "labelEn") Example: >>> to_camel("content_metrics") "contentMetrics" >>> to_camel("this_year") "thisYear" """ components = string.split("_") return components[0] + "".join(x.capitalize() for x in components[1:]) class ContentMetric(BaseModel): """KPI 지표 카드 대시보드 상단에 표시되는 핵심 성과 지표(KPI) 카드입니다. Attributes: id: 지표 고유 ID (예: "total-views", "total-engagement") label: 한글 라벨 (예: "총 조회수") label_en: 영문 라벨 (예: "Total Views") value: 포맷팅된 값 (예: "1.2M", "8.2%") trend: 증감 추세 값 (예: 12.5 → 12.5% 증가) trend_direction: 추세 방향 ("up" 또는 "down") Example: >>> metric = ContentMetric( ... id="total-views", ... label="총 조회수", ... label_en="Total Views", ... value="1.2M", ... trend=12.5, ... trend_direction="up" ... ) """ id: str label: str label_en: str value: str trend: float trend_direction: Literal["up", "down", "-"] = Field(alias="trendDirection") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class MonthlyData(BaseModel): """월별 추이 데이터 전년 대비 월별 조회수 비교 데이터입니다. Attributes: month: 월 표시 (예: "1월", "2월") this_year: 올해 해당 월 조회수 last_year: 작년 해당 월 조회수 Example: >>> data = MonthlyData( ... month="1월", ... this_year=150000, ... last_year=120000 ... ) """ month: str this_year: int = Field(alias="thisYear") last_year: int = Field(alias="lastYear") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class DailyData(BaseModel): """일별 추이 데이터 (mode=day 전용) 최근 30일과 이전 30일의 일별 조회수 비교 데이터입니다. Attributes: date: 날짜 표시 (예: "1/18", "1/19") this_period: 최근 30일 조회수 last_period: 이전 30일 동일 요일 조회수 """ date: str this_period: int = Field(alias="thisPeriod") last_period: int = Field(alias="lastPeriod") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class TopContent(BaseModel): """인기 영상 조회수 기준 상위 인기 영상 정보입니다. Attributes: id: YouTube 영상 ID title: 영상 제목 thumbnail: 썸네일 이미지 URL platform: 플랫폼 ("youtube" 또는 "instagram") views: 포맷팅된 조회수 (예: "125K") engagement: 참여율 (예: "8.2%") date: 업로드 날짜 (예: "2026.01.15") Example: >>> content = TopContent( ... id="video-id-1", ... title="힐링 영상", ... thumbnail="https://i.ytimg.com/...", ... platform="youtube", ... views="125K", ... engagement="8.2%", ... date="2026.01.15" ... ) """ id: str title: str thumbnail: str platform: Literal["youtube", "instagram"] views: str engagement: str date: str model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class AudienceData(BaseModel): """시청자 분석 데이터 시청자의 연령대, 성별, 지역 분포 데이터입니다. Attributes: age_groups: 연령대별 비율 리스트 [{"label": "18-24", "percentage": 35}, ...] gender: 성별 조회수 {"male": 45000, "female": 55000} top_regions: 상위 지역 리스트 [{"region": "서울", "percentage": 42}, ...] Example: >>> data = AudienceData( ... age_groups=[{"label": "18-24", "percentage": 35}], ... gender={"male": 45, "female": 55}, ... top_regions=[{"region": "서울", "percentage": 42}] ... ) """ age_groups: list[dict[str, Any]] = Field(alias="ageGroups") gender: dict[str, int] top_regions: list[dict[str, Any]] = Field(alias="topRegions") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class PlatformMetric(BaseModel): """플랫폼별 메트릭 플랫폼별 세부 지표입니다 (예: 구독자 증가, 구독 취소). Attributes: id: 메트릭 고유 ID (예: "subscribers-gained") label: 지표명 (예: "구독자 증가") value: 포맷팅된 값 (예: "1.2K") unit: 단위 (선택, 예: "명", "%") trend: 증감 추세 값 trend_direction: 추세 방향 ("up" 또는 "down") Example: >>> metric = PlatformMetric( ... id="subscribers-gained", ... label="구독자 증가", ... value="1.2K", ... trend=8.5, ... trend_direction="up" ... ) """ id: str label: str value: str unit: Optional[str] = None trend: float trend_direction: Literal["up", "down", "-"] = Field(alias="trendDirection") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class PlatformData(BaseModel): """플랫폼별 데이터 플랫폼별로 그룹화된 메트릭 데이터입니다. Attributes: platform: 플랫폼 종류 ("youtube" 또는 "instagram") display_name: 표시할 플랫폼 이름 (예: "YouTube") metrics: 플랫폼별 메트릭 리스트 Example: >>> data = PlatformData( ... platform="youtube", ... display_name="YouTube", ... metrics=[ ... PlatformMetric( ... id="subscribers", ... label="구독자 증가", ... value="1.2K", ... trend=8.5, ... trend_direction="up" ... ) ... ] ... ) """ platform: Literal["youtube", "instagram"] display_name: str = Field(alias="displayName") metrics: list[PlatformMetric] model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class DashboardResponse(BaseModel): """대시보드 전체 응답 GET /api/dashboard/stats 엔드포인트의 전체 응답 스키마입니다. 모든 대시보드 데이터를 포함합니다. Attributes: content_metrics: KPI 지표 카드 리스트 monthly_data: 월별 추이 데이터 (전년 대비) top_content: 인기 영상 리스트 audience_data: 시청자 분석 데이터 platform_data: 플랫폼별 메트릭 데이터 Example: >>> response = DashboardResponse( ... content_metrics=[...], ... monthly_data=[...], ... top_content=[...], ... audience_data=AudienceData(...), ... platform_data=[...] ... ) >>> json_str = response.model_dump_json() # JSON 직렬화 """ content_metrics: list[ContentMetric] = Field(alias="contentMetrics") monthly_data: list[MonthlyData] = Field(default=[], alias="monthlyData") daily_data: list[DailyData] = Field(default=[], alias="dailyData") top_content: list[TopContent] = Field(alias="topContent") audience_data: AudienceData = Field(alias="audienceData") platform_data: list[PlatformData] = Field(default=[], alias="platformData") model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class ConnectedAccount(BaseModel): """연결된 소셜 계정 정보 Attributes: id: SocialAccount 테이블 ID (계정 선택 시 이 값을 social_account_id로 전달) platform: 플랫폼 (예: "youtube") platform_username: 플랫폼 사용자명 (예: "@channelname") platform_user_id: 플랫폼 사용자 고유 ID channel_title: YouTube 채널 제목 (platform_data에서 추출) connected_at: 연동 일시 is_active: 활성화 상태 """ id: int platform: str platform_user_id: str platform_username: Optional[str] = None channel_title: Optional[str] = None connected_at: datetime is_active: bool model_config = ConfigDict( alias_generator=to_camel, populate_by_name=True, ) class ConnectedAccountsResponse(BaseModel): """연결된 소셜 계정 목록 응답 Attributes: accounts: 연결된 계정 목록 """ accounts: list[ConnectedAccount] class CacheDeleteResponse(BaseModel): """캐시 삭제 응답 Attributes: deleted_count: 삭제된 캐시 키 개수 message: 처리 결과 메시지 """ deleted_count: int message: str