fix(report): ClinicSnapshot/YouTubeAudit/Instagram*/Facebook* Optional 완화

required로 두면 LLM 응답이나 수집 데이터 누락 시 pydantic ValidationError로
리포트 endpoint 전체가 500으로 죽음. 실제 테스트(청담오라클)에서 LLM이
weekly_view_growth, established 등 10개 필드를 null 반환하는 케이스 확인.

- ClinicSnapshot/YouTubeAudit: schemas + models 양쪽 모두 Optional (LLM 입력 검증
  + FastAPI 응답 검증 둘 다 통과 필요)
- InstagramAccount/InstagramAudit/FacebookPage/FacebookAudit: models만 (인스타·페북 빈
  계정/페이지 케이스 대응)
- list[T] 필드는 기본값 [] 부여

트레이드오프: 스키마 레벨 데이터 완결성 보장 약화. 운영하며 자주 비는 필드
패턴 보고 collection 단계 보강 필요.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
Mina Choi 2026-05-29 16:42:04 +09:00
parent 71b605eaa6
commit 5dbc7d7ffe
2 changed files with 94 additions and 84 deletions

View File

@ -68,16 +68,18 @@ class RegistryData(BaseModel):
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
# _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
@ -121,21 +123,23 @@ class TopVideo(BaseModel):
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]
# YouTube 미수집 병원에서 _build_youtube_audit가 채울 수 없는 필드 빔.
# required면 ValidationError로 리포트 실패 → 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
channel_description: str | None = None
linked_urls: list[LinkedUrl] = []
playlists: list[str] = []
top_videos: list[TopVideo] = []
diagnosis: list[DiagnosisItem] = []
# --- Instagram ---

View File

@ -66,16 +66,18 @@ class RegistryData(CamelModel):
class ClinicSnapshot(CamelModel):
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
# _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
@ -115,42 +117,45 @@ class TopVideo(CamelModel):
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]
# YouTube 채널 없는 병원이면 _build_youtube_audit가 채울 수 없는 필드들 (channel_name 등)이 빔.
# required면 ValidationError로 리포트 실패 → 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
channel_description: str | None = None
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
# 인스타 계정(KR/EN) 미수집 시 빈 필드 가능 — 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]
accounts: list[InstagramAccount] = []
diagnosis: list[DiagnosisItem] = []
class BrandInconsistencyValue(CamelModel):
@ -167,31 +172,32 @@ class BrandInconsistency(CamelModel):
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
# 페북 페이지(KR/EN) 미수집 시 빈 필드 가능 — 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
pages: list[FacebookPage] = []
diagnosis: list[DiagnosisItem] = []
brand_inconsistencies: list[BrandInconsistency] = []
consolidation_recommendation: str | None = None
class OtherChannel(CamelModel):