1110수정ver일본어탭-로마자 (#10)
Browse files- 1110수정ver일본어탭-로마자 (085e5d8942789440f16915f42b05fa82b150a417)
- app.py +121 -18
- requirements.txt +3 -1
app.py
CHANGED
|
@@ -288,6 +288,11 @@ jp_common_rules = """
|
|
| 288 |
東国製薬(동국제약):ヒガシコクセイヤク(히가시코쿠세이야쿠-한자의 일본발음)トンクック製薬(통쿡쿠제약)
|
| 289 |
"""
|
| 290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, model="gpt-4o-mini", api_key=None, temperature=0.3, sleep_sec=1):
|
| 292 |
jp_main = jp_syn = eng_syn = comment = ""
|
| 293 |
|
|
@@ -299,8 +304,109 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 299 |
import re
|
| 300 |
is_japanese_input = bool(re.search(r'[\u3040-\u309F\u30A0-\u30FF]', word)) and not has_jp_notation
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
prompts = {
|
| 305 |
"브랜드": f"""너는 한국어/일본어/영어 패션 브랜드 동의어 전문가야. '{word}'라는 브랜드 이름의 동의어를 최대한 정확하게 찾아서 정리해줘.
|
| 306 |
{"⚠️ 중요: 입력에 '일문 표기'가 포함되어 있습니다. 이 일문 표기를 참고하여 동의어를 생성해주세요." if has_jp_notation else ""}
|
|
@@ -316,7 +422,6 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 316 |
f"(의미 번역 금지, 실제 브랜드의 음차 표기만 허용)"
|
| 317 |
}
|
| 318 |
|
| 319 |
-
|
| 320 |
- 동의어(일문): 브랜드 관련 일본어 동의어, 없으면 비워두기, 중복 제거, ex.마르디 메크르디 - 마르디 매크르디,마르디 (Typing 변경 관점)
|
| 321 |
{f" * 참고: 제공된 일문 표기를 동의어 생성 시 참고하되, 동의어에 포함시키지 마세요" if has_jp_notation else ""}
|
| 322 |
- 동의어(영문): '{word.split('(')[0].strip() if has_jp_notation else word}'의 영어 공식명 기반 최대 3개
|
|
@@ -334,7 +439,6 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 334 |
동의어(영문): word1|word2|word3
|
| 335 |
생성 이유: (대표키워드와 동의어를 이렇게 정한 간단한 이유 1문장)""",
|
| 336 |
|
| 337 |
-
|
| 338 |
"카테고리": f"""너는 한국어/일본어/영어 패션 카테고리 동의어 전문가야. '{word}' 카테고리 관련 동의어를 최대한 정확하게 찾아서 정리해줘.
|
| 339 |
{"⚠️ 중요: 입력에 '일문 표기'가 포함되어 있습니다. 이 일문 표기를 참고하여 동의어를 생성해주세요." if has_jp_notation else ""}
|
| 340 |
{"⚠️ 중요: 입력이 일본어입니다. 대표키워드(일문)는 '{word}'를 그대로 사용하고, 동의어만 생성해주세요." if is_japanese_input else ""}
|
|
@@ -436,18 +540,12 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 436 |
생성 이유: (대표키워드와 동의어를 이렇게 정한 간단한 이유 1문장)"""
|
| 437 |
}
|
| 438 |
|
| 439 |
-
|
| 440 |
prompt = prompts.get(category, f"'{word}' 단어의 동의어를 찾아 대표키워드, 일본어 동의어, 영어 동의어를 알려줘.")
|
| 441 |
|
| 442 |
# 공식 영문명이 있으면 프롬프트 수정
|
| 443 |
if official_eng:
|
| 444 |
prompt += f" 영어 동의어는 반드시 '{official_eng}' 공식 영문명을 기준으로 정확히 3개까지만 제시해줘."
|
| 445 |
|
| 446 |
-
# try:
|
| 447 |
-
# response = openai.chat.completions.create(
|
| 448 |
-
# model="gpt-5-mini",
|
| 449 |
-
# messages=[{"role": "user", "content": prompt}]
|
| 450 |
-
# )
|
| 451 |
try:
|
| 452 |
completion = generate_with_model(
|
| 453 |
model_choice=model,
|
|
@@ -457,33 +555,30 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 457 |
)
|
| 458 |
time.sleep(sleep_sec)
|
| 459 |
|
| 460 |
-
|
| 461 |
for line in completion.splitlines():
|
| 462 |
if line.startswith("대표키워드(일문):"):
|
| 463 |
raw_main = line.split(":", 1)[1].strip()
|
| 464 |
-
# | 로 구분된 경우 첫 번째 값만 사용
|
| 465 |
jp_main = raw_main.split("|")[0].strip()
|
| 466 |
elif line.startswith("동의어(일문):"):
|
| 467 |
jp_syn = line.split(":", 1)[1].strip()
|
| 468 |
elif line.startswith("동의어(영문):"):
|
| 469 |
raw_eng = line.split(":", 1)[1].strip()
|
| 470 |
-
# ✅ 영문 동의어 후처리: 소문자 변환 + 공백 제거
|
| 471 |
eng_list = [x.strip().lower().replace(" ", "") for x in raw_eng.split("|") if x.strip()]
|
| 472 |
eng_syn = "|".join(eng_list)
|
| 473 |
elif line.lower().startswith("생성 이유") or line.lower().startswith("comment"):
|
| 474 |
comment = line.split(":", 1)[1].strip()
|
| 475 |
-
|
|
|
|
| 476 |
if jp_main and jp_syn:
|
| 477 |
jp_main_norm = normalize_word(jp_main)
|
| 478 |
jp_syn_list = [x.strip() for x in jp_syn.split("|") if x.strip()]
|
| 479 |
-
# 대표키워드와 중복 제거 + 동의어 내부 중복 제거
|
| 480 |
jp_syn_list = [x for x in jp_syn_list if normalize_word(x) != jp_main_norm]
|
| 481 |
-
jp_syn_list = list(dict.fromkeys(jp_syn_list))
|
| 482 |
jp_syn = "|".join(jp_syn_list)
|
| 483 |
|
| 484 |
if eng_syn:
|
| 485 |
eng_syn_list = [x.strip().lower().replace(" ", "") for x in eng_syn.split("|") if x.strip()]
|
| 486 |
-
eng_syn_list = list(dict.fromkeys(eng_syn_list))
|
| 487 |
eng_syn = "|".join(eng_syn_list)
|
| 488 |
|
| 489 |
except Exception as e:
|
|
@@ -494,19 +589,27 @@ def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, m
|
|
| 494 |
if not text:
|
| 495 |
return ""
|
| 496 |
text = text.strip()
|
| 497 |
-
# なし, N/A, None, - 등을 빈 문자열로 처리
|
| 498 |
if text.lower() in ["なし", "n/a", "none", "-", "없음"]:
|
| 499 |
return ""
|
| 500 |
return text
|
| 501 |
|
| 502 |
-
# ✅ 항상 4개 반환 (비정상일 때도 안전)
|
| 503 |
return (
|
| 504 |
clean_none_value(jp_main),
|
| 505 |
clean_none_value(jp_syn),
|
| 506 |
clean_none_value(eng_syn),
|
| 507 |
clean_none_value(comment)
|
| 508 |
)
|
|
|
|
|
|
|
|
|
|
| 509 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
# -------------------------------
|
| 512 |
# GPT 기반 한국어 동의어 조회 (공식 영문명 지원)
|
|
|
|
| 288 |
東国製薬(동국제약):ヒガシコクセイヤク(히가시코쿠세이야쿠-한자의 일본발음)トンクック製薬(통쿡쿠제약)
|
| 289 |
"""
|
| 290 |
|
| 291 |
+
# 상단 import 섹션에 추가
|
| 292 |
+
from hangul_romanize import Transliter
|
| 293 |
+
from hangul_romanize.rule import academic
|
| 294 |
+
|
| 295 |
+
|
| 296 |
def get_jp_synonyms_formatted(category, word, official_eng=None, use_gpt=True, model="gpt-4o-mini", api_key=None, temperature=0.3, sleep_sec=1):
|
| 297 |
jp_main = jp_syn = eng_syn = comment = ""
|
| 298 |
|
|
|
|
| 304 |
import re
|
| 305 |
is_japanese_input = bool(re.search(r'[\u3040-\u309F\u30A0-\u30FF]', word)) and not has_jp_notation
|
| 306 |
|
| 307 |
+
# ========================================
|
| 308 |
+
# 🆕 신규 로직: 국문만 입력 + 브랜드일 시 표준 로마자 변환!
|
| 309 |
+
# ========================================
|
| 310 |
+
if not has_jp_notation and not is_japanese_input and category == "브랜드":
|
| 311 |
+
try:
|
| 312 |
+
# Step 1: 한글 → 표준 로마자 변환
|
| 313 |
+
transliter = Transliter(academic)
|
| 314 |
+
romanized = transliter.translit(word)
|
| 315 |
+
|
| 316 |
+
# 각 단어별 첫 글자 대문자 변환
|
| 317 |
+
romanized_title = ' '.join([w.capitalize() for w in romanized.split()])
|
| 318 |
+
|
| 319 |
+
# Step 2: 표준 로마자 기반 프롬프트
|
| 320 |
+
prompt = f"""너는 일본어 가타카나 변환 전문가야.
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
브랜드명(한글): {word}
|
| 324 |
+
로마자 표기: {romanized_title}
|
| 325 |
+
|
| 326 |
+
위 로마자 발음을 정확히 기준으로:
|
| 327 |
+
1. 일본어 가타카나 대표키워드 1개 생성 (로마자 발음 그대로!)
|
| 328 |
+
2. 발음 변형 동의어 생성 (히라가나, 장음/촉음 변형 등)
|
| 329 |
+
3. 영문 동의어는 '{romanized_title}' 기준 소문자/공백제거 변형만
|
| 330 |
+
|
| 331 |
+
{jp_common_rules}
|
| 332 |
+
|
| 333 |
+
⚠️ 중요 규칙:
|
| 334 |
+
- 대표키워드: {word}
|
| 335 |
+
- 대표키워드(일문)는 '{romanized_title}' 발음을 정확히 따를 것
|
| 336 |
+
- 의미 번역 절대 금지, 음차 표기만!
|
| 337 |
+
- 동의어에는 대표키워드와 동일한 단어 포함 금지
|
| 338 |
+
- 동의어 목록 내 중복 제거
|
| 339 |
|
| 340 |
+
출력 형식:
|
| 341 |
+
대표키워드: {word}
|
| 342 |
+
대표키워드(일문): [가타카나]
|
| 343 |
+
동의어(일문): [가타카나1|가타카나2]
|
| 344 |
+
동의어(영문): [eng1|eng2]
|
| 345 |
+
생성 이유: {romanized_title} 표준 로마자 철자 기반 생성"""
|
| 346 |
+
|
| 347 |
+
result = generate_with_model(
|
| 348 |
+
model_choice=model,
|
| 349 |
+
api_key=api_key,
|
| 350 |
+
prompt=prompt,
|
| 351 |
+
temperature=temperature
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
time.sleep(sleep_sec)
|
| 355 |
+
|
| 356 |
+
# 파싱
|
| 357 |
+
for line in result.splitlines():
|
| 358 |
+
if line.startswith("대표키워드(일문):"):
|
| 359 |
+
raw_main = line.split(":", 1)[1].strip()
|
| 360 |
+
jp_main = raw_main.split("|")[0].strip()
|
| 361 |
+
elif line.startswith("동의어(일문):"):
|
| 362 |
+
jp_syn = line.split(":", 1)[1].strip()
|
| 363 |
+
elif line.startswith("동의어(영문):"):
|
| 364 |
+
raw_eng = line.split(":", 1)[1].strip()
|
| 365 |
+
eng_list = [x.strip().lower().replace(" ", "") for x in raw_eng.split("|") if x.strip()]
|
| 366 |
+
eng_syn = "|".join(eng_list)
|
| 367 |
+
elif line.lower().startswith("생성 이유"):
|
| 368 |
+
comment = line.split(":", 1)[1].strip()
|
| 369 |
+
|
| 370 |
+
# 로마자 정보 추가
|
| 371 |
+
comment = f"🔤 {romanized_title} → {comment}" if comment else f"🔤 로마자 기반: {romanized_title}"
|
| 372 |
+
|
| 373 |
+
# ✅ 중복 제거
|
| 374 |
+
if jp_main and jp_syn:
|
| 375 |
+
jp_main_norm = normalize_word(jp_main)
|
| 376 |
+
jp_syn_list = [x.strip() for x in jp_syn.split("|") if x.strip()]
|
| 377 |
+
jp_syn_list = [x for x in jp_syn_list if normalize_word(x) != jp_main_norm]
|
| 378 |
+
jp_syn_list = list(dict.fromkeys(jp_syn_list))
|
| 379 |
+
jp_syn = "|".join(jp_syn_list)
|
| 380 |
+
|
| 381 |
+
if eng_syn:
|
| 382 |
+
eng_syn_list = [x.strip().lower().replace(" ", "") for x in eng_syn.split("|") if x.strip()]
|
| 383 |
+
eng_syn_list = list(dict.fromkeys(eng_syn_list))
|
| 384 |
+
eng_syn = "|".join(eng_syn_list)
|
| 385 |
+
|
| 386 |
+
# ✅ なし, N/A 처리
|
| 387 |
+
def clean_none_value(text):
|
| 388 |
+
if not text:
|
| 389 |
+
return ""
|
| 390 |
+
text = text.strip()
|
| 391 |
+
if text.lower() in ["なし", "n/a", "none", "-", "없음"]:
|
| 392 |
+
return ""
|
| 393 |
+
return text
|
| 394 |
+
|
| 395 |
+
return (
|
| 396 |
+
clean_none_value(jp_main),
|
| 397 |
+
clean_none_value(jp_syn),
|
| 398 |
+
clean_none_value(eng_syn),
|
| 399 |
+
clean_none_value(comment)
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
+
except Exception as e:
|
| 403 |
+
comment = f"⚠️ 로마자 변환 실패: {str(e)}"
|
| 404 |
+
# 실패하면 기존 로직으로 폴백
|
| 405 |
+
|
| 406 |
+
# ========================================
|
| 407 |
+
# 기존 로직 (일문 입력, 국문+일문, 카테고리/색상/속성/일반)
|
| 408 |
+
# ========================================
|
| 409 |
+
# 카테고리별 프롬프트
|
| 410 |
prompts = {
|
| 411 |
"브랜드": f"""너는 한국어/일본어/영어 패션 브랜드 동의어 전문가야. '{word}'라는 브랜드 이름의 동의어를 최대한 정확하게 찾아서 정리해줘.
|
| 412 |
{"⚠️ 중요: 입력에 '일문 표기'가 포함되어 있습니다. 이 일문 표기를 참고하여 동의어를 생성해주세요." if has_jp_notation else ""}
|
|
|
|
| 422 |
f"(의미 번역 금지, 실제 브랜드의 음차 표기만 허용)"
|
| 423 |
}
|
| 424 |
|
|
|
|
| 425 |
- 동의어(일문): 브랜드 관련 일본어 동의어, 없으면 비워두기, 중복 제거, ex.마르디 메크르디 - 마르디 매크르디,마르디 (Typing 변경 관점)
|
| 426 |
{f" * 참고: 제공된 일문 표기를 동의어 생성 시 참고하되, 동의어에 포함시키지 마세요" if has_jp_notation else ""}
|
| 427 |
- 동의어(영문): '{word.split('(')[0].strip() if has_jp_notation else word}'의 영어 공식명 기반 최대 3개
|
|
|
|
| 439 |
동의어(영문): word1|word2|word3
|
| 440 |
생성 이유: (대표키워드와 동의어를 이렇게 정한 간단한 이유 1문장)""",
|
| 441 |
|
|
|
|
| 442 |
"카테고리": f"""너는 한국어/일본어/영어 패션 카테고리 동의어 전문가야. '{word}' 카테고리 관련 동의어를 최대한 정확하게 찾아서 정리해줘.
|
| 443 |
{"⚠️ 중요: 입력에 '일문 표기'가 포함되어 있습니다. 이 일문 표기를 참고하여 동의어를 생성해주세요." if has_jp_notation else ""}
|
| 444 |
{"⚠️ 중요: 입력이 일본어입니다. 대표키워드(일문)는 '{word}'를 그대로 사용하고, 동의어만 생성해주세요." if is_japanese_input else ""}
|
|
|
|
| 540 |
생성 이유: (대표키워드와 동의어를 이렇게 정한 간단한 이유 1문장)"""
|
| 541 |
}
|
| 542 |
|
|
|
|
| 543 |
prompt = prompts.get(category, f"'{word}' 단어의 동의어를 찾아 대표키워드, 일본어 동의어, 영어 동의어를 알려줘.")
|
| 544 |
|
| 545 |
# 공식 영문명이 있으면 프롬프트 수정
|
| 546 |
if official_eng:
|
| 547 |
prompt += f" 영어 동의어는 반드시 '{official_eng}' 공식 영문명을 기준으로 정확히 3개까지만 제시해줘."
|
| 548 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
try:
|
| 550 |
completion = generate_with_model(
|
| 551 |
model_choice=model,
|
|
|
|
| 555 |
)
|
| 556 |
time.sleep(sleep_sec)
|
| 557 |
|
|
|
|
| 558 |
for line in completion.splitlines():
|
| 559 |
if line.startswith("대표키워드(일문):"):
|
| 560 |
raw_main = line.split(":", 1)[1].strip()
|
|
|
|
| 561 |
jp_main = raw_main.split("|")[0].strip()
|
| 562 |
elif line.startswith("동의어(일문):"):
|
| 563 |
jp_syn = line.split(":", 1)[1].strip()
|
| 564 |
elif line.startswith("동의어(영문):"):
|
| 565 |
raw_eng = line.split(":", 1)[1].strip()
|
|
|
|
| 566 |
eng_list = [x.strip().lower().replace(" ", "") for x in raw_eng.split("|") if x.strip()]
|
| 567 |
eng_syn = "|".join(eng_list)
|
| 568 |
elif line.lower().startswith("생성 이유") or line.lower().startswith("comment"):
|
| 569 |
comment = line.split(":", 1)[1].strip()
|
| 570 |
+
|
| 571 |
+
# ✅ 중복 제거 로직
|
| 572 |
if jp_main and jp_syn:
|
| 573 |
jp_main_norm = normalize_word(jp_main)
|
| 574 |
jp_syn_list = [x.strip() for x in jp_syn.split("|") if x.strip()]
|
|
|
|
| 575 |
jp_syn_list = [x for x in jp_syn_list if normalize_word(x) != jp_main_norm]
|
| 576 |
+
jp_syn_list = list(dict.fromkeys(jp_syn_list))
|
| 577 |
jp_syn = "|".join(jp_syn_list)
|
| 578 |
|
| 579 |
if eng_syn:
|
| 580 |
eng_syn_list = [x.strip().lower().replace(" ", "") for x in eng_syn.split("|") if x.strip()]
|
| 581 |
+
eng_syn_list = list(dict.fromkeys(eng_syn_list))
|
| 582 |
eng_syn = "|".join(eng_syn_list)
|
| 583 |
|
| 584 |
except Exception as e:
|
|
|
|
| 589 |
if not text:
|
| 590 |
return ""
|
| 591 |
text = text.strip()
|
|
|
|
| 592 |
if text.lower() in ["なし", "n/a", "none", "-", "없음"]:
|
| 593 |
return ""
|
| 594 |
return text
|
| 595 |
|
|
|
|
| 596 |
return (
|
| 597 |
clean_none_value(jp_main),
|
| 598 |
clean_none_value(jp_syn),
|
| 599 |
clean_none_value(eng_syn),
|
| 600 |
clean_none_value(comment)
|
| 601 |
)
|
| 602 |
+
```
|
| 603 |
+
|
| 604 |
+
## 3. 테스트
|
| 605 |
|
| 606 |
+
수정 후 테스트해보세요:
|
| 607 |
+
```
|
| 608 |
+
입력: "아크네스튜디오" (분류: 브랜드)
|
| 609 |
+
예상 결과:
|
| 610 |
+
- 로마자: Akeuneseutyudio
|
| 611 |
+
- 일문: アクネスタジオ
|
| 612 |
+
- Comment: 🔤 Akeuneseutyudio → ...
|
| 613 |
|
| 614 |
# -------------------------------
|
| 615 |
# GPT 기반 한국어 동의어 조회 (공식 영문명 지원)
|
requirements.txt
CHANGED
|
@@ -7,4 +7,6 @@ openpyxl
|
|
| 7 |
jaconv
|
| 8 |
pykakasi
|
| 9 |
rapidfuzz
|
| 10 |
-
huggingface_hub
|
|
|
|
|
|
|
|
|
| 7 |
jaconv
|
| 8 |
pykakasi
|
| 9 |
rapidfuzz
|
| 10 |
+
huggingface_hub
|
| 11 |
+
korean-romanizer
|
| 12 |
+
hangul-romanize
|