from __future__ import annotations from models.common import CamelModel from models.status import Severity, ChannelStatus, DataSource, Language, VideoType, AnnotationType class ScreenshotAnnotation(CamelModel): type: AnnotationType x: float y: float width: float | None = None height: float | None = None label: str | None = None color: str | None = None class ScreenshotEvidence(CamelModel): id: str url: str channel: str captured_at: str caption: str source_url: str | None = None annotations: list[ScreenshotAnnotation] | None = None class DiagnosisItem(CamelModel): category: str detail: str severity: Severity evidence_ids: list[str] | None = None class LeadDoctor(CamelModel): name: str credentials: str rating: float review_count: int class PriceRange(CamelModel): min: str max: str currency: str class LogoImages(CamelModel): circle: str | None = None horizontal: str | None = None korean: str | None = None class BrandColors(CamelModel): primary: str accent: str text: str class RegistryData(CamelModel): district: str | None = None branches: str | None = None brand_group: str | None = None website_en: str | None = None naver_place_url: str | None = None gangnam_unni_url: str | None = None google_maps_url: str | None = None class ClinicSnapshot(CamelModel): # _build_clinic_snapshot은 source 데이터 있을 때만 필드 추가 (`if x:` 가드). # 강남언니/홈페이지 수집 누락된 병원에서 required면 ValidationError로 리포트 전체 실패. name: str | None = None name_en: str | None = None staff_count: int | None = None lead_doctor: LeadDoctor | None = None overall_rating: float | None = None total_reviews: int | None = None certifications: list[str] = [] location: str | None = None phone: str | None = None domain: str | None = None logo_images: LogoImages | None = None brand_colors: BrandColors | None = None source: DataSource | None = None registry_data: RegistryData | None = None class ChannelScore(CamelModel): channel: str icon: str score: int max_score: int status: Severity headline: str class WeeklyViewGrowth(CamelModel): absolute: int percentage: float class EstimatedRevenue(CamelModel): min: int max: int class LinkedUrl(CamelModel): label: str url: str class TopVideo(CamelModel): title: str views: int uploaded_ago: str type: VideoType duration: str | None = None class YouTubeAudit(CamelModel): channel_name: str handle: str subscribers: int total_videos: int total_views: int weekly_view_growth: WeeklyViewGrowth estimated_monthly_revenue: EstimatedRevenue avg_video_length: str upload_frequency: str channel_created_date: str channel_description: str linked_urls: list[LinkedUrl] playlists: list[str] top_videos: list[TopVideo] diagnosis: list[DiagnosisItem] class InstagramAccount(CamelModel): handle: str language: Language label: str posts: int followers: int following: int category: str profile_link: str highlights: list[str] reels_count: int content_format: str profile_photo: str bio: str class InstagramAudit(CamelModel): accounts: list[InstagramAccount] = [] diagnosis: list[DiagnosisItem] = [] class BrandInconsistencyValue(CamelModel): channel: str value: str is_correct: bool class BrandInconsistency(CamelModel): field: str values: list[BrandInconsistencyValue] impact: str recommendation: str class FacebookPage(CamelModel): url: str page_name: str language: Language label: str followers: int following: int category: str bio: str logo: str logo_description: str link: str linked_domain: str reviews: int recent_post_age: str has_whatsapp: bool | None = None post_frequency: str top_content_type: str | None = None engagement: str class FacebookAudit(CamelModel): pages: list[FacebookPage] = [] diagnosis: list[DiagnosisItem] = [] brand_inconsistencies: list[BrandInconsistency] = [] consolidation_recommendation: str | None = None class OtherChannel(CamelModel): name: str status: ChannelStatus details: str url: str | None = None class TrackingPixel(CamelModel): name: str installed: bool details: str | None = None class SnsLink(CamelModel): platform: str url: str location: str class AdditionalDomain(CamelModel): domain: str purpose: str class WebsiteAudit(CamelModel): primary_domain: str additional_domains: list[AdditionalDomain] sns_links_on_site: bool sns_links_detail: list[SnsLink] | None = None tracking_pixels: list[TrackingPixel] main_cta: str class AsIsToBeItem(CamelModel): area: str as_is: str to_be: str class StrategyDetail(CamelModel): strategy: str detail: str class PlatformStrategy(CamelModel): platform: str icon: str current_metric: str target_metric: str strategies: list[StrategyDetail] class NewChannelProposal(CamelModel): channel: str priority: str rationale: str class TransformationProposal(CamelModel): brand_identity: list[AsIsToBeItem] content_strategy: list[AsIsToBeItem] platform_strategies: list[PlatformStrategy] website_improvements: list[AsIsToBeItem] new_channel_proposals: list[NewChannelProposal] class RoadmapTask(CamelModel): task: str completed: bool class RoadmapMonth(CamelModel): month: int title: str subtitle: str tasks: list[RoadmapTask] class KPIMetric(CamelModel): metric: str current: str target_3_month: str target_12_month: str class MarketingReportResponse(CamelModel): id: str clinic_name: str | None = None clinic_name_en: str | None = None created_at: str target_url: str overall_score: int clinic_snapshot: ClinicSnapshot channel_scores: list[ChannelScore] youtube_audit: YouTubeAudit instagram_audit: InstagramAudit facebook_audit: FacebookAudit other_channels: list[OtherChannel] website_audit: WebsiteAudit problem_diagnosis: list[DiagnosisItem] transformation: TransformationProposal roadmap: list[RoadmapMonth] kpi_dashboard: list[KPIMetric] screenshots: list[ScreenshotEvidence] | None = None