File size: 10,022 Bytes
1271db4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
"""
ν…μŠ€νŠΈ 처리 κ΄€λ ¨ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λͺ¨μŒ
- ν…μŠ€νŠΈ 뢄리 및 μ •μ œ
- ν‚€μ›Œλ“œ μΆ”μΆœ
- Gemini API ν‚€ 톡합 관리 적용
"""

import re
import google.generativeai as genai
import os
import logging
import api_utils  # API ν‚€ 톡합 관리λ₯Ό μœ„ν•œ μž„ν¬νŠΈ

# λ‘œκΉ… μ„€μ •
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)

# ===== Gemini λͺ¨λΈ 관리 ν•¨μˆ˜λ“€ =====
def get_gemini_model():
    """api_utilsμ—μ„œ Gemini λͺ¨λΈ κ°€μ Έμ˜€κΈ° (톡합 관리)"""
    try:
        model = api_utils.get_gemini_model()
        if model:
            logger.info("Gemini λͺ¨λΈ λ‘œλ“œ 성곡 (api_utils 톡합 관리)")
            return model
        else:
            logger.warning("μ‚¬μš© κ°€λŠ₯ν•œ Gemini API ν‚€κ°€ μ—†μŠ΅λ‹ˆλ‹€.")
            return None
    except Exception as e:
        logger.error(f"Gemini λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨: {e}")
        return None

# ν…μŠ€νŠΈ 뢄리 및 μ •μ œ ν•¨μˆ˜
def clean_and_split(text, only_korean=False):
    """ν…μŠ€νŠΈλ₯Ό λΆ„λ¦¬ν•˜κ³  μ •μ œν•˜λŠ” ν•¨μˆ˜"""
    text = re.sub(r"[()\[\]-]", " ", text)
    text = text.replace("/", " ")
    
    if only_korean:
        # ν•œκΈ€λ§Œ μΆ”μΆœ μ˜΅μ…˜μ΄ μΌœμ§„ 경우
        # κ³΅λ°±μ΄λ‚˜ μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•œ λ’€ ν•œκΈ€λ§Œ μΆ”μΆœ
        words = re.split(r"[ ,]", text)
        cleaned = []
        for word in words:
            word = word.strip()
            # ν•œκΈ€λ§Œ 남기고 λ‹€λ₯Έ λ¬ΈμžλŠ” 제거
            word = re.sub(r"[^κ°€-힣]", "", word)
            if word and len(word) >= 1:  # 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆκ³  1κΈ€μž 이상인 경우만 μΆ”κ°€
                cleaned.append(word)
    else:
        # ν•œκΈ€λ§Œ μΆ”μΆœ μ˜΅μ…˜μ΄ κΊΌμ§„ 경우 - 단어 ν†΅μ§Έλ‘œ 처리
        # 곡백과 μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•˜μ—¬ 단어 전체λ₯Ό μœ μ§€
        words = re.split(r"[,\s]+", text)
        cleaned = []
        for word in words:
            word = word.strip()
            if word and len(word) >= 1:  # 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆκ³  1κΈ€μž 이상인 경우만 μΆ”κ°€
                cleaned.append(word)
    
    return cleaned

