358 lines
7.3 KiB
Python
358 lines
7.3 KiB
Python
from __future__ import annotations
|
|
from pydantic import BaseModel
|
|
from models.status import Severity, ChannelStatus, DataSource, Language, VideoType, AnnotationType
|
|
|
|
|
|
class ScreenshotAnnotation(BaseModel):
|
|
type: AnnotationType
|
|
x: float
|
|
y: float
|
|
width: float | None = None
|
|
height: float | None = None
|
|
label: str | None = None
|
|
color: str | None = None
|
|
|
|
|
|
class ScreenshotEvidence(BaseModel):
|
|
id: str
|
|
url: str
|
|
channel: str
|
|
captured_at: str
|
|
caption: str
|
|
source_url: str | None = None
|
|
annotations: list[ScreenshotAnnotation] | None = None
|
|
|
|
|
|
class DiagnosisItem(BaseModel):
|
|
category: str
|
|
detail: str
|
|
severity: Severity
|
|
evidence_ids: list[str] | None = None
|
|
|
|
|
|
# --- ClinicSnapshot ---
|
|
|
|
class LeadDoctor(BaseModel):
|
|
name: str
|
|
credentials: str
|
|
rating: float
|
|
review_count: int
|
|
|
|
|
|
class PriceRange(BaseModel):
|
|
min: str
|
|
max: str
|
|
currency: str
|
|
|
|
|
|
class LogoImages(BaseModel):
|
|
circle: str | None = None
|
|
horizontal: str | None = None
|
|
korean: str | None = None
|
|
|
|
|
|
class BrandColors(BaseModel):
|
|
primary: str
|
|
accent: str
|
|
text: str
|
|
|
|
|
|
class RegistryData(BaseModel):
|
|
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(BaseModel):
|
|
name: str
|
|
name_en: str
|
|
staff_count: int
|
|
lead_doctor: LeadDoctor
|
|
overall_rating: float
|
|
total_reviews: int
|
|
certifications: list[str]
|
|
location: str
|
|
phone: str
|
|
domain: str
|
|
logo_images: LogoImages | None = None
|
|
brand_colors: BrandColors | None = None
|
|
source: DataSource | None = None
|
|
registry_data: RegistryData | None = None
|
|
|
|
|
|
# --- ChannelScore ---
|
|
|
|
class ChannelScore(BaseModel):
|
|
channel: str
|
|
icon: str
|
|
score: int
|
|
max_score: int
|
|
status: Severity
|
|
headline: str
|
|
|
|
|
|
# --- YouTube ---
|
|
|
|
class WeeklyViewGrowth(BaseModel):
|
|
absolute: int
|
|
percentage: float
|
|
|
|
|
|
class EstimatedRevenue(BaseModel):
|
|
min: int
|
|
max: int
|
|
|
|
|
|
class LinkedUrl(BaseModel):
|
|
label: str
|
|
url: str
|
|
|
|
|
|
class TopVideo(BaseModel):
|
|
title: str
|
|
views: int
|
|
uploaded_ago: str
|
|
type: VideoType
|
|
duration: str | None = None
|
|
|
|
|
|
class YouTubeAudit(BaseModel):
|
|
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]
|
|
|
|
|
|
# --- Instagram ---
|
|
|
|
class InstagramAccount(BaseModel):
|
|
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(BaseModel):
|
|
accounts: list[InstagramAccount]
|
|
diagnosis: list[DiagnosisItem]
|
|
|
|
|
|
# --- Facebook ---
|
|
|
|
class BrandInconsistencyValue(BaseModel):
|
|
channel: str
|
|
value: str
|
|
is_correct: bool
|
|
|
|
|
|
class BrandInconsistency(BaseModel):
|
|
field: str
|
|
values: list[BrandInconsistencyValue]
|
|
impact: str
|
|
recommendation: str
|
|
|
|
|
|
class FacebookPage(BaseModel):
|
|
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
|
|
post_frequency: str | None = None
|
|
top_content_type: str | None = None
|
|
engagement: str | None = None
|
|
|
|
|
|
class FacebookAudit(BaseModel):
|
|
pages: list[FacebookPage]
|
|
diagnosis: list[DiagnosisItem]
|
|
brand_inconsistencies: list[BrandInconsistency]
|
|
consolidation_recommendation: str
|
|
|
|
|
|
# --- 기타 채널 / 웹사이트 ---
|
|
|
|
class OtherChannel(BaseModel):
|
|
name: str
|
|
status: ChannelStatus
|
|
details: str
|
|
url: str | None = None
|
|
|
|
|
|
class TrackingPixel(BaseModel):
|
|
name: str
|
|
installed: bool
|
|
details: str | None = None
|
|
|
|
|
|
class SnsLink(BaseModel):
|
|
platform: str
|
|
url: str
|
|
location: str
|
|
|
|
|
|
class AdditionalDomain(BaseModel):
|
|
domain: str
|
|
purpose: str
|
|
|
|
|
|
class WebsiteAudit(BaseModel):
|
|
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
|
|
|
|
|
|
# --- Transformation ---
|
|
|
|
class AsIsToBeItem(BaseModel):
|
|
area: str
|
|
as_is: str
|
|
to_be: str
|
|
|
|
|
|
class StrategyDetail(BaseModel):
|
|
strategy: str
|
|
detail: str
|
|
|
|
|
|
class PlatformStrategy(BaseModel):
|
|
platform: str
|
|
icon: str
|
|
current_metric: str
|
|
target_metric: str
|
|
strategies: list[StrategyDetail]
|
|
|
|
|
|
class NewChannelProposal(BaseModel):
|
|
channel: str
|
|
priority: str
|
|
rationale: str
|
|
|
|
|
|
class TransformationProposal(BaseModel):
|
|
brand_identity: list[AsIsToBeItem]
|
|
content_strategy: list[AsIsToBeItem]
|
|
platform_strategies: list[PlatformStrategy]
|
|
website_improvements: list[AsIsToBeItem]
|
|
new_channel_proposals: list[NewChannelProposal]
|
|
|
|
|
|
# --- Roadmap / KPI ---
|
|
|
|
class RoadmapTask(BaseModel):
|
|
task: str
|
|
completed: bool
|
|
|
|
|
|
class RoadmapMonth(BaseModel):
|
|
month: int
|
|
title: str
|
|
subtitle: str
|
|
tasks: list[RoadmapTask]
|
|
|
|
|
|
class KPIMetric(BaseModel):
|
|
metric: str
|
|
current: str
|
|
target_3_month: str
|
|
target_12_month: str
|
|
|
|
|
|
# --- ReportInput (prompt 템플릿 변수) ---
|
|
|
|
class ReportInput(BaseModel):
|
|
clinic_name: str | None = None
|
|
clinic_name_en: str | None = None
|
|
address: str | None = None
|
|
phone: str | None = None
|
|
slogan: str | None = None
|
|
services: str | None = None
|
|
doctors: str | None = None
|
|
instagram: str | None = None
|
|
facebook: str | None = None
|
|
naver_blog: str | None = None
|
|
youtube: str | None = None
|
|
gangnam_unni: str | None = None
|
|
market_competitors: str | None = None
|
|
market_keywords: str | None = None
|
|
market_trend: str | None = None
|
|
market_target_audience: str | None = None
|
|
|
|
|
|
# --- MarketingReport ---
|
|
|
|
class MarketingReport(BaseModel):
|
|
id: str
|
|
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
|
|
|
|
|
|
ReportOutput = MarketingReport
|
|
|
|
|
|
# --- YouTubeDiagnosis ---
|
|
|
|
class YouTubeDiagnosisInput(BaseModel):
|
|
channel_name: str | None = None
|
|
subscribers: int | None = None
|
|
total_videos: int | None = None
|
|
total_views: int | None = None
|
|
avg_video_length: str | None = None
|
|
upload_frequency: str | None = None
|
|
top_videos: str | None = None
|
|
playlists: str | None = None
|
|
|
|
|
|
class YouTubeDiagnosisOutput(BaseModel):
|
|
diagnosis: list[DiagnosisItem]
|