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
parent
71b605eaa6
commit
5dbc7d7ffe
|
|
@ -68,16 +68,18 @@ class RegistryData(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class ClinicSnapshot(BaseModel):
|
class ClinicSnapshot(BaseModel):
|
||||||
name: str
|
# _build_clinic_snapshot은 source 데이터 있을 때만 채움 (`if x:` 가드).
|
||||||
name_en: str
|
# required면 강남언니/홈페이지 누락 병원에서 ValidationError로 리포트 실패.
|
||||||
staff_count: int
|
name: str | None = None
|
||||||
lead_doctor: LeadDoctor
|
name_en: str | None = None
|
||||||
overall_rating: float
|
staff_count: int | None = None
|
||||||
total_reviews: int
|
lead_doctor: LeadDoctor | None = None
|
||||||
certifications: list[str]
|
overall_rating: float | None = None
|
||||||
location: str
|
total_reviews: int | None = None
|
||||||
phone: str
|
certifications: list[str] = []
|
||||||
domain: str
|
location: str | None = None
|
||||||
|
phone: str | None = None
|
||||||
|
domain: str | None = None
|
||||||
logo_images: LogoImages | None = None
|
logo_images: LogoImages | None = None
|
||||||
brand_colors: BrandColors | None = None
|
brand_colors: BrandColors | None = None
|
||||||
source: DataSource | None = None
|
source: DataSource | None = None
|
||||||
|
|
@ -121,21 +123,23 @@ class TopVideo(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class YouTubeAudit(BaseModel):
|
class YouTubeAudit(BaseModel):
|
||||||
channel_name: str
|
# YouTube 미수집 병원에서 _build_youtube_audit가 채울 수 없는 필드 빔.
|
||||||
handle: str
|
# required면 ValidationError로 리포트 실패 → Optional로 받아 부분 응답 허용.
|
||||||
subscribers: int
|
channel_name: str | None = None
|
||||||
total_videos: int
|
handle: str | None = None
|
||||||
total_views: int
|
subscribers: int | None = None
|
||||||
weekly_view_growth: WeeklyViewGrowth
|
total_videos: int | None = None
|
||||||
estimated_monthly_revenue: EstimatedRevenue
|
total_views: int | None = None
|
||||||
avg_video_length: str
|
weekly_view_growth: WeeklyViewGrowth | None = None
|
||||||
upload_frequency: str
|
estimated_monthly_revenue: EstimatedRevenue | None = None
|
||||||
channel_created_date: str
|
avg_video_length: str | None = None
|
||||||
channel_description: str
|
upload_frequency: str | None = None
|
||||||
linked_urls: list[LinkedUrl]
|
channel_created_date: str | None = None
|
||||||
playlists: list[str]
|
channel_description: str | None = None
|
||||||
top_videos: list[TopVideo]
|
linked_urls: list[LinkedUrl] = []
|
||||||
diagnosis: list[DiagnosisItem]
|
playlists: list[str] = []
|
||||||
|
top_videos: list[TopVideo] = []
|
||||||
|
diagnosis: list[DiagnosisItem] = []
|
||||||
|
|
||||||
|
|
||||||
# --- Instagram ---
|
# --- Instagram ---
|
||||||
|
|
|
||||||
|
|
@ -66,16 +66,18 @@ class RegistryData(CamelModel):
|
||||||
|
|
||||||
|
|
||||||
class ClinicSnapshot(CamelModel):
|
class ClinicSnapshot(CamelModel):
|
||||||
name: str
|
# _build_clinic_snapshot은 source 데이터 있을 때만 필드 추가 (`if x:` 가드).
|
||||||
name_en: str
|
# 강남언니/홈페이지 수집 누락된 병원에서 required면 ValidationError로 리포트 전체 실패.
|
||||||
staff_count: int
|
name: str | None = None
|
||||||
lead_doctor: LeadDoctor
|
name_en: str | None = None
|
||||||
overall_rating: float
|
staff_count: int | None = None
|
||||||
total_reviews: int
|
lead_doctor: LeadDoctor | None = None
|
||||||
certifications: list[str]
|
overall_rating: float | None = None
|
||||||
location: str
|
total_reviews: int | None = None
|
||||||
phone: str
|
certifications: list[str] = []
|
||||||
domain: str
|
location: str | None = None
|
||||||
|
phone: str | None = None
|
||||||
|
domain: str | None = None
|
||||||
logo_images: LogoImages | None = None
|
logo_images: LogoImages | None = None
|
||||||
brand_colors: BrandColors | None = None
|
brand_colors: BrandColors | None = None
|
||||||
source: DataSource | None = None
|
source: DataSource | None = None
|
||||||
|
|
@ -115,42 +117,45 @@ class TopVideo(CamelModel):
|
||||||
|
|
||||||
|
|
||||||
class YouTubeAudit(CamelModel):
|
class YouTubeAudit(CamelModel):
|
||||||
channel_name: str
|
# YouTube 채널 없는 병원이면 _build_youtube_audit가 채울 수 없는 필드들 (channel_name 등)이 빔.
|
||||||
handle: str
|
# required면 ValidationError로 리포트 실패 → Optional로 받아 부분 응답 허용.
|
||||||
subscribers: int
|
channel_name: str | None = None
|
||||||
total_videos: int
|
handle: str | None = None
|
||||||
total_views: int
|
subscribers: int | None = None
|
||||||
weekly_view_growth: WeeklyViewGrowth
|
total_videos: int | None = None
|
||||||
estimated_monthly_revenue: EstimatedRevenue
|
total_views: int | None = None
|
||||||
avg_video_length: str
|
weekly_view_growth: WeeklyViewGrowth | None = None
|
||||||
upload_frequency: str
|
estimated_monthly_revenue: EstimatedRevenue | None = None
|
||||||
channel_created_date: str
|
avg_video_length: str | None = None
|
||||||
channel_description: str
|
upload_frequency: str | None = None
|
||||||
linked_urls: list[LinkedUrl]
|
channel_created_date: str | None = None
|
||||||
playlists: list[str]
|
channel_description: str | None = None
|
||||||
top_videos: list[TopVideo]
|
linked_urls: list[LinkedUrl] = []
|
||||||
diagnosis: list[DiagnosisItem]
|
playlists: list[str] = []
|
||||||
|
top_videos: list[TopVideo] = []
|
||||||
|
diagnosis: list[DiagnosisItem] = []
|
||||||
|
|
||||||
|
|
||||||
class InstagramAccount(CamelModel):
|
class InstagramAccount(CamelModel):
|
||||||
handle: str
|
# 인스타 계정(KR/EN) 미수집 시 빈 필드 가능 — Optional.
|
||||||
language: Language
|
handle: str | None = None
|
||||||
label: str
|
language: Language | None = None
|
||||||
posts: int
|
label: str | None = None
|
||||||
followers: int
|
posts: int | None = None
|
||||||
following: int
|
followers: int | None = None
|
||||||
category: str
|
following: int | None = None
|
||||||
profile_link: str
|
category: str | None = None
|
||||||
highlights: list[str]
|
profile_link: str | None = None
|
||||||
reels_count: int
|
highlights: list[str] = []
|
||||||
content_format: str
|
reels_count: int | None = None
|
||||||
profile_photo: str
|
content_format: str | None = None
|
||||||
bio: str
|
profile_photo: str | None = None
|
||||||
|
bio: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class InstagramAudit(CamelModel):
|
class InstagramAudit(CamelModel):
|
||||||
accounts: list[InstagramAccount]
|
accounts: list[InstagramAccount] = []
|
||||||
diagnosis: list[DiagnosisItem]
|
diagnosis: list[DiagnosisItem] = []
|
||||||
|
|
||||||
|
|
||||||
class BrandInconsistencyValue(CamelModel):
|
class BrandInconsistencyValue(CamelModel):
|
||||||
|
|
@ -167,31 +172,32 @@ class BrandInconsistency(CamelModel):
|
||||||
|
|
||||||
|
|
||||||
class FacebookPage(CamelModel):
|
class FacebookPage(CamelModel):
|
||||||
url: str
|
# 페북 페이지(KR/EN) 미수집 시 빈 필드 가능 — Optional.
|
||||||
page_name: str
|
url: str | None = None
|
||||||
language: Language
|
page_name: str | None = None
|
||||||
label: str
|
language: Language | None = None
|
||||||
followers: int
|
label: str | None = None
|
||||||
following: int
|
followers: int | None = None
|
||||||
category: str
|
following: int | None = None
|
||||||
bio: str
|
category: str | None = None
|
||||||
logo: str
|
bio: str | None = None
|
||||||
logo_description: str
|
logo: str | None = None
|
||||||
link: str
|
logo_description: str | None = None
|
||||||
linked_domain: str
|
link: str | None = None
|
||||||
reviews: int
|
linked_domain: str | None = None
|
||||||
recent_post_age: str
|
reviews: int | None = None
|
||||||
has_whatsapp: bool
|
recent_post_age: str | None = None
|
||||||
|
has_whatsapp: bool | None = None
|
||||||
post_frequency: str | None = None
|
post_frequency: str | None = None
|
||||||
top_content_type: str | None = None
|
top_content_type: str | None = None
|
||||||
engagement: str | None = None
|
engagement: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class FacebookAudit(CamelModel):
|
class FacebookAudit(CamelModel):
|
||||||
pages: list[FacebookPage]
|
pages: list[FacebookPage] = []
|
||||||
diagnosis: list[DiagnosisItem]
|
diagnosis: list[DiagnosisItem] = []
|
||||||
brand_inconsistencies: list[BrandInconsistency]
|
brand_inconsistencies: list[BrandInconsistency] = []
|
||||||
consolidation_recommendation: str
|
consolidation_recommendation: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class OtherChannel(CamelModel):
|
class OtherChannel(CamelModel):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue