feat(saju-lab): core.py — 60갑자 + 십성 + 십이운성 + calculate_saju (30/30 reference pass)
This commit is contained in:
78
saju-lab/tests/test_core.py
Normal file
78
saju-lab/tests/test_core.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""calculator/core.py — saju-web TS 엔진과 1:1 매칭 검증.
|
||||
|
||||
fixtures/reference_saju.json 의 30 case (TS calculateSaju 결과)와 Python 구현 일치 여부 검증.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.calculator.core import calculate_saju
|
||||
|
||||
|
||||
REF_PATH = Path(__file__).parent / "fixtures" / "reference_saju.json"
|
||||
REF = json.loads(REF_PATH.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
out = []
|
||||
for ch in name:
|
||||
if ch.isupper():
|
||||
out.append("_" + ch.lower())
|
||||
else:
|
||||
out.append(ch)
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def _normalize(d):
|
||||
"""TS camelCase → Python snake_case 변환 (deep)."""
|
||||
if isinstance(d, dict):
|
||||
return {_camel_to_snake(k): _normalize(v) for k, v in d.items()}
|
||||
if isinstance(d, list):
|
||||
return [_normalize(x) for x in d]
|
||||
return d
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"case",
|
||||
REF,
|
||||
ids=lambda c: f"{c['input']['year']}-{c['input']['month']:02d}-{c['input']['day']:02d}-{c['input'].get('hour')}-{c['input']['gender']}",
|
||||
)
|
||||
def test_calculate_saju_matches_reference(case):
|
||||
inp = case["input"]
|
||||
expected = _normalize(case["expected"]["saju"])
|
||||
|
||||
actual = calculate_saju(
|
||||
inp["year"], inp["month"], inp["day"],
|
||||
inp.get("hour"), inp["gender"],
|
||||
)
|
||||
|
||||
# 4기둥 비교
|
||||
for pillar in ["year", "month", "day", "hour"]:
|
||||
if expected.get(pillar) is None:
|
||||
assert actual.get(pillar) is None, f"{pillar} should be None"
|
||||
continue
|
||||
if pillar == "hour" and inp.get("hour") is None:
|
||||
assert actual.get(pillar) is None
|
||||
continue
|
||||
ep = expected[pillar]
|
||||
ap = actual[pillar]
|
||||
for field in ["stem", "branch", "stem_kr", "branch_kr", "element", "ten_god", "fortune"]:
|
||||
assert ap[field] == ep[field], (
|
||||
f"{pillar}.{field}: actual={ap[field]} expected={ep[field]} "
|
||||
f"(input={inp})"
|
||||
)
|
||||
|
||||
# day_stem
|
||||
assert actual["day_stem"] == expected["day_stem"]
|
||||
|
||||
# gender
|
||||
assert actual["gender"] == expected["gender"]
|
||||
|
||||
# birth_date
|
||||
bd = actual["birth_date"]
|
||||
assert bd["year"] == inp["year"]
|
||||
assert bd["month"] == inp["month"]
|
||||
assert bd["day"] == inp["day"]
|
||||
if inp.get("hour") is not None:
|
||||
assert bd.get("hour") == inp["hour"]
|
||||
Reference in New Issue
Block a user