Compare commits

..

No commits in common. "33e479de02b7291b041444517ae61fd8270114e7" and "e9da08bee8ce7b173c13c2fc68e2c1046c8db641" have entirely different histories.

2 changed files with 65 additions and 72 deletions

View File

@ -26,6 +26,7 @@ from app.home.schemas.home_schema import (
ImageUploadResultItem, ImageUploadResultItem,
ImageUrlItem, ImageUrlItem,
ManualMarketingRequest, ManualMarketingRequest,
MarketingAnalysisResponse,
ProcessedInfo, ProcessedInfo,
# MarketingAnalysis, # MarketingAnalysis,
) )
@ -134,32 +135,31 @@ METRO_CITY_MAP = {
} }
def _extract_region_from_address(road_address: str | None, jibun_address: str | None = None) -> str: def _extract_region_from_address(road_address: str | None) -> str:
"""주소에서 시/군 이름 추출 — 도로명 우선, 실패 시 지번으로 재시도""" """roadAddress에서 시/군 이름 추출
def _parse(address: str) -> str: 매칭 우선순위:
token_set = set(address.split()) 1. KOREAN_CITIES 직접 매칭 (/ 접미사 포함)
for city in KOREAN_CITIES: 2. KOREAN_CITIES 접미사 생략 매칭
if city in token_set: # 완전 일치 (토큰 단위) 3. 주소 번째 토큰이 /군으로 끝나는 경우 (: "전북 군산시 ...")
return city 4. 주소 번째 토큰이 /동인 경우 번째 토큰으로 광역시 매핑 (: "서울 강남구 ...")
if city[:-1] in token_set: # 접미사 생략 일치 (토큰 단위) """
return city if not road_address:
tokens = address.split()
if len(tokens) >= 2:
second = tokens[1]
if second.endswith("") or second.endswith(""):
return second
if second.endswith("") or second.endswith(""):
return METRO_CITY_MAP.get(tokens[0], "")
return "" return ""
if road_address: for city in KOREAN_CITIES:
result = _parse(road_address) if city in road_address:
if result: return city
return result if city[:-1] in road_address:
return city
if jibun_address: tokens = road_address.split()
return _parse(jibun_address) if len(tokens) >= 2:
second = tokens[1]
if second.endswith("") or second.endswith(""):
return second
if second.endswith("") or second.endswith(""):
return METRO_CITY_MAP.get(tokens[0], "")
return "" return ""
@ -292,15 +292,14 @@ async def _crawling_logic(
marketing_analysis = None marketing_analysis = None
if scraper.base_info: if scraper.base_info:
road_address = scraper.base_info.get("roadAddress", "") road_address = scraper.base_info.get("roadAddress", "") or scraper.base_info.get("address", "")
jibun_address = scraper.base_info.get("address", "")
customer_name = scraper.base_info.get("name", "") customer_name = scraper.base_info.get("name", "")
region = _extract_region_from_address(road_address, jibun_address) region = _extract_region_from_address(road_address)
processed_info = ProcessedInfo( processed_info = ProcessedInfo(
customer_name=customer_name, customer_name=customer_name,
region=region, region=region,
detail_region_info=road_address or jibun_address or "", detail_region_info=road_address or "",
) )
step2_elapsed = (time.perf_counter() - step2_start) * 1000 step2_elapsed = (time.perf_counter() - step2_start) * 1000
@ -313,30 +312,13 @@ async def _crawling_logic(
logger.info("[crawling] Step 3: ChatGPT 마케팅 분석 시작...") logger.info("[crawling] Step 3: ChatGPT 마케팅 분석 시작...")
try: try:
# Step 3-1: ChatGPT 서비스 초기화 및 입력 데이터 구성 marketing_analysis, m_id = await _run_marketing_analysis(
chatgpt_service = ChatgptService() customer_name=customer_name,
input_marketing_data = { region=region,
"customer_name": customer_name, detail_region_info=road_address or "",
"region": region,
"detail_region_info": road_address or "",
}
# Step 3-2: GPT API 호출 → 구조화된 마케팅 분석 결과 반환
marketing_analysis = await chatgpt_service.generate_structured_output(
marketing_prompt, input_marketing_data
)
# Step 3-3: 분석 결과 DB 저장
marketing_intel = MarketingIntel(
place_id=scraper.place_id, place_id=scraper.place_id,
intel_result=marketing_analysis.model_dump(), session=session,
) )
session.add(marketing_intel)
await session.commit()
await session.refresh(marketing_intel)
m_id = marketing_intel.id
logger.debug(f"[MarketingPrompt] INSERT place_id={marketing_intel.place_id} id={marketing_intel.id}")
step3_elapsed = (time.perf_counter() - step3_start) * 1000 step3_elapsed = (time.perf_counter() - step3_start) * 1000
logger.info( logger.info(
f"[crawling] Step 3 완료 - 마케팅 분석 성공 ({step3_elapsed:.1f}ms)" f"[crawling] Step 3 완료 - 마케팅 분석 성공 ({step3_elapsed:.1f}ms)"
@ -385,6 +367,33 @@ async def _crawling_logic(
} }
async def _run_marketing_analysis(
customer_name: str,
region: str,
detail_region_info: str,
place_id: Optional[str],
session: AsyncSession,
):
"""ChatGPT 마케팅 분석 실행 → MarketingIntel 저장 → (MarketingPromptOutput, m_id) 반환"""
chatgpt_service = ChatgptService()
input_marketing_data = {
"customer_name": customer_name,
"region": region,
"detail_region_info": detail_region_info,
}
structured_report = await chatgpt_service.generate_structured_output(
marketing_prompt, input_marketing_data
)
marketing_intel = MarketingIntel(
place_id=place_id,
intel_result=structured_report.model_dump(),
)
session.add(marketing_intel)
await session.commit()
await session.refresh(marketing_intel)
logger.debug(f"[MarketingPrompt] INSERT place_id={marketing_intel.place_id} id={marketing_intel.id}")
return structured_report, marketing_intel.id
@router.post( @router.post(
"/marketing", "/marketing",
@ -401,14 +410,13 @@ async def _crawling_logic(
- **marketing_analysis**: ChatGPT 마케팅 분석 결과 - **marketing_analysis**: ChatGPT 마케팅 분석 결과
- **m_id**: 마케팅 분석 결과 ID (이후 영상생성 파이프라인에 사용) - **m_id**: 마케팅 분석 결과 ID (이후 영상생성 파이프라인에 사용)
""", """,
response_model=CrawlingResponse, response_model=MarketingAnalysisResponse,
tags=["Marketing"], tags=["Marketing"],
) )
async def manual_marketing( async def manual_marketing(
request_body: ManualMarketingRequest, request_body: ManualMarketingRequest,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
): ):
# Step 1: 주소에서 지역명 추출 및 processed_info 구성
region = _extract_region_from_address(request_body.address) region = _extract_region_from_address(request_body.address)
processed_info = ProcessedInfo( processed_info = ProcessedInfo(
customer_name=request_body.store_name, customer_name=request_body.store_name,
@ -416,28 +424,13 @@ async def manual_marketing(
detail_region_info=request_body.address, detail_region_info=request_body.address,
) )
try: try:
# Step 2: GPT API 호출 → 마케팅 분석 결과 생성 marketing_analysis, m_id = await _run_marketing_analysis(
# place_id 없이 업체명+주소만으로 분석 (크롤링 없이 직접 입력된 경우) customer_name=request_body.store_name,
chatgpt_service = ChatgptService() region=region,
input_marketing_data = { detail_region_info=request_body.address,
"customer_name": request_body.store_name,
"region": region,
"detail_region_info": request_body.address,
}
marketing_analysis = await chatgpt_service.generate_structured_output(
marketing_prompt, input_marketing_data
)
# Step 3: 분석 결과 DB 저장 (place_id=None — 네이버 장소와 연결되지 않음)
marketing_intel = MarketingIntel(
place_id=None, place_id=None,
intel_result=marketing_analysis.model_dump(), session=session,
) )
session.add(marketing_intel)
await session.commit()
await session.refresh(marketing_intel)
m_id = marketing_intel.id
logger.debug(f"[MarketingPrompt] INSERT id={marketing_intel.id}")
except ChatGPTResponseError as e: except ChatGPTResponseError as e:
logger.error( logger.error(
f"[marketing] ChatGPT Error: status={e.status}, " f"[marketing] ChatGPT Error: status={e.status}, "
@ -454,7 +447,7 @@ async def manual_marketing(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="마케팅 분석 중 오류가 발생했습니다.", detail="마케팅 분석 중 오류가 발생했습니다.",
) )
return CrawlingResponse( return MarketingAnalysisResponse(
status="completed", status="completed",
processed_info=processed_info, processed_info=processed_info,
marketing_analysis=marketing_analysis, marketing_analysis=marketing_analysis,

View File

@ -234,7 +234,7 @@ class CrawlingResponse(BaseModel):
description="처리 상태 (completed: 성공, failed: ChatGPT 분석 실패)" description="처리 상태 (completed: 성공, failed: ChatGPT 분석 실패)"
) )
image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록") image_list: Optional[list[str]] = Field(None, description="이미지 URL 목록")
image_count: Optional[int] = Field(None, description="이미지 개수") image_count: int = Field(..., description="이미지 개수")
processed_info: Optional[ProcessedInfo] = Field( processed_info: Optional[ProcessedInfo] = Field(
None, description="가공된 장소 정보 (customer_name, region, detail_region_info)" None, description="가공된 장소 정보 (customer_name, region, detail_region_info)"
) )