rouge-like-matcing-LOVElog / tests /test_cluster.py
jun0-ds
deploy: HF Spaces 배포
2e03d75
"""B2: 유형 카드 분류 테스트."""
import json
from pathlib import Path
from matching_engine.cluster import classify, compute_cluster_scores
from matching_engine.models import ClusterType, Profile
DATA_PATH = Path(__file__).parent.parent / "heartlog" / "data" / "seed_profiles_1000.json"
def load_profiles() -> list[Profile]:
with open(DATA_PATH, encoding="utf-8") as f:
return [Profile(**p) for p in json.load(f)]
# --- 단위 테스트 ---
class TestComputeClusterScores:
def test_returns_all_five_types(self):
traits = {k: 0.0 for k in ["cooperation", "leadership", "emotional_depth",
"pace", "humor", "risk", "contact_frequency",
"affection", "jealousy", "planning"]}
big_five = {k: 0.0 for k in ["openness", "conscientiousness",
"extraversion", "agreeableness", "neuroticism"]}
scores = compute_cluster_scores(traits, big_five)
assert set(scores.keys()) == set(ClusterType)
def test_flame_high_for_risk_pace_humor(self):
"""risk/pace/humor/extraversion 높으면 불꽃형 점수 높아야 함."""
traits = {"risk": 2.5, "pace": 2.5, "humor": 2.0,
"cooperation": 0.0, "leadership": 0.0, "emotional_depth": 0.0,
"contact_frequency": 0.0, "affection": 0.0, "jealousy": 0.0, "planning": 0.0}
big_five = {"extraversion": 2.0, "openness": 0.0, "conscientiousness": 0.0,
"agreeableness": 0.0, "neuroticism": 0.0}
scores = compute_cluster_scores(traits, big_five)
assert scores[ClusterType.FLAME] == max(scores.values())
def test_stable_high_for_cooperation_planning(self):
traits = {"cooperation": 2.5, "planning": 2.5, "contact_frequency": 2.0,
"leadership": 0.0, "emotional_depth": 0.0, "pace": 0.0,
"humor": 0.0, "risk": 0.0, "affection": 0.0, "jealousy": 0.0}
big_five = {"agreeableness": 2.0, "conscientiousness": 2.0,
"openness": 0.0, "extraversion": 0.0, "neuroticism": 0.0}
scores = compute_cluster_scores(traits, big_five)
assert scores[ClusterType.STABLE] == max(scores.values())
class TestClassify:
def test_returns_tuple(self):
traits = {"risk": 3.0, "pace": 3.0, "humor": 3.0,
"cooperation": 0.0, "leadership": 0.0, "emotional_depth": 0.0,
"contact_frequency": 0.0, "affection": 0.0, "jealousy": 0.0, "planning": 0.0}
big_five = {"extraversion": 3.0, "openness": 0.0, "conscientiousness": 0.0,
"agreeableness": 0.0, "neuroticism": 0.0}
top1, top2 = classify(traits, big_five)
assert isinstance(top1, ClusterType)
def test_top2_none_when_gap_large(self):
"""1위와 2위 차이가 크면 top2는 None."""
traits = {"risk": 3.0, "pace": 3.0, "humor": 3.0,
"cooperation": -3.0, "leadership": -3.0, "emotional_depth": -3.0,
"contact_frequency": -3.0, "affection": -3.0, "jealousy": -3.0, "planning": -3.0}
big_five = {"extraversion": 3.0, "openness": -3.0, "conscientiousness": -3.0,
"agreeableness": -3.0, "neuroticism": -3.0}
_, top2 = classify(traits, big_five)
assert top2 is None
# --- 시드 데이터 통합 테스트 ---
class TestSeedProfileClassification:
"""시드 프로필의 아키타입 → 클러스터 매핑 검증 (90% 이상 일치)."""
MIN_RATIO = 0.90 # 노이즈로 인한 경계 이탈 허용
def setup_method(self):
self.profiles = load_profiles()
def _get_profiles_by_archetype(self, archetype: str) -> list[Profile]:
return [p for p in self.profiles if p.archetype == archetype]
def _assert_majority_maps(self, archetype: str, expected: ClusterType) -> None:
profiles = self._get_profiles_by_archetype(archetype)
clusters = [classify(p.traits, p.big_five)[0] for p in profiles]
match_count = sum(1 for c in clusters if c == expected)
ratio = match_count / len(profiles)
assert ratio >= self.MIN_RATIO, (
f"{archetype}{expected.value}: {match_count}/{len(profiles)} "
f"({ratio:.0%}, 최소 {self.MIN_RATIO:.0%} 필요)"
)
def test_all_profiles_classify(self):
for p in self.profiles:
top1, _ = classify(p.traits, p.big_five)
assert isinstance(top1, ClusterType)
def test_flame_archetype_maps_to_flame(self):
self._assert_majority_maps("불꽃형", ClusterType.FLAME)
def test_stable_archetype_maps_to_stable(self):
self._assert_majority_maps("안정형", ClusterType.STABLE)
def test_independent_archetype_maps_to_free(self):
self._assert_majority_maps("독립형", ClusterType.FREE)
def test_strategy_archetype_maps_to_strategy(self):
self._assert_majority_maps("전략형", ClusterType.STRATEGY)
def test_emotion_archetype_maps_to_emotion(self):
self._assert_majority_maps("감성형", ClusterType.EMOTION)
def test_jealous_archetype_maps_to_emotion(self):
self._assert_majority_maps("질투형", ClusterType.EMOTION)
def test_humor_archetype_maps_to_flame(self):
self._assert_majority_maps("유머형", ClusterType.FLAME)
def test_realistic_archetype_maps_to_strategy(self):
self._assert_majority_maps("현실형", ClusterType.STRATEGY)
def test_devoted_mostly_maps_to_stable(self):
"""헌신형은 대부분 안정형으로 매핑 (일부 감정형 허용, 60% 이상)."""
profiles = self._get_profiles_by_archetype("헌신형")
clusters = [classify(p.traits, p.big_five)[0] for p in profiles]
stable_count = sum(1 for c in clusters if c == ClusterType.STABLE)
ratio = stable_count / len(profiles)
assert ratio >= 0.60, f"헌신형 중 안정형 {stable_count}/{len(profiles)} ({ratio:.0%})"