def filter_keywords_with_gemini(pairs, gemini_model=None):
    """Gemini AIλ₯Ό μ‚¬μš©ν•˜μ—¬ ν‚€μ›Œλ“œ μ‘°ν•© 필터링 (κ°œμ„ λ²„μ „) - API ν‚€ 톡합 관리"""
    if gemini_model is None:
        # api_utilsμ—μ„œ Gemini λͺ¨λΈ κ°€μ Έμ˜€κΈ°
        gemini_model = get_gemini_model()
        
    if gemini_model is None:
        logger.error("Gemini λͺ¨λΈμ„ κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€. λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
        # μ•ˆμ „ν•˜κ²Œ 처리: λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€
        all_keywords = set()
        for pair in pairs:
            for keyword in pair:
                all_keywords.add(keyword)
        return list(all_keywords)
        
    # λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό λͺ©λ‘μœΌλ‘œ μΆ”μΆœ (제거된 ν‚€μ›Œλ“œ ν™•μΈμš©)
    all_keywords = set()
    for pair in pairs:
        for keyword in pair:
            all_keywords.add(keyword)
    
    # λ„ˆλ¬΄ λ§Žμ€ 쌍이 있으면 μ œν•œ
    max_pairs = 50  # μ΅œλŒ€ 50개 쌍만 처리
    pairs_to_process = list(pairs)[:max_pairs] if len(pairs) > max_pairs else pairs
    
    logger.info(f"필터링할 ν‚€μ›Œλ“œ 쌍: 총 {len(pairs)}개 쀑 {len(pairs_to_process)}개 처리")
    
    # 보수적인 ν”„λ‘¬ν”„νŠΈ μ‚¬μš© - ν‚€μ›Œλ“œ 제거 μ΅œμ†Œν™”
    prompt = (
        "λ‹€μŒμ€ μ†ŒλΉ„μžκ°€ 검색할 κ°€λŠ₯성이 μžˆλŠ” ν‚€μ›Œλ“œ 쌍 λͺ©λ‘μž…λ‹ˆλ‹€.\n"
        "각 μŒμ€ 같은 단어 μ‘°ν•©μ΄μ§€λ§Œ μˆœμ„œλ§Œ λ‹€λ₯Έ κ²½μš°μž…λ‹ˆλ‹€ (예: μ†μ§ˆμ˜€μ§•μ–΄ vs μ˜€μ§•μ–΄μ†μ§ˆ).\n\n"
        "μ•„λž˜μ˜ 기쀀에 따라 각 μŒμ—μ„œ 더 μžμ—°μŠ€λŸ¬μš΄ ν‚€μ›Œλ“œλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”:\n"
        "1. μ†ŒλΉ„μžκ°€ μΌμƒμ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ„ μš°μ„  μ„ νƒν•˜μ„Έμš”.\n"
        "2. 두 ν‚€μ›Œλ“œκ°€ λͺ¨λ‘ μžμ—°μŠ€λŸ½κ±°λ‚˜ μ˜λ―Έκ°€ μ•½κ°„ λ‹€λ₯΄λ‹€λ©΄, λ°˜λ“œμ‹œ λ‘˜ λ‹€ μœ μ§€ν•˜μ„Έμš”.\n"
        "3. ν™•μ‹€νžˆ λΉ„μžμ—°μŠ€λŸ½κ±°λ‚˜ μ–΄μƒ‰ν•œ κ²½μš°μ—λ§Œ μ œκ±°ν•˜μ„Έμš”.\n"
        "4. λΆˆν™•μ‹€ν•œ κ²½μš°μ—λŠ” λ°˜λ“œμ‹œ ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•˜μ„Έμš”.\n"
        "5. μˆ«μžλ‚˜ μ˜μ–΄κ°€ ν¬ν•¨λœ ν‚€μ›Œλ“œλŠ” ν•œκΈ€ 메인 ν‚€μ›Œλ“œκ°€ μ•žμͺ½μ— μ˜€λŠ” ν˜•νƒœλ₯Ό μ„ νƒν•˜μ„Έμš”. (예: '10kg μ˜€μ§•μ–΄' 보닀 'μ˜€μ§•μ–΄ 10kg' 선택)\n"
        "6. κ²€μƒ‰λŸ‰μ΄ 0인 ν‚€μ›Œλ“œλΌλ„ 일상적인 ν‘œν˜„μ΄λΌλ©΄ κ°€λŠ₯ν•œ μœ μ§€ν•˜μ„Έμš”. λͺ…λ°±ν•˜κ²Œ 비정상적인 ν‘œν˜„λ§Œ μ œκ±°ν•˜μ„Έμš”.\n\n"
        "주의: 기본적으둜 λŒ€λΆ€λΆ„μ˜ ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•˜κ³ , 맀우 λͺ…ν™•ν•˜κ²Œ λΉ„μžμ—°μŠ€λŸ¬μš΄ κ²ƒλ§Œ μ œκ±°ν•˜μ„Έμš”.\n\n"
        "κ²°κ³ΌλŠ” λ‹€μŒ ν˜•μ‹μœΌλ‘œ μ œκ³΅ν•΄μ£Όμ„Έμš”:\n"
        "- μ„ νƒλœ ν‚€μ›Œλ“œ (이유: μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ΄κΈ° λ•Œλ¬Έ)\n"
        "- μ„ νƒλœ ν‚€μ›Œλ“œ1, μ„ νƒλœ ν‚€μ›Œλ“œ2 (이유: λ‘˜ λ‹€ μžμ—°μŠ€λŸ½κ³  μ˜λ―Έκ°€ 쑰금 닀름)\n\n"
    )
    
    # ν‚€μ›Œλ“œ 쌍 λͺ©λ‘
    formatted = "\n".join([f"- {a}, {b}" for a, b in pairs_to_process])
    full_prompt = prompt + formatted
    
    try:
        # νƒ€μž„μ•„μ›ƒ μΆ”κ°€
        logger.info(f"Gemini API 호좜 μ‹œμž‘ - {len(pairs_to_process)}개 ν‚€μ›Œλ“œ 쌍 처리 쀑...")
        
        # 응닡 λ°›κΈ° (νƒ€μž„μ•„μ›ƒ κΈ°λŠ₯이 있으면 μΆ”κ°€)
        response = gemini_model.generate_content(full_prompt)
        
        logger.info("Gemini API 응닡 성곡")
        lines = response.text.strip().split("\n")
        
        # μ„ νƒλœ ν‚€μ›Œλ“œ μΆ”μΆœ (μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 경우 λͺ¨λ‘ 포함)
        final_keywords = []
        for line in lines:
            if line.startswith("-"):
                # 이유 λΆ€λΆ„ 제거
                keywords_part = line.strip("- ").split("(이유:")[0].strip()
                # μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ ν‚€μ›Œλ“œ λͺ¨λ‘ μΆ”κ°€
                for kw in keywords_part.split(","):
                    kw = kw.strip()
                    if kw:
                        final_keywords.append(kw)
        
        # μ²˜λ¦¬λ˜μ§€ μ•Šμ€ 쌍의 첫 번째 ν‚€μ›Œλ“œλ„ μΆ”κ°€ (LLM이 μ²˜λ¦¬ν•˜μ§€ μ•Šμ€ ν‚€μ›Œλ“œ)
        if len(pairs) > max_pairs:
            logger.info(f"μΆ”κ°€ ν‚€μ›Œλ“œ 처리: 남은 {len(pairs) - max_pairs}개 쌍의 첫 번째 ν‚€μ›Œλ“œ μΆ”κ°€")
            for pair in list(pairs)[max_pairs:]:
                # 각 쌍의 첫 번째 ν‚€μ›Œλ“œλ§Œ μ‚¬μš©
                final_keywords.append(pair[0])
        
        # μ„ νƒλœ ν‚€μ›Œλ“œκ°€ μ—†μœΌλ©΄ κΈ°μ‘΄ ν‚€μ›Œλ“œ λͺ¨λ‘ λ°˜ν™˜
        if not final_keywords:
            logger.warning("κ²½κ³ : μ„ νƒλœ ν‚€μ›Œλ“œκ°€ μ—†μ–΄ λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
            final_keywords = list(all_keywords)
        
        # μˆœμ„œ κ°•μ œ μˆ˜μ • 
        corrected_keywords = []
        
        # λ‹¨μœ„μ™€ 숫자 κ΄€λ ¨ μ •κ·œμ‹ νŒ¨ν„΄
        unit_pattern = re.compile(r'(?i)(kg|g|mm|cm|ml|l|리터|개|팩|λ°•μŠ€|μ„ΈνŠΈ|2l|l2)')
        number_pattern = re.compile(r'\d+')
        
        for kw in final_keywords:
            # 곡백으둜 뢄리
            if ' ' in kw:
                parts = kw.split()
                first_part = parts[0]
                
                # 첫 뢀뢄이 λ‹¨μœ„λ‚˜ 숫자λ₯Ό ν¬ν•¨ν•˜λŠ”μ§€ 확인
                if (unit_pattern.search(first_part) or number_pattern.search(first_part)) and len(parts) > 1:
                    # μˆœμ„œ λ°”κΎΈκΈ°: λ‹¨μœ„/숫자 뢀뢄을 λ’€λ‘œ 이동
                    corrected_kw = " ".join(parts[1:] + [first_part])
                    logger.info(f"ν‚€μ›Œλ“œ μˆœμ„œ κ°•μ œ μˆ˜μ •: '{kw}' -> '{corrected_kw}'")
                    corrected_keywords.append(corrected_kw)
                else:
                    corrected_keywords.append(kw)
            else:
                corrected_keywords.append(kw)
        
        # νŠΉλ³„ 처리: "L μ˜€μ§•μ–΄", "2L μ˜€μ§•μ–΄" 같은 경우λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν™•μΈν•˜κ³  μˆ˜μ •
        specific_fixes = []
        for kw in corrected_keywords:
            # νŠΉμ • νŒ¨ν„΄ 체크
            l_pattern = re.compile(r'^([0-9]*L) (.+)$', re.IGNORECASE)
            match = l_pattern.match(kw)
            
            if match:
                # L λ‹¨μœ„λ₯Ό λ’€λ‘œ 이동
                l_part = match.group(1)
                main_part = match.group(2)
                fixed_kw = f"{main_part} {l_part}"
                logger.info(f"특수 νŒ¨ν„΄ μˆ˜μ •: '{kw}' -> '{fixed_kw}'")
                specific_fixes.append(fixed_kw)
            else:
                specific_fixes.append(kw)
        
        # 제거된 ν‚€μ›Œλ“œ λͺ©λ‘ 확인
        selected_set = set(specific_fixes)
        removed_keywords = all_keywords - selected_set
        
        # 제거된 ν‚€μ›Œλ“œ 좜λ ₯
        logger.info("\n=== LLM에 μ˜ν•΄ 제거된 ν‚€μ›Œλ“œ λͺ©λ‘ ===")
        for kw in removed_keywords:
            logger.info(f"  - {kw}")
        logger.info(f"총 {len(all_keywords)}개 쀑 {len(removed_keywords)}개 제거됨 ({len(selected_set)}개 μœ μ§€)\n")
        
        return specific_fixes
        
    except Exception as e:
        logger.error(f"Gemini 였λ₯˜: {e}")
        logger.error("였λ₯˜ λ°œμƒμœΌλ‘œ 인해 λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
        logger.error(f"였λ₯˜ μœ ν˜•: {type(e).__name__}")
        import traceback
        traceback.print_exc()
        
        # μ•ˆμ „ν•˜κ²Œ 처리: λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ 
        logger.info(f"μ•ˆμ „ λͺ¨λ“œ: {len(all_keywords)}개 ν‚€μ›Œλ“œ λͺ¨λ‘ μœ μ§€")
        return list(all_keywords)

def get_search_volume_range(total_volume):
    """총 κ²€μƒ‰λŸ‰μ„ 기반으둜 κ²€μƒ‰λŸ‰ ꡬ간을 λ°˜ν™˜"""
    if total_volume == 0:
        return "100미만"
    elif total_volume <= 100:
        return "100미만"
    elif total_volume <= 1000:
        return "1000미만"
    elif total_volume <= 2000:
        return "2000미만"
    elif total_volume <= 5000:
        return "5000미만"
    elif total_volume <= 10000:
        return "10000미만"
    else:
        return "10000이상"