직접입력시 업종추가

feature-imageMatch
김성경 2026-07-03 15:59:52 +09:00
parent 46449a7a3f
commit 4d8462651a
4 changed files with 36 additions and 10 deletions

View File

@ -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 = {

View File

@ -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):

View File

@ -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):
try:
response = await self.client.beta.chat.completions.parse( response = await self.client.beta.chat.completions.parse(
model=model, model=model,
messages=[{"role": "user", "content": content}], messages=[{"role": "user", "content": content}],
response_format=output_format 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}")

View File

@ -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;