|
""" |
|
|
|
logger.info("Gemini API νΈμΆ μμ...") |
|
# Gemini API νΈμΆ - μ¨λλ₯Ό λμ¬μ λ λ€μν κ²°κ³Ό μμ± |
|
response = client.models.generate_content( |
|
model="gemini-2.0-flash", |
|
contents=prompt, |
|
config=GenerateContentConfig( |
|
tools=config_tools, # κ²μ μμ§μ λ°λΌ λꡬ μ€μ |
|
response_modalities=["TEXT"], |
|
temperature=0.95, # μ¨λλ₯Ό λμ¬μ λ λ€μν κ²°κ³Ό |
|
max_output_tokens=2000, |
|
top_p=0.9, # λ λ€μν ν ν° μ ν |
|
top_k=40 # ν보 ν ν° μ μ¦κ° |
|
) |
|
) |
|
|
|
logger.info("Gemini API μλ΅ μμ μλ£") |
|
|
|
# μλ΅μμ ν
μ€νΈ μΆμΆ |
|
result_text = "" |
|
for part in response.candidates[0].content.parts: |
|
if hasattr(part, 'text'): |
|
result_text += part.text |
|
|
|
# κ²°κ³Ό μ μ - λ²νΈ, μ€λͺ
, κΈ°νΈ μ κ±° λ° μ€λ³΅ μ κ±° κ°ν |
|
lines = result_text.strip().split('\n') |
|
clean_keywords = [] |
|
seen_keywords = set() # μ€λ³΅ λ°©μ§λ₯Ό μν μ§ν© |
|
|
|
for line in lines: |
|
# λΉ μ€ κ±΄λλ°κΈ° |
|
if not line.strip(): |
|
continue |
|
|
|
# μ μ μμ
|
|
clean_line = line.strip() |
|
|
|
# λ²νΈ μ κ±° (1., 2., 3. λ±) |
|
import re |
|
clean_line = re.sub(r'^\d+\.?\s*', '', clean_line) |
|
|
|
# λΆλ¦Ώ ν¬μΈνΈ μ κ±° (-, *, β’, β
, β λ±) |
|
clean_line = re.sub(r'^[-*β’β
β]\s*', '', clean_line) |
|
|
|
# κ΄νΈ μ μ€λͺ
μ κ±° |
|
clean_line = re.sub(r'\([^)]*\)', '', clean_line) |
|
|
|
# μΆκ° μ€λͺ
μ κ±° (: μ΄ν λ΄μ©) |
|
if ':' in clean_line: |
|
clean_line = clean_line.split(':')[0] |
|
|
|
# 곡백 μ 리 |
|
clean_line = clean_line.strip() |
|
|
|
# μ ν¨ν ν€μλλ§ μΆκ° (2κΈμ μ΄μ, 50κΈμ μ΄ν) |
|
if clean_line and 2 <= len(clean_line) <= 50: |
|
# λΈλλλͺ
μ΄λ μ μ‘°μ
체λͺ
νν°λ§ |
|
brand_keywords = ['μΌμ±', 'μμ§', 'LG', 'μ ν', 'μμ΄ν°', 'κ°€λμ', 'λμ΄ν€', 'μλλ€μ€', 'μ€νλ²
μ€'] |
|
if not any(brand in clean_line for brand in brand_keywords): |
|
# μ€λ³΅ κ²μ¬ - λμλ¬Έμ κ΅¬λΆ μμ΄ μ²΄ν¬ |
|
clean_lower = clean_line.lower() |
|
if clean_lower not in seen_keywords: |
|
seen_keywords.add(clean_lower) |
|
clean_keywords.append(clean_line) |
|
|
|
# 50κ°λ‘ μ ννλ, λΆμ‘±νλ©΄ μΆκ° μμ± μμ² |
|
if len(clean_keywords) < 50: |
|
logger.info(f"ν€μλ λΆμ‘± ({len(clean_keywords)}κ°), μΆκ° μμ± νμ") |
|
|
|
# λΆμ‘±ν λ§νΌ μΆκ° μμ±μ μν 보쑰 ν둬ννΈ |
|
additional_prompt = f""" |
|
κΈ°μ‘΄μ μμ±λ ν€μλ: {', '.join(clean_keywords)} |
|
|
|
μ ν€μλλ€κ³Ό μ λ μ€λ³΅λμ§ μλ μμ ν μλ‘μ΄ {category} κ΄λ ¨ μΌνν€μλλ₯Ό {50 - len(clean_keywords)}κ° λ μμ±νμΈμ. |
|
|
|
λ°λμ μ§μΌμΌ ν κ·μΉ: |
|
- κΈ°μ‘΄ ν€μλμ μ λ μ€λ³΅λμ§ μμ |
|
- μμ¬, νν, κΈ°λ₯μ λ€μνκ² μ‘°ν© |
|
- λΈλλλͺ
μ λ κΈμ§ |
|
- μμ ν€μλλ§ μΆλ ₯ (λ²νΈ, μ€λͺ
, κΈ°νΈ κΈμ§) |
|
|
|
μμ μΆλ ₯: |
|
μ€ν
μΈλ¦¬μ€ μλ° |
|
κ³ λ¬΄ λ§€νΈ |
|
μ 리 μ©κΈ° |
|
""" |
|
|
|
# μΆκ° μμ± μμ² |
|
additional_response = client.models.generate_content( |
|
model="gemini-2.0-flash", |
|
contents=additional_prompt, |
|
config=GenerateContentConfig( |
|
response_modalities=["TEXT"], |
|
temperature=0.98, # λ λμ μ¨λλ‘ λ€μμ± λ³΄μ₯ |
|
max_output_tokens=1000, |
|
top_p=0.95, |
|
top_k=50 |
|
) |
|
) |
|
|
|
# μΆκ° ν€μλ μ²λ¦¬ |
|
additional_text = "" |
|
for part in additional_response.candidates[0].content.parts: |
|
if hasattr(part, 'text'): |
|
additional_text += part.text |
|
|
|
additional_lines = additional_text.strip().split('\n') |
|
for line in additional_lines: |
|
if not line.strip(): |
|
continue |
|
|
|
clean_line = line.strip() |
|
clean_line = re.sub(r'^\d+\.?\s*', '', clean_line) |
|
clean_line = re.sub(r'^[-*β’β
β]\s*', '', clean_line) |
|
clean_line = re.sub(r'\([^)]*\)', '', clean_line) |
|
|
|
if ':' in clean_line: |
|
clean_line = clean_line.split(':')[0] |
|
|
|
clean_line = clean_line.strip() |
|
|
|
if clean_line and 2 <= len(clean_line) <= 50: |
|
brand_keywords = ['μΌμ±', 'μμ§', 'LG', 'μ ν', 'μμ΄ν°', 'κ°€λμ', 'λμ΄ν€', 'μλλ€μ€', 'μ€νλ²
μ€'] |
|
if not any(brand in clean_line for brand in brand_keywords): |
|
clean_lower = clean_line.lower() |
|
if clean_lower not in seen_keywords and len(clean_keywords) < 50: |
|
seen_keywords.add(clean_lower) |
|
clean_keywords.append(clean_line) |
|
|
|
# 50κ°λ‘ μ ν |
|
clean_keywords = clean_keywords[:50] |
|
|
|
# μ΅μ’
μ
νλ‘ μμλ 무μμν |
|
random.shuffle(clean_keywords) |
|
|
|
# μ΅μ’
κ²°κ³Ό λ¬Έμμ΄ μμ± |
|
final_result = '\n'.join(clean_keywords) |
|
|
|
logger.info(f"μ΅μ’
μ μ λ ν€μλ κ°μ: {len(clean_keywords)}κ°") |
|
logger.info(f"μ€λ³΅ μ κ±°λ ν€μλ μ: {len(seen_keywords)}κ°") |
|
logger.info("=== λ€μμ± κ°ν μΌνν€μλ μμ± μλ£ ===") |
|
|
|
# κ·ΈλΌμ΄λ© λ©νλ°μ΄ν° λ‘κ·Έ |
|
if hasattr(response.candidates[0], 'grounding_metadata'): |
|
logger.info("Google κ²μ κ·ΈλΌμ΄λ© λ©νλ°μ΄ν° νμΈλ¨") |
|
if hasattr(response.candidates[0].grounding_metadata, 'web_search_queries'): |
|
queries = response.candidates[0].grounding_metadata.web_search_queries |
|
logger.info(f"μ€νλ κ²μ 쿼리: {queries}") |
|
|
|
return final_result |
|
|
|
except Exception as e: |
|
error_msg = f"μ€λ₯ λ°μ: {str(e)}" |
|
logger.error(error_msg) |
|
logger.error("GEMINI_API_KEY νκ²½λ³μκ° μ¬λ°λ₯΄κ² μ€μ λμλμ§ νμΈν΄μ£ΌμΈμ.") |
|
return f"{error_msg}\n\nGEMINI_API_KEY νκ²½λ³μκ° μ¬λ°λ₯΄κ² μ€μ λμλμ§ νμΈν΄μ£ΌμΈμ." |
|
|
|
def generate_with_logs(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine): |
|
"""ν€μλ μμ±κ³Ό λ‘κ·Έλ₯Ό ν¨κ» λ°ννλ ν¨μ""" |
|
logger.info("=== λ€μμ± κ°ν μΌνν€μλ μμ± μμ ===") |
|
|
|
# ν€μλ μμ± |
|
result = generate_sourcing_keywords(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine) |
|
|
|
# μ΅κ·Ό λ‘κ·Έ κ°μ Έμ€κΈ° |
|
logs = get_recent_logs() |
|
|
|
return result, logs |
|
|
|
# Gradio μΈν°νμ΄μ€ κ΅¬μ± |
|
def create_interface(): |
|
with gr.Blocks( |
|
title="π― λ€μμ± κ°ν μΌνν€μλ μμ€ν
", |
|
theme=gr.themes.Soft(), |
|
css=""" |
|
.gradio-container { |
|
max-width: 1200px !important; |
|
} |
|
.title-header { |
|
text-align: center; |
|
background: linear-gradient(45deg, |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
font-size: 2.5em; |
|
font-weight: bold; |
|
margin-bottom: 20px; |
|
} |
|
.subtitle { |
|
text-align: center; |
|
color: |
|
font-size: 1.2em; |
|
margin-bottom: 30px; |
|
} |
|
""" |
|
) as demo: |
|
|
|
# ν€λ |
|
gr.HTML(""" |
|
<div class="title-header">π― λ€μμ± κ°ν μΌνν€μλ μμ€ν
</div> |
|
<div class="subtitle">π λ§€λ² μμ ν λ€λ₯Έ κ²°κ³Ό! μ€λ³΅ μλ μΌνν€μλ μ λ¬Έ λ°κ΅΄ νλ‘κ·Έλ¨</div> |
|
""") |
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
gr.Markdown("### π λ€μμ± κ°ν μ€μ ") |
|
|
|
# κ²μ μμ§ μ ν μΆκ° |
|
search_engine = gr.Dropdown( |
|
choices=[ |
|
"λͺ¨λ κ²μ μμ§ ν΅ν© λΆμ (μΆμ²)", |
|
"Google κ²μ κ·ΈλΌμ΄λ©λ§", |
|
"λ€μ΄λ² κ²μ APIλ§", |
|
"DuckDuckGo κ²μλ§", |
|
"κ²μ μμ΄ AIλ§ μ¬μ©" |
|
], |
|
label="π κ²μ μμ§ μ ν", |
|
value="λͺ¨λ κ²μ μμ§ ν΅ν© λΆμ (μΆμ²)" |
|
) |
|
|
|
# 1. μΌν μΉ΄ν
κ³ λ¦¬ μ ν |
|
category = gr.Dropdown( |
|
choices=["λλ€μ μ©", "ν¨μ
μ‘ν", "μν/건κ°", "μΆμ°/μ‘μ", "μ€ν¬μΈ /λ μ ", "λμ§νΈ/κ°μ ", "κ°κ΅¬/μΈν
리μ΄", "ν¨μ
μλ₯", "νμ₯ν/λ―Έμ©"], |
|
label="ποΈ μΌν μΉ΄ν
κ³ λ¦¬", |
|
value="μν/건κ°" |
|
) |
|
|
|
# 2. μΆκ° μμ²μ¬ν |
|
additional_request = gr.Textbox( |
|
label="π μΆκ° μμ²μ¬ν (λ€μμ± μ€μ¬)", |
|
placeholder="μ: λ€μν μμ¬, μλ‘μ΄ νν, λ
νΉν κΈ°λ₯ λ±", |
|
lines=2 |
|
) |
|
|
|
# 3. μΆμ νμ΄λ° |
|
launch_timing = gr.Radio( |
|
choices=["λλ€μ μ©", "μ¦μμμ±", "κΈ°νν"], |
|
label="β° μΆμ νμ΄λ°", |
|
value="μ¦μμμ±" |
|
) |
|
|
|
# 4. κ³μ μ± |
|
seasonality = gr.Radio( |
|
choices=["λλ€μ μ©", "λ΄", "μ¬λ¦", "κ°μ", "겨μΈ", "λΉκ³μ "], |
|
label="π± κ³μ μ±", |
|
value="λΉκ³μ " |
|
) |
|
|
|
with gr.Column(scale=1): |
|
gr.Markdown("### π° λͺ©ν μ€μ ") |
|
|
|
# 5. λ§€μΆ λͺ©ν |
|
sales_target = gr.Radio( |
|
choices=["λλ€μ μ©", "100λ§μ μ΄ν", "100-500λ§μ", "500-1μ²λ§μ", "1μ²-5μ²λ§μ", "5μ²λ§μ μ΄μ"], |
|
label="π΅ λ§€μΆ λͺ©ν", |
|
value="100-500λ§μ" |
|
) |
|
|
|
# 6. νλ§€ μ±λ |
|
sales_channel = gr.Radio( |
|
choices=["λλ€μ μ©", "μ€νλ§μΌ", "SNSλ§μΌν
", "κ΄κ³ μ§ν", "μ€νλΌμΈ"], |
|
label="π± νλ§€ μ±λ", |
|
value="μ€νλ§μΌ" |
|
) |
|
|
|
# 7. κ²½μ κ°λ |
|
competition_level = gr.Radio( |
|
choices=[ |
|
"λλ€μ μ©", |
|
"μ΄λ³΄", |
|
"μ€μ", |
|
"κ³ μ" |
|
], |
|
label="βοΈ κ²½μ κ°λ", |
|
value="μ΄λ³΄" |
|
) |
|
|
|
# μ€ν λ²νΌ |
|
generate_btn = gr.Button( |
|
"π λ€μμ± κ°ν μΌνν€μλ λ°κ΅΄ μμ (λ§€λ² λ€λ₯Έ κ²°κ³Ό)", |
|
variant="primary", |
|
size="lg" |
|
) |
|
|
|
# κ²°κ³Ό μΆλ ₯ |
|
with gr.Row(): |
|
with gr.Column(scale=2): |
|
gr.Markdown("### π λ€μμ± κ°ν μΌνν€μλ (50κ°)") |
|
output = gr.Textbox( |
|
label="μ€λ³΅ μλ μΌνν€μλ κ²°κ³Ό (λ§€λ² μμ ν λ€λ¦)", |
|
lines=30, |
|
max_lines=50, |
|
placeholder="μ¬κΈ°μ λ§€λ² λ€λ₯Έ 50κ°μ μΌνν€μλκ° μΆλ ₯λ©λλ€...", |
|
show_copy_button=True |
|
) |
|
|
|
with gr.Column(scale=1): |
|
gr.Markdown("### π μ€ν λ‘κ·Έ") |
|
log_output = gr.Textbox( |
|
label="μμ€ν
λ‘κ·Έ", |
|
lines=30, |
|
max_lines=50, |
|
placeholder="μμ€ν
μ€ν λ‘κ·Έκ° μ¬κΈ°μ νμλ©λλ€...", |
|
show_copy_button=True |
|
) |
|
|
|
# μ΄λ²€νΈ μ°κ²° |
|
generate_btn.click( |
|
fn=generate_with_logs, |
|
inputs=[ |
|
category, |
|
additional_request, |
|
launch_timing, |
|
seasonality, |
|
sales_target, |
|
sales_channel, |
|
competition_level, |
|
search_engine |
|
], |
|
outputs=[output, log_output], |
|
show_progress=True |
|
) |
|
|
|
# μ¬μ©λ² μλ΄ |
|
with gr.Accordion("π λ€μμ± κ°ν μ¬μ©λ² μλ΄", open=False): |
|
gr.Markdown(""" |
|
|
|
|
|
|
|
- **μμ ν μ€λ³΅ λ°©μ§**: λ§€λ² μ€νν λλ§λ€ μμ ν λ€λ₯Έ ν€μλ μμ± |
|
- **λλ€ μλ μμ€ν
**: νμ¬ μκ°μ κΈ°λ°μΌλ‘ ν λλ€ μλλ‘ μμΈ‘ λΆκ°λ₯ |
|
- **λ€μν μ‘°ν© λ³΄μ₯**: μμ¬ΓννΓκΈ°λ₯μ 3μ°¨μ μ‘°ν©μΌλ‘ 무ν λ€μμ± |
|
- **μ€λ³΅ κ²μ¬ κ°ν**: λμλ¬Έμ κ΅¬λΆ μλ μ격ν μ€λ³΅ μ κ±° |
|
- **μ¨λ μ‘°μ **: AI μμ± νλΌλ―Έν° μ΅μ νλ‘ μ°½μμ± κ·Ήλν |
|
|
|
|
|
1. **μλ κΈ°λ° λλ€ν**: λ§μ΄ν¬λ‘μ΄ λ¨μ μκ° κΈ°λ° λλ€ μλ |
|
2. **3μ°¨μ μ‘°ν© μμ€ν
**: |
|
- μμ¬: μ€λ¦¬μ½, μ€ν
μΈλ¦¬μ€, μΈλΌλ―Ή, λλ무 λ± 20μ’
|
|
- νν: μ μ΄μ, μν, μ¬λ¦Ό, ν΄λμ© λ± 20μ’
|
|
- κΈ°λ₯: λ°©μ, νκ· , λ§κ·Έλ€ν±, λ³΄μ¨ λ± 20μ’
|
|
3. **μ€λ³΅ λ°©μ§ μκ³ λ¦¬μ¦**: μμ± μ€ μ€μκ° μ€λ³΅ κ²μ¬ |
|
4. **μΆκ° μμ± μμ€ν
**: λΆμ‘±μ μλμΌλ‘ μΆκ° ν€μλ μμ± |
|
|
|
|
|
- **ν€μλλ³ λ
립 μ μ©**: κ° ν€μλλ§λ€ λ€λ₯Έ 쑰건 μ‘°ν© |
|
- **μμΈ‘ λΆκ°λ₯μ±**: κ°μ μ€μ μ΄λΌλ λ§€λ² λ€λ₯Έ κ²°κ³Ό |
|
- **μ‘°ν© νλ°**: μμ² κ°μ§ κ°λ₯ν μ‘°ν©μΌλ‘ 무ν λ€μμ± |
|
|
|
|
|
import os |
|
import logging |
|
import sys |
|
import random |
|
import requests |
|
import json |
|
from datetime import datetime |
|
from google import genai |
|
from google.genai.types import Tool, GenerateContentConfig, GoogleSearch |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(levelname)s - %(message)s', |
|
handlers=[ |
|
logging.StreamHandler(sys.stdout), |
|
logging.FileHandler('sourcing_app.log', encoding='utf-8') |
|
] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
DIVERSE_SEED_POOLS = { |
|
"ν¨μ
μ‘ν": [ |
|
"μ‘μΈμ리", "μ₯μ ꡬ", "κ°λ°©", "μ§κ°", "λͺ¨μ", "μ€μΉ΄ν", "벨νΈ", "μ κΈλΌμ€", "ν€μ΄μ‘μΈμ리", "μκ³μ€", |
|
"ν€λ§", "λΈλ‘μΉ", "λͺ©κ±Έμ΄", "νμ°", "λ°μ§", "κ·κ±Έμ΄", "νΈλν°μΌμ΄μ€", "νμ°μΉ", "ν΄λ¬μΉ", "ν νΈλ°±" |
|
], |
|
"μν/건κ°": [ |
|
"μ£Όλ°©μ©ν", "μμ€μ©ν", "μ²μμ©ν", "μλ©μ©ν", "건κ°μ©ν", "μλ£μ©ν", "λ§μ¬μ§μ©ν", "μ΄λμ©ν", |
|
"λ€μ΄μ΄νΈμ©ν", "νμ₯μ§", "μΈμ ", "μ΄νΈ", "μΉμ½", "λΉλ", "μ건", "λ² κ°", "μ΄λΆ", "μΏ μ
", "λ§€νΈ" |
|
], |
|
"μΆμ°/μ‘μ": [ |
|
"μ μμ©ν", "μ‘μμ©ν", "μΆμ°μ©ν", "μμ°λΆμ©ν", "μ μμμ©ν", "μ΄μ μμ©ν", "κΈ°μ κ·", "μ λ³", |
|
"μ λͺ¨μ°¨", "μΉ΄μνΈ", "μκΈ°μ·", "μ₯λκ°", "κ΅μ‘μ©ν", "μ±
", "κ·Έλ¦Όμ±
", "νΌμ¦", "λΈλ‘", "μΈν", "λμ΄λ§€νΈ" |
|
], |
|
"μ€ν¬μΈ /λ μ ": [ |
|
"μ΄λμ©ν", "ν¬μ€μ©ν", "μκ°μ©ν", "μμμ©ν", "λ±μ°μ©ν", "μΊ νμ©ν", "λμμ©ν", "골νμ©ν", |
|
"μΆκ΅¬μ©ν", "λꡬμ©ν", "λ°°λλ―Όν΄μ©ν", "νꡬμ©ν", "ν
λμ€μ©ν", "μμ κ±°μ©ν", "μ€μΌμ΄νΈλ³΄λμ©ν" |
|
], |
|
"λμ§νΈ/κ°μ ": [ |
|
"μ€λ§νΈν°μ‘μΈμ리", "μ»΄ν¨ν°μ©ν", "νλΈλ¦Ώμ©ν", "μ΄μ΄ν°", "μ€νΌμ»€", "μΆ©μ κΈ°", "μΌμ΄λΈ", "λ§μ°μ€ν¨λ", |
|
"ν€λ³΄λ", "λ§μ°μ€", "μΉμΊ ", "λ§μ΄ν¬", "ν€λμ
", "κ²μν¨λ", "USB", "λ©λͺ¨λ¦¬μΉ΄λ", "νμλ±
ν¬" |
|
], |
|
"κ°κ΅¬/μΈν
리μ΄": [ |
|
"μλ©κ°κ΅¬", "μΉ¨μ€κ°κ΅¬", "κ±°μ€κ°κ΅¬", "μ£Όλ°©κ°κ΅¬", "μμ€κ°κ΅¬", "μ¬λ¬΄μ©κ°κ΅¬", "μΈν
리μ΄μν", "μ‘°λͺ
", |
|
"컀νΌ", "λΈλΌμΈλ", "μΉ΄ν«", "λ¬κ·Έ", "μ‘μ", "κ±°μΈ", "μκ³", "νλΆ", "κ½λ³", "μΊλ€", "λ°©ν₯μ " |
|
], |
|
"ν¨μ
μλ₯": [ |
|
"ν°μ
μΈ ", "μ
μΈ ", "λΈλΌμ°μ€", "μνΌμ€", "μ€μ»€νΈ", "λ°μ§", "μ²λ°μ§", "λ κΉ
μ€", "μμΌ", "μ½νΈ", |
|
"μ νΌ", "κ°λ건", "λνΈ", "νλ", "μ‘°λΌ", "μμ·", "μ μ·", "μλ§", "μ€ννΉ", "μ΄λ볡" |
|
], |
|
"νμ₯ν/λ―Έμ©": [ |
|
"μ€ν¨μΌμ΄", "λ©μ΄ν¬μ
", "ν΄λ μ§", "λ§μ€ν¬ν©", "μ ν¬λ¦Ό", "λ‘μ
", "μμΌμ€", "ν¬λ¦Ό", "립밀", |
|
"립μ€ν±", "μμ΄μλ", "λ§μ€μΉ΄λΌ", "νμ΄λ°μ΄μ
", "컨μ€λ¬", "λΈλ¬μ
", "νμ΄λΌμ΄ν°", "λ€μΌ", "ν₯μ" |
|
] |
|
} |
|
|
|
|
|
MATERIAL_KEYWORDS = [ |
|
"μ€λ¦¬μ½", "μ€ν
μΈλ¦¬μ€", "μΈλΌλ―Ή", "μ 리", "λ무", "λλ무", "λ©΄", "λ¦°λ¨", "ν΄λ¦¬μμ€ν°", "λμΌλ‘ ", |
|
"κ³ λ¬΄", "νλΌμ€ν±", "μ’
μ΄", "κ°μ£½", "μΈμ‘°κ°μ£½", "λ©ν", "μ루미λ", "μ² ", "ꡬ리", "ν©λ" |
|
] |
|
|
|
|
|
SHAPE_KEYWORDS = [ |
|
"μ μ΄μ", "ν΄λμ©", "λ―Έλ", "λν", "μν", "μ¬κ°", "νμ", "μ§μ¬κ°", "μΌκ°", "μ‘κ°", |
|
"μ¬λ¦Ό", "λκΊΌμ΄", "μμ", "κΈ΄", "μ§§μ", "λμ", "μ’μ", "κΉμ", "μμ", "곑μ " |
|
] |
|
|
|
|
|
FUNCTION_KEYWORDS = [ |
|
"λ°©μ", "λ―ΈλλΌλ°©μ§", "νκ· ", "λμμ κ±°", "보μ¨", "보λ", "μ건", "ν‘μ", "μ°¨λ¨", "보νΈ", |
|
"λ§κ·Έλ€ν±", "μμ", "λμ ", "ν¬λͺ
", "λΆν¬λͺ
", "λ°κ΄", "λ°μ¬", "μ μΆ", "νλ ₯", "κ³ μ " |
|
] |
|
|
|
|
|
def initialize_gemini(): |
|
logger.info("Gemini API ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μμ") |
|
api_key = os.getenv("GEMINI_API_KEY") |
|
if not api_key: |
|
logger.error("GEMINI_API_KEY νκ²½λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
raise ValueError("GEMINI_API_KEY νκ²½λ³μκ° μ€μ λμ§ μμμ΅λλ€.") |
|
|
|
client = genai.Client(api_key=api_key) |
|
logger.info("Gemini API ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μλ£") |
|
return client |
|
|
|
def get_recent_logs(): |
|
"""μ΅κ·Ό λ‘κ·Έλ₯Ό κ°μ Έμ€λ ν¨μ""" |
|
try: |
|
with open('sourcing_app.log', 'r', encoding='utf-8') as f: |
|
lines = f.readlines() |
|
|
|
return ''.join(lines[-50:]) |
|
except FileNotFoundError: |
|
return "λ‘κ·Έ νμΌμ μ°Ύμ μ μμ΅λλ€." |
|
except Exception as e: |
|
return f"λ‘κ·Έ μ½κΈ° μ€λ₯: {str(e)}" |
|
|
|
def generate_diverse_keyword_combinations(category, count=60): |
|
"""λ€μν ν€μλ μ‘°ν©μ μμ±νλ ν¨μ""" |
|
logger.info(f"λ€μν ν€μλ μ‘°ν© μμ± μμ: {category}, {count}κ°") |
|
|
|
combinations = [] |
|
category_pool = DIVERSE_SEED_POOLS.get(category, DIVERSE_SEED_POOLS["μν/건κ°"]) |
|
|
|
|
|
single_keywords = random.sample(category_pool, min(12, len(category_pool))) |
|
combinations.extend(single_keywords) |
|
|
|
|
|
for _ in range(18): |
|
material = random.choice(MATERIAL_KEYWORDS) |
|
item = random.choice(category_pool) |
|
combinations.append(f"{material} {item}") |
|
|
|
|
|
for _ in range(18): |
|
shape = random.choice(SHAPE_KEYWORDS) |
|
item = random.choice(category_pool) |
|
combinations.append(f"{shape} {item}") |
|
|
|
|
|
for _ in range(12): |
|
function = random.choice(FUNCTION_KEYWORDS) |
|
item = random.choice(category_pool) |
|
combinations.append(f"{function} {item}") |
|
|
|
|
|
combinations = list(set(combinations)) |
|
random.shuffle(combinations) |
|
|
|
logger.info(f"μμ±λ μ‘°ν© μ: {len(combinations)}κ°") |
|
return combinations[:count] |
|
|
|
def search_all_engines(query): |
|
"""λͺ¨λ κ²μ μμ§μ μ¬μ©νμ¬ λ°μ΄ν°λ₯Ό μ·¨ν©νλ ν¨μ""" |
|
logger.info(f"λͺ¨λ κ²μ μμ§μΌλ‘ κ²μ μμ: {query}") |
|
|
|
all_results = { |
|
"google": "", |
|
"naver": "", |
|
"duckduckgo": "" |
|
} |
|
|
|
|
|
try: |
|
naver_client_id = os.getenv("NAVER_CLIENT_ID") |
|
naver_client_secret = os.getenv("NAVER_CLIENT_SECRET") |
|
|
|
if naver_client_id and naver_client_secret: |
|
url = "https://openapi.naver.com/v1/search/shop.json" |
|
headers = { |
|
"X-Naver-Client-Id": naver_client_id, |
|
"X-Naver-Client-Secret": naver_client_secret |
|
} |
|
params = {"query": query, "display": 10} |
|
|
|
response = requests.get(url, headers=headers, params=params, timeout=10) |
|
if response.status_code == 200: |
|
data = response.json() |
|
naver_data = [] |
|
for item in data.get('items', [])[:5]: |
|
naver_data.append(f"μν: {item.get('title', '').replace('<b>', '').replace('</b>', '')}") |
|
naver_data.append(f"κ°κ²©: {item.get('lprice', '')}μ") |
|
naver_data.append(f"μΉ΄ν
κ³ λ¦¬: {item.get('category1', '')}") |
|
all_results["naver"] = "\n".join(naver_data) |
|
logger.info("λ€μ΄λ² κ²μ μλ£") |
|
else: |
|
all_results["naver"] = "λ€μ΄λ² API κ²μ μ€ν¨" |
|
else: |
|
all_results["naver"] = "λ€μ΄λ² API ν€κ° μ€μ λμ§ μμ" |
|
except Exception as e: |
|
all_results["naver"] = f"λ€μ΄λ² κ²μ μ€λ₯: {str(e)}" |
|
|
|
|
|
try: |
|
url = "https://api.duckduckgo.com/" |
|
params = { |
|
"q": query, |
|
"format": "json", |
|
"no_html": "1", |
|
"skip_disambig": "1" |
|
} |
|
|
|
response = requests.get(url, params=params, timeout=10) |
|
if response.status_code == 200: |
|
data = response.json() |
|
|
|
ddg_data = [] |
|
|
|
if data.get('Abstract'): |
|
ddg_data.append(f"μμ½: {data['Abstract']}") |
|
|
|
|
|
for topic in data.get('RelatedTopics', [])[:3]: |
|
if isinstance(topic, dict) and topic.get('Text'): |
|
ddg_data.append(f"κ΄λ ¨μ 보: {topic['Text']}") |
|
|
|
all_results["duckduckgo"] = "\n".join(ddg_data) if ddg_data else "DuckDuckGoμμ κ΄λ ¨ μ 보 μμ" |
|
logger.info("DuckDuckGo κ²μ μλ£") |
|
else: |
|
all_results["duckduckgo"] = "DuckDuckGo κ²μ μ€ν¨" |
|
except Exception as e: |
|
all_results["duckduckgo"] = f"DuckDuckGo κ²μ μ€λ₯: {str(e)}" |
|
|
|
|
|
all_results["google"] = "Google κ²μ κ·ΈλΌμ΄λ© μλ μ€ν" |
|
|
|
logger.info("λͺ¨λ κ²μ μμ§ λ°μ΄ν° μμ§ μλ£") |
|
return all_results |
|
|
|
def comprehensive_market_analysis(category, seasonality, sales_target): |
|
"""λ€μμ± κ°νλ μμ₯ λΆμ""" |
|
logger.info("λ€μμ± κ°ν μμ₯ λΆμ μμ") |
|
|
|
|
|
random.seed(datetime.now().microsecond) |
|
|
|
|
|
search_angles = [ |
|
"νμμν", "μ μν", "μΈκΈ°μν", "μ κ°μν", "κ³ κΈμν", "ν μΈμν", |
|
"κ°νΈμν", "μ€μ©μν", "νΈλ λμν", "μ¨μμν", "λ² μ€νΈμν", "μΆμ²μν" |
|
] |
|
|
|
search_queries = [] |
|
|
|
|
|
category_pool = DIVERSE_SEED_POOLS.get(category, DIVERSE_SEED_POOLS["μν/건κ°"]) |
|
|
|
for angle in search_angles: |
|
for item in random.sample(category_pool, 3): |
|
search_queries.append(f"{item} {angle}") |
|
|
|
|
|
for material in random.sample(MATERIAL_KEYWORDS, 5): |
|
search_queries.append(f"{material} {category} μν") |
|
|
|
|
|
for shape in random.sample(SHAPE_KEYWORDS, 5): |
|
search_queries.append(f"{shape} {category} μμ΄ν
") |
|
|
|
|
|
random.shuffle(search_queries) |
|
search_queries = search_queries[:15] |
|
|
|
comprehensive_data = {} |
|
|
|
for i, query in enumerate(search_queries): |
|
logger.info(f"λ€μμ± κ²μ {i+1}/15: {query}") |
|
comprehensive_data[f"query_{i+1}"] = search_all_engines(query) |
|
|
|
|
|
import time |
|
time.sleep(0.5) |
|
|
|
|
|
summary = "=== λ€μμ± κ°ν μμ₯ λΆμ κ²°κ³Ό ===\n\n" |
|
|
|
|
|
result_keys = list(comprehensive_data.keys()) |
|
random.shuffle(result_keys) |
|
|
|
summary += "π λ€μν μμ₯ κ²μ κ²°κ³Ό:\n" |
|
for key in result_keys[:10]: |
|
results = comprehensive_data.get(key, {}) |
|
if results.get("naver"): |
|
summary += f"β’ {results['naver'][:60]}...\n" |
|
summary += "\n" |
|
|
|
logger.info("λ€μμ± κ°ν λΆμ μλ£") |
|
return summary |
|
|
|
def search_with_api(query, search_engine="Google κ²μ κ·ΈλΌμ΄λ©λ§"): |
|
"""κ°λ³ κ²μ μμ§μΌλ‘ κ²μνλ ν¨μ (λ¨μΌ μμ§ μ νμ μ¬μ©)""" |
|
logger.info(f"κ²μ μμ§: {search_engine}, 쿼리: {query}") |
|
|
|
search_results = "" |
|
|
|
try: |
|
if search_engine == "λ€μ΄λ² κ²μ APIλ§": |
|
|
|
naver_client_id = os.getenv("NAVER_CLIENT_ID") |
|
naver_client_secret = os.getenv("NAVER_CLIENT_SECRET") |
|
|
|
if naver_client_id and naver_client_secret: |
|
url = "https://openapi.naver.com/v1/search/shop.json" |
|
headers = { |
|
"X-Naver-Client-Id": naver_client_id, |
|
"X-Naver-Client-Secret": naver_client_secret |
|
} |
|
params = {"query": query, "display": 10} |
|
|
|
response = requests.get(url, headers=headers, params=params) |
|
if response.status_code == 200: |
|
data = response.json() |
|
for item in data.get('items', [])[:5]: |
|
search_results += f"μνλͺ
: {item.get('title', '')}\n" |
|
search_results += f"κ°κ²©: {item.get('lprice', '')}μ\n" |
|
search_results += f"μΉ΄ν
κ³ λ¦¬: {item.get('category1', '')}\n\n" |
|
else: |
|
search_results = "λ€μ΄λ² API κ²μ μ€ν¨" |
|
else: |
|
search_results = "λ€μ΄λ² API ν€κ° μ€μ λμ§ μμ" |
|
|
|
elif search_engine == "DuckDuckGo κ²μλ§": |
|
|
|
try: |
|
url = "https://api.duckduckgo.com/" |
|
params = { |
|
"q": query, |
|
"format": "json", |
|
"no_html": "1", |
|
"skip_disambig": "1" |
|
} |
|
|
|
response = requests.get(url, params=params, timeout=10) |
|
if response.status_code == 200: |
|
data = response.json() |
|
|
|
|
|
if data.get('Abstract'): |
|
search_results += f"μμ½: {data['Abstract']}\n\n" |
|
|
|
|
|
for topic in data.get('RelatedTopics', [])[:5]: |
|
if isinstance(topic, dict) and topic.get('Text'): |
|
search_results += f"κ΄λ ¨ μ 보: {topic['Text']}\n" |
|
|
|
if not search_results: |
|
search_results = "DuckDuckGoμμ κ΄λ ¨ μ 보λ₯Ό μ°Ύμ§ λͺ»ν¨" |
|
else: |
|
search_results = "DuckDuckGo κ²μ μ€ν¨" |
|
except Exception as e: |
|
search_results = f"DuckDuckGo κ²μ μ€λ₯: {str(e)}" |
|
|
|
elif search_engine == "κ²μ μμ΄ AIλ§ μ¬μ©": |
|
search_results = "κ²μ μμ΄ AI μ§μλ§ μ¬μ©νμ¬ ν€μλ μμ±" |
|
|
|
else: |
|
|
|
search_results = "Google κ²μ κ·ΈλΌμ΄λ© μ¬μ©" |
|
|
|
except Exception as e: |
|
logger.error(f"κ²μ μ€λ₯: {str(e)}") |
|
search_results = f"κ²μ μ€λ₯: {str(e)}" |
|
|
|
logger.info(f"κ²μ κ²°κ³Ό κΈΈμ΄: {len(search_results)} λ¬Έμ") |
|
return search_results |
|
|
|
def apply_random_selection_for_keywords(category, launch_timing, seasonality, sales_target, sales_channel, competition_level): |
|
"""κ° ν€μλλ§λ€ λλ€νκ² μ‘°κ±΄μ μ μ©νκΈ° μν μ€μ λ¬Έμμ΄ μμ±""" |
|
|
|
|
|
categories = ["ν¨μ
μ‘ν", "μν/건κ°", "μΆμ°/μ‘μ", "μ€ν¬μΈ /λ μ ", "λμ§νΈ/κ°μ ", "κ°κ΅¬/μΈν
리μ΄", "ν¨μ
μλ₯", "νμ₯ν/λ―Έμ©"] |
|
launch_timings = ["μ¦μμμ±", "κΈ°νν"] |
|
seasonalities = ["λ΄", "μ¬λ¦", "κ°μ", "겨μΈ", "λΉκ³μ "] |
|
sales_targets = ["100λ§μ μ΄ν", "100-500λ§μ", "500-1μ²λ§μ", "1μ²-5μ²λ§μ", "5μ²λ§μ μ΄μ"] |
|
sales_channels = ["μ€νλ§μΌ", "SNSλ§μΌν
", "κ΄κ³ μ§ν", "μ€νλΌμΈ"] |
|
competition_levels = ["μ΄λ³΄", "μ€μ", "κ³ μ"] |
|
|
|
|
|
random_settings = { |
|
'category_random': category == "λλ€μ μ©", |
|
'launch_timing_random': launch_timing == "λλ€μ μ©", |
|
'seasonality_random': seasonality == "λλ€μ μ©", |
|
'sales_target_random': sales_target == "λλ€μ μ©", |
|
'sales_channel_random': sales_channel == "λλ€μ μ©", |
|
'competition_level_random': competition_level == "λλ€μ μ©", |
|
'categories': categories, |
|
'launch_timings': launch_timings, |
|
'seasonalities': seasonalities, |
|
'sales_targets': sales_targets, |
|
'sales_channels': sales_channels, |
|
'competition_levels': competition_levels |
|
} |
|
|
|
|
|
fixed_values = { |
|
'category': category if category != "λλ€μ μ©" else None, |
|
'launch_timing': launch_timing if launch_timing != "λλ€μ μ©" else None, |
|
'seasonality': seasonality if seasonality != "λλ€μ μ©" else None, |
|
'sales_target': sales_target if sales_target != "λλ€μ μ©" else None, |
|
'sales_channel': sales_channel if sales_channel != "λλ€μ μ©" else None, |
|
'competition_level': competition_level if competition_level != "λλ€μ μ©" else None |
|
} |
|
|
|
logger.info("=== ν€μλλ³ λλ€ μ€μ ===") |
|
logger.info(f"μΉ΄ν
κ³ λ¦¬ λλ€: {random_settings['category_random']}") |
|
logger.info(f"μΆμνμ΄λ° λλ€: {random_settings['launch_timing_random']}") |
|
logger.info(f"κ³μ μ± λλ€: {random_settings['seasonality_random']}") |
|
logger.info(f"λ§€μΆλͺ©ν λλ€: {random_settings['sales_target_random']}") |
|
logger.info(f"νλ§€μ±λ λλ€: {random_settings['sales_channel_random']}") |
|
logger.info(f"κ²½μκ°λ λλ€: {random_settings['competition_level_random']}") |
|
|
|
return random_settings, fixed_values |
|
|
|
def generate_sourcing_keywords(category, additional_request, launch_timing, seasonality, sales_target, sales_channel, competition_level, search_engine="Google κ²μ κ·ΈλΌμ΄λ©λ§"): |
|
"""λ€μμ± κ°νλ μΌν ν€μλ 50κ°λ₯Ό μμ±νλ ν¨μ""" |
|
logger.info("=== λ€μμ± κ°ν μΌνν€μλ μμ± μμ ===") |
|
logger.info(f"μ
λ ₯ 쑰건 - κ²μμμ§: {search_engine}") |
|
logger.info(f"μ
λ ₯ 쑰건 - μΉ΄ν
κ³ λ¦¬: {category}") |
|
logger.info(f"μ
λ ₯ 쑰건 - μΆκ°μμ²: {additional_request}") |
|
logger.info(f"μ
λ ₯ 쑰건 - μΆμνμ΄λ°: {launch_timing}") |
|
logger.info(f"μ
λ ₯ 쑰건 - κ³μ μ±: {seasonality}") |
|
logger.info(f"μ
λ ₯ 쑰건 - λ§€μΆλͺ©ν: {sales_target}") |
|
logger.info(f"μ
λ ₯ 쑰건 - νλ§€μ±λ: {sales_channel}") |
|
logger.info(f"μ
λ ₯ 쑰건 - κ²½μκ°λ: {competition_level}") |
|
|
|
try: |
|
logger.info("Gemini ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μ€...") |
|
client = initialize_gemini() |
|
|
|
|
|
current_time = datetime.now() |
|
random_seed = current_time.microsecond + current_time.second * 1000 |
|
random.seed(random_seed) |
|
logger.info(f"λλ€ μλ μ€μ : {random_seed}") |
|
|
|
|
|
logger.info("λ€μμ± κ°ν ν둬ννΈ κ΅¬μ± μ€...") |
|
|
|
|
|
random_settings, fixed_values = apply_random_selection_for_keywords( |
|
category, launch_timing, seasonality, sales_target, sales_channel, competition_level |
|
) |
|
|
|
|
|
diverse_combinations = generate_diverse_keyword_combinations(category, 60) |
|
logger.info(f"λ€μν μ‘°ν© μμ± μλ£: {len(diverse_combinations)}κ°") |
|
|
|
|
|
search_info = "" |
|
config_tools = [] |
|
|
|
if search_engine == "λͺ¨λ κ²μ μμ§ ν΅ν© λΆμ (μΆμ²)": |
|
logger.info("π λ€μμ± κ°ν ν΅ν© λΆμ μμ...") |
|
|
|
|
|
google_search_tool = Tool(google_search=GoogleSearch()) |
|
config_tools = [google_search_tool] |
|
|
|
|
|
comprehensive_analysis = comprehensive_market_analysis(category, seasonality, sales_target) |
|
|
|
search_info = f""" |
|
π === λ€μμ± κ°ν ν΅ν© λΆμ κ²°κ³Ό === |
|
|
|
π Google κ²μ κ·ΈλΌμ΄λ©: μ€μκ° λ€μν μΌνν€μλ νΈλ λ λΆμ (μλ μ€ν) |
|
π λ€μ΄λ² μΌν API: νκ΅ μΌνλͺ° λ€μν ν€μλ λ°μ΄ν° λΆμ |
|
π DuckDuckGo κ²μ: κΈλ‘λ² λ€μν μΌνν€μλ μ 보 λΆμ |
|
|
|
{comprehensive_analysis} |
|
|
|
π‘ μ λͺ¨λ λ°μ΄ν°λ₯Ό μ’
ν©νμ¬ λ§€λ² λ€λ₯Έ μ‘°ν©μ μΌνν€μλλ₯Ό μμ±ν©λλ€. |
|
π² λλ€ μλ: {random_seed} (λ§€λ² λ€λ₯Έ κ²°κ³Ό 보μ₯) |
|
""" |
|
|
|
elif search_engine == "Google κ²μ κ·ΈλΌμ΄λ©λ§": |
|
logger.info("Google κ²μ λꡬ μ€μ μ€...") |
|
google_search_tool = Tool(google_search=GoogleSearch()) |
|
config_tools = [google_search_tool] |
|
search_info = f"Google κ²μ κ·ΈλΌμ΄λ©μ ν΅ν λ€μν μ€μκ° μΌνν€μλ λΆμ (μλ: {random_seed})" |
|
|
|
elif search_engine in ["λ€μ΄λ² κ²μ APIλ§", "DuckDuckGo κ²μλ§"]: |
|
logger.info(f"{search_engine} μ¬μ©νμ¬ λ€μν μΌνν€μλ μ‘°μ¬ μ€...") |
|
|
|
search_queries = [] |
|
|
|
|
|
base_items = random.sample(diverse_combinations, 8) |
|
for item in base_items: |
|
search_queries.append(f"{item} μΌνν€μλ") |
|
|
|
search_results = "" |
|
for query in search_queries: |
|
result = search_with_api(query, search_engine) |
|
search_results += f"[κ²μμ΄: {query}]\n{result}\n\n" |
|
|
|
search_info = f"{search_engine} λ€μν μΌνν€μλ κ²μ κ²°κ³Ό (μλ: {random_seed}):\n{search_results}" |
|
|
|
else: |
|
logger.info("κ²μ μμ΄ AI μ§μλ§ μ¬μ©") |
|
search_info = f"AI λ΄μ₯ μ§μμ κΈ°λ°μΌλ‘ λ€μν μΌνν€μλ μμ± (μλ: {random_seed})" |
|
|
|
|
|
diverse_sample = random.sample(diverse_combinations, 20) |
|
|
|
prompt = f""" |
|
π― λ€μμ± κ°ν μΌνν€μλ λ°κ΅΄ μμ€ν
v5.0 |
|
|
|
β‘ μ€μ: μ λ μ€λ³΅λμ§ μλ λ€μν ν€μλλ§ μμ±νμΈμ! |
|
|
|
π¬ μν μ μ |
|
λΉμ μ λ§€λ² μμ ν λ€λ₯Έ μ‘°ν©μ μΌνν€μλλ₯Ό μμ±νλ μ λ¬Έκ°μ
λλ€. |
|
|
|
π― λͺ©ν |
|
μ£Όμ΄μ§ 쑰건μ λ§λ μ€μ μΌνν€μλ 50κ°λ₯Ό λ°κ΅΄νλ, μ λ μ€λ³΅λμ§ μκ³ λ§€λ² λ€λ₯Έ μ‘°ν©μΌλ‘ ꡬμ±νμμμ€. |
|
|
|
π μ
λ ₯λ 쑰건: |
|
μΉ΄ν
κ³ λ¦¬: {category} |
|
μΆκ° μμ²μ¬ν: {additional_request} |
|
μΆμνμ΄λ°: {launch_timing} |
|
κ³μ μ±: {seasonality} |
|
λ§€μΆλͺ©ν: {sales_target} |
|
νλ§€μ±λ: {sales_channel} |
|
κ²½μκ°λ: {competition_level} |
|
κ²μμμ§: {search_engine} |
|
|
|
π μΌνν€μλ λΆμ μ 보: |
|
{search_info} |
|
|
|
π² λ€μμ± λ³΄μ₯ μ°Έκ³ μ‘°ν© μμ (μ΄κ²κ³Ό λ€λ₯΄κ² μμ±νμΈμ): |
|
{', '.join(diverse_sample[:10])} |
|
|
|
β οΈ ν€μλλ³ λλ€ μ μ© κ·μΉ: |
|
κ° ν€μλλ§λ€ λ€μκ³Ό κ°μ΄ μ μ©νμΈμ: |
|
|
|
{"- μΉ΄ν
κ³ λ¦¬: λ§€ ν€μλλ§λ€ " + str(random_settings['categories']) + " μ€μμ λλ€ μ ν" if random_settings['category_random'] else f"- μΉ΄ν
κ³ λ¦¬: {fixed_values['category']} κ³ μ "} |
|
{"- μΆμνμ΄λ°: λ§€ ν€μλλ§λ€ " + str(random_settings['launch_timings']) + " μ€μμ λλ€ μ ν" if random_settings['launch_timing_random'] else f"- μΆμνμ΄λ°: {fixed_values['launch_timing']} κ³ μ "} |
|
{"- κ³μ μ±: λ§€ ν€μλλ§λ€ " + str(random_settings['seasonalities']) + " μ€μμ λλ€ μ ν" if random_settings['seasonality_random'] else f"- κ³μ μ±: {fixed_values['seasonality']} κ³ μ "} |
|
{"- λ§€μΆλͺ©ν: λ§€ ν€μλλ§λ€ " + str(random_settings['sales_targets']) + " μ€μμ λλ€ μ ν" if random_settings['sales_target_random'] else f"- λ§€μΆλͺ©ν: {fixed_values['sales_target']} κ³ μ "} |
|
{"- νλ§€μ±λ: λ§€ ν€μλλ§λ€ " + str(random_settings['sales_channels']) + " μ€μμ λλ€ μ ν" if random_settings['sales_channel_random'] else f"- νλ§€μ±λ: {fixed_values['sales_channel']} κ³ μ "} |
|
{"- κ²½μκ°λ: λ§€ ν€μλλ§λ€ " + str(random_settings['competition_levels']) + " μ€μμ λλ€ μ ν" if random_settings['competition_level_random'] else f"- κ²½μκ°λ: {fixed_values['competition_level']} κ³ μ "} |
|
|
|
βοΈ λ€μμ± κ°ν μν¬νλ‘μ° |
|
1λ¨κ³: μμ ν μλ‘μ΄ μ‘°ν© μμ± |
|
- μ΄μ κ²°κ³Όμ μ λ μ€λ³΅λμ§ μλ ν€μλ μ‘°ν© |
|
- μμ¬({', '.join(MATERIAL_KEYWORDS[:5])}) + μνλͺ
μ‘°ν© |
|
- νν({', '.join(SHAPE_KEYWORDS[:5])}) + μνλͺ
μ‘°ν© |
|
- κΈ°λ₯({', '.join(FUNCTION_KEYWORDS[:5])}) + μνλͺ
μ‘°ν© |
|
|
|
2λ¨κ³: μ€λ³΅ λ°©μ§ νν°λ§ |
|
- λμΌν ν€μλ μ‘°ν© μμ λ°°μ |
|
- μ μ¬ν μλ―Έμ ν€μλ μ‘°ν© λ°°μ |
|
- λ§€λ² μλ‘μ΄ κ°λλ‘ μ κ·Ό |
|
|
|
3λ¨κ³: 50κ° λ€μν ν€μλ μ λ³ |
|
- λΈλλλͺ
μ λ κΈμ§ |
|
- 볡μ‘ν κΈ°μ μ©μ΄ κΈμ§ |
|
- μ΅λ 2κ° λ¨μ΄ μ‘°ν©λ§ νμ© |
|
|
|
β οΈ λ€μμ± κ°ν ν€μλ κ΅¬μ± κ·μΉ (λ§€μ° μ€μ): |
|
|
|
π« μ λ κΈμ§ μ¬ν: |
|
- λμΌνκ±°λ μ μ¬ν ν€μλ λ°λ³΅ |
|
- λΈλλλͺ
(μΌμ±, LG, λμ΄ν€ λ±) |
|
- 볡μ‘ν κΈ°μ μ©μ΄ |
|
- 3κ° μ΄μ 볡ν©μ΄ |
|
|
|
β
λ°λμ λ€μνκ² ν¬ν¨ν΄μΌ ν νν: |
|
1. μμ¬λ³ ν€μλ (μ: λλ무 λλ§, ꡬ리 μ»΅) |
|
2. ννλ³ ν€μλ (μ: μν μ μ, μ¬λ¦Ό μΌμ΄μ€) |
|
3. κΈ°λ₯λ³ ν€μλ (μ: λ°©μ νμ°μΉ, νκ· μ건) |
|
4. μΉ΄ν
κ³ λ¦¬λ³ ν€μλ (μ: μλ©ν¨, 쑰리λꡬ) |
|
|
|
π― λ€μμ± λ³΄μ₯ μ λ΅: |
|
- μ λ κ°μ μμ¬λ₯Ό 2λ² μ΄μ μ¬μ©νμ§ λ§μΈμ |
|
- μ λ κ°μ ννλ₯Ό 2λ² μ΄μ μ¬μ©νμ§ λ§μΈμ |
|
- μ λ κ°μ κΈ°λ₯μ 2λ² μ΄μ μ¬μ©νμ§ λ§μΈμ |
|
- λ§€ ν€μλλ§λ€ μμ ν λ€λ₯Έ μ‘°ν©μΌλ‘ μμ±νμΈμ |
|
|
|
μ¬λ°λ₯Έ λ€μν ν€μλ μμ: |
|
β
λλ무 λλ§ (μμ¬+μν) |
|
β
μν μ μ (νν+μν) |
|
β
λ°©μ νμ°μΉ (κΈ°λ₯+μν) |
|
β
μΈλΌλ―Ή λ¨Έκ·Έμ»΅ (μμ¬+μν) |
|
β
μ μ΄μ μ λ° (νν+μν) |
|
β
νκ· μ건 (κΈ°λ₯+μν) |
|
|
|
μλͺ»λ λ°λ³΅ ν€μλ μμ: |
|
β λλ무 λλ§, λλ무 μ κ°λ½ (μμ¬ λ°λ³΅) |
|
β μν μ μ, μν μλ° (νν λ°λ³΅) |
|
β λ°©μ νμ°μΉ, λ°©μ μΌμ΄μ€ (κΈ°λ₯ λ°λ³΅) |
|
|
|
π μΆλ ₯ νμ: |
|
μ€μ§ μμ ν λ€λ₯Έ μΌνν€μλλ§ ν μ€μ© 50κ° μΆλ ₯ |
|
- λ²νΈ κΈμ§ |
|
- μ€λͺ
κΈμ§ |
|
- κΈ°νΈλ νΉμλ¬Έμ κΈμ§ |
|
- κ΄νΈ μ μ€λͺ
κΈμ§ |
|
- μμ ν€μλλ§ μΆλ ₯ |
|
- μ λ μ€λ³΅ κΈμ§ |
|
|
|
μμ μΆλ ₯ νν (λ§€λ² μμ ν λ€λ₯΄κ²): |
|
μ 리 νλΆ |
|
μ μ΄μ μμ |
|
νκ· λλ§ |
|
μ루미λ ν
λΈλ¬ |
|
μ¬λ¦Ό νμΌν¨ |
|
|
|
β‘ μ§κΈ λ°λ‘ μ λ μ€λ³΅λμ§ μλ μμ ν μλ‘μ΄ μΌνν€μλ 50κ°λ₯Ό κ°κ° λ€λ₯Έ λλ€ μ‘°κ±΄μ μ μ©νμ¬ μΆλ ₯νμΈμ. |
|
λ§€λ² μ€νν λλ§λ€ μμ ν λ€λ₯Έ κ²°κ³Όκ° λμμΌ ν©λλ€! |