基于Streamlit构建的风水命理计算器

这是一个基于Streamlit构建的风水命理计算器Web应用。

它结合了中国传统风水理论,包括八宅风水、三元九运、五行生克等,为用户提供个人命理分析和方位建议。

代码结构:

  1. 导入必要的库

  2. 设置Streamlit页面配置和中文字体支持

  3. 定义两个枚举类:Direction(方位)和Element(五行)

  4. 定义一个FengShuiCalculator类,包含风水命理计算的核心逻辑

  5. 定义几个可视化函数,用于生成雷达图、柱状图、罗盘图和饼图

  6. 定义main函数,构建Streamlit应用的界面和交互

下面我们详细解读各个部分。

一、导入的库:

  • streamlit:用于构建Web应用

  • pandas, numpy:数据处理

  • matplotlib, plotly:绘图

  • datetime:获取当前年份

  • enum, typing:定义枚举和类型提示

  • sys, math:系统与数学函数

二、页面配置和中文字体设置

三、枚举类:

  • Direction:表示8个方位和中宫,每个方位有中文名称和角度(用于雷达图)

  • Element:表示五行(金木水火土),每个元素有对应的颜色

四、FengShuiCalculator类:

  1. 初始化:接收出生年份、性别等参数,并进行验证。

  2. 计算命卦数(gua_number):根据出生年份和性别,按照一定规则计算。这里区分了1900-1999年和2000-2099年,男女不同。

    注意:当计算结果为5时,男性转换为2,女性转换为8。

  3. 根据命卦数获取五行属性和卦名。

  4. 根据命卦数获取东西四命分组(东四命:1、3、4、9;西四命:2、6、7、8)。

  5. 根据命卦数获取八宅风水的方位映射(生气、延年、天医、伏位、祸害、六煞、五鬼、绝命分别对应的方位)。

  6. 计算特定方位的能量梯度值:结合基础能量(三元九运)、方位修正系数、时间修正系数(流年飞星)和个人命卦修正系数。

  7. 分析所有方位的吉凶和能量,返回每个方位的详细信息(中文名称、能量值、吉凶类型、吉凶等级、五行属性、颜色、是否吉利)。

  8. 获取风水建议:包括住宅朝向、卧室方位、办公方位、吉祥颜色和注意事项。

  9. 其他辅助方法,如获取大运阶段、五行生克等。

五、可视化函数:

  1. create_radar_chart:创建雷达图,展示8个方位(不包括中宫)的能量值。

  2. create_bar_chart:创建柱状图,展示9个方位的能量值,并按照能量值排序。

  3. create_compass_diagram:创建罗盘图,在圆形上标注9个方位的能量值和吉凶。

  4. create_pie_chart:创建饼图,展示吉凶等级的分布。

六、Streamlit主函数main:

  1. 设置页面标题和描述。

  2. 侧边栏:输入个人信息(出生年份、性别,以及可选的农历月份、日期和时辰)和示例按钮。

  3. 主内容区:

    • 展示基本信息卡片(命卦数、卦名、五行属性、命理分组)

    • 四个选项卡:

      a. 能量可视化:展示罗盘图、雷达图、柱状图和饼图。

      b. 方位分析:以表格形式展示每个方位的详细信息,并解释吉凶方位的含义。

      c. 风水建议:展示针对个人的风水建议。

      d. 详细信息:展示个人命理信息、当前年份信息和能量计算说明。

一、概述

这是一个融合传统风水理论和现代数据可视化的交互式Web应用,主要用于计算个人命理、方位吉凶和风水建议。

二、架构

1. 核心依赖库

复制代码
# Web框架和数据可视化
import streamlit as st  # Web应用框架
import pandas as pd, numpy as np  # 数据处理
import matplotlib.pyplot as plt  # 静态图表
import plotly.graph_objects as go, plotly.express as px  # 交互式图表

2. 核心类设计

(1) Direction枚举类
复制代码
class Direction(Enum):
    """定义8个方位+中宫"""
    # 实现了方位到中文名称、角度的映射
    def get_chinese_name(self) -> str  # 返回中文方位名
    def get_angle(self) -> float  # 返回极坐标角度(0-315度)
(2) Element枚举类
复制代码
class Element(Enum):
    """五行(金木水火土)"""
    def get_color(self) -> str  # 返回五行对应的颜色
(3) FengShuiCalculator主计算类

这是应用的核心逻辑类,实现了完整的命理计算。

三、算法解析

1. 命卦计算算法 (get_gua_number)

复制代码
# 基于洛书九宫和八宅风水理论
# 1900-1999年:男性 (100-年份末两位)%9
# 1900-1999年:女性 (年份末两位-4)%9
# 2000-2099年:男性 (99-年份末两位)%9
# 2000-2099年:女性 (年份末两位+6)%9
# 特殊处理:结果为5时,男→2(坤),女→8(艮)

2. 方位吉凶映射 (get_direction_mapping)

每个命卦对应8个方位,各有不同吉凶属性:

  • 吉方:生气(大吉)、延年(中吉)、天医(中吉)、伏位(小吉)

  • 凶方:祸害(小凶)、六煞(次凶)、五鬼(大凶)、绝命(大凶)

3. 能量梯度计算模型

复制代码
energy = base_energy * direction_coefficient * time_coefficient * personal_coefficient
(1) 基础能量 (_get_base_energy)

