업체 직접 입력 기능구현
parent
73e5da3f08
commit
f7e3142ca4
|
|
@ -53,3 +53,4 @@ Dockerfile
|
||||||
|
|
||||||
zzz/
|
zzz/
|
||||||
credentials/service_account.json
|
credentials/service_account.json
|
||||||
|
o2o-castad-scheduler/
|
||||||
|
|
@ -25,6 +25,8 @@ from app.home.schemas.home_schema import (
|
||||||
ImageUploadResponse,
|
ImageUploadResponse,
|
||||||
ImageUploadResultItem,
|
ImageUploadResultItem,
|
||||||
ImageUrlItem,
|
ImageUrlItem,
|
||||||
|
ManualMarketingRequest,
|
||||||
|
MarketingAnalysisResponse,
|
||||||
ProcessedInfo,
|
ProcessedInfo,
|
||||||
# MarketingAnalysis,
|
# MarketingAnalysis,
|
||||||
)
|
)
|
||||||
|
|
@ -310,71 +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(
|
||||||
step3_1_start = time.perf_counter()
|
customer_name=customer_name,
|
||||||
chatgpt_service = ChatgptService()
|
region=region,
|
||||||
step3_1_elapsed = (time.perf_counter() - step3_1_start) * 1000
|
detail_region_info=road_address or "",
|
||||||
logger.debug(
|
place_id=scraper.place_id,
|
||||||
f"[crawling] Step 3-1: 서비스 초기화 완료 ({step3_1_elapsed:.1f}ms)"
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Step 3-2: 프롬프트 생성
|
|
||||||
# step3_2_start = time.perf_counter()
|
|
||||||
input_marketing_data = {
|
|
||||||
"customer_name": customer_name,
|
|
||||||
"region": region,
|
|
||||||
"detail_region_info": road_address or "",
|
|
||||||
}
|
|
||||||
# prompt = chatgpt_service.build_market_analysis_prompt()
|
|
||||||
# prompt1 = marketing_prompt.build_prompt(input_marketing_data)
|
|
||||||
# step3_2_elapsed = (time.perf_counter() - step3_2_start) * 1000
|
|
||||||
|
|
||||||
# Step 3-3: GPT API 호출
|
|
||||||
step3_3_start = time.perf_counter()
|
|
||||||
structured_report = await chatgpt_service.generate_structured_output(
|
|
||||||
marketing_prompt, input_marketing_data
|
|
||||||
)
|
|
||||||
marketing_intelligence = MarketingIntel(
|
|
||||||
place_id = scraper.place_id,
|
|
||||||
intel_result = structured_report.model_dump()
|
|
||||||
)
|
|
||||||
session.add(marketing_intelligence)
|
|
||||||
await session.commit()
|
|
||||||
await session.refresh(marketing_intelligence)
|
|
||||||
m_id = marketing_intelligence.id
|
|
||||||
logger.debug(f"[MarketingPrompt] INSERT placeid {marketing_intelligence.place_id}")
|
|
||||||
step3_3_elapsed = (time.perf_counter() - step3_3_start) * 1000
|
|
||||||
logger.info(
|
|
||||||
f"[crawling] Step 3-3: GPT API 호출 완료 - ({step3_3_elapsed:.1f}ms)"
|
|
||||||
)
|
|
||||||
logger.debug(
|
|
||||||
f"[crawling] Step 3-3: GPT API 호출 완료 - ({step3_3_elapsed:.1f}ms)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Step 3-4: 응답 파싱 (크롤링에서 가져온 facility_info 전달)
|
|
||||||
step3_4_start = time.perf_counter()
|
|
||||||
logger.debug(
|
|
||||||
f"[crawling] Step 3-4: 응답 파싱 시작 - facility_info: {scraper.facility_info}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 요약 Deprecated / 20250115 / Selling points를 첫 prompt에서 추출 중
|
|
||||||
# parsed = await chatgpt_service.parse_marketing_analysis(
|
|
||||||
# structured_report, facility_info=scraper.facility_info
|
|
||||||
# )
|
|
||||||
|
|
||||||
# marketing_analysis = MarketingAnalysis(**parsed)
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"structured_report = {structured_report.model_dump()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
marketing_analysis = structured_report
|
|
||||||
|
|
||||||
step3_4_elapsed = (time.perf_counter() - step3_4_start) * 1000
|
|
||||||
logger.debug(
|
|
||||||
f"[crawling] Step 3-4: 응답 파싱 완료 ({step3_4_elapsed:.1f}ms)"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)"
|
||||||
|
|
@ -395,7 +339,6 @@ async def _crawling_logic(
|
||||||
f"[crawling] Step 3 FAILED - GPT 마케팅 분석 중 오류: {e} ({step3_elapsed:.1f}ms)"
|
f"[crawling] Step 3 FAILED - GPT 마케팅 분석 중 오류: {e} ({step3_elapsed:.1f}ms)"
|
||||||
)
|
)
|
||||||
logger.exception("[crawling] Step 3 상세 오류:")
|
logger.exception("[crawling] Step 3 상세 오류:")
|
||||||
# GPT 실패 시에도 크롤링 결과는 반환
|
|
||||||
marketing_analysis = None
|
marketing_analysis = None
|
||||||
gpt_status = "failed"
|
gpt_status = "failed"
|
||||||
else:
|
else:
|
||||||
|
|
@ -413,8 +356,6 @@ async def _crawling_logic(
|
||||||
logger.info(f"[crawling] - Step 2 (정보가공): {step2_elapsed:.1f}ms")
|
logger.info(f"[crawling] - Step 2 (정보가공): {step2_elapsed:.1f}ms")
|
||||||
if "step3_elapsed" in locals():
|
if "step3_elapsed" in locals():
|
||||||
logger.info(f"[crawling] - Step 3 (GPT 분석): {step3_elapsed:.1f}ms")
|
logger.info(f"[crawling] - Step 3 (GPT 분석): {step3_elapsed:.1f}ms")
|
||||||
if "step3_3_elapsed" in locals():
|
|
||||||
logger.info(f"[crawling] - GPT API 호출: {step3_3_elapsed:.1f}ms")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": gpt_status if 'gpt_status' in locals() else "completed",
|
"status": gpt_status if 'gpt_status' in locals() else "completed",
|
||||||
|
|
@ -426,6 +367,94 @@ 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(
|
||||||
|
"/marketing",
|
||||||
|
summary="업체명+주소 직접 입력 마케팅 분석",
|
||||||
|
description="""
|
||||||
|
네이버 크롤링 없이 업체명과 주소를 직접 입력받아 마케팅 분석을 수행합니다.
|
||||||
|
|
||||||
|
## 요청 필드
|
||||||
|
- **customer_name**: 업체명 / 브랜드명 (필수)
|
||||||
|
- **address**: 도로명 또는 지번 주소 (필수)
|
||||||
|
|
||||||
|
## 반환 정보
|
||||||
|
- **processed_info**: 가공된 장소 정보 (customer_name, region, detail_region_info)
|
||||||
|
- **marketing_analysis**: ChatGPT 마케팅 분석 결과
|
||||||
|
- **m_id**: 마케팅 분석 결과 ID (이후 영상생성 파이프라인에 사용)
|
||||||
|
""",
|
||||||
|
response_model=MarketingAnalysisResponse,
|
||||||
|
tags=["Marketing"],
|
||||||
|
)
|
||||||
|
async def manual_marketing(
|
||||||
|
request_body: ManualMarketingRequest,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
region = _extract_region_from_address(request_body.address)
|
||||||
|
processed_info = ProcessedInfo(
|
||||||
|
customer_name=request_body.store_name,
|
||||||
|
region=region,
|
||||||
|
detail_region_info=request_body.address,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
marketing_analysis, m_id = await _run_marketing_analysis(
|
||||||
|
customer_name=request_body.store_name,
|
||||||
|
region=region,
|
||||||
|
detail_region_info=request_body.address,
|
||||||
|
place_id=None,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
except ChatGPTResponseError as e:
|
||||||
|
logger.error(
|
||||||
|
f"[marketing] ChatGPT Error: status={e.status}, "
|
||||||
|
f"code={e.error_code}, message={e.error_message}"
|
||||||
|
)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||||
|
detail=f"마케팅 분석 중 ChatGPT 오류가 발생했습니다: {e.error_message}",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[marketing] 마케팅 분석 중 오류: {e}")
|
||||||
|
logger.exception("[marketing] 상세 오류:")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail="마케팅 분석 중 오류가 발생했습니다.",
|
||||||
|
)
|
||||||
|
return MarketingAnalysisResponse(
|
||||||
|
status="completed",
|
||||||
|
processed_info=processed_info,
|
||||||
|
marketing_analysis=marketing_analysis,
|
||||||
|
m_id=m_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _autocomplete_logic(autocomplete_item:dict):
|
async def _autocomplete_logic(autocomplete_item:dict):
|
||||||
step1_start = time.perf_counter()
|
step1_start = time.perf_counter()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -289,10 +289,10 @@ class MarketingIntel(Base):
|
||||||
comment="고유 식별자",
|
comment="고유 식별자",
|
||||||
)
|
)
|
||||||
|
|
||||||
place_id: Mapped[str] = mapped_column(
|
place_id: Mapped[Optional[str]] = mapped_column(
|
||||||
String(36),
|
String(36),
|
||||||
nullable=False,
|
nullable=True,
|
||||||
comment="매장 소스별 고유 식별자",
|
comment="매장 소스별 고유 식별자 (네이버 크롤링 시 'nv{id}' 형식; 직접 입력 시 NULL)",
|
||||||
)
|
)
|
||||||
|
|
||||||
intel_result : Mapped[dict[str, Any]] = mapped_column(
|
intel_result : Mapped[dict[str, Any]] = mapped_column(
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,36 @@ class CrawlingResponse(BaseModel):
|
||||||
m_id : int = Field(..., description="마케팅 분석 결과 ID")
|
m_id : int = Field(..., description="마케팅 분석 결과 ID")
|
||||||
|
|
||||||
|
|
||||||
|
class ManualMarketingRequest(BaseModel):
|
||||||
|
"""업체명+주소 직접 입력 마케팅 분석 요청"""
|
||||||
|
|
||||||
|
model_config = ConfigDict(
|
||||||
|
json_schema_extra={
|
||||||
|
"example": {
|
||||||
|
"store_name": "스테이 머뭄",
|
||||||
|
"address": "전북특별자치도 군산시 절골길 18",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
store_name: str = Field(..., description="업체명 / 브랜드명")
|
||||||
|
address: str = Field(..., description="도로명 또는 지번 주소")
|
||||||
|
|
||||||
|
|
||||||
|
class MarketingAnalysisResponse(BaseModel):
|
||||||
|
"""업체명+주소 직접 입력 마케팅 분석 응답"""
|
||||||
|
|
||||||
|
status: str = Field(
|
||||||
|
default="completed",
|
||||||
|
description="처리 상태 (completed: 성공, failed: ChatGPT 분석 실패)",
|
||||||
|
)
|
||||||
|
processed_info: ProcessedInfo = Field(..., description="가공된 장소 정보")
|
||||||
|
marketing_analysis: Optional[MarketingPromptOutput] = Field(
|
||||||
|
None, description="마케팅 분석 결과. 실패 시 null"
|
||||||
|
)
|
||||||
|
m_id: int = Field(..., description="마케팅 분석 결과 ID")
|
||||||
|
|
||||||
|
|
||||||
class ErrorResponse(BaseModel):
|
class ErrorResponse(BaseModel):
|
||||||
"""에러 응답 스키마"""
|
"""에러 응답 스키마"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- ============================================================
|
||||||
|
-- Migration: marketing.place_id NULL 허용
|
||||||
|
-- Date: 2026-05-28
|
||||||
|
-- Description: 업체명+주소 직접 입력으로 마케팅 분석 생성 시
|
||||||
|
-- 네이버 place_id가 없으므로 NULL 허용으로 변경
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
ALTER TABLE marketing MODIFY place_id VARCHAR(36) NULL COMMENT '매장 소스별 고유 식별자 (네이버 크롤링 시 nv{id} 형식; 직접 입력 시 NULL)';
|
||||||
Loading…
Reference in New Issue