"""사주 궁합 — 두 사주 일주(일간 오행 + 일지) 매칭 → 점수 + breakdown. saju-web/app/compatibility/result/page.tsx의 `calculateCompatibility()` 로직과 1:1 매핑. 알고리즘 (saju-web TS): score = 50 (base) # 일간 오행 관계 if element1 == element2: score += 10 # same elif relation in (produce, produced): score += 25 # 상생 else: score -= 10 # 상극 (overcome/overcomed) # 일지 관계 if 六合 (sixHarmony): score += 20 if 三合 (threeHarmony): score += 15 if 沖 (conflict): score -= 20 score = clamp(score, 0, 100) breakdown은 saju-lab 자체 확장 — 각 카테고리별 (score_delta, description) 제공. """ from __future__ import annotations from typing import Any, Dict from .constants import FIVE_ELEMENTS # ─── 오행 상생/상극 맵 (saju-web과 동일) ────────────────────────────── _PRODUCE_MAP = {"木": "火", "火": "土", "土": "金", "金": "水", "水": "木"} _OVERCOME_MAP = {"木": "土", "火": "金", "土": "水", "金": "木", "水": "火"} # ─── 지지 6합/6충 (saju-web과 동일) ─────────────────────────────────── _SIX_HARMONY: dict[str, str] = { "子": "丑", "丑": "子", "寅": "亥", "亥": "寅", "卯": "戌", "戌": "卯", "辰": "酉", "酉": "辰", "巳": "申", "申": "巳", "午": "未", "未": "午", } # 삼합 그룹 (saju-web threeHarmonyGroups) _THREE_HARMONY_GROUPS = [ {"申", "子", "辰"}, {"寅", "午", "戌"}, {"亥", "卯", "未"}, {"巳", "酉", "丑"}, ] _CONFLICT: dict[str, str] = { "子": "午", "午": "子", "丑": "未", "未": "丑", "寅": "申", "申": "寅", "卯": "酉", "酉": "卯", "辰": "戌", "戌": "辰", "巳": "亥", "亥": "巳", } # ─── 헬퍼 ───────────────────────────────────────────────────────── def _get_element_relation(el1: str, el2: str) -> str: """오행 관계 판별 — 'same' | 'produce' | 'produced' | 'overcome' | 'overcomed'. saju-web getElementRelation()과 1:1 매핑. """ if el1 == el2: return "same" if _PRODUCE_MAP.get(el1) == el2: return "produce" # el1 → el2 (el1이 el2를 생함) if _PRODUCE_MAP.get(el2) == el1: return "produced" # el2 → el1 (el1이 el2로부터 생을 받음) if _OVERCOME_MAP.get(el1) == el2: return "overcome" # el1 → el2 (el1이 el2를 극함) return "overcomed" # el2 → el1 (el1이 el2로부터 극을 받음) def _get_branch_relation(b1: str, b2: str) -> dict: """지지 관계 — {six_harmony, three_harmony, conflict}. saju-web getBranchRelation()과 1:1 매핑. """ is_three = any(b1 in g and b2 in g and b1 != b2 for g in _THREE_HARMONY_GROUPS) return { "six_harmony": _SIX_HARMONY.get(b1) == b2, "three_harmony": is_three, "conflict": _CONFLICT.get(b1) == b2, } def _day_master_element_delta(el1: str, el2: str) -> tuple[int, str, str]: """일간 오행 매칭 delta + relation tag + description.""" relation = _get_element_relation(el1, el2) if relation == "same": return 10, "same", f"일간 동일 오행 ({el1}={el2}) — 안정적이나 자극은 약함" if relation in ("produce", "produced"): return 25, relation, f"일간 상생 관계 ({el1}↔{el2}) — 서로 도움" # overcome / overcomed return -10, relation, f"일간 상극 관계 ({el1}↔{el2}) — 갈등 가능" def _branch_interaction_delta(b1: str, b2: str) -> tuple[int, dict, str]: """일지 6합/3합/충 delta + flags + description.""" rel = _get_branch_relation(b1, b2) delta = 0 parts: list[str] = [] if rel["six_harmony"]: delta += 20 parts.append(f"일지 6합 ({b1}+{b2})") if rel["three_harmony"]: delta += 15 parts.append(f"일지 3합 ({b1}+{b2})") if rel["conflict"]: delta -= 20 parts.append(f"일지 충 ({b1}↔{b2})") if not parts: parts.append(f"일지 중립 ({b1}, {b2})") return delta, rel, " · ".join(parts) # ─── 메인 ───────────────────────────────────────────────────────── def calculate_compatibility(saju_a: Dict[str, Any], saju_b: Dict[str, Any]) -> Dict[str, Any]: """두 사주 궁합 점수 (0~100) + breakdown. Args: saju_a: calculate_saju() 결과 — `saju["day"]["stem"|"branch"|"element"]` 사용 saju_b: 두 번째 사주 Returns: { "score": int (0~100), "breakdown": { "day_master_element": {"score": int, "relation": str, "description": str}, "branch_interaction": {"score": int, "flags": dict, "description": str}, "base": 50, } } """ day_a = saju_a["day"] day_b = saju_b["day"] el1 = day_a.get("element") or FIVE_ELEMENTS[day_a["stem"]] el2 = day_b.get("element") or FIVE_ELEMENTS[day_b["stem"]] b1 = day_a["branch"] b2 = day_b["branch"] elem_delta, elem_relation, elem_desc = _day_master_element_delta(el1, el2) branch_delta, branch_flags, branch_desc = _branch_interaction_delta(b1, b2) raw = 50 + elem_delta + branch_delta score = max(0, min(100, raw)) return { "score": score, "breakdown": { "base": 50, "day_master_element": { "score": elem_delta, "relation": elem_relation, "element_a": el1, "element_b": el2, "description": elem_desc, }, "branch_interaction": { "score": branch_delta, "flags": branch_flags, "branch_a": b1, "branch_b": b2, "description": branch_desc, }, }, }