基于三元九运理论,每20年一运:

  • 一运(1864-1883): 0.8, 二运: 0.6, ..., 九运(2024-2043): 1.0
(2) 方位系数 (_get_direction_coefficient)

各方位固有能量系数:

  • 南(火): 1.1, 东(木): 1.0, 西南(土): 0.8, 等
(3) 时间系数 (_get_time_coefficient)

基于流年飞星理论,每年不同:

  • 2024(八白左辅星): 1.0, 2025(九紫右弼星): 1.1, 等
(4) 个人系数 (_get_personal_coefficient)

基于个人命卦与方位关系:

  • 生气方: 1.5, 延年: 1.3, ..., 绝命: 0.5

四、可视化

1. 罗盘图 (create_compass_diagram)

  • 使用matplotlib绘制

  • 圆形布局显示8个方位+中宫

  • 颜色编码表示吉凶等级

2. 雷达图 (create_radar_chart)

  • 极坐标显示各方位能量分布

  • 多边形填充增强视觉

3. 柱状图 (create_bar_chart)

  • 使用Plotly交互式图表

  • 按能量值排序显示

  • 悬停显示详细信息

4. 饼图 (create_pie_chart)

  • 显示吉凶等级分布比例

  • 环形图设计

五、Streamlit界面

1. 页面布局

复制代码
st.set_page_config(
    page_title="风水命理计算器",
    page_icon="🏮",
    layout="wide",  # 宽屏布局
    initial_sidebar_state="expanded"
)

2. 侧边栏设计

  • 用户输入:出生年份、性别

  • 高级选项:农历月日、时辰

  • 示例按钮:快速填充测试数据

  • 关于说明:应用背景介绍

3. 主界面结构

采用选项卡式布局

  1. 📈 能量可视化:图表展示

  2. 🧭 方位分析:详细数据表格

  3. 💡 风水建议:个性化建议

  4. 📋 详细信息:命理解读

六、数据处理

复制代码
用户输入 → 验证 → 命卦计算 → 五行属性 → 方位映射
    ↓
能量计算 → 吉凶分析 → 可视化 → 建议生成
    ↓
界面展示

七、特性

1. 算法特点

  • 多重系数综合:基础+方位+时间+个人

  • 动态调整:年份变化影响计算结果

  • 传统理论结合:八宅风水+三元九运+流年飞星

2. 用户体验

  • 实时计算:输入即计算

  • 多重可视化:4种图表从不同角度展示

  • 详细解释:每个结果都有理论依据

  • 响应式设计:适应不同屏幕

3. 代码质量

  • 类型提示:完整的类型注解

  • 错误处理:输入验证和异常捕获

  • 模块化设计:高内聚低耦合

  • 配置分离:常量集中管理

八、完整代码

复制代码
import streamlit as st
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
from datetime import datetime
from enum import Enum
from typing import Dict, List, Tuple, Optional
import sys
import math

