직접입력시 업종추가
parent
46449a7a3f
commit
4d8462651a
|
|
@ -421,6 +421,7 @@ async def _crawling_logic(
|
||||||
## 요청 필드
|
## 요청 필드
|
||||||
- **customer_name**: 업체명 / 브랜드명 (필수)
|
- **customer_name**: 업체명 / 브랜드명 (필수)
|
||||||
- **address**: 도로명 또는 지번 주소 (필수)
|
- **address**: 도로명 또는 지번 주소 (필수)
|
||||||
|
- **category**: 업종/카테고리 자유 입력 (선택, 예: 펜션, 카페). 비우면 업체명 기반 AI 분류
|
||||||
|
|
||||||
## 반환 정보
|
## 반환 정보
|
||||||
- **processed_info**: 가공된 장소 정보 (customer_name, region, detail_region_info)
|
- **processed_info**: 가공된 장소 정보 (customer_name, region, detail_region_info)
|
||||||
|
|
@ -445,9 +446,9 @@ async def manual_marketing(
|
||||||
# Step 2: GPT API 호출 → 마케팅 분석 결과 생성
|
# Step 2: GPT API 호출 → 마케팅 분석 결과 생성
|
||||||
# place_id 없이 업체명+주소만으로 분석 (크롤링 없이 직접 입력된 경우)
|
# place_id 없이 업체명+주소만으로 분석 (크롤링 없이 직접 입력된 경우)
|
||||||
chatgpt_service = ChatgptService()
|
chatgpt_service = ChatgptService()
|
||||||
# 요청에 industry가 있으면 사용, 없으면 업체명 기반 AI 분류
|
# 크롤링 경로와 동일하게 category를 우선 사용하고, 없으면 업체명 기반 AI 분류
|
||||||
industry = request_body.industry or await _resolve_industry(
|
industry = await _resolve_industry(
|
||||||
"", request_body.store_name
|
request_body.category, request_body.store_name
|
||||||
)
|
)
|
||||||
processed_info.industry = industry
|
processed_info.industry = industry
|
||||||
input_marketing_data = {
|
input_marketing_data = {
|
||||||
|
|
|
||||||
|
|
@ -260,13 +260,14 @@ class ManualMarketingRequest(BaseModel):
|
||||||
"example": {
|
"example": {
|
||||||
"store_name": "스테이 머뭄",
|
"store_name": "스테이 머뭄",
|
||||||
"address": "전북특별자치도 군산시 절골길 18",
|
"address": "전북특별자치도 군산시 절골길 18",
|
||||||
|
"category": "펜션",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
store_name: str = Field(..., description="업체명 / 브랜드명")
|
store_name: str = Field(..., description="업체명 / 브랜드명")
|
||||||
address: str = Field(..., description="도로명 또는 지번 주소")
|
address: str = Field(..., description="도로명 또는 지번 주소")
|
||||||
industry: str = Field(default="", description="업종 분류 (stay|restaurant|cafe|salon|clinic|fitness|academy|attraction). 비우면 업체명 기반 AI 분류")
|
category: str = Field(default="", description="업체 업종/카테고리 자유 입력 (예: 펜션, 카페). 크롤링 경로와 동일하게 AI가 8개 industry enum으로 자동 분류하는 데 사용. 비우면 업체명 기반 AI 분류")
|
||||||
|
|
||||||
|
|
||||||
class ErrorResponse(BaseModel):
|
class ErrorResponse(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, ValidationError
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
|
|
@ -131,11 +131,23 @@ class ChatgptService:
|
||||||
})
|
})
|
||||||
last_error = None
|
last_error = None
|
||||||
for attempt in range(self.max_retries + 1):
|
for attempt in range(self.max_retries + 1):
|
||||||
response = await self.client.beta.chat.completions.parse(
|
try:
|
||||||
model=model,
|
response = await self.client.beta.chat.completions.parse(
|
||||||
messages=[{"role": "user", "content": content}],
|
model=model,
|
||||||
response_format=output_format
|
messages=[{"role": "user", "content": content}],
|
||||||
)
|
response_format=output_format
|
||||||
|
)
|
||||||
|
except (ValidationError, json.JSONDecodeError) as e:
|
||||||
|
# 모델이 스키마에 맞지 않는 JSON을 반환한 경우 (예: trailing characters).
|
||||||
|
# 확률적 출력 문제일 수 있으므로 재시도 대상에 포함한다.
|
||||||
|
logger.warning(
|
||||||
|
f"[ChatgptService({self.model_type})] Structured output parse failed "
|
||||||
|
f"(attempt {attempt + 1}/{self.max_retries + 1}): {e}"
|
||||||
|
)
|
||||||
|
last_error = ChatGPTResponseError("parse_error", type(e).__name__, str(e))
|
||||||
|
if attempt < self.max_retries:
|
||||||
|
logger.info(f"[ChatgptService({self.model_type})] Retrying request...")
|
||||||
|
continue
|
||||||
# Response 디버그 로깅
|
# Response 디버그 로깅
|
||||||
logger.debug(f"[ChatgptService({self.model_type})] attempt: {attempt}")
|
logger.debug(f"[ChatgptService({self.model_type})] attempt: {attempt}")
|
||||||
logger.debug(f"[ChatgptService({self.model_type})] Response ID: {response.id}")
|
logger.debug(f"[ChatgptService({self.model_type})] Response ID: {response.id}")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- ============================================================
|
||||||
|
-- Migration: marketing 테이블에 image_match 컬럼 추가
|
||||||
|
-- Date: 2026-07-03
|
||||||
|
-- Description: 이미지 슬롯 배정 결과물 저장
|
||||||
|
-- - lyric 생성 사전 단계(get_image_tags_by_task_id → template_matching_taged_image)에서
|
||||||
|
-- 산출된 슬롯별 이미지 배정 결과를 저장
|
||||||
|
-- - 영상 생성 시점에는 저장된 결과를 읽어 음악 URL/주소만 보완하여 재사용
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
ALTER TABLE marketing
|
||||||
|
ADD COLUMN image_match JSON NULL
|
||||||
|
COMMENT '이미지 슬롯 배정 결과물' AFTER subtitle;
|
||||||
Loading…
Reference in New Issue