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_overrides가 강남언니 데이터로 일부 필드를 채우고 LLM이 나머지를 채우는데, # 둘 다 누락 가능 — required로 두면 ValidationError로 리포트 전체 실패. name: str | None = None name_en: str | None = None established: str | None = None years_in_business: int | None = None staff_count: int | None = None lead_doctor: LeadDoctor | None = None overall_rating: float | None = None total_reviews: int | None = None price_range: PriceRange | None = None certifications: list[str] = [] media_appearances: list[str] = [] medical_tourism: list[str] = [] location: str | None = None nearest_station: 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): # YouTube 채널 미수집 / LLM 누락 시 None 가능 — 통째 Optional. channel_name: str | None = None handle: str | None = None subscribers: int | None = None total_videos: int | None = None total_views: int | None = None weekly_view_growth: WeeklyViewGrowth | None = None estimated_monthly_revenue: EstimatedRevenue | None = None avg_video_length: str | None = None upload_frequency: str | None = None channel_created_date: str | None = None subscriber_rank: str | None = None channel_description: str | None = None linked_urls: list[LinkedUrl] = [] playlists: list[str] = [] top_videos: list[TopVideo] = [] diagnosis: list[DiagnosisItem] = [] class InstagramAccount(CamelModel): # 계정 미수집 시 None 가능 — 통째 Optional. handle: str | None = None language: Language | None = None label: str | None = None posts: int | None = None followers: int | None = None following: int | None = None category: str | None = None profile_link: str | None = None highlights: list[str] = [] reels_count: int | None = None content_format: str | None = None profile_photo: str | None = None bio: str | None = None 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): # 페이지 미수집 / LLM 누락 시 None 가능 — 통째 Optional. url: str | None = None page_name: str | None = None language: Language | None = None label: str | None = None followers: int | None = None following: int | None = None category: str | None = None bio: str | None = None logo: str | None = None logo_description: str | None = None link: str | None = None linked_domain: str | None = None reviews: int | None = None recent_post_age: str | None = None has_whatsapp: bool | None = None post_frequency: str | None = None top_content_type: str | None = None engagement: str | None = None 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