"""
紫微斗数系统实现
基于iztro-py库实现紫微斗数排盘和分析
"""
import logging
from datetime import datetime
from typing import Any, Dict, Optional
from core.base_system import BaseFortuneSystem
from core.exceptions import DependencyError, SystemError, ValidationError
from .formatter import ZiweiFormatter
logger = logging.getLogger(__name__)
try:
from iztro_py import astro
IZTRO_AVAILABLE = True
except ImportError:
logger.warning("iztro-py not installed, ZiweiSystem will not work")
IZTRO_AVAILABLE = False
astro = None # type: ignore
class ZiweiSystem(BaseFortuneSystem):
"""紫微斗数系统实现"""
# 十二宫位名称(中文,与 iztro-py 返回的名称保持一致)
PALACES = [
"命宫",
"兄弟宫",
"夫妻宫",
"子女宫",
"财帛宫",
"疾厄宫",
"迁移宫",
"交友宫", # 对应 iztro-py 的 friendsPalace,旧称"仆役宫"
"官禄宫",
"田宅宫",
"福德宫",
"父母宫",
]
# 中英文宫位名称映射(iztro-py 使用英文名称)
PALACE_NAME_MAP = {
"命宫": "soulPalace",
"兄弟": "siblingsPalace",
"夫妻": "spousePalace",
"子女": "childrenPalace",
"财帛": "wealthPalace",
"疾厄": "healthPalace",
"迁移": "surfacePalace",
"仆役": "friendsPalace",
"官禄": "careerPalace",
"田宅": "propertyPalace",
"福德": "spiritPalace",
"父母": "parentsPalace",
}
def __init__(self):
if not IZTRO_AVAILABLE:
raise DependencyError(
"iztro-py library is not installed. Please install it with: pip install iztro-py"
)
self.formatter = ZiweiFormatter()
def get_system_name(self) -> str:
return "紫微斗数"
def get_system_version(self) -> str:
try:
import iztro_py
return getattr(iztro_py, "__version__", "0.1.0")
except Exception:
return "0.1.0"
def _convert_datetime_for_horoscope(self, dt: datetime) -> tuple:
"""
转换 datetime 为 iztro-py horoscope 方法所需格式
Args:
dt: datetime 对象
Returns:
(date_str, hour_index) 元组
- date_str: "YYYY-M-D" 格式(月和日不补零)
- hour_index: 时辰索引 0-11(每个时辰2小时)
"""
# 格式化日期字符串,去掉月份和日期的前导零
date_str = f"{dt.year}-{dt.month}-{dt.day}"
# 计算时辰索引(0-11,每个时辰2小时)
hour_index = dt.hour // 2
return date_str, hour_index
def get_chart(self, birth_info: Dict[str, Any], language: str = "zh-CN") -> Dict[str, Any]:
"""
获取紫微斗数排盘
Args:
birth_info: 生辰信息
language: 输出语言
Returns:
排盘详细信息
"""
self.validate_birth_info(birth_info)
try:
# 应用真太阳时修正(如果启用)
adjusted_time_index = self.apply_solar_time_correction(birth_info)
# 根据历法类型调用不同的方法
if birth_info.get("calendar", "solar") == "lunar":
astrolabe = astro.by_lunar(
birth_info["date"],
adjusted_time_index, # 使用修正后的时辰
birth_info["gender"],
birth_info.get("is_leap_month", False),
)
else:
astrolabe = astro.by_solar(
birth_info["date"],
adjusted_time_index,
birth_info["gender"], # 使用修正后的时辰
)
# 设置语言
astrolabe.set_language(language)
# 格式化输出
return self.formatter.format_chart(astrolabe)
except ValidationError:
raise
except ImportError as e:
logger.error(f"Missing dependency for chart generation: {e}")
raise DependencyError(f"依赖缺失: {str(e)}")
except Exception as e:
logger.exception("Unexpected error generating ziwei chart")
raise SystemError(f"排盘失败: {str(e)}")
def get_fortune(
self,
birth_info: Dict[str, Any],
query_date: Optional[datetime] = None,
language: str = "zh-CN",
) -> Dict[str, Any]:
"""
获取紫微斗数运势(大限、流年、流月、流日、流时)
Args:
birth_info: 生辰信息
query_date: 查询日期,默认当前时间
language: 输出语言
Returns:
运势信息
"""
self.validate_birth_info(birth_info)
if query_date is None:
query_date = datetime.now()
try:
# 应用真太阳时修正(如果启用)
adjusted_time_index = self.apply_solar_time_correction(birth_info)
# 先获取星盘
if birth_info.get("calendar", "solar") == "lunar":
astrolabe = astro.by_lunar(
birth_info["date"],
adjusted_time_index, # 使用修正后的时辰
birth_info["gender"],
birth_info.get("is_leap_month", False),
)
else:
astrolabe = astro.by_solar(
birth_info["date"],
adjusted_time_index,
birth_info["gender"], # 使用修正后的时辰
)
# 设置语言
astrolabe.set_language(language)
# 获取运势(iztro-py 需要日期字符串和时辰索引)
date_str, hour_index = self._convert_datetime_for_horoscope(query_date)
horoscope = astrolabe.horoscope(date_str, hour_index)
# 格式化输出
return self.formatter.format_fortune(horoscope, query_date)
except ValidationError:
raise
except ImportError as e:
logger.error(f"Missing dependency for fortune generation: {e}")
raise DependencyError(f"依赖缺失: {str(e)}")
except Exception as e:
logger.exception("Unexpected error generating ziwei fortune")
raise SystemError(f"运势查询失败: {str(e)}")
def analyze_palace(
self, birth_info: Dict[str, Any], palace_name: str, language: str = "zh-CN"
) -> Dict[str, Any]:
"""
分析特定宫位
Args:
birth_info: 生辰信息
palace_name: 宫位名称(中文,如"命宫")
language: 输出语言
Returns:
宫位详细分析
"""
if palace_name not in self.PALACES:
raise ValidationError(
f"无效的宫位名称: {palace_name}. 有效宫位: {', '.join(self.PALACES)}"
)
self.validate_birth_info(birth_info)
try:
# 获取完整星盘(formatter 已经将宫位名转换为中文)
chart = self.get_chart(birth_info, language)
# 找到指定宫位(直接匹配中文名)
target_palace = None
for palace in chart["palaces"]:
if palace["name"] == palace_name:
target_palace = palace
break
if target_palace is None:
raise SystemError(f"未找到宫位: {palace_name}")
# 格式化宫位分析
return self.formatter.format_palace_analysis(target_palace, chart["basic_info"])
except (ValidationError, SystemError):
raise
except Exception as e:
logger.exception("Unexpected error analyzing palace")
raise SystemError(f"宫位分析失败: {str(e)}")
def get_supported_palaces(self) -> list:
"""返回支持的宫位列表"""
return self.PALACES.copy()
def get_capabilities(self) -> Dict[str, bool]:
"""返回系统功能支持"""
return {
"chart": True,
"fortune": True,
"palace_analysis": True,
"compatibility": False, # 暂不支持合盘
"transit": False, # 暂不支持推运
}