people-reading/backend/app/services/bazi_calculator.py
2026-05-12 20:50:15 +08:00

150 lines
5.9 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.

from datetime import datetime
try:
from lunar_python import Lunar, Solar
except ImportError: # pragma: no cover - production image installs the package
Lunar = None
Solar = None
STEM_WUXING = {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
}
BRANCH_WUXING = {
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
"": "",
}
class BaziCalculator:
def calculate(self, payload: dict) -> dict:
birth_date = payload["birth_date"]
time_unknown = bool(payload.get("time_unknown"))
birth_time = payload.get("birth_time") if not time_unknown else "12:00"
hour, minute = self._parse_time(birth_time or "12:00")
year, month, day = self._parse_date(birth_date)
calendar_type = payload.get("calendar_type", "solar")
if Solar and Lunar:
if calendar_type == "lunar":
lunar_month = -month if payload.get("is_leap_month") else month
lunar = Lunar.fromYmdHms(year, lunar_month, day, hour, minute, 0)
solar = lunar.getSolar()
else:
solar = Solar.fromYmdHms(year, month, day, hour, minute, 0)
lunar = solar.getLunar()
eight = lunar.getEightChar()
pillars = {
"year": eight.getYear(),
"month": eight.getMonth(),
"day": eight.getDay(),
"time": eight.getTime() if not time_unknown else "时辰不详",
}
return {
"calendar_type": calendar_type,
"is_leap_month": bool(payload.get("is_leap_month")),
"birth_date": birth_date,
"birth_time": None if time_unknown else f"{hour:02d}:{minute:02d}",
"time_unknown": time_unknown,
"birth_place": payload.get("birth_place"),
"nickname": payload.get("nickname"),
"gender": payload.get("gender"),
"solar_date": f"{solar.getYear():04d}-{solar.getMonth():02d}-{solar.getDay():02d}",
"lunar_date": f"{lunar.getYear()}{lunar.getMonth()}{lunar.getDay()}",
"pillars": pillars,
"wuxing": {
"year": eight.getYearWuXing(),
"month": eight.getMonthWuXing(),
"day": eight.getDayWuXing(),
"time": eight.getTimeWuXing() if not time_unknown else "时辰不详",
},
"shi_shen": {
"year": eight.getYearShiShenGan(),
"month": eight.getMonthShiShenGan(),
"day": "日主",
"time": eight.getTimeShiShenGan() if not time_unknown else "时辰不详",
},
"wuxing_balance": self._count_wuxing(pillars),
"day_master": eight.getDayGan(),
"chart_notes": [
"出生地仅用于报告语境,不参与经度校正。",
"时辰不详时,时柱相关判断会降低权重。",
]
if time_unknown
else ["出生地仅用于报告语境,不参与经度校正。"],
}
return self._fallback_chart(payload, year, month, day, hour, minute, time_unknown)
def _fallback_chart(self, payload: dict, year: int, month: int, day: int, hour: int, minute: int, time_unknown: bool) -> dict:
stems = "甲乙丙丁戊己庚辛壬癸"
branches = "子丑寅卯辰巳午未申酉戌亥"
seed = year * 372 + month * 31 + day + hour
def pillar(offset: int) -> str:
return stems[(seed + offset) % 10] + branches[(seed + offset) % 12]
pillars = {
"year": pillar(0),
"month": pillar(13),
"day": pillar(27),
"time": pillar(41) if not time_unknown else "时辰不详",
}
return {
"calendar_type": payload.get("calendar_type", "solar"),
"is_leap_month": bool(payload.get("is_leap_month")),
"birth_date": payload["birth_date"],
"birth_time": None if time_unknown else f"{hour:02d}:{minute:02d}",
"time_unknown": time_unknown,
"birth_place": payload.get("birth_place"),
"nickname": payload.get("nickname"),
"gender": payload.get("gender"),
"solar_date": payload["birth_date"],
"lunar_date": "本地未安装 lunar_python暂用娱乐排盘",
"pillars": pillars,
"wuxing": {"year": "参考", "month": "参考", "day": "参考", "time": "参考"},
"shi_shen": {"year": "参考", "month": "参考", "day": "日主", "time": "参考"},
"wuxing_balance": self._count_wuxing(pillars),
"day_master": pillar(27)[0],
"chart_notes": ["本地未安装 lunar_python当前为兜底娱乐排盘。"],
}
def _parse_date(self, value: str) -> tuple[int, int, int]:
parsed = datetime.strptime(value, "%Y-%m-%d")
return parsed.year, parsed.month, parsed.day
def _parse_time(self, value: str) -> tuple[int, int]:
parsed = datetime.strptime(value, "%H:%M")
return parsed.hour, parsed.minute
def _count_wuxing(self, pillars: dict) -> dict:
counts = {"": 0, "": 0, "": 0, "": 0, "": 0}
for pillar in pillars.values():
if not isinstance(pillar, str) or pillar == "时辰不详":
continue
for char in pillar:
element = STEM_WUXING.get(char) or BRANCH_WUXING.get(char)
if element:
counts[element] += 1
return counts