405 lines
15 KiB
Python
405 lines
15 KiB
Python
import json
|
|
import logging
|
|
import re
|
|
|
|
from openai import AsyncOpenAI
|
|
|
|
from config import apikey_settings
|
|
|
|
# 로거 설정
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# fmt: off
|
|
LYRICS_PROMPT_TEMPLATE_ORI = """
|
|
1.Act as a content marketing expert with domain knowledges in [pension/staying services] in Korea, Goal: plan viral content creation that lead online reservations and promotion
|
|
2.Conduct an in-depth analysis of [업체명:{customer_name}] in [지역명:{region}] by examining their official website or informations, photos on never map and online presence. Create a comprehensive "[지역 상세: {detail_region_info}]_Brand & Marketing Intelligence Report in Korean, that includes:
|
|
|
|
**Core Analysis:**
|
|
- Target customer segments & personas
|
|
- Unique Selling Propositions (USPs) and competitive differentiators
|
|
- Comprehensive competitor landscape analysis (direct & indirect competitors)
|
|
- Market positioning assessment
|
|
|
|
**Content Strategy Framework:**
|
|
- Seasonal content calendar with trend integration
|
|
- Visual storytelling direction (shot-by-shot creative guidance)
|
|
- Brand tone & voice guidelines
|
|
- Content themes aligned with target audience behaviors
|
|
|
|
**SEO & AEO Optimization:**
|
|
- Recommended primary and long-tail keywords
|
|
- SEO-optimized taglines and meta descriptions
|
|
- Answer Engine Optimization (AEO) content suggestions
|
|
- Local search optimization strategies
|
|
|
|
**Actionable Recommendations:**
|
|
- Content distribution strategy across platforms
|
|
- KPI measurement framework
|
|
- Budget allocation recommendations by content type
|
|
|
|
콘텐츠 기획(Lyrics, Prompt for SUNO)
|
|
1. Based on the Brand & Marketing Intelligence Report for [업체명 + 지역명 / {customer_name} ({region})], create original lyrics and define music attributes (song mood, BPM, genres, and key musical motifs, Prompt for Suno.com) specifically tailored for viral content.
|
|
2. The lyrics should include, the name of [ Promotion Subject], [location], [main target],[Famous place, accessible in 10min], promotional words including but not limited to [인스타 감성], [사진같은 하루]
|
|
|
|
Deliver outputs optimized for three formats:1 minute. Ensure that each version aligns with the brand's core identity and is suitable for use in digital marketing and social media campaigns, in Korean
|
|
""".strip()
|
|
# fmt: on
|
|
|
|
LYRICS_PROMPT_TEMPLATE = """
|
|
[ROLE]
|
|
Content marketing expert and creative songwriter specializing in pension/accommodation services
|
|
|
|
[INPUT]
|
|
- Business Name: {customer_name}
|
|
- Region: {region}
|
|
- Region Details: {detail_region_info}
|
|
- Output Language: {language}
|
|
|
|
[INTERNAL ANALYSIS - DO NOT OUTPUT]
|
|
Analyze the following internally to inform lyrics creation:
|
|
- Target customer segments and personas
|
|
- Unique Selling Propositions (USPs)
|
|
- Regional characteristics and nearby attractions (within 10 min access)
|
|
- Seasonal appeal points
|
|
- Emotional triggers for the target audience
|
|
|
|
[LYRICS REQUIREMENTS]
|
|
1. Must Include Elements:
|
|
- Business name (TRANSLATED or TRANSLITERATED to {language})
|
|
- Region name (TRANSLATED or TRANSLITERATED to {language})
|
|
- Main target audience appeal
|
|
- Nearby famous places or regional characteristics
|
|
|
|
2. Keywords to Incorporate (use language-appropriate trendy expressions):
|
|
- Korean: 인스타 감성, 사진같은 하루, 힐링, 여행, 감성 숙소
|
|
- English: Instagram vibes, picture-perfect day, healing, travel, getaway
|
|
- Chinese: 网红打卡, 治愈系, 旅行, 度假, 拍照圣地
|
|
- Japanese: インスタ映え, 写真のような一日, 癒し, 旅行, 絶景
|
|
- Thai: ที่พักสวย, ฮีลใจ, เที่ยว, ถ่ายรูป, วิวสวย
|
|
- Vietnamese: check-in đẹp, healing, du lịch, nghỉ dưỡng, view đẹp
|
|
|
|
3. Structure:
|
|
- Length: For 1-minute video (approximately 8-12 lines)
|
|
- Flow: Verse structure suitable for music
|
|
- Rhythm: Natural speech rhythm in the specified language
|
|
|
|
4. Tone:
|
|
- Emotional and heartfelt
|
|
- Trendy and viral-friendly
|
|
- Relatable to target audience
|
|
|
|
[CRITICAL LANGUAGE REQUIREMENT - ABSOLUTE RULE]
|
|
ALL OUTPUT MUST BE 100% WRITTEN IN {language} - NO EXCEPTIONS
|
|
- ALL lyrics content: {language} ONLY
|
|
- ALL proper nouns (business names, region names, place names): MUST be translated or transliterated to {language}
|
|
- Korean input like "군산" must become "Gunsan" in English, "群山" in Chinese, "グンサン" in Japanese, etc.
|
|
- Korean input like "스테이 머뭄" must become "Stay Meoum" in English, "住留" in Chinese, "ステイモーム" in Japanese, etc.
|
|
- ZERO Korean characters (한글) allowed when output language is NOT Korean
|
|
- ZERO mixing of languages - the entire output must be monolingual in {language}
|
|
- This is a NON-NEGOTIABLE requirement
|
|
- Any output containing characters from other languages is considered a COMPLETE FAILURE
|
|
- Violation of this rule invalidates the entire response
|
|
|
|
[OUTPUT RULES - STRICTLY ENFORCED]
|
|
- Output lyrics ONLY
|
|
- Lyrics MUST be written ENTIRELY in {language} - NO EXCEPTIONS
|
|
- ALL names and places MUST be in {language} script/alphabet
|
|
- NO Korean (한글), Chinese (漢字), Japanese (仮名), Thai (ไทย), or Vietnamese (Tiếng Việt) characters unless that is the selected output language
|
|
- NO titles, descriptions, analysis, or explanations
|
|
- NO greetings or closing remarks
|
|
- NO additional commentary before or after lyrics
|
|
- NO line numbers or labels
|
|
- Follow the exact format below
|
|
|
|
[OUTPUT FORMAT - SUCCESS]
|
|
---
|
|
[Lyrics ENTIRELY in {language} here - no other language characters allowed]
|
|
---
|
|
|
|
[OUTPUT FORMAT - FAILURE]
|
|
If you cannot generate lyrics due to insufficient information, invalid input, or any other reason:
|
|
---
|
|
ERROR: [Brief reason for failure in English]
|
|
---
|
|
""".strip()
|
|
# fmt: on
|
|
|
|
MARKETING_ANALYSIS_PROMPT_TEMPLATE = """
|
|
[ROLE]
|
|
Content marketing expert specializing in pension/accommodation services in Korea
|
|
|
|
[INPUT]
|
|
- Business Name: {customer_name}
|
|
- Region: {region}
|
|
- Region Details: {detail_region_info}
|
|
|
|
[ANALYSIS REQUIREMENTS]
|
|
Provide comprehensive marketing analysis including:
|
|
1. Target Customer Segments
|
|
- Primary and secondary target personas
|
|
- Age groups, travel preferences, booking patterns
|
|
2. Unique Selling Propositions (USPs)
|
|
- Key differentiators based on location and region details
|
|
- Competitive advantages
|
|
3. Regional Characteristics
|
|
- Nearby attractions and famous places (within 10 min access)
|
|
- Local food, activities, and experiences
|
|
- Transportation accessibility
|
|
4. Seasonal Appeal Points
|
|
- Best seasons to visit
|
|
- Seasonal activities and events
|
|
- Peak/off-peak marketing opportunities
|
|
5. Marketing Keywords
|
|
- Recommended hashtags and search keywords
|
|
- Trending terms relevant to the property
|
|
|
|
[ADDITIONAL REQUIREMENTS]
|
|
1. Recommended Tags
|
|
- Generate 5 recommended hashtags/tags based on the business characteristics
|
|
- Tags should be trendy, searchable, and relevant to accommodation marketing
|
|
- Return as JSON with key "tags"
|
|
- **MUST be written in Korean (한국어)**
|
|
|
|
[CRITICAL LANGUAGE REQUIREMENT - ABSOLUTE RULE]
|
|
ALL OUTPUT MUST BE WRITTEN IN KOREAN (한국어)
|
|
- Analysis sections: Korean only
|
|
- Tags: Korean only
|
|
- This is a NON-NEGOTIABLE requirement
|
|
- Any output in English or other languages is considered a FAILURE
|
|
- Violation of this rule invalidates the entire response
|
|
|
|
[OUTPUT RULES - STRICTLY ENFORCED]
|
|
- Output analysis ONLY
|
|
- ALL content MUST be written in Korean (한국어) - NO EXCEPTIONS
|
|
- NO greetings or closing remarks
|
|
- NO additional commentary before or after analysis
|
|
- Follow the exact format below
|
|
|
|
[OUTPUT FORMAT - SUCCESS]
|
|
---
|
|
## 타겟 고객 분석
|
|
[한국어로 작성된 타겟 고객 분석]
|
|
|
|
## 핵심 차별점 (USP)
|
|
[한국어로 작성된 USP 분석]
|
|
|
|
## 지역 특성
|
|
[한국어로 작성된 지역 특성 분석]
|
|
|
|
## 시즌별 매력 포인트
|
|
[한국어로 작성된 시즌별 분석]
|
|
|
|
## 마케팅 키워드
|
|
[한국어로 작성된 마케팅 키워드]
|
|
|
|
## JSON Data
|
|
```json
|
|
{{
|
|
"tags": ["태그1", "태그2", "태그3", "태그4", "태그5"]
|
|
}}
|
|
```
|
|
---
|
|
|
|
[OUTPUT FORMAT - FAILURE]
|
|
If you cannot generate analysis due to insufficient information, invalid input, or any other reason:
|
|
---
|
|
ERROR: [Brief reason for failure in English]
|
|
---
|
|
""".strip()
|
|
# fmt: on
|
|
|
|
|
|
class ChatgptService:
|
|
"""ChatGPT API 서비스 클래스
|
|
|
|
GPT 5.0 모델을 사용하여 마케팅 가사 및 분석을 생성합니다.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
customer_name: str,
|
|
region: str,
|
|
detail_region_info: str = "",
|
|
language: str = "Korean",
|
|
):
|
|
# 최신 모델: gpt-5-mini
|
|
self.model = "gpt-5-mini"
|
|
self.client = AsyncOpenAI(api_key=apikey_settings.CHATGPT_API_KEY)
|
|
self.customer_name = customer_name
|
|
self.region = region
|
|
self.detail_region_info = detail_region_info
|
|
self.language = language
|
|
|
|
def build_lyrics_prompt(self) -> str:
|
|
"""LYRICS_PROMPT_TEMPLATE에 고객 정보를 대입하여 완성된 프롬프트 반환"""
|
|
return LYRICS_PROMPT_TEMPLATE.format(
|
|
customer_name=self.customer_name,
|
|
region=self.region,
|
|
detail_region_info=self.detail_region_info,
|
|
language=self.language,
|
|
)
|
|
|
|
def build_market_analysis_prompt(self) -> str:
|
|
"""MARKETING_ANALYSIS_PROMPT_TEMPLATE에 고객 정보를 대입하여 완성된 프롬프트 반환"""
|
|
return MARKETING_ANALYSIS_PROMPT_TEMPLATE.format(
|
|
customer_name=self.customer_name,
|
|
region=self.region,
|
|
detail_region_info=self.detail_region_info,
|
|
)
|
|
|
|
async def _call_gpt_api(self, prompt: str) -> str:
|
|
"""GPT API를 직접 호출합니다 (내부 메서드).
|
|
|
|
Args:
|
|
prompt: GPT에 전달할 프롬프트
|
|
|
|
Returns:
|
|
GPT 응답 문자열
|
|
|
|
Raises:
|
|
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
|
|
"""
|
|
completion = await self.client.chat.completions.create(
|
|
model=self.model, messages=[{"role": "user", "content": prompt}]
|
|
)
|
|
message = completion.choices[0].message.content
|
|
return message or ""
|
|
|
|
async def generate(
|
|
self,
|
|
prompt: str | None = None,
|
|
) -> str:
|
|
"""GPT에게 프롬프트를 전달하여 결과를 반환합니다.
|
|
|
|
Args:
|
|
prompt: GPT에 전달할 프롬프트 (None이면 기본 가사 프롬프트 사용)
|
|
|
|
Returns:
|
|
GPT 응답 문자열
|
|
|
|
Raises:
|
|
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
|
|
"""
|
|
if prompt is None:
|
|
prompt = self.build_lyrics_prompt()
|
|
|
|
print(f"[ChatgptService] Generated Prompt (length: {len(prompt)})")
|
|
logger.info(f"[ChatgptService] Starting GPT request with model: {self.model}")
|
|
|
|
# GPT API 호출
|
|
response = await self._call_gpt_api(prompt)
|
|
|
|
print(f"[ChatgptService] SUCCESS - Response length: {len(response)}")
|
|
logger.info(f"[ChatgptService] SUCCESS - Response length: {len(response)}")
|
|
return response
|
|
|
|
async def summarize_marketing(self, text: str) -> str:
|
|
"""마케팅 텍스트를 항목으로 구분하여 500자로 요약 정리.
|
|
|
|
Args:
|
|
text: 요약할 마케팅 텍스트
|
|
|
|
Returns:
|
|
요약된 텍스트
|
|
|
|
Raises:
|
|
APIError, APIConnectionError, RateLimitError: OpenAI API 오류
|
|
"""
|
|
prompt = f"""[ROLE]
|
|
마케팅 콘텐츠 요약 전문가
|
|
|
|
[INPUT]
|
|
{text}
|
|
|
|
[TASK]
|
|
위 텍스트를 분석하여 핵심 내용을 항목별로 구분하여 500자 이내로 요약해주세요.
|
|
|
|
[OUTPUT REQUIREMENTS]
|
|
- 5개 항목으로 구분: 타겟 고객, 핵심 차별점, 지역 특성, 시즌별 포인트, 추천 키워드
|
|
- 각 항목은 줄바꿈으로 구분
|
|
- 총 500자 이내로 요약
|
|
- 핵심 정보만 간결하게 포함
|
|
- 한국어로 작성
|
|
- 특수문자 사용 금지 (괄호, 슬래시, 하이픈, 물결표 등 제외)
|
|
- 쉼표와 마침표만 사용하여 자연스러운 문장으로 작성
|
|
|
|
[OUTPUT FORMAT - 반드시 아래 형식 준수]
|
|
---
|
|
타겟 고객
|
|
[대상 고객층을 자연스러운 문장으로 설명]
|
|
|
|
핵심 차별점
|
|
[숙소의 차별화 포인트를 자연스러운 문장으로 설명]
|
|
|
|
지역 특성
|
|
[주변 관광지와 지역 특색을 자연스러운 문장으로 설명]
|
|
|
|
시즌별 포인트
|
|
[계절별 매력 포인트를 자연스러운 문장으로 설명]
|
|
|
|
추천 키워드
|
|
[마케팅에 활용할 키워드를 쉼표로 구분하여 나열]
|
|
---
|
|
"""
|
|
|
|
result = await self.generate(prompt=prompt)
|
|
|
|
# --- 구분자 제거
|
|
if result.startswith("---"):
|
|
result = result[3:].strip()
|
|
if result.endswith("---"):
|
|
result = result[:-3].strip()
|
|
|
|
return result
|
|
|
|
async def parse_marketing_analysis(
|
|
self, raw_response: str, facility_info: str | None = None
|
|
) -> dict:
|
|
"""ChatGPT 마케팅 분석 응답을 파싱하고 요약하여 딕셔너리로 반환
|
|
|
|
Args:
|
|
raw_response: ChatGPT 마케팅 분석 응답 원문
|
|
facility_info: 크롤링에서 가져온 편의시설 정보 문자열
|
|
|
|
Returns:
|
|
dict: {"report": str, "tags": list[str], "facilities": list[str]}
|
|
"""
|
|
tags: list[str] = []
|
|
facilities: list[str] = []
|
|
report = raw_response
|
|
|
|
# JSON 블록 추출 시도
|
|
json_match = re.search(r"```json\s*(\{.*?\})\s*```", raw_response, re.DOTALL)
|
|
if json_match:
|
|
try:
|
|
json_data = json.loads(json_match.group(1))
|
|
tags = json_data.get("tags", [])
|
|
print(f"[parse_marketing_analysis] GPT 응답에서 tags 파싱 완료: {tags}")
|
|
# JSON 블록을 제외한 리포트 부분 추출
|
|
report = raw_response[: json_match.start()].strip()
|
|
# --- 구분자 제거
|
|
if report.startswith("---"):
|
|
report = report[3:].strip()
|
|
if report.endswith("---"):
|
|
report = report[:-3].strip()
|
|
except json.JSONDecodeError:
|
|
print("[parse_marketing_analysis] JSON 파싱 실패")
|
|
pass
|
|
|
|
# 크롤링에서 가져온 facility_info로 facilities 설정
|
|
print(f"[parse_marketing_analysis] 크롤링 facility_info 원본: {facility_info}")
|
|
if facility_info:
|
|
# 쉼표로 구분된 편의시설 문자열을 리스트로 변환
|
|
facilities = [f.strip() for f in facility_info.split(",") if f.strip()]
|
|
print(f"[parse_marketing_analysis] facility_info 파싱 결과: {facilities}")
|
|
else:
|
|
facilities = ["등록된 정보 없음"]
|
|
print("[parse_marketing_analysis] facility_info 없음 - '등록된 정보 없음' 설정")
|
|
|
|
# 리포트 내용을 500자로 요약
|
|
if report:
|
|
report = await self.summarize_marketing(report)
|
|
|
|
print(f"[parse_marketing_analysis] 최종 facilities: {facilities}")
|
|
return {"report": report, "tags": tags, "facilities": facilities}
|