206 lines
9.5 KiB
Python
206 lines
9.5 KiB
Python
import re
|
|
import random
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
# =============================================================================
|
|
# TitleGenerator 클래스 (테스트에 필요한 부분만 발췌하여 최소 구현)
|
|
# =============================================================================
|
|
class TitleGenerator:
|
|
def __init__(self, locator_manager, browser_controller, logger, toggle_states, gpt_client):
|
|
self.logger = logger
|
|
self.browser_controller = browser_controller
|
|
self.locator_manager = locator_manager
|
|
self.toggle_states = toggle_states
|
|
|
|
# 실제 코드에서는 MongoDBManager 등을 초기화하지만,
|
|
# 테스트에서는 더미 객체를 사용하므로 여기서는 None 처리
|
|
self.forbidden_word_manager = None
|
|
self.gpt_client = gpt_client
|
|
# 실제 번역 라이브러리 대신, translate_product_name을 단순 반환으로 구현
|
|
self.translator = None
|
|
|
|
def translate_product_name(self, original_name: str) -> str:
|
|
# 테스트에서는 번역 없이 원본 상품명을 그대로 사용
|
|
return original_name
|
|
|
|
def is_word_forbidden(self, word: str) -> bool:
|
|
"""더미 금지어 매니저로 위임 (없으면 False 반환)"""
|
|
if self.forbidden_word_manager:
|
|
return self.forbidden_word_manager.is_word_forbidden(word)
|
|
return False
|
|
|
|
def is_valid_word(self, word: str) -> bool:
|
|
"""숫자만이거나 영어+숫자로만 이루어진 단어는 유효하지 않다고 판단"""
|
|
return not (word.isdigit() or re.fullmatch(r'[A-Za-z0-9]+', word))
|
|
|
|
def extract_special_words(self, original_name: str) -> list:
|
|
"""원본 상품명에서 숫자 또는 영어+숫자로만 이루어진 단어를 추출"""
|
|
return [word for word in original_name.split() if word.isdigit() or re.fullmatch(r'[A-Za-z0-9]+', word)]
|
|
|
|
def filter_invalid_words(self, words: list) -> list:
|
|
"""영어 또는 영어+숫자로만 이루어진 단어를 제외"""
|
|
return [word for word in words if not re.fullmatch(r'[A-Za-z0-9]+', word)]
|
|
|
|
def process_top_titles(self, top_titles: list) -> list:
|
|
"""검색 결과 상위 제목들에서 유효하지 않은 단어들을 제거"""
|
|
filtered_titles = []
|
|
for title in top_titles:
|
|
filtered_words = self.filter_invalid_words(title.split())
|
|
filtered_titles.append(' '.join(filtered_words))
|
|
return filtered_titles
|
|
|
|
def generate_product_title(self, original_name: str, keyword_name: str, search_result: list, product_category: str) -> str:
|
|
"""
|
|
상품명을 생성하는 메서드
|
|
|
|
1. 원본 상품명을 번역(테스트에서는 그대로 사용)
|
|
2. GPT Client를 통해 원본과 키워드의 관련성을 판단
|
|
3. 검색 결과에서 상위 제목과 가격을 추출하여 처리
|
|
4. 키워드, 상위 제목, 원본의 특수 단어 등을 합쳐 최종 키워드 목록을 생성
|
|
5. 금지어 필터링 후, GPT Client를 통해 최종 상품명을 생성하여 반환
|
|
"""
|
|
# 1. 번역 (여기서는 그대로 사용)
|
|
translated_name = self.translate_product_name(original_name)
|
|
self.logger.debug(f"translated_name: {translated_name}")
|
|
|
|
# 2. 관련성 판단 (더미 GPT Client 사용)
|
|
is_related = self.gpt_client.is_related_product(translated_name, keyword_name)
|
|
if not is_related:
|
|
self.logger.debug("원본상품명과 키워드 간 관련성이 낮아 추가 검증 필요.")
|
|
|
|
# 3. 검색 결과 처리: 상위 제목, 가격 추출
|
|
top_titles = []
|
|
top_prices = []
|
|
if search_result:
|
|
for product in search_result:
|
|
if "title" in product and "price" in product:
|
|
top_titles.append(product["title"])
|
|
top_prices.append(product["price"])
|
|
self.logger.debug(f"top_titles: {top_titles}")
|
|
self.logger.debug(f"top_prices: {top_prices}")
|
|
else:
|
|
self.logger.warning("검색 결과가 비어 있습니다.")
|
|
|
|
# 4. 검색된 제목 필터링
|
|
filtered_top_titles = self.process_top_titles(top_titles)
|
|
|
|
# 5. 키워드 상품명에서 첫 4개 단어 추출
|
|
essential_keywords = keyword_name.split()[:4]
|
|
self.logger.debug(f"essential_keywords (첫 4개): {essential_keywords}")
|
|
|
|
# [keyword_name]과 필터링된 제목들을 합쳐 단어 단위 분해 후 집합 처리
|
|
keyword_title = list(set(
|
|
word for title in [keyword_name] + filtered_top_titles
|
|
for word in title.split()
|
|
))
|
|
self.logger.debug(f"초기 keyword_title: {keyword_title}")
|
|
|
|
# 6. 유효하지 않은 단어 제거
|
|
keyword_title = [word for word in keyword_title if self.is_valid_word(word)]
|
|
self.logger.debug(f"keyword_title after filtering invalid words: {keyword_title}")
|
|
keyword_title = list(set(keyword_title))
|
|
self.logger.debug(f"final keyword_title after deduplication: {keyword_title}")
|
|
|
|
# 7. 원본 상품명에서 특수 단어 추출 후 포함
|
|
special_words = self.extract_special_words(original_name)
|
|
self.logger.debug(f"special_words from original_name: {special_words}")
|
|
keyword_title.extend(special_words)
|
|
keyword_title = list(set(keyword_title))
|
|
self.logger.debug(f"keyword_title including special words: {keyword_title}")
|
|
|
|
# 8. 필수 키워드 중 랜덤 2개 선택 후 추가
|
|
required_keywords = random.sample(essential_keywords, min(2, len(essential_keywords)))
|
|
self.logger.debug(f"randomly selected required_keywords: {required_keywords}")
|
|
keyword_title.extend(required_keywords)
|
|
keyword_title = list(set(keyword_title))
|
|
|
|
# 9. 금지어 필터링 (더미 forbidden_word_manager 사용)
|
|
keyword_title = [word for word in keyword_title if not self.is_word_forbidden(word)]
|
|
self.logger.debug(f"keyword_title after forbidden filter: {keyword_title}")
|
|
|
|
# 10. 최종 상품명 생성 (더미 GPT Client의 generate_product_name_next 호출)
|
|
product_title = self.gpt_client.generate_product_name_next(words=keyword_title, original_name=original_name, top_titles=top_titles)
|
|
self.logger.debug(f"final product_title: {product_title}")
|
|
|
|
return product_title
|
|
|
|
# =============================================================================
|
|
# 더미(Stub) 의존 객체 클래스들
|
|
# =============================================================================
|
|
class DummyLocatorManager:
|
|
def get_locator(self, category, locator_name):
|
|
return f"{category}_{locator_name}"
|
|
|
|
class DummyBrowserController:
|
|
def __init__(self):
|
|
self.page = None # 테스트에서는 사용하지 않음
|
|
|
|
class DummyGPTClient:
|
|
def is_related_product(self, translated_name, keyword_name):
|
|
# 테스트에서는 항상 관련 있다고 가정
|
|
return True
|
|
|
|
def generate_product_name_next(self, words, original_name, top_titles):
|
|
# 테스트용: 원본 상품명과 정렬된 단어들을 조합하여 반환
|
|
return f"{original_name} - {' '.join(sorted(words))}"
|
|
|
|
class DummyForbiddenWordManager:
|
|
def is_word_forbidden(self, word):
|
|
# 예를 들어, "bad"라는 단어는 금지어로 판단
|
|
return word.lower() == "bad"
|
|
|
|
# =============================================================================
|
|
# 테스트 함수: generate_product_title 메서드 검증
|
|
# =============================================================================
|
|
def test_generate_product_title():
|
|
# 로깅 설정
|
|
logger = logging.getLogger("TitleGeneratorTest")
|
|
logger.setLevel(logging.DEBUG)
|
|
handler = logging.StreamHandler()
|
|
formatter = logging.Formatter("%(levelname)s - %(message)s")
|
|
handler.setFormatter(formatter)
|
|
if not logger.handlers:
|
|
logger.addHandler(handler)
|
|
|
|
# 더미 토글 상태 (네이버 API 키 등 실제 사용되지 않음)
|
|
toggle_states = {"clientID": "dummy_id", "clientSecret": "dummy_secret"}
|
|
|
|
# 더미 의존 객체 생성
|
|
locator_manager = DummyLocatorManager()
|
|
browser_controller = DummyBrowserController()
|
|
gpt_client = DummyGPTClient()
|
|
|
|
# TitleGenerator 인스턴스 생성
|
|
title_generator = TitleGenerator(locator_manager, browser_controller, logger, toggle_states, gpt_client)
|
|
|
|
# 금지어 매니저를 더미 객체로 교체 (예: "bad"는 금지어)
|
|
title_generator.forbidden_word_manager = DummyForbiddenWordManager()
|
|
|
|
# 테스트용 샘플 입력값
|
|
original_name = "Super Deluxe Coffee Maker"
|
|
keyword_name = "Coffee Maker Deluxe"
|
|
search_result = [
|
|
{"title": "Best Coffee Maker", "price": 15000},
|
|
{"title": "Deluxe Espresso Machine", "price": 25000},
|
|
{"related_tags": ["coffee", "maker", "espresso"]}
|
|
]
|
|
product_category = "Kitchen Appliances"
|
|
|
|
# generate_product_title 메서드 호출
|
|
generated_title = title_generator.generate_product_title(
|
|
original_name=original_name,
|
|
keyword_name=keyword_name,
|
|
search_result=search_result,
|
|
product_category=product_category
|
|
)
|
|
|
|
print("\nGenerated Product Title:")
|
|
print(generated_title)
|
|
|
|
# =============================================================================
|
|
# 메인 실행: 테스트 함수 호출
|
|
# =============================================================================
|
|
if __name__ == "__main__":
|
|
test_generate_product_title()
|