Files
VRCWT-OSC/VRWT_Tool/VRC_OSC/script/test.py
2026-06-24 00:38:14 +09:00

266 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
目的:
VRChat表示名とTOML guest名を fuzzy matching する
特徴:
- 記号除去
- 全角半角統一
- 小文字化
- 日本語 -> ローマ字変換
- rapidfuzz による類似度
- ambiguous 判定
- テストケース付き
前提:
pip install rapidfuzz pykakasi
期待結果:
- 類似候補を score 順に表示
- 危険な曖昧ケースを検知
"""
import re
import sys
import unicodedata
from typing import List, Tuple
try:
from rapidfuzz import fuzz
from rapidfuzz import process
except Exception as e:
print(f"[FATAL] rapidfuzz import failed: {e}")
input("Press Enter to exit...")
sys.exit(1)
try:
from pykakasi import kakasi
except Exception as e:
print(f"[FATAL] pykakasi import failed: {e}")
input("Press Enter to exit...")
sys.exit(1)
# =========================
# 設定
# =========================
AUTO_LINK_SCORE = 95
AMBIGUOUS_DIFF = 10
TEST_GUESTS = [
"okada",
"tanaka",
"yamada",
"messykenty",
]
TEST_CURRENT_USERS = [
"岡田",
"ikada",
"okada_",
"",
"tanaka!!!",
"田中",
"やまだ",
"messy_kenty",
"messy.kenty",
"MESSYKENTY",
"ma",
"めっしーけんてぃ",
"山田",
]
# =========================
# ローマ字変換
# =========================
kks = kakasi()
def jp_to_romaji(text: str) -> str:
try:
converted = kks.convert(text)
result = []
for item in converted:
result.append(item["hepburn"])
return "".join(result)
except Exception as e:
print(f"[ERROR] romaji convert failed: {text} : {e}")
return text
# =========================
# 正規化
# =========================
def normalize_name(name: str) -> str:
"""
正規化内容:
- 全角半角統一
- 小文字化
- 日本語→ローマ字
- 記号除去
"""
# Unicode normalize
name = unicodedata.normalize("NFKC", name)
# 小文字化
name = name.lower()
# 日本語→ローマ字
name = jp_to_romaji(name)
# 記号除去
name = re.sub(r"[^a-z0-9]", "", name)
return name.strip()
# =========================
# 類似候補検索
# =========================
def find_candidates(
target_guest: str,
current_users: List[str],
) -> List[Tuple[str, float]]:
normalized_target = normalize_name(target_guest)
normalized_map = {}
for user in current_users:
normalized_map[user] = normalize_name(user)
results = []
for original_name, normalized_name in normalized_map.items():
score = fuzz.ratio(
normalized_target,
normalized_name,
)
results.append((original_name, score))
results.sort(key=lambda x: x[1], reverse=True)
return results
# =========================
# 判定
# =========================
def evaluate_guest_match(
guest_name: str,
current_users: List[str],
):
print("")
print("=" * 60)
print(f"[CHECK] guest={guest_name}")
normalized_guest = normalize_name(guest_name)
print(f"[INFO] normalized_guest={normalized_guest}")
candidates = find_candidates(
guest_name,
current_users,
)
if not candidates:
print("[ERROR] no candidates")
return
print("")
print("[CHECK] candidate list")
for idx, (name, score) in enumerate(candidates[:5], start=1):
print(f" {idx}. {name:<20} score={score:.2f}")
top_name, top_score = candidates[0]
second_score = 0
if len(candidates) >= 2:
second_score = candidates[1][1]
diff = top_score - second_score
print("")
print(f"[INFO] top_score={top_score:.2f}")
print(f"[INFO] second_score={second_score:.2f}")
print(f"[INFO] diff={diff:.2f}")
# 自動確定
if (
top_score >= AUTO_LINK_SCORE
and diff >= AMBIGUOUS_DIFF
):
print("")
print("[INFO] AUTO LINK SAFE")
print(f"[INFO] linked => {top_name}")
return
# 曖昧
print("")
print("[WARN] ambiguous match")
print("[WARN] auto link disabled")
print("")
print("[ACTION] manual review required")
# =========================
# main
# =========================
def main():
print("[CHECK] normalize preview")
for user in TEST_CURRENT_USERS:
print(
f"{user:<20}"
f" => "
f"{normalize_name(user)}"
)
print("")
print("[CHECK] guest matching test")
for guest in TEST_GUESTS:
evaluate_guest_match(
guest,
TEST_CURRENT_USERS,
)
print("")
print("[INFO] completed")
if __name__ == "__main__":
print("[CHECK] start")
answer = input(
"[ACTION] run fuzzy test? (yes/no): "
).strip().lower()
if answer != "yes":
print("[FATAL] cancelled")
input("Press Enter to exit...")
sys.exit(1)
main()