|
""" |
|
ํค์๋ ๋งค์นญ ๋ฐ ์์นํญ ๊ณ์ฐ ๊ฐ์ - ์ ์ฒด ์ฝ๋ |
|
v4.0 - ์คํ์ด์ค๋ฐ ์ฒ๋ฆฌ ๊ฐ์ + ์ฌ๋ฐ๋ฅธ ํธ๋ ๋ ๋ถ์ ๋ก์ง ์ ์ฉ |
|
- ๊ธฐ์กด ๋ชจ๋ ๊ธฐ๋ฅ ์ ์งํ๋ฉด์ ์ต์ ํ |
|
- ์คํ์ด์ค๋ฐ ์ ๊ฑฐ ํ ๊ฒ์/๋น๊ต ๋ก์ง ์ ์ฉ |
|
- ์ฌ๋ฐ๋ฅธ ์ฆ๊ฐ์จ ๊ณ์ฐ: ์ฌํด ์๋ฃ์ vs ์๋
๋์ |
|
- ๐ ๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์: ์ค์ +์์ ๋ฐ์ดํฐ ์ค ์ต๋๊ฐ |
|
- ๐ ๊ฐ์ฅ ์์นํญ์ด ๋์ ์: ์ฐ์๋ ์๊ฐ ์์น๋ฅ ์ค ์ต๋๊ฐ |
|
""" |
|
|
|
import logging |
|
import pandas as pd |
|
from datetime import datetime |
|
import re |
|
import time |
|
import random |
|
from typing import Dict, List, Optional |
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
def normalize_keyword(keyword): |
|
"""ํค์๋ ์ ๊ทํ - ์คํ์ด์ค๋ฐ ์ฒ๋ฆฌ ๊ฐ์ """ |
|
if not keyword: |
|
return "" |
|
|
|
|
|
keyword = keyword.strip() |
|
|
|
|
|
keyword = re.sub(r'\s+', ' ', keyword) |
|
|
|
|
|
keyword = re.sub(r'[^\w\s๊ฐ-ํฃ]', '', keyword) |
|
|
|
return keyword |
|
|
|
def normalize_keyword_for_api(keyword): |
|
"""API ํธ์ถ์ฉ ํค์๋ ์ ๊ทํ (์คํ์ด์ค ์ ๊ฑฐ)""" |
|
normalized = normalize_keyword(keyword) |
|
return normalized.replace(" ", "") |
|
|
|
def normalize_keyword_for_comparison(keyword): |
|
"""๋น๊ต์ฉ ํค์๋ ์ ๊ทํ (์คํ์ด์ค ์ ์ง)""" |
|
return normalize_keyword(keyword).lower() |
|
|
|
def normalize_keyword_advanced(keyword): |
|
"""๊ณ ๊ธ ํค์๋ ์ ๊ทํ - ๋งค์นญ ๋ฌธ์ ํด๊ฒฐ""" |
|
if not keyword: |
|
return "" |
|
|
|
|
|
keyword = str(keyword).strip() |
|
|
|
|
|
keyword = re.sub(r'\s+', ' ', keyword) |
|
|
|
|
|
keyword = re.sub(r'[^\w\s๊ฐ-ํฃ]', '', keyword) |
|
|
|
|
|
keyword = keyword.lower() |
|
|
|
return keyword |
|
|
|
def create_keyword_variations(keyword): |
|
"""ํค์๋ ๋ณํ ๋ฒ์ ๋ค ์์ฑ - ์คํ์ด์ค๋ฐ ์ฒ๋ฆฌ ๊ฐํ""" |
|
base = normalize_keyword_advanced(keyword) |
|
variations = [base] |
|
|
|
|
|
no_space = base.replace(" ", "") |
|
if no_space != base: |
|
variations.append(no_space) |
|
|
|
|
|
variations.append(base.replace(" ", "-")) |
|
variations.append(base.replace(" ", "_")) |
|
|
|
|
|
words = base.split() |
|
if len(words) == 2: |
|
reversed_keyword = f"{words[1]} {words[0]}" |
|
variations.append(reversed_keyword) |
|
variations.append(reversed_keyword.replace(" ", "")) |
|
|
|
return list(set(variations)) |
|
|
|
def find_matching_keyword_row(analysis_keyword, keywords_df): |
|
"""๊ฐ์ ๋ ํค์๋ ๋งค์นญ ํจ์""" |
|
if keywords_df is None or keywords_df.empty: |
|
return None |
|
|
|
analysis_variations = create_keyword_variations(analysis_keyword) |
|
|
|
logger.info(f"๋ถ์ ํค์๋ ๋ณํ๋ค: {analysis_variations}") |
|
|
|
|
|
for idx, row in keywords_df.iterrows(): |
|
df_keyword = str(row.get('์กฐํฉ ํค์๋', '')) |
|
df_variations = create_keyword_variations(df_keyword) |
|
|
|
for analysis_var in analysis_variations: |
|
for df_var in df_variations: |
|
if analysis_var == df_var and len(analysis_var) > 1: |
|
logger.info(f"์ ํํ ๋งค์นญ ์ฑ๊ณต: '{analysis_keyword}' = '{df_keyword}'") |
|
return row |
|
|
|
|
|
for idx, row in keywords_df.iterrows(): |
|
df_keyword = str(row.get('์กฐํฉ ํค์๋', '')) |
|
df_variations = create_keyword_variations(df_keyword) |
|
|
|
for analysis_var in analysis_variations: |
|
for df_var in df_variations: |
|
if len(analysis_var) > 2 and len(df_var) > 2: |
|
if analysis_var in df_var or df_var in analysis_var: |
|
similarity = len(set(analysis_var) & set(df_var)) / len(set(analysis_var) | set(df_var)) |
|
if similarity > 0.7: |
|
logger.info(f"๋ถ๋ถ ๋งค์นญ ์ฑ๊ณต: '{analysis_keyword}' โ '{df_keyword}' (์ ์ฌ๋: {similarity:.2f})") |
|
return row |
|
|
|
logger.warning(f"ํค์๋ ๋งค์นญ ์คํจ: '{analysis_keyword}'") |
|
logger.info(f"๋ฐ์ดํฐํ๋ ์ ํค์๋ ์ํ: {keywords_df['์กฐํฉ ํค์๋'].head(5).tolist()}") |
|
return None |
|
|
|
def generate_prediction_data(trend_data_3year, keyword): |
|
""" |
|
์ ๊ตํ ์์ ๋ฐ์ดํฐ ์์ฑ ํจ์ |
|
- ํธ๋ ๋ ๋ฐ์ดํฐ ์์ง ํ ๋ฐ๋ก ํธ์ถํ์ฌ ์์ ๋ฐ์ดํฐ ์ถ๊ฐ |
|
- ๊ณ์ ์ฑ, ์ฆ๊ฐ ํธ๋ ๋, ์ ๋
๋๋น ์ฑ์ฅ๋ฅ ๋ชจ๋ ๊ณ ๋ ค |
|
""" |
|
if not trend_data_3year: |
|
logger.warning("โ ์์ ๋ฐ์ดํฐ ์์ฑ ์คํจ: trend_data_3year ์์") |
|
return trend_data_3year |
|
|
|
try: |
|
current_date = datetime.now() |
|
current_year = current_date.year |
|
current_month = current_date.month |
|
|
|
logger.info(f"๐ฎ ์์ ๋ฐ์ดํฐ ์์ฑ ์์: {keyword} ({current_year}๋
{current_month}์ ๊ธฐ์ค)") |
|
|
|
for kw, data in trend_data_3year.items(): |
|
if not data or not data.get('monthly_volumes') or not data.get('dates'): |
|
continue |
|
|
|
volumes = data['monthly_volumes'] |
|
dates = data['dates'] |
|
|
|
|
|
yearly_data = {} |
|
|
|
for i, date_str in enumerate(dates): |
|
try: |
|
date_obj = datetime.strptime(date_str, "%Y-%m-%d") |
|
if i < len(volumes): |
|
volume = volumes[i] |
|
if isinstance(volume, str): |
|
volume = float(volume.replace(',', '')) |
|
volume = int(volume) if volume else 0 |
|
|
|
year = date_obj.year |
|
month = date_obj.month |
|
|
|
if year not in yearly_data: |
|
yearly_data[year] = {} |
|
yearly_data[year][month] = volume |
|
|
|
except Exception as e: |
|
logger.warning(f"โ ๏ธ ๋ ์ง ํ์ฑ ์ค๋ฅ: {date_str}") |
|
continue |
|
|
|
logger.info(f"๐ ๋ถ์๋ ์ฐ๋: {list(yearly_data.keys())}") |
|
|
|
|
|
if current_year not in yearly_data: |
|
yearly_data[current_year] = {} |
|
|
|
current_year_data = yearly_data[current_year] |
|
last_year_data = yearly_data.get(current_year - 1, {}) |
|
two_years_ago_data = yearly_data.get(current_year - 2, {}) |
|
|
|
logger.info(f"๐ ์ฌํด ์ค์ ๋ฐ์ดํฐ: {len(current_year_data)}๊ฐ์") |
|
logger.info(f"๐ ์๋
์ฐธ์กฐ ๋ฐ์ดํฐ: {len(last_year_data)}๊ฐ์") |
|
|
|
|
|
for future_month in range(current_month + 1, 13): |
|
if future_month in current_year_data: |
|
continue |
|
|
|
predicted_volume = calculate_predicted_volume( |
|
future_month, current_year_data, last_year_data, |
|
two_years_ago_data, current_month |
|
) |
|
|
|
if predicted_volume is not None: |
|
current_year_data[future_month] = predicted_volume |
|
logger.info(f"๐ฎ ์์ ์์ฑ: {current_year}๋
{future_month}์ = {predicted_volume:,}ํ") |
|
|
|
|
|
updated_volumes = [] |
|
updated_dates = [] |
|
|
|
|
|
all_months = [] |
|
for year in sorted(yearly_data.keys()): |
|
for month in sorted(yearly_data[year].keys()): |
|
all_months.append((year, month, yearly_data[year][month])) |
|
|
|
for year, month, volume in all_months: |
|
updated_volumes.append(volume) |
|
updated_dates.append(f"{year}-{month:02d}-01") |
|
|
|
|
|
data['monthly_volumes'] = updated_volumes |
|
data['dates'] = updated_dates |
|
|
|
logger.info(f"โ
{kw} ์์ ๋ฐ์ดํฐ ํตํฉ ์๋ฃ: ์ด {len(updated_volumes)}๊ฐ์") |
|
|
|
logger.info(f"๐ ์ ์ฒด ์์ ๋ฐ์ดํฐ ์์ฑ ์๋ฃ: {keyword}") |
|
return trend_data_3year |
|
|
|
except Exception as e: |
|
logger.error(f"โ ์์ ๋ฐ์ดํฐ ์์ฑ ์ค๋ฅ: {e}") |
|
return trend_data_3year |
|
|
|
def calculate_predicted_volume(target_month, current_year_data, last_year_data, |
|
two_years_ago_data, current_month): |
|
""" |
|
์ ๊ตํ ์์ ๋ณผ๋ฅจ ๊ณ์ฐ |
|
- ๋ค์ค ์์ธ ๊ณ ๋ ค: ์๋
๋์, ์ฆ๊ฐ ํธ๋ ๋, ๊ณ์ ์ฑ, ์ฑ์ฅ๋ฅ |
|
""" |
|
try: |
|
|
|
last_year_same_month = last_year_data.get(target_month, 0) |
|
two_years_ago_same_month = two_years_ago_data.get(target_month, 0) |
|
|
|
if last_year_same_month == 0: |
|
logger.warning(f"โ ๏ธ {target_month}์ ์๋
๋ฐ์ดํฐ ์์") |
|
return None |
|
|
|
|
|
base_volume = last_year_same_month |
|
|
|
|
|
growth_rate = 1.0 |
|
if two_years_ago_same_month > 0: |
|
growth_rate = last_year_same_month / two_years_ago_same_month |
|
logger.info(f"๐ {target_month}์ ์ ๋
์ฑ์ฅ๋ฅ : {growth_rate:.2f}๋ฐฐ") |
|
|
|
|
|
trend_factor = 1.0 |
|
if len(current_year_data) >= 2: |
|
|
|
recent_ratios = [] |
|
for month in range(max(1, current_month - 2), current_month + 1): |
|
if month in current_year_data and month in last_year_data: |
|
if last_year_data[month] > 0: |
|
ratio = current_year_data[month] / last_year_data[month] |
|
recent_ratios.append(ratio) |
|
|
|
if recent_ratios: |
|
trend_factor = sum(recent_ratios) / len(recent_ratios) |
|
logger.info(f"๐ ์ต๊ทผ ํธ๋ ๋ ํฉํฐ: {trend_factor:.2f}") |
|
|
|
|
|
seasonal_factor = 1.0 |
|
if target_month > 1 and target_month - 1 in last_year_data and target_month in last_year_data: |
|
|
|
if last_year_data[target_month - 1] > 0: |
|
seasonal_factor = last_year_data[target_month] / last_year_data[target_month - 1] |
|
logger.info(f"๐ {target_month}์ ๊ณ์ ์ฑ ํฉํฐ: {seasonal_factor:.2f}") |
|
|
|
|
|
predicted_volume = int( |
|
base_volume * ( |
|
0.4 * growth_rate + |
|
0.4 * trend_factor + |
|
0.2 * seasonal_factor |
|
) |
|
) |
|
|
|
|
|
if current_year_data: |
|
recent_avg = sum(current_year_data.values()) / len(current_year_data) |
|
if predicted_volume > recent_avg * 5: |
|
predicted_volume = int(recent_avg * 2) |
|
logger.warning(f"โ ๏ธ {target_month}์ ๊ธ์ฆ ๋ณด์ : {predicted_volume:,}ํ") |
|
elif predicted_volume < recent_avg * 0.1: |
|
predicted_volume = int(recent_avg * 0.5) |
|
logger.warning(f"โ ๏ธ {target_month}์ ๊ธ๊ฐ ๋ณด์ : {predicted_volume:,}ํ") |
|
|
|
logger.info(f"๐ฏ {target_month}์ ์์ ๊ณ์ฐ: {last_year_same_month:,} ร (์ฑ์ฅ{growth_rate:.2f} + ํธ๋ ๋{trend_factor:.2f} + ๊ณ์ {seasonal_factor:.2f}) = {predicted_volume:,}") |
|
|
|
return predicted_volume |
|
|
|
except Exception as e: |
|
logger.error(f"โ {target_month}์ ์์ ๊ณ์ฐ ์ค๋ฅ: {e}") |
|
return None |
|
|
|
def enhance_trend_data_with_predictions(trend_data_3year, keyword): |
|
""" |
|
๊ธฐ์กด ํธ๋ ๋ ๋ฐ์ดํฐ์ ์์ ๋ฐ์ดํฐ ์ถ๊ฐ |
|
- ๋ฉ์ธ ํธ๋ ๋ ์์ง ํจ์์์ ํธ์ถ |
|
""" |
|
if not trend_data_3year: |
|
return trend_data_3year |
|
|
|
logger.info(f"๐ ํธ๋ ๋ ๋ฐ์ดํฐ ์์ ํ์ฅ ์์: {keyword}") |
|
|
|
enhanced_data = generate_prediction_data(trend_data_3year, keyword) |
|
|
|
|
|
for kw, data in enhanced_data.items(): |
|
if data and data.get('monthly_volumes'): |
|
total_months = len(data['monthly_volumes']) |
|
current_year = datetime.now().year |
|
|
|
|
|
current_year_count = 0 |
|
for date_str in data['dates']: |
|
try: |
|
if date_str.startswith(str(current_year)): |
|
current_year_count += 1 |
|
except: |
|
continue |
|
|
|
logger.info(f"โ
{kw} ์ต์ข
๋ฐ์ดํฐ: ์ ์ฒด {total_months}๊ฐ์, ์ฌํด {current_year_count}๊ฐ์") |
|
|
|
return enhanced_data |
|
|
|
def calculate_max_growth_rate_with_predictions(trend_data_3year, keyword): |
|
"""์ฌ๋ฐ๋ฅธ ํธ๋ ๋ ๋ถ์ ๋ก์ง - ์ฌ์ฉ์ ์๊ตฌ์ฌํญ ์ ์ฉ""" |
|
if not trend_data_3year: |
|
logger.error("โ trend_data_3year๊ฐ ์์ต๋๋ค") |
|
return "๋ฐ์ดํฐ ์์" |
|
|
|
try: |
|
keyword_data = None |
|
for kw, data in trend_data_3year.items(): |
|
keyword_data = data |
|
logger.info(f"๐ ํค์๋ ๋ฐ์ดํฐ ๋ฐ๊ฒฌ: {kw}") |
|
break |
|
|
|
if not keyword_data or not keyword_data.get('monthly_volumes') or not keyword_data.get('dates'): |
|
logger.error("โ keyword_data ๊ตฌ์กฐ ๋ฌธ์ ") |
|
return "๋ฐ์ดํฐ ์์" |
|
|
|
volumes = keyword_data['monthly_volumes'] |
|
dates = keyword_data['dates'] |
|
|
|
|
|
current_date = datetime.now() |
|
current_year = current_date.year |
|
current_month = current_date.month |
|
current_day = current_date.day |
|
|
|
|
|
if current_day >= 2: |
|
completed_year = current_year |
|
completed_month = current_month - 1 |
|
else: |
|
completed_year = current_year |
|
completed_month = current_month - 2 |
|
|
|
|
|
while completed_month <= 0: |
|
completed_month += 12 |
|
completed_year -= 1 |
|
|
|
logger.info(f"๐
ํ์ฌ: {current_year}๋
{current_month}์ {current_day}์ผ") |
|
logger.info(f"๐ ์๋ฃ๋ ๋ง์ง๋ง ๋ฐ์ดํฐ: {completed_year}๋
{completed_month}์") |
|
|
|
|
|
all_data = [] |
|
|
|
for i, date_str in enumerate(dates): |
|
try: |
|
date_obj = datetime.strptime(date_str, "%Y-%m-%d") |
|
|
|
if i < len(volumes): |
|
volume = volumes[i] |
|
if isinstance(volume, str): |
|
volume = float(volume.replace(',', '')) |
|
volume = int(volume) if volume else 0 |
|
|
|
all_data.append({ |
|
'year': date_obj.year, |
|
'month': date_obj.month, |
|
'volume': volume, |
|
'date_str': date_str, |
|
'date_obj': date_obj, |
|
'sort_key': f"{date_obj.year:04d}{date_obj.month:02d}" |
|
}) |
|
|
|
except Exception as e: |
|
logger.warning(f"โ ๏ธ ๋ ์ง ํ์ฑ ์ค๋ฅ: {date_str} - {e}") |
|
continue |
|
|
|
|
|
all_data = sorted(all_data, key=lambda x: x['sort_key']) |
|
|
|
|
|
this_year_completed_volume = None |
|
last_year_same_month_volume = None |
|
|
|
for data in all_data: |
|
|
|
if data['year'] == completed_year and data['month'] == completed_month: |
|
this_year_completed_volume = data['volume'] |
|
logger.info(f"๐ ์ฌํด {completed_month}์ ์ค๋ฐ์ดํฐ: {this_year_completed_volume:,}ํ") |
|
|
|
|
|
if data['year'] == completed_year - 1 and data['month'] == completed_month: |
|
last_year_same_month_volume = data['volume'] |
|
logger.info(f"๐ ์๋
{completed_month}์ ์ค๋ฐ์ดํฐ: {last_year_same_month_volume:,}ํ") |
|
|
|
|
|
growth_rate = 0 |
|
if this_year_completed_volume is not None and last_year_same_month_volume is not None and last_year_same_month_volume > 0: |
|
growth_rate = (this_year_completed_volume - last_year_same_month_volume) / last_year_same_month_volume |
|
logger.info(f"๐ ๊ณ์ฐ๋ ์ฆ๊ฐ์จ: {growth_rate:+.3f} ({growth_rate * 100:+.1f}%)") |
|
else: |
|
logger.warning("โ ๏ธ ์ฆ๊ฐ์จ ๊ณ์ฐ์ ์ํ ๋ฐ์ดํฐ๊ฐ ๋ถ์กฑํฉ๋๋ค.") |
|
|
|
|
|
combined_data = [] |
|
month_names = ["", "1์", "2์", "3์", "4์", "5์", "6์", "7์", "8์", "9์", "10์", "11์", "12์"] |
|
|
|
|
|
for data in all_data: |
|
if data['year'] == completed_year - 1 and data['month'] == 12: |
|
combined_data.append({ |
|
'year': data['year'], |
|
'month': data['month'], |
|
'volume': data['volume'], |
|
'data_type': '์๋
์ค์ ', |
|
'sort_key': f"{data['year']:04d}{data['month']:02d}" |
|
}) |
|
logger.info(f"๐ ์๋
12์ ์ค๋ฐ์ดํฐ: {data['volume']:,}ํ") |
|
break |
|
|
|
|
|
for month in range(1, completed_month + 1): |
|
for data in all_data: |
|
if data['year'] == completed_year and data['month'] == month: |
|
combined_data.append({ |
|
'year': data['year'], |
|
'month': data['month'], |
|
'volume': data['volume'], |
|
'data_type': '์ค์ ', |
|
'sort_key': f"{data['year']:04d}{data['month']:02d}" |
|
}) |
|
logger.info(f"๐ {month}์ ์ค๋ฐ์ดํฐ: {data['volume']:,}ํ") |
|
break |
|
|
|
|
|
for month in range(completed_month + 1, 13): |
|
|
|
last_year_volume = None |
|
for data in all_data: |
|
if data['year'] == completed_year - 1 and data['month'] == month: |
|
last_year_volume = data['volume'] |
|
break |
|
|
|
if last_year_volume is not None: |
|
|
|
predicted_volume = int(last_year_volume * (1 + growth_rate)) |
|
predicted_volume = max(predicted_volume, 0) |
|
|
|
combined_data.append({ |
|
'year': completed_year, |
|
'month': month, |
|
'volume': predicted_volume, |
|
'data_type': '์์', |
|
'sort_key': f"{completed_year:04d}{month:02d}" |
|
}) |
|
|
|
logger.info(f"๐ฎ {month}์ ์์๋ฐ์ดํฐ: {predicted_volume:,}ํ (์๋
{last_year_volume:,}ํ ร {1 + growth_rate:.3f})") |
|
|
|
|
|
combined_data = sorted(combined_data, key=lambda x: x['sort_key']) |
|
|
|
|
|
max_growth_rate = 0 |
|
max_growth_info = "๋ฐ์ดํฐ ์์" |
|
|
|
for i in range(len(combined_data) - 1): |
|
start_data = combined_data[i] |
|
end_data = combined_data[i + 1] |
|
|
|
if start_data['volume'] > 0: |
|
month_growth_rate = ((end_data['volume'] - start_data['volume']) / start_data['volume']) * 100 |
|
|
|
|
|
if month_growth_rate > max_growth_rate: |
|
max_growth_rate = month_growth_rate |
|
|
|
start_month_name = month_names[start_data['month']] |
|
end_month_name = month_names[end_data['month']] |
|
|
|
|
|
if start_data['year'] != end_data['year']: |
|
period_desc = f"{start_data['year']}๋
{start_month_name}({start_data['volume']:,}ํ)์์ {end_data['year']}๋
{end_month_name}({end_data['volume']:,}ํ)์ผ๋ก" |
|
else: |
|
period_desc = f"{start_month_name}({start_data['volume']:,}ํ)์์ {end_month_name}({end_data['volume']:,}ํ)์ผ๋ก" |
|
|
|
|
|
if start_data['data_type'] in ['์์'] and end_data['data_type'] in ['์์']: |
|
data_type = "์์ ๊ธฐ๋ฐ" |
|
elif start_data['data_type'] in ['์ค์ ', '์๋
์ค์ '] and end_data['data_type'] in ['์ค์ ', '์๋
์ค์ ']: |
|
data_type = "์ค์ ๊ธฐ๋ฐ" |
|
else: |
|
data_type = "์ค์ โ์์ ๊ธฐ๋ฐ" |
|
|
|
max_growth_info = f"{period_desc} {max_growth_rate:.1f}% ์์น ({data_type})" |
|
|
|
|
|
if max_growth_rate == 0: |
|
min_decline_rate = float('inf') |
|
for i in range(len(combined_data) - 1): |
|
start_data = combined_data[i] |
|
end_data = combined_data[i + 1] |
|
|
|
if start_data['volume'] > 0: |
|
month_growth_rate = ((end_data['volume'] - start_data['volume']) / start_data['volume']) * 100 |
|
|
|
if abs(month_growth_rate) < abs(min_decline_rate): |
|
min_decline_rate = month_growth_rate |
|
|
|
start_month_name = month_names[start_data['month']] |
|
end_month_name = month_names[end_data['month']] |
|
|
|
if start_data['year'] != end_data['year']: |
|
period_desc = f"{start_data['year']}๋
{start_month_name}({start_data['volume']:,}ํ)์์ {end_data['year']}๋
{end_month_name}({end_data['volume']:,}ํ)์ผ๋ก" |
|
else: |
|
period_desc = f"{start_month_name}({start_data['volume']:,}ํ)์์ {end_month_name}({end_data['volume']:,}ํ)์ผ๋ก" |
|
|
|
if start_data['data_type'] in ['์์'] and end_data['data_type'] in ['์์']: |
|
data_type = "์์ ๊ธฐ๋ฐ" |
|
elif start_data['data_type'] in ['์ค์ ', '์๋
์ค์ '] and end_data['data_type'] in ['์ค์ ', '์๋
์ค์ ']: |
|
data_type = "์ค์ ๊ธฐ๋ฐ" |
|
else: |
|
data_type = "์ค์ โ์์ ๊ธฐ๋ฐ" |
|
|
|
max_growth_info = f"{period_desc} {abs(min_decline_rate):.1f}% ๊ฐ์ ({data_type})" |
|
|
|
logger.info(f"๐ ๊ฐ์ฅ ์์นํญ์ด ๋์ ์: {max_growth_info}") |
|
return max_growth_info |
|
|
|
except Exception as e: |
|
logger.error(f"โ ์์นํญ ๊ณ์ฐ ์ค๋ฅ: {e}") |
|
import traceback |
|
logger.error(f"โ ์คํ ํธ๋ ์ด์ค: {traceback.format_exc()}") |
|
return "๊ณ์ฐ ์ค๋ฅ" |
|
|
|
def get_peak_month_with_predictions(trend_data_3year, keyword): |
|
"""๐ ๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์ ์ฐพ๊ธฐ - ์ค์ +์์ ๋ฐ์ดํฐ ํ์ฉ""" |
|
if not trend_data_3year: |
|
return "์ฐ์ค" |
|
|
|
try: |
|
keyword_data = None |
|
for kw, data in trend_data_3year.items(): |
|
keyword_data = data |
|
break |
|
|
|
if not keyword_data or not keyword_data.get('monthly_volumes') or not keyword_data.get('dates'): |
|
return "์ฐ์ค" |
|
|
|
volumes = keyword_data['monthly_volumes'] |
|
dates = keyword_data['dates'] |
|
|
|
|
|
current_date = datetime.now() |
|
current_year = current_date.year |
|
current_month = current_date.month |
|
current_day = current_date.day |
|
|
|
|
|
if current_day >= 2: |
|
completed_year = current_year |
|
completed_month = current_month - 1 |
|
else: |
|
completed_year = current_year |
|
completed_month = current_month - 2 |
|
|
|
while completed_month <= 0: |
|
completed_month += 12 |
|
completed_year -= 1 |
|
|
|
|
|
all_data = [] |
|
for i, date_str in enumerate(dates): |
|
try: |
|
date_obj = datetime.strptime(date_str, "%Y-%m-%d") |
|
if i < len(volumes): |
|
volume = volumes[i] |
|
if isinstance(volume, str): |
|
volume = float(volume.replace(',', '')) |
|
volume = int(volume) if volume else 0 |
|
|
|
all_data.append({ |
|
'year': date_obj.year, |
|
'month': date_obj.month, |
|
'volume': volume |
|
}) |
|
except: |
|
continue |
|
|
|
|
|
this_year_completed_volume = None |
|
last_year_same_month_volume = None |
|
|
|
for data in all_data: |
|
if data['year'] == completed_year and data['month'] == completed_month: |
|
this_year_completed_volume = data['volume'] |
|
if data['year'] == completed_year - 1 and data['month'] == completed_month: |
|
last_year_same_month_volume = data['volume'] |
|
|
|
growth_rate = 0 |
|
if this_year_completed_volume is not None and last_year_same_month_volume is not None and last_year_same_month_volume > 0: |
|
growth_rate = (this_year_completed_volume - last_year_same_month_volume) / last_year_same_month_volume |
|
|
|
|
|
year_data = [] |
|
month_names = ["", "1์", "2์", "3์", "4์", "5์", "6์", "7์", "8์", "9์", "10์", "11์", "12์"] |
|
|
|
|
|
for month in range(1, completed_month + 1): |
|
for data in all_data: |
|
if data['year'] == completed_year and data['month'] == month: |
|
year_data.append({ |
|
'month': month, |
|
'volume': data['volume'], |
|
'data_type': '์ค์ ' |
|
}) |
|
break |
|
|
|
|
|
for month in range(completed_month + 1, 13): |
|
last_year_volume = None |
|
for data in all_data: |
|
if data['year'] == completed_year - 1 and data['month'] == month: |
|
last_year_volume = data['volume'] |
|
break |
|
|
|
if last_year_volume is not None: |
|
predicted_volume = int(last_year_volume * (1 + growth_rate)) |
|
predicted_volume = max(predicted_volume, 0) |
|
|
|
year_data.append({ |
|
'month': month, |
|
'volume': predicted_volume, |
|
'data_type': '์์' |
|
}) |
|
|
|
|
|
if not year_data: |
|
return "์ฐ์ค" |
|
|
|
max_data = max(year_data, key=lambda x: x['volume']) |
|
month_name = month_names[max_data['month']] |
|
data_type_suffix = " - ์์" if max_data['data_type'] == '์์' else "" |
|
|
|
return f"{month_name}({max_data['volume']:,}ํ){data_type_suffix}" |
|
|
|
except Exception as e: |
|
logger.error(f"ํผํฌ์ ๋ถ์ ์ค๋ฅ: {e}") |
|
return "์ฐ์ค" |
|
|
|
def calculate_3year_growth_rate_improved(volumes): |
|
"""์๋
๋๋น ์ฆ๊ฐ์จ ๊ณ์ฐ (3๋
๋ฐ์ดํฐ์ฉ)""" |
|
if len(volumes) < 24: |
|
return 0 |
|
|
|
try: |
|
|
|
first_year = volumes[:12] |
|
last_year = volumes[-12:] |
|
|
|
first_year_avg = sum(first_year) / len(first_year) |
|
last_year_avg = sum(last_year) / len(last_year) |
|
|
|
if first_year_avg == 0: |
|
return 0 |
|
|
|
growth_rate = ((last_year_avg - first_year_avg) / first_year_avg) * 100 |
|
return min(max(growth_rate, -50), 200) |
|
|
|
except Exception as e: |
|
logger.error(f"์๋
๋๋น ์ฆ๊ฐ์จ ๊ณ์ฐ ์ค๋ฅ: {e}") |
|
return 0 |
|
|
|
def calculate_max_growth_rate_pure_logic(trend_data_3year, keyword): |
|
"""์์ ๋ก์ง์ผ๋ก ์ต๋ ์์นํญ ๊ณ์ฐ - ์์ ๋ฐ์ดํฐ ํฌํจ ๋ฒ์ """ |
|
return calculate_max_growth_rate_with_predictions(trend_data_3year, keyword) |
|
|
|
def analyze_season_cycle_with_llm(trend_data_3year, keyword, total_volume, gemini_model): |
|
"""LLM์ ์ด์ฉํ ์์ฆ ์ํ ์์ฑ ์ฌ์ดํด ๋ถ์""" |
|
if not trend_data_3year or not gemini_model: |
|
return "๋น์์ฆ์ํ", "์ธ์ ๋ ์ง ์ง์
๊ฐ๋ฅ", "๋ฐ์ดํฐ ๋ถ์กฑ" |
|
|
|
try: |
|
keyword_data = None |
|
for kw, data in trend_data_3year.items(): |
|
keyword_data = data |
|
break |
|
|
|
if not keyword_data or not keyword_data.get('monthly_volumes'): |
|
return "๋น์์ฆ์ํ", "์ธ์ ๋ ์ง ์ง์
๊ฐ๋ฅ", "๋ฐ์ดํฐ ๋ถ์กฑ" |
|
|
|
volumes = keyword_data['monthly_volumes'] |
|
dates = keyword_data['dates'] |
|
|
|
recent_12_volumes = volumes[-12:] if len(volumes) >= 12 else volumes |
|
recent_12_dates = dates[-12:] if len(dates) >= 12 else dates |
|
|
|
if len(recent_12_volumes) < 12: |
|
return "๋น์์ฆ์ํ", "์ธ์ ๋ ์ง ์ง์
๊ฐ๋ฅ", "๋ฐ์ดํฐ ๋ถ์กฑ" |
|
|
|
monthly_data_str = "" |
|
max_volume = 0 |
|
max_month = "" |
|
for i, (date, volume) in enumerate(zip(recent_12_dates, recent_12_volumes)): |
|
try: |
|
date_obj = datetime.strptime(date, "%Y-%m-%d") |
|
month_name = f"{date_obj.year}๋
{date_obj.month}์" |
|
monthly_data_str += f"{month_name}: {volume:,}ํ\n" |
|
|
|
|
|
if volume > max_volume: |
|
max_volume = volume |
|
max_month = f"{date_obj.month}์({volume:,}ํ)" |
|
|
|
except: |
|
monthly_data_str += f"์-{i+1}: {volume:,}ํ\n" |
|
|
|
current_date = datetime.now() |
|
current_month = current_date.month |
|
|
|
prompt = f""" |
|
ํค์๋: '{keyword}' |
|
ํ์ฌ ๊ฒ์๋: {total_volume:,}ํ |
|
ํ์ฌ ์์ : {current_date.year}๋
{current_month}์ |
|
|
|
์๋ณ ๊ฒ์๋ ๋ฐ์ดํฐ (์ต๊ทผ 12๊ฐ์): |
|
{monthly_data_str} |
|
|
|
๋ค์ ํ์์ผ๋ก๋ง ๋ต๋ณํ์ธ์: |
|
|
|
์ํ์ ํ: [๋ด์์ฆ์ํ/์ฌ๋ฆ์์ฆ์ํ/๊ฐ์์์ฆ์ํ/๊ฒจ์ธ์์ฆ์ํ/๋น์์ฆ์ํ/ํฌ๋ฆฌ์ค๋ง์ค์ด๋ฒคํธ์ํ/๋ฐธ๋ฐํ์ธ์ด๋ฒคํธ์ํ/์ด๋ฒ์ด๋ ์ด๋ฒคํธ์ํ/์ํ๊ธฐ์ด๋ฒคํธ์ํ/๊ธฐํ์ด๋ฒคํธ์ํ] |
|
ํผํฌ์: [X์] (๊ฒ์๋์ด ๊ฐ์ฅ ๋์ ์, ์ค์ ์์น ํฌํจ) |
|
์ฑ์ฅ์: [X์] (์ฆ๊ฐํญ์ด ๊ฐ์ฅ ๋์ ์) |
|
ํ์ฌ์ํ: {current_month}์ ๊ธฐ์ค [๋์
๊ธฐ/์ฑ์ฅ๊ธฐ/์์ ๊ธฐ/์ ํด๊ธฐ/๋น์์ฆ๊ธฐ๊ฐ] |
|
์ง์
์ถ์ฒ: [๊ตฌ์ฒด์ ์ ์ ์] |
|
""" |
|
|
|
response = gemini_model.generate_content(prompt) |
|
result_text = response.text.strip() |
|
|
|
lines = result_text.split('\n') |
|
product_type = "๋น์์ฆ์ํ" |
|
peak_month = max_month if max_month else "์ฐ์ค" |
|
growth_month = "์ฐ์ค" |
|
current_status = "์์ ๊ธฐ" |
|
entry_recommendation = "์ธ์ ๋ ์ง ์ง์
๊ฐ๋ฅ" |
|
|
|
for line in lines: |
|
line = line.strip() |
|
if line.startswith('์ํ์ ํ:'): |
|
product_type = line.replace('์ํ์ ํ:', '').strip() |
|
elif line.startswith('ํผํฌ์:'): |
|
extracted_peak = line.replace('ํผํฌ์:', '').strip() |
|
if '(' in extracted_peak and ')' in extracted_peak: |
|
peak_month = extracted_peak |
|
else: |
|
peak_month = max_month if max_month else extracted_peak |
|
elif line.startswith('์ฑ์ฅ์:'): |
|
growth_month = line.replace('์ฑ์ฅ์:', '').strip() |
|
elif line.startswith('ํ์ฌ์ํ:'): |
|
current_status = line.replace('ํ์ฌ์ํ:', '').strip() |
|
elif line.startswith('์ง์
์ถ์ฒ:'): |
|
entry_recommendation = line.replace('์ง์
์ถ์ฒ:', '').strip() |
|
|
|
detail_info = f"์ํ์ ํ: {product_type} | ํผํฌ์: {peak_month} | ์ฑ์ฅ์: {growth_month} | ํ์ฌ์ํ: {current_status}" |
|
|
|
logger.info(f"LLM ์์ฆ ๋ถ์ ์๋ฃ: {product_type}, {entry_recommendation}") |
|
return product_type, entry_recommendation, detail_info |
|
|
|
except Exception as e: |
|
logger.error(f"LLM ์์ฆ ์ฌ์ดํด ๋ถ์ ์ค๋ฅ: {e}") |
|
return "๋น์์ฆ์ํ", "์ธ์ ๋ ์ง ์ง์
๊ฐ๋ฅ", "LLM ๋ถ์ ์ค๋ฅ" |
|
|
|
def analyze_sourcing_strategy_improved(keyword, volume_data, trend_data_1year, trend_data_3year, filtered_keywords_df, gemini_model): |
|
"""๊ฐ์ ๋ ์์ฑ์ ๋ต ๋ถ์ - ํฌ๋งทํ
์์ ๋ฐ ๊ด์ฌ๋ ๋ถ์ ๊ฐํ""" |
|
|
|
total_volume = volume_data.get('์ด๊ฒ์๋', 0) |
|
current_date = datetime.now() |
|
current_month = current_date.month |
|
current_year = current_date.year |
|
|
|
|
|
growth_analysis = calculate_max_growth_rate_with_predictions(trend_data_3year, keyword) |
|
|
|
|
|
peak_month_with_volume = get_peak_month_with_predictions(trend_data_3year, keyword) |
|
|
|
|
|
if gemini_model: |
|
product_type, entry_timing, season_detail = analyze_season_cycle_with_llm(trend_data_3year, keyword, total_volume, gemini_model) |
|
else: |
|
|
|
product_type = "์ฐ์ค์ํ" |
|
if total_volume > 50000: |
|
product_type = "์ธ๊ธฐ์ํ" |
|
elif total_volume > 10000: |
|
product_type = "์ค๊ฐ์ํ" |
|
elif total_volume > 0: |
|
product_type = "ํ์์ํ" |
|
|
|
|
|
involvement_level = analyze_involvement_level(keyword, total_volume, gemini_model) |
|
|
|
|
|
trend_warning = "" |
|
if not trend_data_3year: |
|
trend_warning = "\n\n๐ก ๋ ์ ํํ ํธ๋ ๋ ๋ฐ์ดํฐ๋ฅผ ์ํด \"1๋จ๊ณ: ๊ธฐ๋ณธ ํค์๋ ์
๋ ฅ\"์ ์คํํด๋ณด์ธ์." |
|
|
|
|
|
result_content = f"""**๐ ์ํ์ ํ** |
|
{product_type} |
|
|
|
{involvement_level} |
|
|
|
**๐ ๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์** |
|
{peak_month_with_volume} |
|
|
|
**๐ ๊ฐ์ฅ ์์นํญ์ด ๋์ ์** |
|
{growth_analysis}{trend_warning}""" |
|
|
|
try: |
|
return {"status": "success", "content": result_content} |
|
except Exception as e: |
|
logger.error(f"์์ฑ์ ๋ต ๋ถ์ ์ค๋ฅ: {e}") |
|
return {"status": "error", "content": "์์ฑ์ ๋ต ๋ถ์์ ์๋ฃํ ์ ์์ต๋๋ค."} |
|
|
|
def analyze_involvement_level(keyword, total_volume, gemini_model): |
|
"""๊ด์ฌ๋ ๋ถ์ ํจ์ - ์ด๋ณด์๊ฐ ํ๋งค๊ฐ๋ฅํ ์์ฑ ๊ธฐ์ค""" |
|
try: |
|
|
|
basic_involvement = get_basic_involvement_level(keyword, total_volume) |
|
|
|
|
|
if gemini_model: |
|
llm_involvement = get_llm_involvement_analysis(keyword, total_volume, gemini_model) |
|
return llm_involvement |
|
else: |
|
return basic_involvement |
|
|
|
except Exception as e: |
|
logger.error(f"๊ด์ฌ๋ ๋ถ์ ์ค๋ฅ: {e}") |
|
return "๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)" |
|
|
|
def get_basic_involvement_level(keyword, total_volume): |
|
"""๊ธฐ๋ณธ ๊ท์น ๊ธฐ๋ฐ ๊ด์ฌ๋ ๋ถ์ - ์ด๋ณด์ ํ๋งค ๊ด์ """ |
|
|
|
|
|
low_involvement_keywords = [ |
|
|
|
"๊ฑฐ์น๋", "๋ฐ์นจ๋", "์ ๋ฆฌํจ", "์ ๋ฆฌ๋", "์๋ฉ", "ํ๋", "์คํ ๋", |
|
"์ฟ ์
", "๋ฒ ๊ฐ", "๋ชฉ๋ฒ ๊ฐ", "๋ฐฉ์", "๋งคํธ", "ํจ๋", |
|
|
|
"์ผ์ด๋ธ", "์ ์ ๋ฆฌ", "์ฝ๋", "์ถฉ์ ๊ธฐ", "์ด๋ํฐ", |
|
|
|
"์ฒญ์์", "์ฒญ์๊ธฐ", "๊ฑธ๋ ", "ํ์ฌ", "๋ธ๋ฌ์", |
|
|
|
"์ฐจ๋์ฉ", "์๋์ฐจ", "ํธ๋ํฐ", "์ค๋งํธํฐ", "ํ๋ธ๋ฆฟ", |
|
|
|
"์ง๊ฒ", "ํํฌ", "์์", "ํด๋ฆฝ", "๊ณ ๋ฆฌ", "๋ง", "ํ๋", |
|
|
|
"๋ฏธ๋๋ผ", "๋
ผ์ฌ๋ฆฝ", "๋ฐฉ์ง", "๋ณดํธ", "์ปค๋ฒ", "์ผ์ด์ค" |
|
] |
|
|
|
|
|
high_involvement_keywords = [ |
|
|
|
"ํด์ง", "ํ์ฅ์ง", "๋ฌผํฐ์", "๋ง์คํฌ", "์ธ์ ", "์ดํธ", "๋ฆฐ์ค", "๋น๋", |
|
"์น์ฝ", "์นซ์", "๊ธฐ์ ๊ท", "์๋ฆฌ๋", "์ฝ๋", |
|
|
|
"๋ผ๋ฉด", "๊ณผ์", "์๋ฃ", "์ปคํผ", "์ฐจ", "์ฐ์ ", "์๊ตฌ๋ฅดํธ", |
|
"์", "๊น", "์ฐธ๊ธฐ๋ฆ", "๊ฐ์ฅ", "๊ณ ์ถ์ฅ", "๋์ฅ", |
|
|
|
"๋
ธํธ๋ถ", "์ปดํจํฐ", "์ค๋งํธํฐ", "ํ๋ธ๋ฆฟ", "์นด๋ฉ๋ผ", "TV", "๋ชจ๋ํฐ", |
|
"๋์ฅ๊ณ ", "์ธํ๊ธฐ", "์์ด์ปจ", "์ฒญ์๊ธฐ", "์ ์๋ ์ธ์ง", |
|
|
|
"์๋ฃ", "๊ฑด๊ฐ์ํ", "์์์ ", "๋นํ๋ฏผ", "์ฝ", "์์ฝํ", |
|
|
|
"๋ช
ํ", "๋ธ๋๋", "๋ญ์
๋ฆฌ", "์๊ณ", "๋ณด์", "๊ธ", "์", "๋ค์ด์๋ชฌ๋" |
|
] |
|
|
|
keyword_lower = keyword.lower() |
|
|
|
|
|
for low_kw in low_involvement_keywords: |
|
if low_kw in keyword_lower: |
|
return "์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ)" |
|
|
|
|
|
for high_kw in high_involvement_keywords: |
|
if high_kw in keyword_lower: |
|
return "๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ)" |
|
|
|
|
|
if total_volume > 100000: |
|
|
|
return "๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ)" |
|
elif total_volume > 50000: |
|
return "๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)" |
|
elif total_volume > 5000: |
|
return "๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)" |
|
else: |
|
|
|
return "์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ)" |
|
|
|
def get_llm_involvement_analysis(keyword, total_volume, gemini_model): |
|
"""LLM์ ์ด์ฉํ ์ ๊ตํ ๊ด์ฌ๋ ๋ถ์ - ์ด๋ณด์ ํ๋งค ๊ด์ ๊ธฐ์ค ์ ์ฉ""" |
|
try: |
|
prompt = f""" |
|
'{keyword}' ์ํ์ ๊ด์ฌ๋๋ฅผ ์ด๋ณด์ ํ๋งค ๊ด์ ์์ ๋ถ์ํด์ฃผ์ธ์. |
|
|
|
๊ฒ์๋: {total_volume:,}ํ |
|
|
|
๊ด์ฌ๋ ์ ์ (์ด๋ณด์๊ฐ ํ๋งค๊ฐ๋ฅํ ์์ฑ ๊ธฐ์ค): |
|
|
|
์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ): |
|
- ๋๊ธฐ์
๋
์ ์ด ์๋ ์์ญ |
|
- ์ฆ์ ๋ถํธํด์ํ๋ ์ ํ (์ง๊ธ ๋ฐ๋ก ํ์ํ ๋ฌธ์ ํด๊ฒฐ) |
|
- ๋ธ๋๋ ์๊ด์์ด ๊ธฐ๋ฅ๋ง ๋๋ฉด ๊ตฌ๋งคํ๋ ์ ํ |
|
- 1๋ง์~3๋ง์๋ ๊ฐ๊ฒฉ, ์๋(100๊ฐ ์ดํ) ์์ ๊ฐ๋ฅ |
|
- ์์: ๋ชฉ๋ฒ ๊ฐ, ์ค๋งํธํฐ๊ฑฐ์น๋, ์๋์ ๋ฆฌํจ, ์ผ์ด๋ธ์ ๋ฆฌ๊ธฐ |
|
|
|
๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ): |
|
- ๋๊ธฐ์
/๋ธ๋๋๊ฐ ์์ฅ์ ๋
์ ํ๋ ์์ญ (์ด๋ณด์ ์ง์
๋ถ๊ฐ) |
|
- ์ํํ(ํด์ง, ์ธ์ , ๋ง์คํฌ ๋ฑ) - ๋ธ๋๋ ์ถฉ์ฑ๋ ๋์ |
|
- ๊ณ ๊ฐ ์ ํ(10๋ง์ ์ด์), ์ ๋ฌธ์ฑ/์ธ์ฆ ํ์ |
|
- ๋์๋ณธ ํ์ํ ์์ดํ
|
|
- ์์: ์ ์์ ํ, ๊ฐ์ , ๋ธ๋๋ ์ํํ, ์๋ฃ์ฉํ |
|
|
|
๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง): |
|
- ๊ฐ๊ฒฉ๋๋ณ๋ก ์ ๊ฐํ(์ ๊ด์ฌ)๊ณผ ๊ณ ๊ฐํ(๊ณ ๊ด์ฌ)์ด ๊ณต์กด |
|
- ํ๊ฒ์ด๋ ์ฉ๋์ ๋ฐ๋ผ ๊ด์ฌ๋๊ฐ ๊ทน๋ช
ํ๊ฒ ๋ฌ๋ผ์ง |
|
- ์์: ์๋ฅ, ์ด๋์ฉํ, ๋ทฐํฐ์ฉํ ๋ฑ |
|
|
|
๋ณตํฉ๊ด์ฌ๋์ํ์ผ๋ก ํ๋จํ ๊ฒฝ์ฐ, ๋ฐ๋์ ๊ตฌ์ฒด์ ์ธ ์ด์ ๋ฅผ ์ค๋ช
ํ์ธ์: |
|
- ๊ฐ๊ฒฉ๋๋ณ ๋ถํ: "1-3๋ง์ ์ค๊ตญ์ฐ(์ ๊ด์ฌ) vs 10-15๋ง์ ๊ตญ์ฐ ์์ (๊ณ ๊ด์ฌ)" |
|
- ํ๊ฒ๋ณ ์ฐจ์ด: "์ผ๋ฐ์ธ์ ์ ๊ด์ฌ vs ์ ๋ฌธ๊ฐ๋ ๊ณ ๊ด์ฌ" |
|
- ์ฉ๋๋ณ ์ฐจ์ด: "์์์ฉ์ ์ ๊ด์ฌ vs ์ฅ๊ธฐ์ฉ์ ๊ณ ๊ด์ฌ" |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ต๋ณํ์ธ์: |
|
[๊ด์ฌ๋ ์ ํ] |
|
[๊ตฌ์ฒด์ ์ธ ํ๋จ ์ด์ - ๊ฐ๊ฒฉ๋/ํ๊ฒ/๋ธ๋๋ ๋
์ ์ฌ๋ถ ๋ฑ์ ๋ช
ํํ ์ ์] |
|
|
|
์ ํ์ง: |
|
์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ) |
|
๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง) |
|
๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ) |
|
""" |
|
|
|
response = gemini_model.generate_content(prompt) |
|
result = response.text.strip() |
|
|
|
|
|
if "์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ)" in result: |
|
return "์ ๊ด์ฌ์ํ(์ด๋ณด์์ฉ)" |
|
elif "๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ)" in result: |
|
return "๊ณ ๊ด์ฌ์ํ(๊ณ ๊ธ์์ฉ)" |
|
elif "๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)" in result: |
|
return "๋ณตํฉ๊ด์ฌ๋์ํ(์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)" |
|
else: |
|
|
|
return get_basic_involvement_level(keyword, total_volume) |
|
|
|
except Exception as e: |
|
logger.error(f"LLM ๊ด์ฌ๋ ๋ถ์ ์ค๋ฅ: {e}") |
|
return get_basic_involvement_level(keyword, total_volume) |
|
|
|
|
|
class CompactKeywordAnalyzer: |
|
"""๊ฐ๊ฒฐํ 7๋จ๊ณ ํค์๋ ๋ถ์๊ธฐ""" |
|
|
|
def __init__(self, gemini_model): |
|
self.gemini_model = gemini_model |
|
self.max_retries = 3 |
|
|
|
def call_llm_with_retry(self, prompt: str, step_name: str = "") -> str: |
|
"""์ฌ์๋ ๋ก์ง์ด ์ ์ฉ๋ LLM ํธ์ถ""" |
|
last_error = None |
|
|
|
for attempt in range(self.max_retries): |
|
try: |
|
logger.info(f"{step_name} ์๋ {attempt + 1}/{self.max_retries}") |
|
response = self.gemini_model.generate_content(prompt) |
|
result = response.text.strip() |
|
|
|
if result and len(result) > 20: |
|
logger.info(f"{step_name} ์ฑ๊ณต") |
|
return result |
|
else: |
|
raise Exception("์๋ต์ด ๋๋ฌด ์งง๊ฑฐ๋ ๋น์ด์์") |
|
|
|
except Exception as e: |
|
last_error = e |
|
logger.warning(f"{step_name} ์คํจ (์๋ {attempt + 1}): {e}") |
|
|
|
if attempt < self.max_retries - 1: |
|
delay = 1.0 * (attempt + 1) + random.uniform(0, 0.5) |
|
time.sleep(delay) |
|
|
|
logger.error(f"{step_name} ๋ชจ๋ ์ฌ์๋ ์คํจ: {last_error}") |
|
return f"{step_name} ๋ถ์์ ์๋ฃํ ์ ์์ต๋๋ค." |
|
|
|
def clean_markdown_and_bold(self, text: str) -> str: |
|
"""๋งํฌ๋ค์ด๊ณผ ๋ณผ๋ ์ฒ๋ฆฌ๋ฅผ ์์ ํ ์ ๊ฑฐ""" |
|
text = re.sub(r'\*\*(.+?)\*\*', r'\1', text) |
|
text = re.sub(r'\*(.+?)\*', r'\1', text) |
|
text = re.sub(r'__(.+?)__', r'\1', text) |
|
text = re.sub(r'_(.+?)_', r'\1', text) |
|
text = re.sub(r'##\s*(.+)', r'\1', text) |
|
text = re.sub(r'#\s*(.+)', r'\1', text) |
|
text = re.sub(r'\*+', '', text) |
|
text = re.sub(r'_+', '', text) |
|
return text.strip() |
|
|
|
def analyze_sourcing_strategy(self, keyword: str, volume_data: dict, keywords_df: Optional[pd.DataFrame], trend_data_1year=None, trend_data_3year=None) -> str: |
|
"""๊ฐ์ ๋ ์์ฑ์ ๋ต ๋ถ์ - ๊ฒฝ์๋ฐ์ดํฐ ์ ๊ฑฐ""" |
|
|
|
try: |
|
sourcing_analysis = analyze_sourcing_strategy_improved( |
|
keyword, volume_data, trend_data_1year, trend_data_3year, keywords_df, self.gemini_model |
|
) |
|
if sourcing_analysis["status"] == "success": |
|
return self.clean_markdown_and_bold(sourcing_analysis["content"]) |
|
else: |
|
return sourcing_analysis["content"] |
|
except Exception as e: |
|
logger.error(f"์์ฑ์ ๋ต ๋ถ์ ์ค๋ฅ: {e}") |
|
return "์์ฑ์ ๋ต ๋ถ์์ ์๋ฃํ ์ ์์ต๋๋ค." |
|
|
|
def analyze_step1_product_type(self, keyword: str, keywords_df: Optional[pd.DataFrame]) -> str: |
|
"""1๋จ๊ณ. ์ํ์ ํ ๋ถ์""" |
|
|
|
related_keywords = "" |
|
if keywords_df is not None and not keywords_df.empty: |
|
top_keywords = keywords_df.head(10)['์กฐํฉ ํค์๋'].tolist() |
|
related_keywords = f"์ฐ๊ดํค์๋: {', '.join(top_keywords)}" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
{related_keywords} |
|
|
|
1๋จ๊ณ. ์ํ์ ํ ๋ถ์ |
|
|
|
์ํ ์ ํ ๋ถ๋ฅ ๊ธฐ์ค: |
|
- ๋ถํธํด๊ฒฐ์ํ: ํน์ ๋ฌธ์ ๋ ๋ถํธํจ์ ์ฆ๊ฐ ํด๊ฒฐํ๋ ์ ํ |
|
- ์
๊ทธ๋ ์ด๋์ํ: ์ถ์ ์ง๊ณผ ๋ง์กฑ๋๋ฅผ ํฅ์์ํค๋ ์ ํ |
|
- ํ์์ํ: ์ผ์์์ ๋ฐ๋์ ํ์ํ๊ณ ๋ฐ๋ณต ๊ตฌ๋งค๋๋ ์ ํ |
|
- ์ทจํฅ์ ๊ฒฉ์ํ: ๊ฐ์ฑ์ , ๊ฐ์ฑ์ ์๊ตฌ๋ฅผ ์๊ทนํ๋ ์ ํ |
|
- ์ตํฉ์ํ: ์ 2๊ฐ ์ด์์ ์ ํ์ด ๊ฒฐํฉ๋ ์ ํ |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
์ฃผ์์ ํ: [์ ํ๋ช
] |
|
{keyword}๋ [๊ตฌ์ฒด์ ์ค๋ช
- ์ ์ด ์ ํ์ธ์ง ๋ณธ์ง์ ๊ฐ์น์ ํด๊ฒฐํ๋ ๋ฌธ์ ๋ฅผ ์ค์ฌ์ผ๋ก 2-3๋ฌธ์ฅ] |
|
|
|
๋ณด์กฐ์ ํ: [ํด๋น ์ ํ๋ค] |
|
[์ ํ1] |
|
- [์ด ์ ํ์ ํด๋นํ๋ ์ด์ 1๋ฌธ์ฅ] |
|
[์ ํ2] |
|
- [์ด ์ ํ์ ํด๋นํ๋ ์ด์ 1๋ฌธ์ฅ] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"1๋จ๊ณ-์ํ์ ํ๋ถ์-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step2_target_customer(self, keyword: str, step1_result: str) -> str: |
|
"""2๋จ๊ณ. ์๋น์ ํ๊ฒ ์ค์ """ |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{step1_result} |
|
|
|
2๋จ๊ณ. ์๋น์ ํ๊ฒ ์ค์ |
|
|
|
๋ค์ ํ์์ผ๋ก ๊ฐ๊ฒฐํ๊ฒ ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
๊ณ ๊ฐ์ํฉ |
|
- [๊ตฌ์ฒด์ ์ธ ๊ตฌ๋งค ์ํฉ๋ค์ ๊ฐ๋จํ] |
|
|
|
ํ๋ฅด์๋ |
|
- [์ฐ๋ น๋, ์ฑ๋ณ, ๋ผ์ดํ์คํ์ผ์ ํตํฉํ์ฌ 1-2์ค๋ก ๊ฐ๊ฒฐํ๊ฒ] |
|
|
|
์ฃผ์ ๋์ฆ |
|
- [ํต์ฌ ๋์ฆ ํ์ค๋ง] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"2๋จ๊ณ-ํ๊ฒ์ค์ -{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step3_sourcing_strategy(self, keyword: str, previous_results: str) -> str: |
|
"""3๋จ๊ณ. ํ๊ฒ๋ณ ์ฐจ๋ณํ๋ ์์ฑ ์ ๋ต ์ ์""" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{previous_results} |
|
|
|
3๋จ๊ณ. ํ๊ฒ๋ณ ์ฐจ๋ณํ๋ ์์ฑ ์ ๋ต ์ ์ |
|
|
|
ํ์ค์ ์ผ๋ก ์จ๋ผ์ธ์์ ์์ฑ ๊ฐ๋ฅํ ์ฐจ๋ณํ ์ ๋ต์ ์ ์ํด์ฃผ์ธ์. |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
ํต์ฌ ๊ตฌ๋งค ๊ณ ๋ ค ์์ 5๊ฐ์ง |
|
1. [์์1 ๊ฐ๋จํ] |
|
2. [์์2 ๊ฐ๋จํ] |
|
3. [์์3 ๊ฐ๋จํ] |
|
4. [์์4 ๊ฐ๋จํ] |
|
5. [์์5 ๊ฐ๋จํ] |
|
|
|
์ฐจ๋ณํ ์์ฑ ์ ๋ต |
|
1. [์ ๋ต๋ช
] |
|
- [ํ์ค์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ๋ฐฉ๋ฒ ํ์ค] |
|
|
|
2. [์ ๋ต๋ช
] |
|
- [ํ์ค์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ๋ฐฉ๋ฒ ํ์ค] |
|
|
|
3. [์ ๋ต๋ช
] |
|
- [ํ์ค์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ๋ฐฉ๋ฒ ํ์ค] |
|
|
|
4. [์ ๋ต๋ช
] |
|
- [ํ์ค์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ๋ฐฉ๋ฒ ํ์ค] |
|
|
|
5. [์ ๋ต๋ช
] |
|
- [ํ์ค์ ์ผ๋ก ์์ฑ ๊ฐ๋ฅํ ๊ตฌ์ฒด์ ๋ฐฉ๋ฒ ํ์ค] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"3๋จ๊ณ-์์ฑ์ ๋ต-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step4_product_recommendation(self, keyword: str, previous_results: str) -> str: |
|
"""4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ""" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{previous_results} |
|
|
|
4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ |
|
|
|
3๋จ๊ณ์์ ๋์ถํ ์ฐจ๋ณํ ์์๋ฅผ ๋ฐ์ํ์ฌ ๋งค์ถ ๊ฐ๋ฅ์ฑ์ด ๋์ ์์๋๋ก ๋ถ์ํด์ฃผ์ธ์. |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
์ฐจ๋ณํ ์ํ ์ถ์ฒ |
|
1. [๊ตฌ์ฒด์ ์ธ ์ํ๋ช
๊ณผ ์ธ๋ถ ํน์ง] |
|
- [์ฃผ์ ํน์ง๋ค, ํ๊ฒ ๊ณ ๊ฐ, ์ฐจ๋ณํ ํฌ์ธํธ๋ฅผ ํ๋ฌธ์ฅ์ผ๋ก] |
|
|
|
2. [๊ตฌ์ฒด์ ์ธ ์ํ๋ช
๊ณผ ์ธ๋ถ ํน์ง] |
|
- [์ฃผ์ ํน์ง๋ค, ํ๊ฒ ๊ณ ๊ฐ, ์ฐจ๋ณํ ํฌ์ธํธ๋ฅผ ํ๋ฌธ์ฅ์ผ๋ก] |
|
|
|
3. [๊ตฌ์ฒด์ ์ธ ์ํ๋ช
๊ณผ ์ธ๋ถ ํน์ง] |
|
- [์ฃผ์ ํน์ง๋ค, ํ๊ฒ ๊ณ ๊ฐ, ์ฐจ๋ณํ ํฌ์ธํธ๋ฅผ ํ๋ฌธ์ฅ์ผ๋ก] |
|
|
|
4. [๊ตฌ์ฒด์ ์ธ ์ํ๋ช
๊ณผ ์ธ๋ถ ํน์ง] |
|
- [์ฃผ์ ํน์ง๋ค, ํ๊ฒ ๊ณ ๊ฐ, ์ฐจ๋ณํ ํฌ์ธํธ๋ฅผ ํ๋ฌธ์ฅ์ผ๋ก] |
|
|
|
5. [๊ตฌ์ฒด์ ์ธ ์ํ๋ช
๊ณผ ์ธ๋ถ ํน์ง] |
|
- [์ฃผ์ ํน์ง๋ค, ํ๊ฒ ๊ณ ๊ฐ, ์ฐจ๋ณํ ํฌ์ธํธ๋ฅผ ํ๋ฌธ์ฅ์ผ๋ก] |
|
|
|
๋ํ์ด๋ฏธ์ง ์ถ์ฒ |
|
1. [์ฒซ ๋ฒ์งธ ์ํ๋ช
] |
|
* [๊ฐ๋จํ ์ดฌ์ ์ปจ์
๊ณผ ํต์ฌ ํฌ์ธํธ ํ์ค] |
|
|
|
2. [๋ ๋ฒ์งธ ์ํ๋ช
] |
|
* [๊ฐ๋จํ ์ดฌ์ ์ปจ์
๊ณผ ํต์ฌ ํฌ์ธํธ ํ์ค] |
|
|
|
3. [์ธ ๋ฒ์งธ ์ํ๋ช
] |
|
* [๊ฐ๋จํ ์ดฌ์ ์ปจ์
๊ณผ ํต์ฌ ํฌ์ธํธ ํ์ค] |
|
|
|
4. [๋ค ๋ฒ์งธ ์ํ๋ช
] |
|
* [๊ฐ๋จํ ์ดฌ์ ์ปจ์
๊ณผ ํต์ฌ ํฌ์ธํธ ํ์ค] |
|
|
|
5. [๋ค์ฏ ๋ฒ์งธ ์ํ๋ช
] |
|
* [๊ฐ๋จํ ์ดฌ์ ์ปจ์
๊ณผ ํต์ฌ ํฌ์ธํธ ํ์ค] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"4๋จ๊ณ-์ํ์ถ์ฒ-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step5_trust_building(self, keyword: str, previous_results: str) -> str: |
|
"""5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง""" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{previous_results} |
|
|
|
5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
1. [์ ๋ขฐ์ฑ ์์1] |
|
- [๊ตฌ์ฒด์ ๋ฐฉ๋ฒ๊ณผ ์ ์ฉ ์์] |
|
|
|
2. [์ ๋ขฐ์ฑ ์์2] |
|
- [๊ตฌ์ฒด์ ๋ฐฉ๋ฒ๊ณผ ์ ์ฉ ์์] |
|
|
|
3. [์ ๋ขฐ์ฑ ์์3] |
|
- [๊ตฌ์ฒด์ ๋ฐฉ๋ฒ๊ณผ ์ ์ฉ ์์] |
|
|
|
4. [์ ๋ขฐ์ฑ ์์4] |
|
- [๊ตฌ์ฒด์ ๋ฐฉ๋ฒ๊ณผ ์ ์ฉ ์์] |
|
|
|
5. [์ ๋ขฐ์ฑ ์์5] |
|
- [๊ตฌ์ฒด์ ๋ฐฉ๋ฒ๊ณผ ์ ์ฉ ์์] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"5๋จ๊ณ-์ ๋ขฐ์ฑ๊ตฌ์ถ-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step6_usp_development(self, keyword: str, previous_results: str) -> str: |
|
"""6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง""" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{previous_results} |
|
|
|
6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง |
|
|
|
4๋จ๊ณ์์ ์ถ์ฒํ 5๊ฐ์ง ์ํ๊ณผ ์ฐ๊ฒฐํ์ฌ ๊ฐ๊ฐ์ USP๋ฅผ ์ ์ํด์ฃผ์ธ์. |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด ์ฌ์ฉ ๊ธ์ง): |
|
|
|
1. [์ฒซ ๋ฒ์งธ ์ํ์ USP ์ ๋ชฉ] |
|
- [ํต์ฌ ๊ฐ์น ์ ์๊ณผ ์ฐจ๋ณํ ํฌ์ธํธ ๊ตฌ์ฒด์ ์ค๋ช
] |
|
|
|
2. [๋ ๋ฒ์งธ ์ํ์ USP ์ ๋ชฉ] |
|
- [ํต์ฌ ๊ฐ์น ์ ์๊ณผ ์ฐจ๋ณํ ํฌ์ธํธ ๊ตฌ์ฒด์ ์ค๋ช
] |
|
|
|
3. [์ธ ๋ฒ์งธ ์ํ์ USP ์ ๋ชฉ] |
|
- [ํต์ฌ ๊ฐ์น ์ ์๊ณผ ์ฐจ๋ณํ ํฌ์ธํธ ๊ตฌ์ฒด์ ์ค๋ช
] |
|
|
|
4. [๋ค ๋ฒ์งธ ์ํ์ USP ์ ๋ชฉ] |
|
- [ํต์ฌ ๊ฐ์น ์ ์๊ณผ ์ฐจ๋ณํ ํฌ์ธํธ ๊ตฌ์ฒด์ ์ค๋ช
] |
|
|
|
5. [๋ค์ฏ ๋ฒ์งธ ์ํ์ USP ์ ๋ชฉ] |
|
- [ํต์ฌ ๊ฐ์น ์ ์๊ณผ ์ฐจ๋ณํ ํฌ์ธํธ ๊ตฌ์ฒด์ ์ค๋ช
] |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"6๋จ๊ณ-USP๊ฐ๋ฐ-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_step7_copy_creation(self, keyword: str, previous_results: str) -> str: |
|
"""7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ - ์ด๋ชจํฐ์ฝ ์ ๊ฑฐ""" |
|
|
|
prompt = f""" |
|
๋น์ ์ ์ด๋ณด ์
๋ฌ๊ฐ ์ํ ํ๋งค ์ฑ๊ณต์ ๋น ๋ฅด๊ฒ ์ด๋ฃฐ ์ ์๋๋ก ๋๋ ์ต๊ณ ์ ์ํ ์์ฑ ๋ฐ ์ํ๊ธฐํ ์ปจ์คํดํธ AI์
๋๋ค. |
|
|
|
๋ถ์ ํค์๋: '{keyword}' |
|
|
|
์ด์ ๋ถ์ ๊ฒฐ๊ณผ: |
|
{previous_results} |
|
|
|
7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ |
|
|
|
6๋จ๊ณ์์ ์ ์ํ 5๊ฐ์ง USP์ ์ฐ๊ฒฐํ์ฌ ๊ฐ๊ฐ์ ํค๋ ์นดํผ๋ฅผ ์ ์ํด์ฃผ์ธ์. |
|
|
|
๋ค์ ํ์์ผ๋ก ๋ถ์ํด์ฃผ์ธ์ (๋ณผ๋, ๋งํฌ๋ค์ด, ์ด๋ชจํฐ์ฝ ์ฌ์ฉ ๊ธ์ง): |
|
|
|
1. [์ฒซ ๋ฒ์งธ USP ์ฐ๊ฒฐ ์นดํผ] |
|
2. [๋ ๋ฒ์งธ USP ์ฐ๊ฒฐ ์นดํผ] |
|
3. [์ธ ๋ฒ์งธ USP ์ฐ๊ฒฐ ์นดํผ] |
|
4. [๋ค ๋ฒ์งธ USP ์ฐ๊ฒฐ ์นดํผ] |
|
5. [๋ค์ฏ ๋ฒ์งธ USP ์ฐ๊ฒฐ ์นดํผ] |
|
|
|
์ค์: |
|
- 30์ ๋ฏธ๋ง์ ๊ฐ๊ฒฐํ ํํน ๋ฌธ์ฅ๋ง ์ถ๋ ฅ |
|
- ์ด๋ชจํฐ์ฝ ์ ๋ ์ฌ์ฉ ๊ธ์ง (๐, ๐จ, โจ, ๐, ๐ ๋ฑ) |
|
- ์ํ ํ๋งค๋ฅผ ์ํ ์์ ํค๋์นดํผ๋ง ์์ฑ |
|
""" |
|
|
|
result = self.call_llm_with_retry(prompt, f"7๋จ๊ณ-์นดํผ์ ์-{keyword}") |
|
return self.clean_markdown_and_bold(result) |
|
|
|
def analyze_conclusion_enhanced(self, keyword: str, previous_results: str, sourcing_strategy_result: str) -> str: |
|
"""๊ฐ์ ๋ ๊ฒฐ๋ก ๋ถ์ - ๊ตฌ์ฒด์ ์๋ณ ์ง์
ํ์ด๋ฐ + 1-7๋จ๊ณ ์ข
ํฉ๋ถ์ ๊ฐํ""" |
|
|
|
logger.info(f"๊ฐ์ ๋ ๊ฒฐ๋ก ๋ถ์ ์์: ํค์๋='{keyword}'") |
|
|
|
|
|
if not sourcing_strategy_result or len(sourcing_strategy_result.strip()) < 10: |
|
logger.warning("์์ฑ์ ๋ต ๊ฒฐ๊ณผ๊ฐ ๋ถ์กฑํฉ๋๋ค.") |
|
sourcing_strategy_result = "๊ธฐ๋ณธ ์์ฑ์ ๋ต ๋ถ์" |
|
|
|
if not previous_results or len(previous_results.strip()) < 10: |
|
logger.warning("7๋จ๊ณ ๋ถ์ ๊ฒฐ๊ณผ๊ฐ ๋ถ์กฑํฉ๋๋ค.") |
|
previous_results = "๊ธฐ๋ณธ 7๋จ๊ณ ๋ถ์" |
|
|
|
|
|
current_date = datetime.now() |
|
current_month = current_date.month |
|
current_year = current_date.year |
|
|
|
|
|
comprehensive_prompt = f""" |
|
'{keyword}' ํค์๋์ ๋ํ ์ด๋ณด์
๋ฌ ๋ง์ถค ์ข
ํฉ ๊ฒฐ๋ก ์ ์์ฑํ์ธ์. |
|
|
|
ํ์ฌ ์์ : {current_year}๋
{current_month}์ |
|
์ค์ ๋ฐ์ดํฐ: {sourcing_strategy_result} |
|
|
|
์ ์ฒด ๋ถ์ ๊ฒฐ๊ณผ: {previous_results} |
|
|
|
๋ค์ ๊ตฌ์กฐ๋ก 700-800์ ๋ถ๋์ ์ค์ง์ ๋์์ด ๋๋ ๊ฒฐ๋ก ์ ์์ฑํ์ธ์: |
|
|
|
1. ์ฒซ ๋ฒ์งธ ๋ฌธ๋จ (350์ ๋ด์ธ) - ์ค์ ๋ฐ์ดํฐ ๊ธฐ๋ฐ ์ง์
๋ถ์: |
|
- '{keyword}'๋ [์ค์ ๊ฒ์๋ ์์น]ํ ๊ฒ์๋๋ ์ํ์ผ๋ก [์ํ ํน์ฑ] |
|
- **๊ด์ฌ๋ ํ๋จ ์ด์ ๋ฅผ ๊ตฌ์ฒด์ ์ผ๋ก ์ค๋ช
**: |
|
* ์ ๊ด์ฌ์ธ ๊ฒฝ์ฐ: "๋๊ธฐ์
๋
์ ์ด ์๊ณ , ๊ณ ๊ฐ์ด ๋ธ๋๋ ์๊ด์์ด [๊ตฌ์ฒด์ ๊ธฐ๋ฅ]๋ง ๋๋ฉด ๋ฐ๋ก ๊ตฌ๋งคํ๋ ํน์ฑ" |
|
* ๊ณ ๊ด์ฌ์ธ ๊ฒฝ์ฐ: "[ํน์ ๋๊ธฐ์
/๋ธ๋๋]๊ฐ ์์ฅ์ ๋
์ ํ๊ณ ์์ด ๊ณ ๊ฐ์ด [๊ตฌ์ฒด์ ์์]๋ฅผ ์ ์คํ ๋น๊ต๊ฒํ ํ๋ ํน์ฑ" |
|
* ๋ณตํฉ๊ด์ฌ์ธ ๊ฒฝ์ฐ: "[๊ตฌ์ฒด์ ๊ฐ๊ฒฉ๋] ์ ๊ฐํ์ ์ ๊ด์ฌ, [๊ตฌ์ฒด์ ๊ฐ๊ฒฉ๋] ๊ณ ๊ฐํ์ ๊ณ ๊ด์ฌ๋ก ๋๋๋ ํน์ฑ" |
|
- ํ์ฌ {current_month}์ ๊ธฐ์ค [์ค์ ํผํฌ์ ๋ฐ์ดํฐ]์์ ํ์ธ๋ ๋ฐ์ ๊ฐ์ด [๊ตฌ์ฒด์ ์ง์
ํ์ด๋ฐ] |
|
- [์ค์ ์์นํญ ๋ฐ์ดํฐ]๋ฅผ ๊ณ ๋ คํ ๋ [๊ตฌ์ฒด์ ์๋ณ ์ค๋น ์ผ์ ] |
|
|
|
2. ๋ ๋ฒ์งธ ๋ฌธ๋จ (350์ ๋ด์ธ) - ๋ถ์ ๊ธฐ๋ฐ ์คํ ์ ๋ต: |
|
- ๋ถ์๋ ์ํ ํน์ฑ์ [๊ตฌ์ฒด์ ํ๊ฒ ๊ณ ๊ฐ๊ณผ ๊ทธ๋ค์ ์ค์ ๋์ฆ]๊ฐ ํต์ฌ์ด๋ฉฐ |
|
- [์ค์ ๋ถ์๋ ์ฐจ๋ณํ ํฌ์ธํธ]๋ฅผ ํ์ฉํ [๊ตฌ์ฒด์ ์์ฑ ๋ฐฉํฅ์ฑ]์ด ์ค์ํฉ๋๋ค |
|
- [๋ถ์๋ ์ ๋ขฐ์ฑ ์์์ USP]๋ฅผ ํตํด [์ค์ ์ ์ฉ ๊ฐ๋ฅํ ๋ง์ผํ
๋ฐฉ๋ฒ] |
|
- ์ด๋ณด์
๋ฌ๋ [๊ตฌ์ฒด์ ์๋ณธ ๊ท๋ชจ์ ๋ฆฌ์คํฌ]๋ฅผ ๊ณ ๋ คํ์ฌ [์ค์ ํ๋ ๊ฐ์ด๋] |
|
|
|
์ค์์ฌํญ: |
|
- ์ค์ ๊ฒ์๋, ํผํฌ์, ์์น๋ฅ ๋ฑ ๊ตฌ์ฒด์ ์์น ํ์ฉ |
|
- "๋ช๋จ๊ณ" ํํ ๊ธ์ง, ์์ฐ์ค๋ฌ์ด ๋ฌธ์ฅ์ผ๋ก ์ฐ๊ฒฐ |
|
- ์ถ์์ ํํ ๋์ ์ด๋ณด์
๋ฌ๊ฐ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ๊ตฌ์ฒด์ ๊ฐ์ด๋ |
|
- ํ์์ ๋ด์ฉ ์ ๊ฑฐ, ์ค์ง์ ๋์์ด ๋๋ ๋ด์ฉ๋ง ํฌํจ |
|
- ํ์ฌ ์({current_month}์) ๊ธฐ์ค ์ฆ์ ์คํ ๊ฐ๋ฅํ ํ๋ ๊ณํ ์ ์ |
|
""" |
|
|
|
try: |
|
logger.info("๊ฐ์ ๋ ๊ฒฐ๋ก LLM ํธ์ถ ์์") |
|
|
|
if self.gemini_model: |
|
response = self.gemini_model.generate_content(comprehensive_prompt) |
|
result = response.text.strip() if response and response.text else "" |
|
|
|
if result and len(result) > 50: |
|
cleaned_result = self.clean_markdown_and_bold(result) |
|
logger.info(f"๊ฐ์ ๋ ๊ฒฐ๋ก ๋ถ์ ์ฑ๊ณต: {len(cleaned_result)} ๋ฌธ์") |
|
return cleaned_result |
|
else: |
|
logger.warning("LLM ์๋ต์ด ๋น์ด์๊ฑฐ๋ ๋๋ฌด ์งง์ต๋๋ค.") |
|
else: |
|
logger.error("Gemini ๋ชจ๋ธ์ด ์์ต๋๋ค.") |
|
|
|
except Exception as e: |
|
logger.error(f"๊ฐ์ ๋ ๊ฒฐ๋ก ๋ถ์ LLM ํธ์ถ ์ค๋ฅ: {e}") |
|
|
|
|
|
logger.info("ํด๋ฐฑ ๊ฒฐ๋ก ์์ฑ") |
|
return f"""'{keyword}'๋ ์ 15,000ํ ์ด์ ๊ฒ์๋๋ ์์ ์ ์ธ ์ํ์ผ๋ก, ํ์ฌ {current_month}์ ๊ธฐ์ค ์ธ์ ๋ ์ง์
๊ฐ๋ฅํ ์ฐ์ค ์ํ์
๋๋ค. ๊ฒ์๋ ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ์ข
ํฉํ๋ฉด ์ด๋ณด์
๋ฌ์๊ฒ ๋ฆฌ์คํฌ๊ฐ ๋ฎ๊ณ ๊พธ์คํ ์์๋ฅผ ํ๋ณดํ ์ ์๋ ์์ดํ
์ผ๋ก ํ๋จ๋ฉ๋๋ค. ์ฒซ ๋ฌ 100-200๊ฐ ์๋ ์์์ผ๋ก ์์ฅ ๋ฐ์์ ํ์ธํ ํ ์ ์ง์ ์ผ๋ก ํ๋ํ๋ ๊ฒ์ด ์์ ํ ์ ๊ทผ๋ฒ์
๋๋ค. |
|
|
|
๋ถ์๋ ์ํ ํน์ฑ์ ํ์ง๊ณผ ๋ด๊ตฌ์ฑ์ ์ค์ํ๋ ์ค์ฉ์ ๊ตฌ๋งค์ธต์ด ์ฃผ ํ๊ฒ์ด๋ฉฐ, AS ์๋น์ค์ ํ์ง๋ณด์ฆ์ ์ ๊ณต์ด ์ฐจ๋ณํ์ ํต์ฌ์
๋๋ค. ๊ณ ๊ฐ ์ ๋ขฐ๋ ๊ตฌ์ถ์ ์ํด์๋ ์๋ฃ์ง ์ถ์ฒ์ด๋ ๊ณ ๊ฐ ์ฒดํ๋ด ํ์ฉ์ด ํจ๊ณผ์ ์ด๋ฉฐ, ์ด๋ณด์
๋ฌ๋ 10-20๋ง์ ์์ค์ ์์ก ํฌ์๋ก ์์ํ์ฌ ์ฌ๊ตฌ๋งค์จ ํฅ์๊ณผ ์ฐ๊ด ์ํ ํ์ฅ์ ํตํ ์์ ์ ๋งค์ถ ํ๋ณด๊ฐ ๊ถ์ฅ๋ฉ๋๋ค.""" |
|
|
|
def parse_step_sections(self, content: str, step_number: int) -> Dict[str, str]: |
|
"""๋จ๊ณ๋ณ ์ํญ๋ชฉ ์น์
ํ์ฑ""" |
|
|
|
if step_number >= 5: |
|
return {"๋ด์ฉ": content} |
|
|
|
lines = content.split('\n') |
|
sections = {} |
|
current_section = None |
|
current_content = [] |
|
|
|
for line in lines: |
|
line = line.strip() |
|
if not line: |
|
continue |
|
|
|
is_section_title = False |
|
|
|
if step_number == 0: |
|
if any(keyword in line for keyword in ['์ํ์ ํ', '๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์', '๊ฐ์ฅ ์์นํญ์ด ๋์ ์']): |
|
is_section_title = True |
|
elif step_number == 1: |
|
if any(keyword in line for keyword in ['์ฃผ์์ ํ', '๋ณด์กฐ์ ํ']): |
|
is_section_title = True |
|
elif step_number == 2: |
|
if any(keyword in line for keyword in ['๊ณ ๊ฐ์ํฉ', 'ํ๋ฅด์๋', '์ฃผ์ ๋์ฆ', '์ฃผ์๋์ฆ']): |
|
is_section_title = True |
|
elif step_number == 3: |
|
if any(keyword in line for keyword in ['ํต์ฌ ๊ตฌ๋งค ๊ณ ๋ ค ์์', '์ฐจ๋ณํ ์์ฑ ์ ๋ต', '๊ตฌ๋งค ๊ณ ๋ ค ์์', '์์ฑ ์ ๋ต']): |
|
is_section_title = True |
|
elif step_number == 4: |
|
if any(keyword in line for keyword in ['์ฐจ๋ณํ ์ํ ์ถ์ฒ', '๋ํ์ด๋ฏธ์ง ์ถ์ฒ']): |
|
is_section_title = True |
|
elif line.endswith(':'): |
|
is_section_title = True |
|
|
|
if is_section_title: |
|
if current_section and current_content: |
|
sections[current_section] = '\n'.join(current_content) |
|
|
|
current_section = line.replace(':', '').strip() |
|
current_content = [] |
|
else: |
|
current_content.append(line) |
|
|
|
if current_section and current_content: |
|
sections[current_section] = '\n'.join(current_content) |
|
|
|
if not sections: |
|
return {"๋ด์ฉ": content} |
|
|
|
return sections |
|
|
|
def format_section_content(self, content: str) -> str: |
|
"""์น์
๋ด์ฉ ํฌ๋งทํ
- ์ฌํํ ์์ด์ฝ์ผ๋ก ๋ณ๊ฒฝ""" |
|
lines = content.split('\n') |
|
formatted_lines = [] |
|
|
|
for line in lines: |
|
line = line.strip() |
|
if not line: |
|
continue |
|
|
|
skip_patterns = [ |
|
'์์ฑ์ ๋ต ๋ถ์', '1๋จ๊ณ. ์ํ์ ํ ๋ถ์', '4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ', |
|
'5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง', '6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง', |
|
'7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ', '๊ฒฐ๋ก ' |
|
] |
|
|
|
should_skip = False |
|
for pattern in skip_patterns: |
|
if pattern in line: |
|
should_skip = True |
|
break |
|
|
|
if should_skip: |
|
continue |
|
|
|
|
|
if any(keyword in line for keyword in ['์ํ์ ํ:', '๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์:', '๊ฐ์ฅ ์์นํญ์ด ๋์ ์:', '์ฃผ์์ ํ:', '๋ณด์กฐ์ ํ:', '๊ณ ๊ฐ์ํฉ:', 'ํ๋ฅด์๋:', '์ฃผ์ ๋์ฆ:', 'ํต์ฌ ๊ตฌ๋งค ๊ณ ๋ ค ์์', '์ฐจ๋ณํ ์์ฑ ์ ๋ต', '์ฐจ๋ณํ ์ํ ์ถ์ฒ', '๋ํ์ด๋ฏธ์ง ์ถ์ฒ']): |
|
|
|
emoji_map = { |
|
'์ํ์ ํ:': '๐๏ธ', |
|
'๊ฐ์ฅ ๊ฒ์๋์ด ๋ง์ ์:': '๐', |
|
'๊ฐ์ฅ ์์นํญ์ด ๋์ ์:': '๐', |
|
'์ฃผ์์ ํ:': '๐ฏ', |
|
'๋ณด์กฐ์ ํ:': '๐', |
|
'๊ณ ๊ฐ์ํฉ:': '๐ค', |
|
'ํ๋ฅด์๋:': '๐ญ', |
|
'์ฃผ์ ๋์ฆ:': '๐ก', |
|
'ํต์ฌ ๊ตฌ๋งค ๊ณ ๋ ค ์์': '๐', |
|
'์ฐจ๋ณํ ์์ฑ ์ ๋ต': '๐ฏ', |
|
'์ฐจ๋ณํ ์ํ ์ถ์ฒ': '๐', |
|
'๋ํ์ด๋ฏธ์ง ์ถ์ฒ': '๐ท' |
|
} |
|
|
|
emoji = "" |
|
for key, value in emoji_map.items(): |
|
if key in line: |
|
emoji = value + " " |
|
break |
|
|
|
formatted_lines.append(f'<div style="font-family: \'Malgun Gothic\', sans-serif; font-size: 22px; font-weight: 700; color: #2c5aa0; margin: 25px 0 12px 0; line-height: 1.4;">{emoji}{line}</div>') |
|
|
|
|
|
elif re.match(r'^\d+\.', line): |
|
number = re.match(r'^(\d+)\.', line).group(1) |
|
number_emoji = ['1๏ธโฃ', '2๏ธโฃ', '3๏ธโฃ', '4๏ธโฃ', '5๏ธโฃ'][int(number)-1] if int(number) <= 5 else f"{number}." |
|
formatted_lines.append(f'<div style="font-family: \'Malgun Gothic\', sans-serif; font-size: 20px; font-weight: 600; color: #2c5aa0; margin: 18px 0 10px 0; line-height: 1.4;">{number_emoji} {line[len(number)+1:].strip()}</div>') |
|
|
|
|
|
elif line.startswith('-') or line.startswith('โข'): |
|
clean_line = re.sub(r'^[-โข]\s*', '', line) |
|
formatted_lines.append(f'<div style="font-family: \'Malgun Gothic\', sans-serif; font-size: 17px; margin: 10px 0 10px 25px; color: #555; line-height: 1.6;">โข {clean_line}</div>') |
|
|
|
|
|
elif line.startswith('*'): |
|
clean_line = re.sub(r'^\*\s*', '', line) |
|
formatted_lines.append(f'<div style="font-family: \'Malgun Gothic\', sans-serif; font-size: 16px; margin: 8px 0 8px 40px; color: #e67e22; line-height: 1.5;">๐ธ {clean_line}</div>') |
|
|
|
|
|
elif line.startswith(' ') or line.startswith('\t'): |
|
clean_line = line.lstrip() |
|
formatted_lines.append(f'<div style="font-family: \'Malgun Gothic\', sans-serif; font-size: 16px; margin: 8px 0 8px 40px; color: #666; line-height: 1.5;">โ {clean_line}</div>') |
|
|
|
|
|
else: |
|
formatted_lines.append(f'<div style="font-family: \'Noto Sans KR\', sans-serif; font-size: 17px; margin: 12px 0; color: #333; line-height: 1.6;">{line}</div>') |
|
|
|
return ''.join(formatted_lines) |
|
|
|
def generate_step_html(self, step_title: str, content: str, step_number: int) -> str: |
|
"""๊ฐ๋ณ ๋จ๊ณ HTML ์์ฑ""" |
|
sections = self.parse_step_sections(content, step_number) |
|
|
|
sections_html = "" |
|
|
|
if step_number >= 5: |
|
sections_html = self.format_section_content(content) |
|
else: |
|
if sections: |
|
for section_title, section_content in sections.items(): |
|
sections_html += f""" |
|
<div style="margin-bottom: 25px; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.05);"> |
|
<div style="background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); padding: 15px; border-bottom: 1px solid #e0e0e0;"> |
|
<div style="margin: 0; font-family: 'Malgun Gothic', sans-serif; font-size: 18px; font-weight: 600; color: #495057;">๐ {section_title}</div> |
|
</div> |
|
<div style="padding: 20px; background: #fefefe;"> |
|
{self.format_section_content(section_content)} |
|
</div> |
|
</div> |
|
""" |
|
else: |
|
sections_html = self.format_section_content(content) |
|
|
|
step_emoji_map = { |
|
"์์ฑ์ ๋ต ๋ถ์": "๐", |
|
"1๋จ๊ณ. ์ํ์ ํ ๋ถ์": "๐ฏ", |
|
"2๋จ๊ณ. ์๋น์ ํ๊ฒ ์ค์ ": "๐ฅ", |
|
"3๋จ๊ณ. ํ๊ฒ๋ณ ์ฐจ๋ณํ๋ ์์ฑ ์ ๋ต ์ ์": "๐", |
|
"4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ": "๐", |
|
"5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง": "๐ก๏ธ", |
|
"6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง": "โญ", |
|
"7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ": "โ๏ธ", |
|
"๊ฒฐ๋ก ": "๐" |
|
} |
|
|
|
step_emoji = step_emoji_map.get(step_title, "๐") |
|
|
|
return f""" |
|
<div style="margin-bottom: 35px; border: 2px solid #dee2e6; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"> |
|
<div style="background: linear-gradient(135deg, #6c757d 0%, #495057 100%); padding: 20px; border-bottom: 2px solid #dee2e6;"> |
|
<div style="margin: 0; font-family: 'Malgun Gothic', sans-serif; font-size: 22px; font-weight: 700; color: white;">{step_emoji} {step_title}</div> |
|
</div> |
|
<div style="padding: 30px; background: white;"> |
|
{sections_html} |
|
</div> |
|
</div> |
|
""" |
|
|
|
def generate_final_html(self, keyword: str, all_steps: Dict[str, str]) -> str: |
|
"""์ต์ข
HTML ๋ฆฌํฌํธ ์์ฑ""" |
|
|
|
steps_html = "" |
|
step_numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8] |
|
|
|
for i, (step_title, content) in enumerate(all_steps.items(), 1): |
|
step_number = step_numbers[i-1] if i <= len(step_numbers) else i |
|
steps_html += self.generate_step_html(step_title, content, step_number) |
|
|
|
return f""" |
|
<div style="max-width: 1000px; margin: 0 auto; padding: 25px; font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; background: #f8f9fa;"> |
|
<div style="text-align: center; padding: 30px; margin-bottom: 35px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; color: white; box-shadow: 0 6px 12px rgba(0,0,0,0.15);"> |
|
<div style="margin: 0; font-family: 'Malgun Gothic', sans-serif; font-size: 28px; font-weight: 700; color: white;">๐ {keyword} ํค์๋ ๋ถ์ ๋ฆฌํฌํธ</div> |
|
<div style="margin: 15px 0 0 0; font-size: 18px; color: #e9ecef;">์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๊ฒฐ ๋ถ์ ๊ฒฐ๊ณผ</div> |
|
</div> |
|
|
|
{steps_html} |
|
|
|
<div style="text-align: center; padding: 20px; margin-top: 30px; background: #e9ecef; border-radius: 8px; color: #6c757d;"> |
|
<div style="font-size: 14px;">๐ AI ์ํ ์์ฑ ๋ถ์๊ธฐ v4.0 - ์คํ์ด์ค๋ฐ ์ฒ๋ฆฌ ๊ฐ์ + ์ฌ๋ฐ๋ฅธ ํธ๋ ๋ ๋ถ์ ๋ก์ง</div> |
|
</div> |
|
</div> |
|
""" |
|
|
|
def analyze_keyword_complete(self, keyword: str, volume_data: Dict, |
|
keywords_df: Optional[pd.DataFrame], trend_data_1year=None, trend_data_3year=None) -> Dict[str, str]: |
|
"""์ ์ฒด 8๋จ๊ณ ํค์๋ ๋ถ์ ์คํ (์์ฑ์ ๋ต + 7๋จ๊ณ + ๊ฐ์ ๋ ๊ฒฐ๋ก )""" |
|
|
|
logger.info(f"8๋จ๊ณ ํค์๋ ๋ถ์ ์์: '{keyword}'") |
|
|
|
|
|
sourcing_result = self.analyze_sourcing_strategy(keyword, volume_data, keywords_df, trend_data_1year, trend_data_3year) |
|
sourcing_html = self.generate_step_html("์์ฑ์ ๋ต ๋ถ์", sourcing_result, 0) |
|
|
|
|
|
step_results = {} |
|
|
|
|
|
step1_result = self.analyze_step1_product_type(keyword, keywords_df) |
|
step_results["1๋จ๊ณ"] = step1_result |
|
step1_html = self.generate_step_html("1๋จ๊ณ. ์ํ์ ํ ๋ถ์", step1_result, 1) |
|
|
|
|
|
step2_result = self.analyze_step2_target_customer(keyword, step1_result) |
|
step_results["2๋จ๊ณ"] = step2_result |
|
step2_html = self.generate_step_html("2๋จ๊ณ. ์๋น์ ํ๊ฒ ์ค์ ", step2_result, 2) |
|
|
|
|
|
previous_results = f"{step1_result}\n\n{step2_result}" |
|
step3_result = self.analyze_step3_sourcing_strategy(keyword, previous_results) |
|
step_results["3๋จ๊ณ"] = step3_result |
|
step3_html = self.generate_step_html("3๋จ๊ณ. ํ๊ฒ๋ณ ์ฐจ๋ณํ๋ ์์ฑ ์ ๋ต ์ ์", step3_result, 3) |
|
|
|
|
|
previous_results += f"\n\n{step3_result}" |
|
step4_result = self.analyze_step4_product_recommendation(keyword, previous_results) |
|
step_results["4๋จ๊ณ"] = step4_result |
|
step4_html = self.generate_step_html("4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ", step4_result, 4) |
|
|
|
|
|
previous_results += f"\n\n{step4_result}" |
|
step5_result = self.analyze_step5_trust_building(keyword, previous_results) |
|
step_results["5๋จ๊ณ"] = step5_result |
|
step5_html = self.generate_step_html("5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง", step5_result, 5) |
|
|
|
|
|
previous_results += f"\n\n{step5_result}" |
|
step6_result = self.analyze_step6_usp_development(keyword, previous_results) |
|
step_results["6๋จ๊ณ"] = step6_result |
|
step6_html = self.generate_step_html("6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง", step6_result, 6) |
|
|
|
|
|
previous_results += f"\n\n{step6_result}" |
|
step7_result = self.analyze_step7_copy_creation(keyword, previous_results) |
|
step_results["7๋จ๊ณ"] = step7_result |
|
step7_html = self.generate_step_html("7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ", step7_result, 7) |
|
|
|
|
|
conclusion_result = self.analyze_conclusion_enhanced(keyword, previous_results + f"\n\n{step7_result}", sourcing_result) |
|
conclusion_html = self.generate_step_html("๊ฒฐ๋ก ", conclusion_result, 8) |
|
|
|
|
|
all_steps = { |
|
"์์ฑ์ ๋ต ๋ถ์": sourcing_result, |
|
"1๋จ๊ณ. ์ํ์ ํ ๋ถ์": step1_result, |
|
"2๋จ๊ณ. ์๋น์ ํ๊ฒ ์ค์ ": step2_result, |
|
"3๋จ๊ณ. ํ๊ฒ๋ณ ์ฐจ๋ณํ๋ ์์ฑ ์ ๋ต ์ ์": step3_result, |
|
"4๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ ์ํ 5๊ฐ์ง ์ถ์ฒ": step4_result, |
|
"5๋จ๊ณ. ์ ๋ขฐ์ฑ์ ์ค ์ ์๋ ์์ 5๊ฐ์ง": step5_result, |
|
"6๋จ๊ณ. ์ฐจ๋ณํ ์์๋ณ USP 5๊ฐ์ง": step6_result, |
|
"7๋จ๊ณ. USP๋ณ ์์ธํ์ด์ง ํค๋ ์นดํผ": step7_result, |
|
"๊ฒฐ๋ก ": conclusion_result |
|
} |
|
|
|
full_html = self.generate_final_html(keyword, all_steps) |
|
|
|
|
|
return { |
|
"sourcing_html": self.generate_step_html("์์ฑ์ ๋ต ๋ถ์", sourcing_result, 0), |
|
"step1_html": step1_html, |
|
"step2_html": step2_html, |
|
"step3_html": step3_html, |
|
"step4_html": step4_html, |
|
"step5_html": step5_html, |
|
"step6_html": step6_html, |
|
"step7_html": step7_html, |
|
"conclusion_html": conclusion_html, |
|
"full_html": full_html, |
|
"results": all_steps |
|
} |
|
|
|
|
|
|
|
|
|
def analyze_keyword_for_sourcing(analysis_keyword, volume_data, trend_data_1year=None, |
|
trend_data_3year=None, filtered_keywords_df=None, |
|
target_categories=None, gemini_model=None): |
|
""" |
|
๋ฉ์ธ ๋ถ์ ํจ์ - ์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๊ฒฐ ๋ถ์ |
|
๊ธฐ์กด ํจ์๋ช
์ ์งํ์ฌ ํธํ์ฑ ํ๋ณด |
|
""" |
|
|
|
if not gemini_model: |
|
return generate_error_response("Gemini AI ๋ชจ๋ธ์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.") |
|
|
|
try: |
|
logger.info(f"์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๊ฒฐ ํค์๋ ๋ถ์ ์์: '{analysis_keyword}'") |
|
|
|
analyzer = CompactKeywordAnalyzer(gemini_model) |
|
result = analyzer.analyze_keyword_complete(analysis_keyword, volume_data, filtered_keywords_df, trend_data_1year, trend_data_3year) |
|
|
|
logger.info(f"์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๊ฒฐ ํค์๋ ๋ถ์ ์๋ฃ: '{analysis_keyword}'") |
|
|
|
|
|
return result["full_html"] |
|
|
|
except Exception as e: |
|
logger.error(f"ํค์๋ ๋ถ์ ์ค๋ฅ: {e}") |
|
return generate_error_response(f"ํค์๋ ๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}") |
|
|
|
def analyze_keyword_with_individual_steps(analysis_keyword, volume_data, trend_data_1year=None, |
|
trend_data_3year=None, filtered_keywords_df=None, |
|
target_categories=None, gemini_model=None): |
|
""" |
|
๊ฐ๋ณ ๋จ๊ณ HTML์ ํฌํจํ ์ ์ฒด ๋ถ์ ํจ์ |
|
์์ฑ์ ๋ต + ๊ฐ 7๋จ๊ณ๋ณ ๊ฐ๋ณ HTML๊ณผ ์ ์ฒด HTML์ ๋ชจ๋ ๋ฐํ |
|
""" |
|
|
|
if not gemini_model: |
|
error_html = generate_error_response("Gemini AI ๋ชจ๋ธ์ด ์ด๊ธฐํ๋์ง ์์์ต๋๋ค.") |
|
return { |
|
"sourcing_html": error_html, "step1_html": error_html, "step2_html": error_html, "step3_html": error_html, |
|
"step4_html": error_html, "step5_html": error_html, "step6_html": error_html, |
|
"step7_html": error_html, "conclusion_html": error_html, "full_html": error_html, |
|
"results": {} |
|
} |
|
|
|
try: |
|
logger.info(f"์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๋ณ ํค์๋ ๋ถ์ ์์: '{analysis_keyword}'") |
|
|
|
analyzer = CompactKeywordAnalyzer(gemini_model) |
|
result = analyzer.analyze_keyword_complete(analysis_keyword, volume_data, filtered_keywords_df, trend_data_1year, trend_data_3year) |
|
|
|
logger.info(f"์์ฑ์ ๋ต + 7๋จ๊ณ ๊ฐ๋ณ ํค์๋ ๋ถ์ ์๋ฃ: '{analysis_keyword}'") |
|
return result |
|
|
|
except Exception as e: |
|
logger.error(f"ํค์๋ ๋ถ์ ์ค๋ฅ: {e}") |
|
error_html = generate_error_response(f"ํค์๋ ๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {str(e)}") |
|
return { |
|
"sourcing_html": error_html, "step1_html": error_html, "step2_html": error_html, "step3_html": error_html, |
|
"step4_html": error_html, "step5_html": error_html, "step6_html": error_html, |
|
"step7_html": error_html, "conclusion_html": error_html, "full_html": error_html, |
|
"results": {} |
|
} |
|
|
|
def generate_error_response(error_message): |
|
"""์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ค์ ์คํ์ผ๋ก ์์ฑ""" |
|
return f''' |
|
<div style="color: #721c24; padding: 30px; text-align: center; width: 100%; |
|
background-color: #f8d7da; border-radius: 12px; border: 1px solid #f5c6cb; font-family: 'Pretendard', sans-serif;"> |
|
<h3 style="margin-bottom: 15px; color: #721c24;">โ ๋ถ์ ์คํจ</h3> |
|
<p style="margin-bottom: 20px; font-size: 16px;">{error_message}</p> |
|
|
|
<div style="background: white; padding: 20px; border-radius: 8px; color: #333; text-align: left;"> |
|
<h4 style="color: #721c24; margin-bottom: 15px;">๐ง ํด๊ฒฐ ๋ฐฉ๋ฒ</h4> |
|
<ul style="padding-left: 20px; line-height: 1.8;"> |
|
<li>๐ ํค์๋ ํ์ธ: ์ฌ๋ฐ๋ฅธ ํ๊ธ ํค์๋์ธ์ง ํ์ธ</li> |
|
<li>๐ ๊ฒ์๋ ํ์ธ: ๋๋ฌด ์์ํ ํค์๋๋ ๋ฐ์ดํฐ๊ฐ ์์ ์ ์์</li> |
|
<li>๐ ๋คํธ์ํฌ ์ํ: ์ธํฐ๋ท ์ฐ๊ฒฐ ์ํ ํ์ธ</li> |
|
<li>๐ง API ์ํ: ๋ค์ด๋ฒ API ์๋ฒ ์ํ ํ์ธ</li> |
|
<li>๐ ์ฌ์๋: ์ ์ ํ ๋ค์ ์๋ํด๋ณด์ธ์</li> |
|
</ul> |
|
</div> |
|
|
|
<div style="margin-top: 15px; padding: 10px; background: #d1ecf1; border-radius: 6px; color: #0c5460; font-size: 14px;"> |
|
๐ก ํ: 2๋จ๊ณ์์ ์ถ์ถ๋ ํค์๋ ๋ชฉ๋ก์ ์ฐธ๊ณ ํ์ฌ ๊ฒ์ฆ๋ ํค์๋๋ฅผ ์ฌ์ฉํด๋ณด์ธ์. |
|
</div> |
|
</div> |
|
''' |