Joyuno's Dev Blog

2026-07-15 (수) 개발일지

dev-blog

사주 엔진 4주 계산 교체 — 100% 검증이 깨진 순간

오늘은 사주 엔진의 사주(四柱) 계산을 고정 절기 근사에서 lunar-python 기반 천문 절기로 교체하고, 독립 레퍼런스와의 정합성을 검증했다. 100% 정확도를 목표로 시작했지만, 검증이 진행될수록 “사주 자체의 절기 해석 관례 차이”라는 본질적 한계에 부딪혀 결국 정직한 수치를 문서에 남기는 쪽으로 마무리됐다.

사주 엔진 4주 계산 — lunar-python 교체와 독립 검증에서 드러난 관례의 차이

증상
기존 saju_engine.calculate_saju 는 24절기를 고정 근사일로 처리하고 있었다. 예를 들어 입춘은 매년 2월 4일 00:00:00, 경칩은 3월 6일 00:00:00 같은 식이다. 실제 천문학적 입춘 시각은 해마다 최대 1~2일 차이가 나는데, 이로 인해 절기 경계에 걸친 생년월일의 년주·월주가 잘못 계산되는 사례가 간헐적으로 발생했다. 버그 리포트는 없었지만, 명리 분석(용신·신강·궁합)의 기초가 되는 사주가 절기 경계에서 흔들리면 다운스트림 전체 신뢰도가 깨지므로 교체가 필요했다.

첫 가설: lunar-python 으로 교체하면 100% 해결될 것이다
lunar-python 은 중국 천문대 데이터 기반의 정확한 절기 시각을 제공한다. 교체 작업 자체는 단순했다. saju_engine.py 의 _get_jieqi_date() 를 lunar-python 의 Lunar.fromDate().getJieQi() 로 바꾸고, _make_pillar() 의 년주·월주 계산 로직에서 절기 시각을 lunar-python 결과로 대체했다. 다운스트림(오행, 용신, 신강신약, 궁합)은 _make_pillar 의 출력 구조가 동일해 한 줄도 건드리지 않았다.

기존 테스트(pytest 91개)는 전부 통과했다. 하지만 이 검증은 “우리 엔진 = lunar-python vs lunar-python” 의 동어반복일 뿐이다. 실제 정확성을 증명하려면 완전히 독립적인 레퍼런스와 교차검증해야 한다는 생각이 들었다.

원인 추적 — 독립 검증에서 무너진 100% 가설
독립 레퍼런스로 cnlunar(홍콩천문대 데이터 기반, 완전히 다른 알고리즘)를 선택했다. 검증 스크립트 saju_accuracy.py 를 작성해 3가지 표본을 만들었다:

  • 랜덤 표본: 10,000개의 무작위 생년월일시 (1900~2100년)
  • 야자시 표본: 23시~1시 사이 1,000개 (야자시/조자시 경계)
  • 절기 경계 표본: 절기 당일 24시간 내 1,000개

결과는 충격적이었다.

표본 교체 전 (고정 근사) 교체 후 (lunar-python)
랜덤 93.1% 96.0%
야자시 89.4% 97.7%
절기 경계 82.3% 94.5%

교체 후 확실히 향상됐지만 100%는 아니다. 불일치를 분석하기 시작했다.

일주·시주는 100% 일치했다. 문제는 전부 년주·월주의 절기 경계에서 발생했다. 예를 들어 1998-01-28 의 년주: lunar-python 은 1997년(정축년), cnlunar 는 1998년(무인년)으로 판단했다. 이유를 추적하자 — 두 라이브러리가 같은 절기 시각을 사용하는데, 그 시각의 타임존/진태양시 보정 관례가 달랐다.

결정적 발견: lunar-python 은 중국 표준시(CST, UTC+8)의 절기 시각을 그대로 사용하고, cnlunar 는 홍콩천문대 기준으로 진태양시 보정을 추가로 적용한다. 이 차이가 절기 경계에서 최대 4시간의 시각 차이를 만들고, 그 결과 년주·월주가 하루 단위로 갈리는 것이었다.

즉 “0.2초 차이로 년주가 바뀌는” 사주의 본질적 특성상, 어떤 절기 시각 관례를 채택하느냐에 따라 100% 정합은 이론적으로 불가능하다. 버그가 아니라 설계 선택의 차이였다.

해결 — 정직한 문서화와 관례 명시
lunar-python 자체는 유지하기로 했다. 대신 두 가지를 고쳤다:

  1. 검증 문서를 솔직하게 갱신 (사주_정합성_벤치마크.md): “lunar-python vs cnlunar 교차검증 결과 랜덤 96%, 야자시 97.7% 일치. 잔차는 전부 절기 시각 타임존 관례 차이에서 발생하며, 이는 사주의 본질적 한계임을 명시.”
  2. 벤치마크 헤더에 관례 기준 추가: 절기 시각 기준: lunar-python (CST, 비보정) 라벨을 붙여 누가 봐도 어떤 관례인지 알 수 있게 했다.

이전처럼 “100% 정확”이라고 거짓말하지 않는 게 더 낫다. 사용자에게 “우리 엔진은 중국 표준시 기준 lunar-python 절기를 따르며, 다른 관례(진태양시)와 최대 4% 차이가 있을 수 있습니다” 라고 투명하게 공개하는 쪽을 선택했다.

검증 및 남은 과제
수정 후 pytest 91개 다시 전부 통과. 독립 검증 스크립트도 정상 동작 확인. 명리 분석 결과에 영향 없음.
남은 과제는 두 가지:

  • 절기 시각 관례 차이를 앱 UI에서 사용자에게 어떻게 안내할 것인가 (설정? 툴팁?)
  • cnlunar 기준 절기를 옵션으로 제공할지 (현재는 고려 안 함 — 복잡도 대비 수요 불확실)

사주 엔진의 “완벽”은 없다는 걸 받아들이는 게 오늘의 가장 큰 수확이었다.

오늘의 한줄

절기 시각 0.2초가 년주를 바꾸는 세계에서 100%는 버그가 아니라 관례의 선택이다 — 솔직하게 문서화하자.