# 设置页面配置
st.set_page_config(
    page_title="风水命理计算器",
    page_icon="🏮",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 设置中文字体支持
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False


class Direction(Enum):
    """方位枚举"""
    NORTH = 0
    NORTHEAST = 1
    EAST = 2
    SOUTHEAST = 3
    SOUTH = 4
    SOUTHWEST = 5
    WEST = 6
    NORTHWEST = 7
    CENTER = 8

    def get_chinese_name(self) -> str:
        """获取中文方位名称"""
        names = {
            Direction.NORTH: "北",
            Direction.NORTHEAST: "东北",
            Direction.EAST: "东",
            Direction.SOUTHEAST: "东南",
            Direction.SOUTH: "南",
            Direction.SOUTHWEST: "西南",
            Direction.WEST: "西",
            Direction.NORTHWEST: "西北",
            Direction.CENTER: "中"
        }
        return names[self]

    def get_angle(self) -> float:
        """获取方位的角度(用于雷达图)"""
        angles = {
            Direction.NORTH: 0,
            Direction.NORTHEAST: 45,
            Direction.EAST: 90,
            Direction.SOUTHEAST: 135,
            Direction.SOUTH: 180,
            Direction.SOUTHWEST: 225,
            Direction.WEST: 270,
            Direction.NORTHWEST: 315,
            Direction.CENTER: 0
        }
        return angles[self]


class Element(Enum):
    """五行枚举"""
    METAL = "金"
    WOOD = "木"
    WATER = "水"
    FIRE = "火"
    EARTH = "土"

    def get_color(self) -> str:
        """获取五行对应的颜色"""
        colors = {
            "金": "#FFD700",  # 金色
            "木": "#32CD32",  # 绿色
            "水": "#1E90FF",  # 蓝色
            "火": "#FF4500",  # 红色
            "土": "#DAA520"  # 土色
        }
        return colors[self.value]


class FengShuiCalculator:
    """风水命理计算器"""

    def __init__(self, birth_year: int, gender: bool, lunar_month: Optional[int] = None,
                 lunar_day: Optional[int] = None, hour: Optional[int] = None):
        """
        初始化风水计算器

        Args:
            birth_year: 出生年份(公历)
            gender: 性别,True为男性,False为女性
            lunar_month: 农历月份(可选)
            lunar_day: 农历日期(可选)
            hour: 出生时辰(0-23,可选)
        """
        self.birth_year = birth_year
        self.gender = gender
        self.lunar_month = lunar_month
        self.lunar_day = lunar_day
        self.hour = hour
        self.current_year = datetime.now().year

        # 验证输入
        self._validate_inputs()

    def _validate_inputs(self) -> None:
        """验证输入参数"""
        if not (1900 <= self.birth_year <= 2099):
            raise ValueError("出生年份需在1900-2099年之间")

        if self.lunar_month and not (1 <= self.lunar_month <= 12):
            raise ValueError("农历月份需在1-12之间")

        if self.lunar_day and not (1 <= self.lunar_day <= 30):
            raise ValueError("农历日期需在1-30之间")

        if self.hour is not None and not (0 <= self.hour <= 23):
            raise ValueError("时辰需在0-23之间")

    def get_gua_number(self) -> int:
        """
        计算命卦数(洛书九宫)

        Returns:
            int: 命卦数(1-9,不包括5)
        """
        # 取年份后两位
        last_two_digits = self.birth_year % 100

        if 1900 <= self.birth_year <= 1999:
            if self.gender:  # 1900-1999年男性
                gua_num = (100 - last_two_digits) % 9
            else:  # 1900-1999年女性
                gua_num = (last_two_digits - 4) % 9
        else:  # 2000-2099年
            if self.gender:  # 2000-2099年男性
                gua_num = (99 - last_two_digits) % 9
            else:  # 2000-2099年女性
                gua_num = (last_two_digits + 6) % 9

        # 处理结果为0的情况(应为9)
        if gua_num == 0:
            gua_num = 9

        # 处理结果为5的情况(五黄居中)
        # 根据传统风水理论:男命5视为2(坤),女命5视为8(艮)
        if gua_num == 5:
            if self.gender:  # 男性
                gua_num = 2
            else:  # 女性
                gua_num = 8

        return gua_num

    def get_element(self, gua_number: Optional[int] = None) -> Tuple[Element, str]:
        """
        获取五行属性和卦名

        Args:
            gua_number: 命卦数,如为None则使用当前对象的命卦

        Returns:
            Tuple[Element, str]: (五行属性, 卦名)
        """
        if gua_number is None:
            gua_number = self.get_gua_number()

        gua_info = {
            1: (Element.WATER, "坎卦"),
            2: (Element.EARTH, "坤卦"),
            3: (Element.WOOD, "震卦"),
            4: (Element.WOOD, "巽卦"),
            6: (Element.METAL, "乾卦"),
            7: (Element.METAL, "兑卦"),
            8: (Element.EARTH, "艮卦"),
            9: (Element.FIRE, "离卦")
        }

        if gua_number not in gua_info:
            raise ValueError(f"无效的命卦数: {gua_number}")

        return gua_info[gua_number]

    def get_life_group(self, gua_number: Optional[int] = None) -> str:
        """
        获取东西四命分组

        Returns:
            str: "东四命" 或 "西四命"
        """
        if gua_number is None:
            gua_number = self.get_gua_number()

        east_group = {1, 3, 4, 9}  # 东四命:坎、震、巽、离
        west_group = {2, 6, 7, 8}  # 西四命:坤、乾、兑、艮

        if gua_number in east_group:
            return "东四命"
        elif gua_number in west_group:
            return "西四命"
        else:
            raise ValueError(f"无效的命卦数: {gua_number}")

    def get_direction_mapping(self, gua_number: Optional[int] = None) -> Dict[str, Direction]:
        """
        根据命卦获取方位映射(八宅风水)

        Args:
            gua_number: 命卦数,如为None则使用当前对象的命卦

        Returns:
            Dict[str, Direction]: 各吉凶方位映射
        """
        if gua_number is None:
            gua_number = self.get_gua_number()

        # 八宅风水方位配置
        direction_maps = {
            # 坎命(1)
            1: {
                "生气": Direction.EAST,  # 大吉
                "延年": Direction.SOUTHEAST,  # 中吉
                "天医": Direction.SOUTH,  # 中吉
                "伏位": Direction.NORTH,  # 小吉
                "祸害": Direction.SOUTHWEST,  # 小凶
                "六煞": Direction.WEST,  # 次凶
                "五鬼": Direction.NORTHEAST,  # 大凶
                "绝命": Direction.NORTHWEST  # 大凶
            },
            # 坤命(2)
            2: {
                "生气": Direction.WEST,
                "延年": Direction.NORTHEAST,
                "天医": Direction.NORTHWEST,
                "伏位": Direction.SOUTH,
                "祸害": Direction.NORTH,
                "六煞": Direction.EAST,
                "五鬼": Direction.SOUTHEAST,
                "绝命": Direction.SOUTHWEST
            },
            # 震命(3)
            3: {
                "生气": Direction.SOUTHEAST,
                "延年": Direction.EAST,
                "天医": Direction.SOUTH,
                "伏位": Direction.NORTH,
                "祸害": Direction.SOUTHWEST,
                "六煞": Direction.WEST,
                "五鬼": Direction.NORTHWEST,
                "绝命": Direction.NORTHEAST
            },
            # 巽命(4)
            4: {
                "生气": Direction.NORTHWEST,
                "延年": Direction.WEST,
                "天医": Direction.NORTHEAST,
                "伏位": Direction.SOUTH,
                "祸害": Direction.SOUTHWEST,
                "六煞": Direction.EAST,
                "五鬼": Direction.SOUTHEAST,
                "绝命": Direction.NORTH
            },
            # 乾命(6)
            6: {
                "生气": Direction.NORTHWEST,
                "延年": Direction.WEST,
                "天医": Direction.NORTHEAST,
                "伏位": Direction.SOUTH,
                "祸害": Direction.SOUTHWEST,
                "六煞": Direction.EAST,
                "五鬼": Direction.SOUTHEAST,
                "绝命": Direction.NORTH
            },
            # 兑命(7)
            7: {
                "生气": Direction.SOUTHWEST,
                "延年": Direction.NORTH,
                "天医": Direction.NORTHEAST,
                "伏位": Direction.EAST,
                "祸害": Direction.SOUTHEAST,
                "六煞": Direction.SOUTH,
                "五鬼": Direction.WEST,
                "绝命": Direction.NORTHWEST
            },
            # 艮命(8)
            8: {
                "生气": Direction.SOUTHWEST,
                "延年": Direction.NORTH,
                "天医": Direction.NORTHEAST,
                "伏位": Direction.EAST,
                "祸害": Direction.SOUTHEAST,
                "六煞": Direction.SOUTH,
                "五鬼": Direction.WEST,
                "绝命": Direction.NORTHWEST
            },
            # 离命(9)
            9: {
                "生气": Direction.SOUTH,
                "延年": Direction.EAST,
                "天医": Direction.SOUTHEAST,
                "伏位": Direction.NORTH,
                "祸害": Direction.WEST,
                "六煞": Direction.NORTHEAST,
                "五鬼": Direction.NORTHWEST,
                "绝命": Direction.SOUTHWEST
            }
        }

        if gua_number not in direction_maps:
            raise ValueError(f"不支持的命卦数: {gua_number}")

        return direction_maps[gua_number]

    def calculate_energy_gradient(self, direction: Direction, year: Optional[int] = None) -> float:
        """
        计算特定方位的能量梯度值

        Args:
            direction: 方位
            year: 年份,如为None则使用当前年份

        Returns:
            float: 能量梯度值
        """
        if year is None:
            year = self.current_year

        # 基础能量值(三元九运理论)
        base_energy = self._get_base_energy(year)

        # 方位修正系数
        direction_coefficient = self._get_direction_coefficient(direction)

        # 时间修正系数
        time_coefficient = self._get_time_coefficient(year)

        # 个人命卦修正
        personal_coefficient = self._get_personal_coefficient(direction)

        # 综合能量梯度
        energy_gradient = base_energy * direction_coefficient * time_coefficient * personal_coefficient

        return energy_gradient

    def _get_base_energy(self, year: int) -> float:
        """
        根据年份获取基础能量值(三元九运)

        Args:
            year: 年份

        Returns:
            float: 基础能量值
        """
        # 三元九运周期计算(每20年一运)
        cycle_start = 1864  # 一运开始年份
        period = ((year - cycle_start) // 20) % 9 + 1

        # 九运能量配置
        period_energy = {
            1: 0.8,  # 一运 (1864-1883)
            2: 0.6,  # 二运 (1884-1903)
            3: 0.4,  # 三运 (1904-1923)
            4: 0.2,  # 四运 (1924-1943)
            5: 0.3,  # 五运 (1944-1963)
            6: 0.4,  # 六运 (1964-1983)
            7: 0.6,  # 七运 (1984-2003)
            8: 0.8,  # 八运 (2004-2023)
            9: 1.0  # 九运 (2024-2043)
        }

        return period_energy.get(period, 0.5)

    def _get_direction_coefficient(self, direction: Direction) -> float:
        """
        获取方位修正系数

        Args:
            direction: 方位

        Returns:
            float: 方位修正系数
        """
        coefficients = {
            Direction.EAST: 1.0,  # 东:木,春季旺
            Direction.SOUTHEAST: 0.9,  # 东南:木,春夏之交
            Direction.SOUTH: 1.1,  # 南:火,夏季旺
            Direction.SOUTHWEST: 0.8,  # 西南:土,夏秋之交
            Direction.WEST: 0.7,  # 西:金,秋季旺
            Direction.NORTHWEST: 0.6,  # 西北:金,秋冬之交
            Direction.NORTH: 0.8,  # 北:水,冬季旺
            Direction.NORTHEAST: 0.7,  # 东北:土,冬春之交
            Direction.CENTER: 1.0  # 中:土
        }
        return coefficients.get(direction, 1.0)

    def _get_time_coefficient(self, year: int) -> float:
        """
        获取时间修正系数(流年飞星)

        Args:
            year: 年份

        Returns:
            float: 时间修正系数
        """
        # 流年飞星影响
        flying_stars = {
            2024: 1.0,  # 八白左辅星
            2025: 1.1,  # 九紫右弼星
            2026: 1.0,  # 一白贪狼星
            2027: 0.9,  # 二黑巨门星
            2028: 0.8,  # 三碧禄存星
            2029: 1.0,  # 四绿文曲星
            2030: 1.1,  # 五黄廉贞星
            2031: 1.0,  # 六白武曲星
            2032: 0.9,  # 七赤破军星
            2033: 0.8  # 八白左辅星
        }

        if year in flying_stars:
            return flying_stars[year]

        # 近年趋势
        if year > 2025:
            return 1.0 + (year - 2025) * 0.01
        else:
            return 1.0 - (2025 - year) * 0.01

    def _get_personal_coefficient(self, direction: Direction) -> float:
        """
        获取个人命卦修正系数

        Args:
            direction: 方位

        Returns:
            float: 个人修正系数
        """
        gua_number = self.get_gua_number()
        direction_map = self.get_direction_mapping(gua_number)

        # 根据方位吉凶确定系数
        for luck_type, dir_obj in direction_map.items():
            if direction == dir_obj:
                if luck_type == "生气":
                    return 1.5
                elif luck_type == "延年":
                    return 1.3
                elif luck_type == "天医":
                    return 1.2
                elif luck_type == "伏位":
                    return 1.0
                elif luck_type == "祸害":
                    return 0.8
                elif luck_type == "六煞":
                    return 0.7
                elif luck_type == "五鬼":
                    return 0.6
                elif luck_type == "绝命":
                    return 0.5

        return 1.0

    def get_life_stage(self, year: Optional[int] = None) -> str:
        """
        获取大运阶段

        Args:
            year: 年份,如为None则使用当前年份

        Returns:
            str: 大运阶段描述
        """
        if year is None:
            year = self.current_year

        age = year - self.birth_year

        if age <= 10:
            return "童年运 (1-10岁)"
        elif age <= 20:
            return "少年运 (11-20岁)"
        elif age <= 40:
            return "中年运 (21-40岁)"
        elif age <= 60:
            return "壮年运 (41-60岁)"
        else:
            return "晚年运 (61岁以上)"

    def analyze_directions(self) -> Dict[str, Dict]:
        """
        分析所有方位的吉凶和能量

        Returns:
            Dict[str, Dict]: 各方位分析结果
        """
        gua_number = self.get_gua_number()
        direction_map = self.get_direction_mapping(gua_number)

        analysis_result = {}
        for direction in Direction:
            # 计算能量值
            energy = self.calculate_energy_gradient(direction)

            # 确定吉凶类型
            luck_type = None
            for type_name, dir_obj in direction_map.items():
                if direction == dir_obj:
                    luck_type = type_name
                    break

            # 判断吉凶等级
            if luck_type in ["生气", "延年", "天医", "伏位"]:
                if energy > 1.2:
                    level = "大吉"
                elif energy > 0.9:
                    level = "中吉"
                else:
                    level = "小吉"
            elif luck_type in ["祸害", "六煞"]:
                if energy > 0.8:
                    level = "小凶"
                else:
                    level = "中凶"
            elif luck_type in ["五鬼", "绝命"]:
                if energy > 0.6:
                    level = "大凶"
                else:
                    level = "次凶"
            else:
                if energy > 0.8:
                    level = "平"
                elif energy > 0.6:
                    level = "小凶"
                else:
                    level = "凶"

            # 五行属性
            direction_elements = {
                Direction.NORTH: "水",
                Direction.NORTHEAST: "土",
                Direction.EAST: "木",
                Direction.SOUTHEAST: "木",
                Direction.SOUTH: "火",
                Direction.SOUTHWEST: "土",
                Direction.WEST: "金",
                Direction.NORTHWEST: "金",
                Direction.CENTER: "土"
            }

            # 吉凶颜色
            level_colors = {
                "大吉": "#4CAF50",  # 绿色
                "中吉": "#8BC34A",  # 浅绿色
                "小吉": "#CDDC39",  # 黄绿色
                "平": "#FFC107",  # 黄色
                "小凶": "#FF9800",  # 橙色
                "中凶": "#FF5722",  # 深橙色
                "大凶": "#F44336",  # 红色
                "次凶": "#E91E63",  # 粉红色
                "凶": "#9C27B0"  # 紫色
            }

            analysis_result[direction.name] = {
                "chinese_name": direction.get_chinese_name(),
                "energy": round(energy, 3),
                "luck_type": luck_type or "无",
                "level": level,
                "element": direction_elements.get(direction, "未知"),
                "color": level_colors.get(level, "#9E9E9E"),
                "favorable": luck_type in ["生气", "延年", "天医", "伏位"] if luck_type else False
            }

        return analysis_result

    def get_recommendations(self) -> Dict[str, List[str]]:
        """
        获取风水建议

        Returns:
            Dict[str, List[str]]: 各类建议
        """
        gua_number = self.get_gua_number()
        element, gua_name = self.get_element(gua_number)
        life_group = self.get_life_group(gua_number)

        recommendations = {
            "住宅朝向": [],
            "卧室方位": [],
            "办公方位": [],
            "吉祥颜色": [],
            "注意事项": []
        }

        # 获取吉方
        direction_map = self.get_direction_mapping(gua_number)
        lucky_directions = {k: v for k, v in direction_map.items()
                            if k in ["生气", "延年", "天医", "伏位"]}

        # 住宅朝向建议
        if "生气" in lucky_directions:
            rec = f"最佳朝向:{lucky_directions['生气'].get_chinese_name()}方(生气方,大吉)"
            recommendations["住宅朝向"].append(rec)

        # 卧室方位建议
        for luck_type, direction in lucky_directions.items():
            if luck_type in ["延年", "天医"]:
                rec = f"卧室宜在{direction.get_chinese_name()}方({luck_type}方)"
                recommendations["卧室方位"].append(rec)

        # 办公方位建议
        if "伏位" in lucky_directions:
            rec = f"办公桌宜面向{lucky_directions['伏位'].get_chinese_name()}方(伏位方,稳定)"
            recommendations["办公方位"].append(rec)

        # 吉祥颜色
        color_map = {
            Element.METAL: ["白色", "金色", "银色"],
            Element.WOOD: ["绿色", "青色"],
            Element.WATER: ["黑色", "蓝色", "灰色"],
            Element.FIRE: ["红色", "紫色", "粉色"],
            Element.EARTH: ["黄色", "棕色", "米色"]
        }
        recommendations["吉祥颜色"] = color_map.get(element, [])

        # 注意事项
        recommendations["注意事项"].append(f"您是{life_group},适合与{life_group}的人合作")
        recommendations["注意事项"].append(f"您的五行属{element.value},宜补{self._get_element_to_support(element)}")

        return recommendations

    def _get_element_to_support(self, element: Element) -> str:
        """获取需要补充的五行"""
        support_map = {
            "金": "土",  # 土生金
            "木": "水",  # 水生木
            "水": "金",  # 金生水
            "火": "木",  # 木生火
            "土": "火"  # 火生土
        }
        return support_map.get(element.value, element.value)

    def _get_period_name(self, year: int) -> str:
        """获取三元九运名称"""
        cycle_start = 1864
        period = ((year - cycle_start) // 20) % 9 + 1

        period_names = {
            1: "一运 (1864-1883)",
            2: "二运 (1884-1903)",
            3: "三运 (1904-1923)",
            4: "四运 (1924-1943)",
            5: "五运 (1944-1963)",
            6: "六运 (1964-1983)",
            7: "七运 (1984-2003)",
            8: "八运 (2004-2023)",
            9: "九运 (2024-2043)"
        }

        return period_names.get(period, "未知")


# 创建可视化图表
def create_radar_chart(analysis_result):
    """创建雷达图展示方位能量"""
    directions = []
    energies = []
    colors = []

    for direction in Direction:
        if direction != Direction.CENTER:  # 中宫不显示在雷达图中
            info = analysis_result[direction.name]
            directions.append(info['chinese_name'])
            energies.append(info['energy'])
            colors.append(info['color'])

    # 确保数据是闭合的(雷达图需要首尾相连)
    directions.append(directions[0])
    energies.append(energies[0])

    # 转换为极坐标
    angles = np.linspace(0, 2 * np.pi, len(directions), endpoint=True)

    fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(projection='polar'))

    # 绘制雷达图
    ax.plot(angles, energies, 'o-', linewidth=2)
    ax.fill(angles, energies, alpha=0.25)

    # 设置标签
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(directions[:-1], fontsize=12)

    # 设置y轴范围
    max_energy = max(energies) if energies else 1.5
    ax.set_ylim(0, max_energy * 1.2)

    # 设置网格
    ax.grid(True)

    # 设置标题
    ax.set_title("方位能量雷达图", fontsize=16, fontweight='bold', va='bottom')

    return fig


def create_bar_chart(analysis_result):
    """创建柱状图展示方位能量"""
    directions = []
    energies = []
    colors = []
    luck_types = []

    for direction in Direction:
        info = analysis_result[direction.name]
        directions.append(info['chinese_name'])
        energies.append(info['energy'])
        colors.append(info['color'])
        luck_types.append(info['luck_type'])

    # 创建DataFrame
    df = pd.DataFrame({
        '方位': directions,
        '能量值': energies,
        '吉凶类型': luck_types,
        '颜色': colors
    })

    # 排序
    df = df.sort_values('能量值', ascending=False)

    # 创建柱状图
    fig = go.Figure(data=[
        go.Bar(
            x=df['方位'],
            y=df['能量值'],
            marker_color=df['颜色'],
            text=df['吉凶类型'],
            textposition='auto',
            hovertext=df.apply(
                lambda row: f"方位: {row['方位']}<br>能量: {row['能量值']:.3f}<br>吉凶: {row['吉凶类型']}", axis=1),
            hoverinfo="text"
        )
    ])

    fig.update_layout(
        title="方位能量柱状图",
        xaxis_title="方位",
        yaxis_title="能量值",
        template="plotly_white",
        height=500
    )

    return fig


def create_compass_diagram(analysis_result):
    """创建罗盘图"""
    fig, ax = plt.subplots(figsize=(8, 8))

    # 设置圆形
    circle = plt.Circle((0, 0), 1, color='lightgray', alpha=0.3)
    ax.add_patch(circle)

    # 设置坐标轴
    ax.set_xlim(-1.2, 1.2)
    ax.set_ylim(-1.2, 1.2)
    ax.set_aspect('equal')
    ax.axis('off')

    # 绘制方位和能量
    for direction in Direction:
        if direction != Direction.CENTER:
            info = analysis_result[direction.name]

            # 计算位置
            angle_rad = np.deg2rad(direction.get_angle())
            x = np.cos(angle_rad) * 0.8
            y = np.sin(angle_rad) * 0.8

            # 绘制点
            ax.plot(x, y, 'o', markersize=20, color=info['color'], alpha=0.7)

            # 添加文本
            ax.text(x, y, f"{info['chinese_name']}\n{info['energy']:.2f}",
                    ha='center', va='center', fontsize=10, fontweight='bold')

    # 添加中心点
    center_info = analysis_result[Direction.CENTER.name]
    ax.plot(0, 0, 'o', markersize=30, color=center_info['color'], alpha=0.7)
    ax.text(0, 0, f"中\n{center_info['energy']:.2f}",
            ha='center', va='center', fontsize=12, fontweight='bold')

    # 添加标题
    ax.set_title("风水罗盘图", fontsize=16, fontweight='bold', pad=20)

    return fig


def create_pie_chart(analysis_result):
    """创建饼图展示吉凶分布"""
    level_counts = {
        "大吉": 0,
        "中吉": 0,
        "小吉": 0,
        "平": 0,
        "小凶": 0,
        "中凶": 0,
        "大凶": 0,
        "次凶": 0,
        "凶": 0
    }

    level_colors = {
        "大吉": "#4CAF50",
        "中吉": "#8BC34A",
        "小吉": "#CDDC39",
        "平": "#FFC107",
        "小凶": "#FF9800",
        "中凶": "#FF5722",
        "大凶": "#F44336",
        "次凶": "#E91E63",
        "凶": "#9C27B0"
    }

    for direction in Direction:
        info = analysis_result[direction.name]
        if info['level'] in level_counts:
            level_counts[info['level']] += 1

    # 过滤掉数量为0的类别
    labels = []
    sizes = []
    colors = []

    for level, count in level_counts.items():
        if count > 0:
            labels.append(level)
            sizes.append(count)
            colors.append(level_colors.get(level, "#9E9E9E"))

    # 创建饼图
    fig = go.Figure(data=[go.Pie(
        labels=labels,
        values=sizes,
        marker_colors=colors,
        hole=0.3,
        textinfo='label+percent',
        hoverinfo='label+value+percent'
    )])

    fig.update_layout(
        title="吉凶分布饼图",
        height=500
    )

    return fig


# Streamlit应用主函数
def main():
    # 应用标题
    st.title("🏮 风水命理计算器")
    st.markdown("基于传统风水理论,提供个人命理分析和方位建议")

    # 侧边栏
    with st.sidebar:
        st.header("🔮 个人信息输入")

        # 输入出生年份
        birth_year = st.number_input(
            "出生年份",
            min_value=1900,
            max_value=2099,
            value=1990,
            step=1,
            help="请输入1900-2099年之间的出生年份"
        )

        # 输入性别
        gender_input = st.radio("性别", ["男", "女"], index=0)
        gender = gender_input == "男"

        # 高级选项
        with st.expander("高级选项(可选)"):
            lunar_month = st.number_input("农历月份", min_value=1, max_value=12, value=None, step=1)
            lunar_day = st.number_input("农历日期", min_value=1, max_value=30, value=None, step=1)
            hour = st.number_input("出生时辰(0-23)", min_value=0, max_value=23, value=None, step=1)

        # 计算按钮
        calculate_btn = st.button("开始计算", type="primary", use_container_width=True)

        # 示例
        st.markdown("---")
        st.markdown("### 📊 示例数据")
        if st.button("1985年男性示例"):
            st.session_state.birth_year = 1985
            st.session_state.gender = True
            st.rerun()
        if st.button("1990年女性示例"):
            st.session_state.birth_year = 1990
            st.session_state.gender = False
            st.rerun()

        # 关于
        st.markdown("---")
        st.markdown("### ℹ️ 关于")
        st.markdown("""
        本计算器基于传统风水理论,包括:
        - 八宅风水
        - 三元九运
        - 五行生克
        - 流年飞星

        结果仅供参考,不作为实际决策依据。
        """)

    # 主内容区域
    if 'birth_year' in st.session_state:
        birth_year = st.session_state.birth_year
    if 'gender' in st.session_state:
        gender = st.session_state.gender

    try:
        # 创建计算器实例
        calculator = FengShuiCalculator(birth_year, gender)

        # 基本信息卡片
        col1, col2, col3, col4 = st.columns(4)

        with col1:
            gua_number = calculator.get_gua_number()
            st.metric("命卦数", gua_number)

        with col2:
            element, gua_name = calculator.get_element(gua_number)
            st.metric("卦名", gua_name)

        with col3:
            element_display = element.value
            st.metric("五行属性", element_display)

        with col4:
            life_group = calculator.get_life_group(gua_number)
            st.metric("命理分组", life_group)

        # 分隔线
        st.markdown("---")

        # 创建选项卡
        tab1, tab2, tab3, tab4 = st.tabs(["📈 能量可视化", "🧭 方位分析", "💡 风水建议", "📋 详细信息"])

        with tab1:
            st.subheader("方位能量可视化")

            # 获取分析结果
            analysis_result = calculator.analyze_directions()

            # 创建两列布局
            col1, col2 = st.columns(2)

            with col1:
                st.subheader("罗盘图")
                compass_fig = create_compass_diagram(analysis_result)
                st.pyplot(compass_fig)

            with col2:
                st.subheader("雷达图")
                radar_fig = create_radar_chart(analysis_result)
                st.pyplot(radar_fig)

            # 柱状图
            st.subheader("能量柱状图")
            bar_fig = create_bar_chart(analysis_result)
            st.plotly_chart(bar_fig, use_container_width=True)

            # 饼图
            col1, col2 = st.columns([2, 1])

            with col1:
                st.subheader("吉凶分布")
                pie_fig = create_pie_chart(analysis_result)
                st.plotly_chart(pie_fig, use_container_width=True)

            with col2:
                st.subheader("图例说明")
                st.markdown("""
                - 🟢 **大吉**:能量 > 1.2
                - 🟡 **中吉**:能量 0.9-1.2
                - 🟡 **小吉**:能量 < 0.9
                - 🟡 **平**:能量 0.8-1.0
                - 🟠 **小凶**:能量 0.6-0.8
                - 🔴 **中凶**:能量 < 0.6
                - 🔴 **大凶**:能量 < 0.6
                """)

        with tab2:
            st.subheader("方位详细分析")

            # 创建DataFrame显示数据
            data = []
            for direction in Direction:
                info = analysis_result[direction.name]
                data.append({
                    "方位": info['chinese_name'],
                    "能量值": info['energy'],
                    "吉凶类型": info['luck_type'],
                    "吉凶等级": info['level'],
                    "五行": info['element'],
                    "是否吉利": "是" if info['favorable'] else "否"
                })

            df = pd.DataFrame(data)

            # 添加颜色映射
            def color_row(row):
                colors = {
                    "大吉": "background-color: #4CAF50; color: white",
                    "中吉": "background-color: #8BC34A; color: white",
                    "小吉": "background-color: #CDDC39; color: black",
                    "平": "background-color: #FFC107; color: black",
                    "小凶": "background-color: #FF9800; color: white",
                    "中凶": "background-color: #FF5722; color: white",
                    "大凶": "background-color: #F44336; color: white",
                    "次凶": "background-color: #E91E63; color: white",
                    "凶": "background-color: #9C27B0; color: white"
                }
                return [colors.get(row['吉凶等级'], '')] * len(row)

            # 显示表格
            st.dataframe(df.style.apply(color_row, axis=1), use_container_width=True)

            # 吉凶方位说明
            st.subheader("吉凶方位说明")

            col1, col2 = st.columns(2)

            with col1:
                st.markdown("### 吉方")
                st.markdown("""
                - **生气**:大吉,主事业财运
                - **延年**:中吉,主健康长寿
                - **天医**:中吉,主健康贵人
                - **伏位**:小吉,主平安稳定
                """)

            with col2:
                st.markdown("### 凶方")
                st.markdown("""
                - **祸害**:小凶,主口舌是非
                - **六煞**:次凶,主烦恼不顺
                - **五鬼**:大凶,主灾病意外
                - **绝命**:大凶,主破财伤身
                """)

        with tab3:
            st.subheader("风水建议")

            recommendations = calculator.get_recommendations()

            col1, col2 = st.columns(2)

            with col1:
                st.markdown("### 🏠 住宅与卧室")
                for rec in recommendations["住宅朝向"]:
                    st.info(rec)
                for rec in recommendations["卧室方位"]:
                    st.success(rec)

            with col2:
                st.markdown("### 💼 办公与颜色")
                for rec in recommendations["办公方位"]:
                    st.warning(rec)

                st.markdown("#### 吉祥颜色")
                colors_html = ""
                for color in recommendations["吉祥颜色"]:
                    colors_html += f'<span style="display:inline-block; padding:5px 15px; margin:5px; background-color:lightgray; border-radius:5px;">{color}</span>'
                st.markdown(colors_html, unsafe_allow_html=True)

            st.markdown("### 📋 注意事项")
            for rec in recommendations["注意事项"]:
                st.markdown(f"- {rec}")

        with tab4:
            st.subheader("详细信息")

            col1, col2 = st.columns(2)

            with col1:
                st.markdown("### 个人命理信息")
                st.markdown(f"**出生年份**:{birth_year}年")
                st.markdown(f"**性别**:{'男' if gender else '女'}")
                st.markdown(f"**命卦**:{gua_number} ({gua_name})")
                st.markdown(f"**五行属性**:{element.value}")
                st.markdown(f"**命理分组**:{life_group}")

                # 大运阶段
                life_stage = calculator.get_life_stage()
                st.markdown(f"**当前大运**:{life_stage}")

                # 五行生克关系
                st.markdown("### 五行生克关系")
                element_obj, _ = calculator.get_element()
                st.markdown(f"**您的五行**:{element_obj.value}")
                st.markdown(f"**宜补充的五行**:{calculator._get_element_to_support(element_obj)}")

            with col2:
                st.markdown("### 当前年份信息")
                current_year = datetime.now().year
                st.markdown(f"**当前年份**:{current_year}年")
                st.markdown(f"**三元九运**:{calculator._get_period_name(current_year)}")
                st.markdown(f"**流年飞星系数**:{calculator._get_time_coefficient(current_year):.2f}")

                # 能量计算说明
                st.markdown("### 能量计算说明")
                st.markdown("""
                能量值 = 基础能量 × 方位系数 × 时间系数 × 个人系数

                - **基础能量**:基于三元九运理论
                - **方位系数**:各方位固有属性
                - **时间系数**:流年飞星影响
                - **个人系数**:个人命卦修正
                """)

    except ValueError as e:
        st.error(f"输入错误:{e}")
    except Exception as e:
        st.error(f"计算错误:{e}")


if __name__ == "__main__":
    main()
相关推荐
@Mr Wang5 小时前
云服务器之使用jupyter运行ipynb文件
服务器·python·jupyter·notebook
Python私教5 小时前
Jupyter是什么?如何安装使用?
ide·python·jupyter
Salt_07285 小时前
DAY 42 图像数据与显存
人工智能·python·机器学习
q_30238195565 小时前
双能突围!能源高效型模型压缩+碳足迹追踪,解锁数据中心与农业AI新价值
人工智能·python·深度学习·能源·课程设计·ai编程
赫凯5 小时前
【强化学习】第三章 马尔可夫决策过程
python·算法
Daily Mirror5 小时前
Day42 Dataset和Dataloader
python
智航GIS5 小时前
1.2 python及pycharm的安装
开发语言·python·pycharm
froginwe115 小时前
Lua 字符串处理指南
开发语言
kszlgy5 小时前
Day38 模型可视化与推理
python