from typing import Literal, Optional from pydantic import BaseModel, ConfigDict, Field class AttributeInfo(BaseModel): """음악 속성 정보""" genre: str = Field(..., description="음악 장르") vocal: str = Field(..., description="보컬 스타일") tempo: str = Field(..., description="템포") mood: str = Field(..., description="분위기") class GenerateRequestImg(BaseModel): """이미지 URL 스키마""" url: str = Field(..., description="이미지 URL") name: Optional[str] = Field(None, description="이미지명 (없으면 URL에서 추출)") class GenerateRequestInfo(BaseModel): """생성 요청 정보 스키마 (이미지 제외)""" customer_name: str = Field(..., description="고객명/가게명") region: str = Field(..., description="지역명") detail_region_info: Optional[str] = Field(None, description="상세 지역 정보") attribute: AttributeInfo = Field(..., description="음악 속성 정보") language: str = Field( default="Korean", description="가사 출력 언어 (Korean, English, Chinese, Japanese, Thai, Vietnamese)", ) class GenerateRequest(GenerateRequestInfo): """기본 생성 요청 스키마 (이미지 없음, JSON body) 이미지 없이 프로젝트 정보만 전달합니다. """ model_config = ConfigDict( json_schema_extra={ "example": { "customer_name": "스테이 머뭄", "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", "attribute": { "genre": "K-Pop", "vocal": "Raspy", "tempo": "110 BPM", "mood": "happy", }, "language": "Korean", } } ) class GenerateUrlsRequest(GenerateRequestInfo): """URL 기반 생성 요청 스키마 (JSON body) GenerateRequestInfo를 상속받아 이미지 목록을 추가합니다. """ model_config = ConfigDict( json_schema_extra={ "example": { "customer_name": "스테이 머뭄", "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", "attribute": { "genre": "K-Pop", "vocal": "Raspy", "tempo": "110 BPM", "mood": "happy", }, "language": "Korean", "images": [ {"url": "https://example.com/images/image_001.jpg"}, {"url": "https://example.com/images/image_002.jpg", "name": "외관"}, ], } } ) images: list[GenerateRequestImg] = Field( ..., description="이미지 URL 목록", min_length=1 ) class GenerateUploadResponse(BaseModel): """파일 업로드 기반 생성 응답 스키마""" task_id: str = Field(..., description="작업 고유 식별자 (UUID7)") status: Literal["processing", "completed", "failed"] = Field( ..., description="작업 상태" ) message: str = Field(..., description="응답 메시지") uploaded_count: int = Field(..., description="업로드된 이미지 개수") class GenerateResponse(BaseModel): """생성 응답 스키마""" task_id: str = Field(..., description="작업 고유 식별자 (UUID7)") status: Literal["processing", "completed", "failed"] = Field( ..., description="작업 상태" ) message: str = Field(..., description="응답 메시지") class CrawlingRequest(BaseModel): """크롤링 요청 스키마""" model_config = ConfigDict( json_schema_extra={ "example": { "url": "https://map.naver.com/p/search/%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84/place/1133638931?c=14.70,0,0,0,dh&placePath=/photo?businessCategory=pension&fromPanelNum=2&locale=ko&searchText=%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84&svcName=map_pcv5×tamp=202512191123&fromPanelNum=2&locale=ko&searchText=%EC%8A%A4%ED%85%8C%EC%9D%B4%EB%A8%B8%EB%AD%84&svcName=map_pcv5×tamp=202512191007&from=map&entry=bmp&filterType=%EC%97%85%EC%B2%B4&businessCategory=pension" } } ) url: str = Field(..., description="네이버 지도 장소 URL") class ProcessedInfo(BaseModel): """가공된 장소 정보 스키마""" customer_name: str = Field(..., description="고객명/가게명 (base_info.name)") region: str = Field(..., description="지역명 (roadAddress에서 추출한 시 이름)") detail_region_info: str = Field(..., description="상세 지역 정보 (roadAddress)") class MarketingAnalysis(BaseModel): """마케팅 분석 결과 스키마""" report: str = Field(..., description="마케팅 분석 리포트") tags: list[str] = Field(default_factory=list, description="추천 태그 목록") facilities: list[str] = Field(default_factory=list, description="추천 부대시설 목록") class CrawlingResponse(BaseModel): """크롤링 응답 스키마""" image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록") image_count: int = Field(..., description="이미지 개수") processed_info: Optional[ProcessedInfo] = Field( None, description="가공된 장소 정보 (customer_name, region, detail_region_info)" ) marketing_analysis: Optional[MarketingAnalysis] = Field( None, description="마케팅 분석 결과 (report, tags, facilities)" ) class ErrorResponse(BaseModel): """에러 응답 스키마""" success: bool = Field(default=False, description="요청 성공 여부") error_code: str = Field(..., description="에러 코드") message: str = Field(..., description="에러 메시지") detail: Optional[str] = Field(None, description="상세 에러 정보")