|
1 | 1 | """src.models.callback_request.py |
2 | | -AI -> 백엔드 요청 DTO |
| 2 | +AI -> 백엔드 콜백 요청 DTO |
| 3 | +
|
| 4 | +통합 장소 검색 결과를 백엔드에 전달하기 위한 모델들입니다. |
3 | 5 | """ |
4 | 6 |
|
5 | 7 | from pydantic import BaseModel, Field, model_validator |
6 | 8 | from typing import List, Literal, Optional |
7 | 9 | from uuid import UUID |
8 | | -from src.models.content_info import ContentInfo |
9 | | -from src.models.place_extraction_dict import PlaceExtractionDict |
| 10 | + |
| 11 | + |
| 12 | +class SnsInfoCallback(BaseModel): |
| 13 | + """SNS 콘텐츠 메타데이터 (콜백용)""" |
| 14 | + platform: Literal["INSTAGRAM", "YOUTUBE", "YOUTUBE_SHORTS", "TIKTOK", "FACEBOOK", "TWITTER"] = Field( |
| 15 | + ..., description="SNS 플랫폼" |
| 16 | + ) |
| 17 | + contentType: str = Field(..., description="콘텐츠 타입 (post, reel, video, shorts 등)") |
| 18 | + url: str = Field(..., description="원본 URL") |
| 19 | + author: Optional[str] = Field(default=None, description="작성자") |
| 20 | + caption: Optional[str] = Field(default=None, description="게시물 본문") |
| 21 | + likesCount: Optional[int] = Field(default=None, description="좋아요 수") |
| 22 | + commentsCount: Optional[int] = Field(default=None, description="댓글 수") |
| 23 | + postedAt: Optional[str] = Field(default=None, description="게시 날짜") |
| 24 | + hashtags: List[str] = Field(default_factory=list, description="해시태그 리스트") |
| 25 | + thumbnailUrl: Optional[str] = Field(default=None, description="대표 이미지 URL") |
| 26 | + imageUrls: List[str] = Field(default_factory=list, description="이미지 URL 리스트") |
| 27 | + authorProfileImageUrl: Optional[str] = Field(default=None, description="작성자 프로필 이미지 URL") |
| 28 | + |
| 29 | + |
| 30 | +class PlaceDetailCallback(BaseModel): |
| 31 | + """네이버 지도 장소 상세 정보 (콜백용)""" |
| 32 | + # 필수 정보 |
| 33 | + placeId: str = Field(..., description="네이버 Place ID") |
| 34 | + name: str = Field(..., description="장소명") |
| 35 | + |
| 36 | + # 위치 정보 |
| 37 | + latitude: Optional[float] = Field(default=None, description="위도") |
| 38 | + longitude: Optional[float] = Field(default=None, description="경도") |
| 39 | + address: Optional[str] = Field(default=None, description="주소") |
| 40 | + roadAddress: Optional[str] = Field(default=None, description="도로명 주소") |
| 41 | + |
| 42 | + # 카테고리/설명 |
| 43 | + category: Optional[str] = Field(default=None, description="카테고리") |
| 44 | + description: Optional[str] = Field(default=None, description="한줄 설명") |
| 45 | + |
| 46 | + # 평점/리뷰 |
| 47 | + rating: Optional[float] = Field(default=None, description="별점 (0.0 ~ 5.0)") |
| 48 | + visitorReviewCount: Optional[int] = Field(default=None, description="방문자 리뷰 수") |
| 49 | + blogReviewCount: Optional[int] = Field(default=None, description="블로그 리뷰 수") |
| 50 | + |
| 51 | + # 영업 정보 |
| 52 | + businessStatus: Optional[str] = Field(default=None, description="영업 상태") |
| 53 | + businessHours: Optional[str] = Field(default=None, description="영업 시간 요약") |
| 54 | + openHoursDetail: List[str] = Field(default_factory=list, description="요일별 상세 영업시간") |
| 55 | + holidayInfo: Optional[str] = Field(default=None, description="휴무일 정보") |
| 56 | + |
| 57 | + # 연락처/링크 |
| 58 | + phoneNumber: Optional[str] = Field(default=None, description="전화번호") |
| 59 | + homepageUrl: Optional[str] = Field(default=None, description="홈페이지 URL") |
| 60 | + naverMapUrl: Optional[str] = Field(default=None, description="네이버 지도 URL") |
| 61 | + reservationAvailable: bool = Field(default=False, description="예약 가능 여부") |
| 62 | + |
| 63 | + # 부가 정보 |
| 64 | + subwayInfo: Optional[str] = Field(default=None, description="지하철 정보") |
| 65 | + directionsText: Optional[str] = Field(default=None, description="찾아가는 길") |
| 66 | + amenities: List[str] = Field(default_factory=list, description="편의시설 목록") |
| 67 | + keywords: List[str] = Field(default_factory=list, description="키워드/태그") |
| 68 | + tvAppearances: List[str] = Field(default_factory=list, description="TV 방송 출연 정보") |
| 69 | + menuInfo: List[str] = Field(default_factory=list, description="대표 메뉴") |
| 70 | + |
| 71 | + # 이미지 |
| 72 | + imageUrl: Optional[str] = Field(default=None, description="대표 이미지 URL") |
| 73 | + imageUrls: List[str] = Field(default_factory=list, description="이미지 URL 목록") |
| 74 | + |
| 75 | + |
| 76 | +class ExtractionStatistics(BaseModel): |
| 77 | + """추출 처리 통계""" |
| 78 | + extractedPlaceNames: List[str] = Field(default_factory=list, description="LLM이 추출한 장소명 리스트") |
| 79 | + totalExtracted: int = Field(default=0, description="LLM이 추출한 장소 수") |
| 80 | + totalFound: int = Field(default=0, description="네이버 지도에서 찾은 장소 수") |
| 81 | + failedSearches: List[str] = Field(default_factory=list, description="검색 실패한 장소명") |
| 82 | + |
10 | 83 |
|
11 | 84 | class AiCallbackRequest(BaseModel): |
| 85 | + """AI 서버 → 백엔드 콜백 요청""" |
12 | 86 | contentId: UUID = Field(..., description="Content UUID") |
13 | 87 | resultStatus: Literal["SUCCESS", "FAILED"] = Field(..., description="처리 결과 상태") |
14 | | - snsPlatform: Literal["INSTAGRAM", "YOUTUBE", "YOUTUBE_SHORTS", "TIKTOK", "FACEBOOK", "TWITTER"] = Field( |
15 | | - ..., description="SNS 플랫폼" |
16 | | - ) |
17 | | - contentInfo: Optional[ContentInfo] = Field(default=None, description="콘텐츠 정보 (SUCCESS 시 필수)") |
18 | | - places: List[PlaceExtractionDict] = Field(default_factory=list, description="장소 정보 리스트") |
19 | | - rawData: Optional[dict] = Field(default=None, description="AI 추출 원본 데이터") |
| 88 | + |
| 89 | + # SNS 정보 |
| 90 | + snsInfo: Optional[SnsInfoCallback] = Field(default=None, description="SNS 콘텐츠 정보") |
| 91 | + |
| 92 | + # 장소 정보 리스트 |
| 93 | + placeDetails: List[PlaceDetailCallback] = Field(default_factory=list, description="장소 상세 정보 리스트") |
| 94 | + |
| 95 | + # 추출 통계 |
| 96 | + statistics: Optional[ExtractionStatistics] = Field(default=None, description="추출 처리 통계") |
| 97 | + |
| 98 | + # 에러 정보 (FAILED 시) |
| 99 | + errorMessage: Optional[str] = Field(default=None, description="실패 시 에러 메시지") |
20 | 100 |
|
21 | 101 | @model_validator(mode="after") |
22 | 102 | def validate_success_payload(cls, model: "AiCallbackRequest") -> "AiCallbackRequest": |
23 | 103 | if model.resultStatus == "SUCCESS": |
24 | | - if model.contentInfo is None: |
25 | | - raise ValueError("contentInfo is required when resultStatus is SUCCESS") |
| 104 | + if model.snsInfo is None: |
| 105 | + raise ValueError("snsInfo is required when resultStatus is SUCCESS") |
26 | 106 | else: |
27 | | - model.places = [] |
| 107 | + # FAILED 시 placeDetails 비우기 |
| 108 | + model.placeDetails = [] |
28 | 109 | return model |
0 commit comments