bazi_system.py•14.8 kB
"""
八字系统实现
基于lunar_python库实现八字(四柱命理)排盘和分析
参考: https://github.com/china-testing/bazi
"""
import logging
from typing import Dict, Any, Optional
from datetime import datetime
from core.base_system import BaseFortuneSystem
from .formatter import BaziFormatter
logger = logging.getLogger(__name__)
try:
from lunar_python import Solar, Lunar
LUNAR_AVAILABLE = True
except ImportError:
logger.warning("lunar_python not installed, BaziSystem will not work")
LUNAR_AVAILABLE = False
class BaziSystem(BaseFortuneSystem):
"""八字系统实现"""
# 十天干
GAN = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
# 十二地支
ZHI = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]
# 五行属性
WU_XING = {
'金': "庚辛申酉",
'木': "甲乙寅卯",
'水': "壬癸子亥",
'火': "丙丁巳午",
'土': "戊己丑辰未戌",
}
# 十神对应关系(以日干为主)
TEN_DEITIES = {
'甲': {'甲': '比肩', '乙': '劫财', '丙': '食神', '丁': '伤官', '戊': '偏财',
'己': '正财', '庚': '七杀', '辛': '正官', '壬': '偏印', '癸': '正印'},
'乙': {'甲': '劫财', '乙': '比肩', '丙': '伤官', '丁': '食神', '戊': '正财',
'己': '偏财', '庚': '正官', '辛': '七杀', '壬': '正印', '癸': '偏印'},
'丙': {'丙': '比肩', '丁': '劫财', '戊': '食神', '己': '伤官', '庚': '偏财',
'辛': '正财', '壬': '七杀', '癸': '正官', '甲': '偏印', '乙': '正印'},
'丁': {'丙': '劫财', '丁': '比肩', '戊': '伤官', '己': '食神', '庚': '正财',
'辛': '偏财', '壬': '正官', '癸': '七杀', '甲': '正印', '乙': '偏印'},
'戊': {'戊': '比肩', '己': '劫财', '庚': '食神', '辛': '伤官', '壬': '偏财',
'癸': '正财', '甲': '七杀', '乙': '正官', '丙': '偏印', '丁': '正印'},
'己': {'戊': '劫财', '己': '比肩', '庚': '伤官', '辛': '食神', '壬': '正财',
'癸': '偏财', '甲': '正官', '乙': '七杀', '丙': '正印', '丁': '偏印'},
'庚': {'庚': '比肩', '辛': '劫财', '壬': '食神', '癸': '伤官', '甲': '偏财',
'乙': '正财', '丙': '七杀', '丁': '正官', '戊': '偏印', '己': '正印'},
'辛': {'庚': '劫财', '辛': '比肩', '壬': '伤官', '癸': '食神', '甲': '正财',
'乙': '偏财', '丙': '正官', '丁': '七杀', '戊': '正印', '己': '偏印'},
'壬': {'壬': '比肩', '癸': '劫财', '甲': '食神', '乙': '伤官', '丙': '偏财',
'丁': '正财', '戊': '七杀', '己': '正官', '庚': '偏印', '辛': '正印'},
'癸': {'壬': '劫财', '癸': '比肩', '甲': '伤官', '乙': '食神', '丙': '正财',
'丁': '偏财', '戊': '正官', '己': '七杀', '庚': '正印', '辛': '偏印'},
}
# 地支藏干
ZHI_CANG_GAN = {
'子': ['癸'],
'丑': ['己', '癸', '辛'],
'寅': ['甲', '丙', '戊'],
'卯': ['乙'],
'辰': ['戊', '乙', '癸'],
'巳': ['丙', '戊', '庚'],
'午': ['丁', '己'],
'未': ['己', '丁', '乙'],
'申': ['庚', '壬', '戊'],
'酉': ['辛'],
'戌': ['戊', '辛', '丁'],
'亥': ['壬', '甲'],
}
def __init__(self):
if not LUNAR_AVAILABLE:
raise RuntimeError("lunar_python library is not installed. Please install it with: pip install lunar_python")
self.formatter = BaziFormatter()
def get_system_name(self) -> str:
return "八字"
def get_system_version(self) -> str:
return "1.0.0"
def get_chart(self, birth_info: Dict[str, Any]) -> Dict[str, Any]:
"""
获取八字排盘
Args:
birth_info: 生辰信息,包含:
- date: 日期 (YYYY-MM-DD)
- time_index: 时辰序号 (0-12),或者使用 hour 指定具体小时
- gender: 性别 (男/女)
- calendar: 历法 (solar/lunar),默认solar
- is_leap_month: 是否闰月(仅农历),默认False
Returns:
八字排盘详细信息
"""
self.validate_birth_info(birth_info)
try:
# 获取lunar对象
lunar = self._get_lunar_object(birth_info)
# 提取四柱
year_pillar = lunar.getYearInGanZhi()
month_pillar = lunar.getMonthInGanZhi()
day_pillar = lunar.getDayInGanZhi()
hour_pillar = lunar.getTimeInGanZhi()
# 分解天干地支
year_gan, year_zhi = year_pillar[0], year_pillar[1]
month_gan, month_zhi = month_pillar[0], month_pillar[1]
day_gan, day_zhi = day_pillar[0], day_pillar[1]
hour_gan, hour_zhi = hour_pillar[0], hour_pillar[1]
# 计算十神
deities = self._calculate_ten_deities(day_gan, [
year_gan, month_gan, day_gan, hour_gan,
year_zhi, month_zhi, day_zhi, hour_zhi
])
# 计算五行
wu_xing = self._calculate_wu_xing([
year_gan, month_gan, day_gan, hour_gan,
year_zhi, month_zhi, day_zhi, hour_zhi
])
# 构建结果
result = {
'solar_date': birth_info['date'],
'lunar_date': lunar.toString(),
'gender': birth_info['gender'],
'pillars': {
'year': {'gan': year_gan, 'zhi': year_zhi, 'pillar': year_pillar},
'month': {'gan': month_gan, 'zhi': month_zhi, 'pillar': month_pillar},
'day': {'gan': day_gan, 'zhi': day_zhi, 'pillar': day_pillar},
'hour': {'gan': hour_gan, 'zhi': hour_zhi, 'pillar': hour_pillar},
},
'eight_char': f"{year_pillar} {month_pillar} {day_pillar} {hour_pillar}",
'zodiac': lunar.getYearShengXiao(),
'deities': deities,
'wu_xing': wu_xing,
'zhi_cang_gan': self._get_zhi_cang_gan(year_zhi, month_zhi, day_zhi, hour_zhi),
'day_master': day_gan, # 日主(日干)
}
return result
except Exception as e:
logger.exception("Error generating bazi chart")
raise RuntimeError(f"八字排盘失败: {str(e)}")
def get_fortune(
self,
birth_info: Dict[str, Any],
query_date: Optional[datetime] = None
) -> Dict[str, Any]:
"""
获取八字运势(大运、流年)
Args:
birth_info: 生辰信息
query_date: 查询日期,默认当前时间
Returns:
运势信息
"""
self.validate_birth_info(birth_info)
if query_date is None:
query_date = datetime.now()
try:
lunar = self._get_lunar_object(birth_info)
# 获取基本八字
chart = self.get_chart(birth_info)
# 计算年龄
birth_year = int(birth_info['date'].split('-')[0])
current_year = query_date.year
age = current_year - birth_year
# 计算大运(简化版,每10年一个大运)
da_yun_index = age // 10
# 获取流年天干地支
query_solar = Solar.fromDate(query_date)
query_lunar = query_solar.getLunar()
liu_nian_gan_zhi = query_lunar.getYearInGanZhi()
result = {
'query_date': query_date.strftime('%Y-%m-%d'),
'age': age,
'day_master': chart['day_master'],
'da_yun': {
'index': da_yun_index,
'age_range': f"{da_yun_index * 10}-{(da_yun_index + 1) * 10 - 1}岁",
'description': f"第{da_yun_index + 1}个大运"
},
'liu_nian': {
'year': current_year,
'gan_zhi': liu_nian_gan_zhi,
'zodiac': query_lunar.getYearShengXiao(),
},
'basic_chart': chart
}
return result
except Exception as e:
logger.exception("Error calculating bazi fortune")
raise RuntimeError(f"运势计算失败: {str(e)}")
def analyze_element(self, birth_info: Dict[str, Any]) -> Dict[str, Any]:
"""
分析五行强弱
Args:
birth_info: 生辰信息
Returns:
五行分析结果
"""
try:
chart = self.get_chart(birth_info)
wu_xing = chart['wu_xing']
# 计算总分
total = sum(wu_xing['scores'].values())
# 计算百分比
percentages = {
element: (score / total * 100 if total > 0 else 0)
for element, score in wu_xing['scores'].items()
}
# 找出最强和最弱的五行
strongest = max(wu_xing['scores'].items(), key=lambda x: x[1])
weakest = min(wu_xing['scores'].items(), key=lambda x: x[1])
# 五行缺失
missing = [elem for elem, score in wu_xing['scores'].items() if score == 0]
result = {
'scores': wu_xing['scores'],
'percentages': percentages,
'strongest': {'element': strongest[0], 'score': strongest[1]},
'weakest': {'element': weakest[0], 'score': weakest[1]},
'missing': missing,
'balance': self._judge_balance(wu_xing['scores']),
'day_master': chart['day_master'],
'day_master_element': self._get_element(chart['day_master']),
}
return result
except Exception as e:
logger.exception("Error analyzing wu xing")
raise RuntimeError(f"五行分析失败: {str(e)}")
def _get_lunar_object(self, birth_info: Dict[str, Any]) -> Lunar:
"""获取lunar对象"""
date_str = birth_info['date']
year, month, day = map(int, date_str.split('-'))
# 计算小时
if 'hour' in birth_info:
hour = birth_info['hour']
elif 'time_index' in birth_info:
time_index = birth_info['time_index']
# 时辰对应关系:0=早子时(0点), 1=丑时(1点), ... 12=晚子时(23点)
# 简化处理:每个时辰2小时,从早子时23点开始
if time_index == 0:
hour = 0 # 早子时 23-1点,用0点代表
else:
hour = (time_index - 1) * 2 + 1 # 其他时辰
else:
hour = 0 # 默认子时
if birth_info.get('calendar', 'solar') == 'lunar':
# 农历
is_leap = birth_info.get('is_leap_month', False)
lunar = Lunar.fromYmd(year, month, day)
# lunar_python 需要用Solar来设置时间
solar = Solar.fromYmdHms(
lunar.getSolar().getYear(),
lunar.getSolar().getMonth(),
lunar.getSolar().getDay(),
hour, 0, 0
)
return solar.getLunar()
else:
# 阳历
solar = Solar.fromYmdHms(year, month, day, hour, 0, 0)
return solar.getLunar()
def _calculate_ten_deities(self, day_gan: str, chars: list) -> Dict:
"""计算十神"""
if day_gan not in self.TEN_DEITIES:
return {}
deity_map = self.TEN_DEITIES[day_gan]
return {
'year_gan': deity_map.get(chars[0], '未知'),
'month_gan': deity_map.get(chars[1], '未知'),
'day_gan': deity_map.get(chars[2], '未知'),
'hour_gan': deity_map.get(chars[3], '未知'),
# 地支中的藏干也需要计算,这里简化处理
}
def _calculate_wu_xing(self, chars: list) -> Dict:
"""计算五行分数"""
scores = {'金': 0, '木': 0, '水': 0, '火': 0, '土': 0}
for char in chars:
for element, chars_str in self.WU_XING.items():
if char in chars_str:
scores[element] += 1
break
return {
'scores': scores,
'description': self._format_wu_xing_desc(scores)
}
def _get_element(self, gan: str) -> str:
"""获取天干的五行属性"""
for element, chars_str in self.WU_XING.items():
if gan in chars_str:
return element
return '未知'
def _format_wu_xing_desc(self, scores: Dict) -> str:
"""格式化五行描述"""
parts = [f"{elem}{score}个" for elem, score in scores.items() if score > 0]
return "、".join(parts)
def _get_zhi_cang_gan(self, year_zhi, month_zhi, day_zhi, hour_zhi) -> Dict:
"""获取地支藏干"""
return {
'year': self.ZHI_CANG_GAN.get(year_zhi, []),
'month': self.ZHI_CANG_GAN.get(month_zhi, []),
'day': self.ZHI_CANG_GAN.get(day_zhi, []),
'hour': self.ZHI_CANG_GAN.get(hour_zhi, []),
}
def _judge_balance(self, scores: Dict) -> str:
"""判断五行平衡"""
values = list(scores.values())
max_val = max(values)
min_val = min(values)
if max_val - min_val <= 1:
return "五行平衡"
elif max_val - min_val <= 3:
return "五行较平衡"
else:
return "五行不平衡"
def analyze_palace(self, birth_info: Dict[str, Any], palace_name: str) -> Dict[str, Any]:
"""
八字系统不支持宫位分析
八字(四柱命理)没有宫位的概念,这是紫微斗数的特性
"""
raise NotImplementedError("八字系统不支持宫位分析,请使用紫微斗数系统")