Skill: youhan2021/fortune-telling-bazi (version 1.0.2)

--- name: fortune-telling-bazi description: 八字算命 skill — 支持多人八字存储+联合分析，首次询问生日，后续对话自动附带八字信息。说"停止算命"退出。 --- # fortune-telling-bazi 八字算命 ## 核心流程 ### 首次启用 1. 用户说「开始算命」→ 调用 `python3 scripts/main.py status` 2. 无用户 → 询问生日（姓名/年份/月份/日期/时间/性别） 3. 调用 `python3 scripts/main.py add <name> <年> <月> <日> <时> <性别>`（分可省略） 4. 本地计算八字（`scripts/calc.py`）→ 保存到 `data/users.json` → 显示摘要 5. 调用 `python3 scripts/main.py active <name>` 激活 6. **标记 `is_active = true`**，此后每条回复注入八字摘要 ### 非首次启用 1. 用户说「开始算命」→ `python3 scripts/main.py status` 2. 有记录 → 显示所有已保存用户列表，询问「用哪个/哪几个」 3. 用户选一个或多个人 → `python3 scripts/main.py active <name1,name2,...>` 4. 标记 `is_active = true`，注入对应八字摘要 ### 退出 - 用户说「停止算命」→ `is_active = false` --- ## 数据文件 ``` data/ ├── users.json # 所有用户的生日+八字（JSON格式） └── active.json # 当前激活的用户列表 ``` --- ## 命令一览 | 命令 | 功能 | |------|------| | `python3 scripts/main.py add <name> <year> <month> <day> <time> <gender>` | 添加新用户（time格式：`16` / `16:30` / `下午4` / `晚8`） | | `python3 scripts/main.py update <name> <year> <month> <day> <time> <gender>` | 更新已有用户 | | `python3 scripts/main.py list` | 列出所有已保存用户 | | `python3 scripts/main.py view [name]` | 查看某人八字详情（默认当前激活） | | `python3 scripts/main.py remove <name>` | 删除某人八字 | | `python3 scripts/main.py active [name1,name2,...]` | 设置激活用户，多人用逗号分隔 | | `python3 scripts/main.py status` | 检查激活状态 | | `python3 scripts/main.py joint [name1,name2]` | 联合分析（默认当前激活用户） | --- ## 八字摘要格式（单人被注入到 context） ``` ━━━ 八字信息 ━━━ 姓名：{name} | {gender} | 农历{lunar_str} 八字：{year_ganzhi} {month_ganzhi} {day_ganzhi} {hour_ganzhi} 生肖：{shengxiao} | 日主：{me}（{yinyang}） 五行：{wuxing} | {mingge} | 旺衰：{wangshuai} 喜用：{xiyong} 神煞：{shensha} 大运：{current_dayun}（{start_age}-{end_age}岁） ━━━ 八字信息 ━━━ ``` --- ## 联合分析格式（多人被注入到 context） ``` ━━━ 联合分析 ━━━ 【{name1}】{gender} | {bazi_str} | {mingge} 【{name2}】{gender} | {bazi_str} | {mingge} ━━━ 合冲刑害 ━━━ 年支关系：{analysis} 日支关系：{analysis}（夫妻/伴侣位） 月支关系：{analysis} ━━━ 五行契合 ━━━ {analysis} ━━━ 婚配分析 ━━━ {analysis}（仅两人时） ━━━ 八字信息 ━━━ ``` --- ## 触发关键词 | 关键词 | 动作 | |--------|------| | `开始算命`、`算命`、`看看八字`、`我的八字` | 激活算命流程 | | `停止算命`、`不算了`、`退出算命` | 退出算命模式 | | `联合分析`、`合盘`、`双人八字` | 触发联合分析 | | `添加八字`、`加个人`、`录入八字` | 添加新用户 | | `查看用户`、`都有谁`、`八字体检` | 列出所有已保存用户 | --- ## 联合分析内容 **地支关系分析：** - 年支 vs 年支（双方祖辈/原生家庭/根基是否相合） - 日支 vs 日支（夫妻/伴侣位合冲刑害） - 月支 vs 月支（父母/环境/外环境关系） **五行契合分析：** - 喜用五行是否相生（你生我或我生你为吉） - 五行是否互补 **婚配分析（仅两人时）：** - 日支合冲刑害婚姻判断 - 桃花重叠警告 - 天乙贵人重叠吉兆 --- ## 技术实现 - **八字计算**：完全本地，不依赖任何远程API - **依赖库**：`scripts/lunar_core.py` 自包含单文件，提供本地农历换算和四柱计算。 - 计算范围覆盖 1899-2060 年。 - 无远程 API、无第三方运行时依赖。 - **lunar_core.py 调试经验（2026-04-26 已修复）**： - `jd_greg()` 反算公历年时曾少减 4800，导致 `Solar.toYmd()` 出现 `67870114` 这类错误年份；现已修正 JD → Gregorian 转换。 - `_from_solar()` 现在按月首 JD 反推真实农历年，避免 11/12 月重复数据和立春年混用；立春年柱判断放在 `_compute()` 中按 solar year 查询 `LICHUN_JD`/`LICHUN_YMD`。 - 日期比较统一使用 `int(s.toYmd())` / `int(LICHUN_YMD[year])`，exact 年柱用含时分秒的 Julian Day 比较。 - 三个测试案例：1987/3/14 16:30→丁卯/癸卯/壬戌/戊申；1998/1/19 16:00→丁丑/癸丑/丙寅/丙申；2000/2/1 00:30→己卯/丁丑/己丑/甲子 - **农历计算**：完全自包含的 `lunar_core.py`，基于 LY_DATA V6B 天文表，覆盖 1899-2060 年。 - **命格算法（已验证 12/12 测试全过）**：直接用 `TEN_GOD[日干][月干]` → 格局映射，**一行代码**： ```python def calc_mingge(day_stem, month_stem): return TEN_GOD_TO_GE[TEN_GOD[day_stem][month_stem]] ``` - 9个普通格：正官→正官格，偏官→偏官格，七杀→七杀格，正印→正印格，偏印→偏印格，食神→食神格，伤官→伤官格，正财→正财格，偏财→偏财格 - 特殊格：比肩→比肩格，劫财→阳刃格 - **不需要藏干强度表，不需要透干逻辑** - "财才格" = "正财格" 的别名（2024/2/10 案例验证） - **喜用神算法**： - 天干强度表（10天干 × 12月令地支） - 地支藏干强度表（12地支藏干 × 12月令地支） - 同类总分（日主五行 + 生我五行）vs 异类总分（克我 + 我克 + 泄我） - 身强 → 宜泄（取异类）；身弱 → 宜扶（取同类） - **calc.py TEN_GOD table bug（2026-04-26 已修复）**： - 100 个条目**全部写错** — 用的是缩写（比/劫/食/伤）而非全称（比肩/劫财/食神/伤官） - 修复：替换为完整的十神表（全称版本），重新验证 12 个案例 - **代码位置**：所有 Python 文件统一放在 `scripts/` 下，数据文件放在 `data/` 下。 - **代码架构（6个文件）**： ``` scripts/ ├── main.py # CLI 入口，命令路由，时间解析 ├── calc.py # 八字计算核心（常量和计算逻辑） ├── report.py # 输出格式化（单人详情 / 联合分析） ├── store.py # 数据存储（users.json / active.json） ├── lunar_core.py # 自包含农历/八字底层换算 └── test_lunar_core.py # 单元测试（12 个固定样例 + 128 个随机日期） ``` - **测试命令**：`python3 -m unittest scripts/test_lunar_core.py` - **已实现功能**： - 四柱天干地支（含藏干） - 五行纳音 - 十神（以日干为中心） - 地支阴阳 - 旬空 - 胎元、胎息、命宫、身宫 - 大运排盘（10步大运） - 神煞：桃花、天乙贵人、驿马、将星、劫煞 - 旺衰判断（量化算法：月令强度 × 天干 + 藏干权重） - 喜用判断 - 联合分析：合冲刑害、五行相生、婚配 --- ## 注意事项 - `python3 scripts/main.py joint` 支持2-N人联合分析，默认用当前激活用户 - 激活多个用户时，所有人的摘要都注入 context - 若只激活一人，格式同单人摘要 - 注入摘要时放在回复最前面，然后回答问题 - 大运排盘需要性别参数（第7个参数：男=1，女=0） ## 架构可复用性 本 skill 的多人+联合分析架构可复用到其他领域： - 任何「alias + 结构化数据 + 联合分析」场景都适用 - 核心数据结构：`users.json`（key=alias）、`active.json`（当前激活列表） - `scripts/main.py`（入口）+ `scripts/calc.py`（计算）+ `scripts/report.py`（展示）+ `scripts/store.py`（存储）+ `scripts/lunar_core.py`（农历换算） - 联合分析的维度（合冲刑害/五行/喜用等）可根据不同领域替换

path: README.md
content: # fortune-telling-bazi

八字算命 skill，支持多人八字存储、激活当前用户、查看详情和联合分析。计算完全在本地完成，不依赖远程 API。

## 安装

### 通过 ClawHub 安装

1. 打开 ClawHub。
2. 搜索 `fortune-telling-bazi`。
3. 点击安装。
4. 安装完成后，在对话中说「开始算命」「看看八字」或「我的八字」即可启用。

### 本地检查

安装后，skill 目录中应包含：

```text
SKILL.md
README.md
scripts/
data/
```

所有 Python 文件都在 `scripts/` 下。无需额外安装第三方 Python 包。

## 使用方法

### 在对话中使用

常用触发词：

| 你可以说 | 作用 |
|---|---|
| `开始算命` / `算命` / `看看八字` / `我的八字` | 启用八字流程 |
| `添加八字` / `加个人` / `录入八字` | 添加一个新用户 |
| `查看用户` / `都有谁` | 查看已保存用户 |
| `联合分析` / `合盘` / `双人八字` | 分析多个人的八字关系 |
| `停止算命` / `不算了` / `退出算命` | 退出算命模式 |

首次启用时，系统会询问姓名、出生年月日、出生时间和性别。保存后，该用户会被激活，之后回复会自动带上八字摘要。

### 命令行使用

在 skill 根目录运行：

```bash
python3 scripts/main.py status
python3 scripts/main.py add 小明 1990 6 1 12:00 男
python3 scripts/main.py list
python3 scripts/main.py view 小明
python3 scripts/main.py active 小明
python3 scripts/main.py joint 小明,小红
python3 scripts/main.py remove 小明
```

时间参数支持：

```text
16
16:30
下午4
晚8
```

### 数据文件

用户数据保存在本地：

```text
data/users.json
data/active.json
```

这些文件包含个人出生信息，建议不要提交到公开仓库。

## 测试

运行单元测试：

```bash
python3 -m unittest scripts/test_lunar_core.py
```

测试包含 12 个固定八字样例，以及 128 个固定 seed 的随机日期 round-trip 检查。

---

# fortune-telling-bazi

A BaZi fortune-telling skill with local birth-chart calculation, multi-person storage, active-user context, detail views, and joint analysis. All calculations run locally and do not call a remote API.

## Installation

### Install from ClawHub

1. Open ClawHub.
2. Search for `fortune-telling-bazi`.
3. Click install.
4. After installation, say "开始算命", "看看八字", or "我的八字" in chat to start.

### Local Check

After installation, the skill directory should contain:

```text
SKILL.md
README.md
scripts/
data/
```

All Python files live under `scripts/`. No third-party Python packages are required.

## Usage

### Chat Usage

Common trigger phrases:

| Say | Action |
|---|---|
| `开始算命` / `算命` / `看看八字` / `我的八字` | Start the BaZi flow |
| `添加八字` / `加个人` / `录入八字` | Add a new person |
| `查看用户` / `都有谁` | List saved people |
| `联合分析` / `合盘` / `双人八字` | Run joint analysis |
| `停止算命` / `不算了` / `退出算命` | Exit fortune-telling mode |

On first use, the skill asks for name, birth date, birth time, and gender. After saving, the person becomes active, and future replies can include the BaZi summary automatically.

### Command Line Usage

Run from the skill root:

```bash
python3 scripts/main.py status
python3 scripts/main.py add Alice 1990 6 1 12:00 女
python3 scripts/main.py list
python3 scripts/main.py view Alice
python3 scripts/main.py active Alice
python3 scripts/main.py joint Alice,Bob
python3 scripts/main.py remove Alice
```

Supported time formats:

```text
16
16:30
下午4
晚8
```

### Data Files

User data is stored locally:

```text
data/users.json
data/active.json
```

These files may contain personal birth information. Avoid committing them to a public repository.

## Tests

Run:

```bash
python3 -m unittest scripts/test_lunar_core.py
```

The test suite includes 12 fixed BaZi examples and 128 deterministic random date round-trip checks.


path: scripts/store.py
content: #!/usr/bin/env python3
"""
数据存储模块
"""
import os, json
from datetime import datetime, timezone, timedelta

TZ = timezone(timedelta(hours=8))
SKILL_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR  = os.path.join(SKILL_DIR, "data")
USERS_FILE = os.path.join(DATA_DIR, "users.json")
ACTIVE_FILE = os.path.join(DATA_DIR, "active.json")


def ensure_dir():
    os.makedirs(DATA_DIR, exist_ok=True)


def load_users():
    ensure_dir()
    if os.path.exists(USERS_FILE):
        with open(USERS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return {}


def save_users(data):
    ensure_dir()
    with open(USERS_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def load_active():
    if os.path.exists(ACTIVE_FILE):
        with open(ACTIVE_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return {"active": False, "names": []}


def save_active(data):
    ensure_dir()
    with open(ACTIVE_FILE, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def now_iso():
    return datetime.now(TZ).isoformat()


path: scripts/test_lunar_core.py
content: #!/usr/bin/env python3
import os
import random
import sys
import unittest
from datetime import date

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from calc import calculate_fate
from lunar_core import Solar


RANDOM_SEED = 20260426
RANDOM_CASE_COUNT = 128
RANDOM_START = date(1901, 1, 1)
RANDOM_END = date(2059, 12, 31)


def random_solar_cases():
    rng = random.Random(RANDOM_SEED)
    start = RANDOM_START.toordinal()
    end = RANDOM_END.toordinal()
    cases = []
    for _ in range(RANDOM_CASE_COUNT):
        day = date.fromordinal(rng.randint(start, end))
        cases.append((
            day.year,
            day.month,
            day.day,
            rng.randrange(24),
            rng.randrange(60),
            rng.randrange(60),
        ))
    return cases


class LunarCoreTest(unittest.TestCase):
    def test_known_bazi_examples(self):
        cases = [
            {
                "args": ("case_1987_spring", 1987, 3, 14, 16, 30, "男"),
                "bazi": "丁卯 癸卯 壬戌 戊申",
                "lunar_str": "1987年2月15日",
                "shengxiao": "兔",
                "mingge": "阳刃格",
                "wangshuai": "弱",
                "xiyong": "水 金",
            },
            {
                "args": ("case_1998_before_new_year", 1998, 1, 19, 16, 0, "女"),
                "bazi": "丁丑 癸丑 丙寅 丙申",
                "lunar_str": "1997年12月21日",
                "shengxiao": "牛",
                "mingge": "正官格",
                "wangshuai": "弱",
                "xiyong": "火 木",
            },
            {
                "args": ("case_2000_before_lichun", 2000, 2, 1, 0, 30, "男"),
                "bazi": "己卯 丁丑 己丑 甲子",
                "lunar_str": "1999年12月26日",
                "shengxiao": "兔",
                "mingge": "偏印格",
                "wangshuai": "弱",
                "xiyong": "土 火",
            },
            {
                "args": ("case_1987_before_lichun", 1987, 1, 30, 0, 0, "女"),
                "bazi": "丙寅 辛丑 己卯 甲子",
                "lunar_str": "1987年1月2日",
                "shengxiao": "兔",
                "mingge": "食神格",
                "wangshuai": "弱",
                "xiyong": "土 火",
            },
            {
                "args": ("case_1987_winter", 1987, 12, 21, 0, 0, "男"),
                "bazi": "丁卯 壬子 甲辰 甲子",
                "lunar_str": "1987年11月1日",
                "shengxiao": "兔",
                "mingge": "偏印格",
                "wangshuai": "强",
                "xiyong": "金 火 土",
            },
            {
                "args": ("case_1969_summer", 1969, 7, 20, 20, 17, "男"),
                "bazi": "己酉 辛未 丙申 戊戌",
                "lunar_str": "1969年6月7日",
                "shengxiao": "鸡",
                "mingge": "正财格",
                "wangshuai": "弱",
                "xiyong": "火 木",
            },
            {
                "args": ("case_1976_autumn", 1976, 9, 9, 9, 9, "女"),
                "bazi": "丙辰 丁酉 甲子 己巳",
                "lunar_str": "1976年8月16日",
                "shengxiao": "龙",
                "mingge": "伤官格",
                "wangshuai": "弱",
                "xiyong": "木 水",
            },
            {
                "args": ("case_1984_start", 1984, 2, 4, 23, 30, "男"),
                "bazi": "甲子 丙寅 戊辰 甲子",
                "lunar_str": "1984年1月3日",
                "shengxiao": "鼠",
                "mingge": "偏印格",
                "wangshuai": "弱",
                "xiyong": "土 火",
            },
            {
                "args": ("case_1990_midday", 1990, 6, 1, 12, 0, "女"),
                "bazi": "庚午 壬午 丁酉 丙午",
                "lunar_str": "1990年5月9日",
                "shengxiao": "马",
                "mingge": "正官格",
                "wangshuai": "强",
                "xiyong": "金 水 土",
            },
            {
                "args": ("case_2012_leap_year", 2012, 2, 29, 6, 45, "男"),
                "bazi": "壬辰 癸卯 庚申 己卯",
                "lunar_str": "2012年2月8日",
                "shengxiao": "龙",
                "mingge": "伤官格",
                "wangshuai": "弱",
                "xiyong": "金 土",
            },
            {
                "args": ("case_2024_new_year", 2024, 2, 10, 8, 0, "女"),
                "bazi": "甲辰 丙寅 甲辰 戊辰",
                "lunar_str": "2024年1月1日",
                "shengxiao": "龙",
                "mingge": "食神格",
                "wangshuai": "强",
                "xiyong": "金 火 土",
            },
            {
                "args": ("case_2059_late", 2059, 12, 31, 22, 59, "男"),
                "bazi": "己卯 丙子 壬申 辛亥",
                "lunar_str": "2059年11月27日",
                "shengxiao": "兔",
                "mingge": "偏财格",
                "wangshuai": "强",
                "xiyong": "木 火 土",
            },
        ]

        self.assertGreaterEqual(len(cases), 10)

        for case in cases:
            with self.subTest(name=case["args"][0]):
                fate = calculate_fate(*case["args"])
                got_bazi = " ".join(fate["bazi"].values())
                self.assertEqual(got_bazi, case["bazi"])
                for field in ("lunar_str", "shengxiao", "mingge", "wangshuai", "xiyong"):
                    self.assertEqual(fate[field], case[field])

    def test_random_solar_lunar_round_trip(self):
        cases = random_solar_cases()
        self.assertGreaterEqual(len(cases), 100)

        for args in cases:
            with self.subTest(args=args):
                solar = Solar.fromYmdHms(*args)
                round_trip = solar.getLunar().getSolar()
                self.assertEqual(
                    (
                        round_trip.getYear(),
                        round_trip.getMonth(),
                        round_trip.getDay(),
                        round_trip.getHour(),
                        round_trip.getMinute(),
                        round_trip.getSecond(),
                    ),
                    args,
                )


if __name__ == "__main__":
    unittest.main()


path: scripts/calc.py
content: #!/usr/bin/env python3
"""
BaZi Core Calculator v6 — Refactored

Architecture (5 layers):
  [1] Constants  — stem/branch/ten-god/na-yin tables (pure data)
  [2] Data model — Pillar, Fate dataclasses
  [3] Helpers    — pure lookup functions
  [4] Core logic — element strength, vitality, afflictions, pillars
  [5] Public API — calculate_fate(), dict_to_fate()

No remote API. Lunar calendar via embedded single-file lunar_core.
"""
import os, sys
from dataclasses import dataclass, field
from typing import List, Dict

# ─── Layer 1: Lunar lib bootstrap ──────────────────────────────────────────────
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, _SCRIPT_DIR)

def _lazy_lunar():
    from lunar_core import Lunar
    return Lunar

Lunar = _lazy_lunar()
from lunar_core import Solar


# ════════════════════════════════════════════════════════════════════════════════
# Layer 1: Constants — 天干/地支/五行/神煞/十神/纳音/旬空
# ════════════════════════════════════════════════════════════════════════════════

STEM_ALL = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']
BRANCH_ALL = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']

# Stem → element / yin-yang
STEM_ELEMENT = {
    '甲':'木', '乙':'木', '丙':'火', '丁':'火', '戊':'土',
    '己':'土', '庚':'金', '辛':'金', '壬':'水', '癸':'水',
}
STEM_YINYANG = {g: ('阳' if i % 2 == 0 else '阴') for i, g in enumerate(STEM_ALL)}

# Branch → element / yin-yang / hidden-stems
BRANCH_ELEMENT = {
    '子':'水', '丑':'土', '寅':'木', '卯':'木', '辰':'土', '巳':'火',
    '午':'火', '未':'土', '申':'金', '酉':'金', '戌':'土', '亥':'水',
}
BRANCH_YINYANG = {z: ('阳' if i % 2 == 0 else '阴') for i, z in enumerate(BRANCH_ALL)}
BRANCH_HIDDEN: Dict[str, List[str]] = {
    '子':['癸'], '丑':['己','癸','辛'], '寅':['甲','丙','戊'],
    '卯':['乙'], '辰':['戊','乙','癸'], '巳':['丙','庚','戊'],
    '午':['丁','己'], '未':['己','丁','乙'], '申':['庚','壬','戊'],
    '酉':['辛'], '戌':['戊','辛','丁'], '亥':['壬','甲'],
}

# Ten gods (十神) — keyed by day-stem × month-stem → full name
TEN_GOD = {
    '甲':{'甲':'比肩','乙':'劫财','丙':'食神','丁':'伤官','戊':'偏财','己':'正财','庚':'七杀','辛':'正官','壬':'偏印','癸':'正印'},
    '乙':{'甲':'劫财','乙':'比肩','丙':'伤官','丁':'食神','戊':'正财','己':'偏财','庚':'正官','辛':'七杀','壬':'正印','癸':'偏印'},
    '丙':{'甲':'偏印','乙':'正印','丙':'比肩','丁':'劫财','戊':'食神','己':'伤官','庚':'偏财','辛':'正财','壬':'七杀','癸':'正官'},
    '丁':{'甲':'正印','乙':'偏印','丙':'劫财','丁':'比肩','戊':'伤官','己':'食神','庚':'正财','辛':'偏财','壬':'正官','癸':'七杀'},
    '戊':{'甲':'七杀','乙':'正官','丙':'偏印','丁':'正印','戊':'比肩','己':'劫财','庚':'食神','辛':'伤官','壬':'偏财','癸':'正财'},
    '己':{'甲':'正官','乙':'七杀','丙':'正印','丁':'偏印','戊':'劫财','己':'比肩','庚':'伤官','辛':'食神','壬':'正财','癸':'偏财'},
    '庚':{'甲':'偏财','乙':'正财','丙':'七杀','丁':'正官','戊':'偏印','己':'正印','庚':'比肩','辛':'劫财','壬':'食神','癸':'伤官'},
    '辛':{'甲':'正财','乙':'偏财','丙':'正官','丁':'七杀','戊':'正印','己':'偏印','庚':'劫财','辛':'比肩','壬':'伤官','癸':'食神'},
    '壬':{'甲':'食神','乙':'伤官','丙':'偏财','丁':'正财','戊':'七杀','己':'正官','庚':'偏印','辛':'正印','壬':'比肩','癸':'劫财'},
    '癸':{'甲':'伤官','乙':'食神','丙':'正财','丁':'偏财','戊':'正官','己':'七杀','庚':'正印','辛':'偏印','壬':'劫财','癸':'比肩'},
}

# 月支主气藏干表（12地支的本气）
# 十神 → 格局名
TEN_GOD_TO_GE = {
    '正官':'正官格', '七杀':'七杀格',
    '正财':'正财格', '偏财':'偏财格',
    '正印':'正印格', '偏印':'偏印格',
    '食神':'食神格', '伤官':'伤官格',
    '比肩':'比肩格', '劫财':'阳刃格',
}

def calc_mingge(day_stem: str, month_stem: str) -> str:
    """
    Compute 命格 (Ming Ge — fate pattern) from day stem and month stem.

    Standard 格局派 algorithm — 月干十神决定命格：
      1. Look up TEN_GOD[day_stem][month_stem] → ten_god full name.
      2. Map ten_god → 格局 name via TEN_GOD_TO_GE.

    This is the standard 格局派 algorithm: 月干十神决定命格。
    """
    ten_god = TEN_GOD[day_stem][month_stem]
    return TEN_GOD_TO_GE.get(ten_god, ten_god)

# NaYin table (纳音) — (stem,branch) → label
NAYIN = {
    ('甲子','甲午'):'海中金', ('乙丑','甲午'):'海中金',
    ('丙寅','丙申'):'炉中火', ('丁卯','丁酉'):'炉中火',
    ('戊辰','戊戌'):'木大林',   ('己巳','己亥'):'木大林',
    ('庚午','庚子'):'路旁土',   ('辛未','辛丑'):'路旁土',
    ('壬申','壬寅'):'剑锋金',   ('癸酉','癸卯'):'剑锋金',
    ('甲戌','甲辰'):'山头火',   ('乙亥','乙巳'):'山头火',
    ('丙子','丙午'):'涧下水',   ('丁丑','丁未'):'涧下水',
    ('戊寅','戊申'):'城墙土',   ('己卯','己酉'):'城墙土',
    ('庚辰','庚戌'):'白蜡金',   ('辛巳','辛亥'):'白蜡金',
    ('壬午','壬子'):'杨柳木',   ('癸未','癸丑'):'杨柳木',
    ('甲申','甲寅'):'井泉水',   ('乙酉','乙卯'):'井泉水',
    ('丙戌','丙辰'):'屋上土',   ('丁亥','丁巳'):'屋上土',
    ('戊子','戊午'):'霹雳火',   ('己丑','己未'):'霹雳火',
    ('庚寅','庚申'):'松柏木',   ('辛卯','辛酉'):'松柏木',
    ('壬辰','壬戌'):'长流水',   ('癸巳','癸亥'):'长流水',
    ('甲辰','甲戌'):'覆灯火',   ('乙巳','乙亥'):'覆灯火',
    ('丙午','丙子'):'天河水',   ('丁未','丁丑'):'天河水',
    ('戊申','戊寅'):'大驿土',   ('己酉','己卯'):'大驿土',
    ('庚戌','庚辰'):'石榴木',   ('辛亥','辛巳'):'石榴木',
    ('壬子','壬午'):'桑柘木',   ('癸丑','癸未'):'桑柘木',
    ('甲寅','甲申'):'大溪水',   ('乙卯','乙酉'):'大溪水',
    ('丙辰','丙戌'):'砂石土',   ('丁巳','丁亥'):'砂石土',
    ('戊午','戊子'):'天上火',   ('己未','己丑'):'天上火',
    ('庚申','庚寅'):'城头土',   ('辛酉','辛卯'):'城头土',
    ('壬戌','壬辰'):'大海水',   ('癸亥','癸巳'):'大海水',
}

# XunKong table (旬空) — day-pillar → void branches
XUN_KONG = {
    '甲子':'戌亥','甲戌':'申酉','甲申':'午未','甲午':'辰巳','甲辰':'寅卯','甲寅':'子丑',
    '乙丑':'戌亥','乙卯':'申酉','乙巳':'午未','乙未':'辰巳','乙酉':'寅卯','乙亥':'子丑',
    '丙子':'戌亥','丙寅':'申酉','丙辰':'午未','丙午':'辰巳','丙申':'寅卯','丙戌':'子丑',
    '丁丑':'戌亥','丁卯':'申酉','丁巳':'午未','丁未':'辰巳','丁酉':'寅卯','丁亥':'子丑',
    '戊子':'戌亥','戊寅':'申酉','戊辰':'午未','戊午':'辰巳','戊申':'寅卯','戊戌':'子丑',
    '己丑':'戌亥','己卯':'申酉','己巳':'午未','己未':'辰巳','己酉':'寅卯','己亥':'子丑',
    '庚子':'戌亥','庚寅':'申酉','庚辰':'午未','庚午':'辰巳','庚申':'寅卯','庚戌':'子丑',
    '辛丑':'戌亥','辛卯':'申酉','辛巳':'午未','辛未':'辰巳','辛酉':'寅卯','辛亥':'子丑',
    '壬子':'戌亥','壬寅':'申酉','壬辰':'午未','壬午':'辰巳','壬申':'寅卯','壬戌':'子丑',
    '癸丑':'戌亥','癸卯':'申酉','癸巳':'午未','癸未':'辰巳','癸酉':'寅卯','癸亥':'子丑',
}

# Branch relations
BRANCH_HEX = {
    ('子','丑'):'子丑合', ('寅','亥'):'寅亥合', ('卯','戌'):'卯戌合',
    ('辰','酉'):'辰酉合', ('巳','申'):'巳申合', ('午','未'):'午未合',
}
BRANCH_CLASH = {
    ('子','午'):'子午冲', ('丑','未'):'丑未冲', ('寅','申'):'寅申冲',
    ('卯','酉'):'卯酉冲', ('辰','戌'):'辰戌冲', ('巳','亥'):'巳亥冲',
}
BRANCH_PUNISHMENT = {
    ('寅','巳','申'):'寅巳申三刑', ('丑','戌','未'):'丑戌未三刑',
    ('子','卯'):'子卯相刑',
    ('辰','辰'):'辰辰自刑', ('午','午'):'午午自刑',
    ('酉','酉'):'酉酉自刑', ('亥','亥'):'亥亥自刑',
}

# Affliction tables (神煞)
PEACH_BLOSSOM = {
    '寅':'卯', '午':'卯', '戌':'卯', '申':'酉', '子':'酉', '辰':'酉',
    '亥':'子', '卯':'子', '未':'子', '巳':'午', '酉':'午', '丑':'午',
}
TIANYI_NOBLE = {
    '甲':['丑','未'], '戊':['丑','未'], '庚':['丑','未'],
    '乙':['子','申'], '己':['子','申'],
    '丙':['亥','酉'], '丁':['亥','酉'],
    '壬':['卯','巳'], '癸':['卯','巳'],
    '辛':['寅','午'],
}
YIMA_HORSE = {
    '申':'寅', '子':'寅', '辰':'寅',
    '亥':'巳', '卯':'巳', '未':'巳',
    '寅':'申', '午':'申', '戌':'申',
    '巳':'亥', '酉':'亥', '丑':'亥',
}
JIANGXING = {
    '子':'子', '午':'子', '辰':'子', '申':'子', '寅':'子',
    '卯':'酉', '酉':'酉', '丑':'酉', '未':'酉', '巳':'酉', '亥':'酉',
}
JIESHA = {
    '申':'亥', '子':'巳', '丑':'寅', '寅':'申', '卯':'亥',
    '辰':'巳', '巳':'寅', '午':'申', '未':'亥', '酉':'寅',
    '戌':'申', '亥':'巳',
}


# ════════════════════════════════════════════════════════════════════════════════
# Layer 2: Data model
# ════════════════════════════════════════════════════════════════════════════════

@dataclass
class Pillar:
    """
    Single pillar (年/月/日/时).
    stem=天干  branch=地支  stem_el=天干五行  branch_el=地支五行
    yinyang=阴阳  ten_god=十神  na_yin=纳音
    hidden=藏干列表  vacant=旬空
    """
    stem:      str
    branch:    str
    stem_el:   str
    branch_el: str
    yinyang:   str
    ten_god:   str
    na_yin:    str
    hidden:    List[str]
    vacant:    str = ''


@dataclass
class Fate:
    """
    Complete baZi result.
    """
    name:           str
    gender:         str
    birth:          Dict

    year_pillar:    Pillar
    month_pillar:   Pillar
    day_pillar:     Pillar
    hour_pillar:    Pillar
    bazi_str:       str

    lunar_str:      str
    shengxiao:      str

    wuxing_count:   Dict[str, float]

    vitality:       str
    favorable:      str
    mingge:         str
    day_stem:       str
    afflictions:    List[str]
    taiyuan:        str
    taixi:          str
    minggong:       str
    shengong:       str
    qiyun:          Dict
    dayun:          List[Dict]

    # Compatibility aliases
    xiyong:         str = ''
    void:           str = ''
    vacant:         str = ''
    _lunar:         object = field(default=None, repr=False)


# ════════════════════════════════════════════════════════════════════════════════
# Layer 3: Pure helper functions
# ════════════════════════════════════════════════════════════════════════════════

def stem_yinyang(s: str) -> str:
    return STEM_YINYANG.get(s, '')

def branch_yinyang(b: str) -> str:
    return BRANCH_YINYANG.get(b, '')

def stem_element(s: str) -> str:
    return STEM_ELEMENT.get(s, '')

def branch_element(b: str) -> str:
    return BRANCH_ELEMENT.get(b, '')

def branch_hidden(b: str) -> List[str]:
    return BRANCH_HIDDEN.get(b, [])

def lookup_nayin(stem: str, branch: str) -> str:
    """纳音查询"""
    return NAYIN.get((stem, branch),
           NAYIN.get((branch, stem), '土'))

def lookup_xunkong(day_stem: str, day_branch: str) -> str:
    """旬空查询"""
    return XUN_KONG.get(day_stem + day_branch, '')


# ════════════════════════════════════════════════════════════════════════════════
# Layer 4: Core computation
# ════════════════════════════════════════════════════════════════════════════════

# Element strength tables
STEM_STRENGTH = {
    '甲': {'子':1.20,'丑':1.06,'寅':1.14,'卯':1.20,'辰':1.10,'巳':1.00,'午':1.00,'未':1.04,'申':1.06,'酉':1.00,'戌':1.00,'亥':1.20},
    '乙': {'子':1.20,'丑':1.06,'寅':1.14,'卯':1.20,'辰':1.10,'巳':1.00,'午':1.00,'未':1.04,'申':1.06,'酉':1.00,'戌':1.00,'亥':1.20},
    '丙': {'子':1.00,'丑':1.00,'寅':1.20,'卯':1.20,'辰':1.06,'巳':1.14,'午':1.20,'未':1.10,'申':1.00,'酉':1.00,'戌':1.04,'亥':1.00},
    '丁': {'子':1.00,'丑':1.00,'寅':1.20,'卯':1.20,'辰':1.06,'巳':1.14,'午':1.20,'未':1.10,'申':1.00,'酉':1.00,'戌':1.04,'亥':1.00},
    '戊': {'子':1.00,'丑':1.10,'寅':1.06,'卯':1.00,'辰':1.10,'巳':1.14,'午':1.20,'未':1.16,'申':1.00,'酉':1.00,'戌':1.14,'亥':1.00},
    '己': {'子':1.00,'丑':1.10,'寅':1.06,'卯':1.00,'辰':1.10,'巳':1.14,'午':1.20,'未':1.16,'申':1.00,'酉':1.00,'戌':1.14,'亥':1.00},
    '庚': {'子':1.00,'丑':1.14,'寅':1.00,'卯':1.00,'辰':1.10,'巳':1.06,'午':1.00,'未':1.10,'申':1.14,'酉':1.20,'戌':1.16,'亥':1.00},
    '辛': {'子':1.00,'丑':1.14,'寅':1.00,'卯':1.00,'辰':1.10,'巳':1.06,'午':1.00,'未':1.10,'申':1.14,'酉':1.20,'戌':1.16,'亥':1.00},
    '壬': {'子':1.20,'丑':1.10,'寅':1.00,'卯':1.00,'辰':1.04,'巳':1.06,'午':1.00,'未':1.00,'申':1.20,'酉':1.20,'戌':1.06,'亥':1.14},
    '癸': {'子':1.20,'丑':1.10,'寅':1.00,'卯':1.00,'辰':1.04,'巳':1.06,'午':1.00,'未':1.00,'申':1.20,'酉':1.20,'戌':1.06,'亥':1.14},
}

BRANCH_HIDDEN_STRENGTH = {
    '子': {'癸': {'子':1.20,'丑':1.10,'寅':1.00,'卯':1.00,'辰':1.04,'巳':1.06,'午':1.00,'未':1.00,'申':1.20,'酉':1.20,'戌':1.06,'亥':1.14}},
    '丑': {'癸': {'子':0.36,'丑':0.33,'寅':0.30,'卯':0.30,'辰':0.312,'巳':0.318,'午':0.30,'未':0.30,'申':0.36,'酉':0.36,'戌':0.318,'亥':0.342},
           '辛': {'子':0.20,'丑':0.228,'寅':0.20,'卯':0.20,'辰':0.23,'巳':0.212,'午':0.20,'未':0.22,'申':0.228,'酉':0.248,'戌':0.232,'亥':0.20},
           '己': {'子':0.50,'丑':0.55,'寅':0.53,'卯':0.50,'辰':0.55,'巳':0.57,'午':0.60,'未':0.58,'申':0.50,'酉':0.50,'戌':0.57,'亥':0.50}},
    '寅': {'丙': {'子':0.30,'丑':0.30,'寅':0.36,'卯':0.36,'辰':0.318,'巳':0.342,'午':0.36,'未':0.33,'申':0.30,'酉':0.30,'戌':0.342,'亥':0.318},
           '甲': {'子':0.84,'丑':0.742,'寅':0.798,'卯':0.84,'辰':0.77,'巳':0.70,'午':0.70,'未':0.728,'申':0.742,'酉':0.70,'戌':0.70,'亥':0.84}},
    '卯': {'乙': {'子':1.20,'丑':1.06,'寅':1.14,'卯':1.20,'辰':1.10,'巳':1.00,'午':1.00,'未':1.04,'申':1.06,'酉':1.00,'戌':1.00,'亥':1.20}},
    '辰': {'乙': {'子':0.36,'丑':0.318,'寅':0.342,'卯':0.36,'辰':0.33,'巳':0.30,'午':0.30,'未':0.312,'申':0.318,'酉':0.30,'戌':0.30,'亥':0.36},
           '癸': {'子':0.24,'丑':0.22,'寅':0.20,'卯':0.20,'辰':0.208,'巳':0.20,'午':0.20,'未':0.20,'申':0.24,'酉':0.24,'戌':0.212,'亥':0.228},
           '戊': {'子':0.50,'丑':0.55,'寅':0.53,'卯':0.50,'辰':0.55,'巳':0.60,'午':0.60,'未':0.58,'申':0.50,'酉':0.50,'戌':0.57,'亥':0.50}},
    '巳': {'庚': {'子':0.30,'丑':0.342,'寅':0.30,'卯':0.30,'辰':0.33,'巳':0.30,'午':0.30,'未':0.33,'申':0.342,'酉':0.36,'戌':0.348,'亥':0.30},
           '丙': {'子':0.70,'丑':0.70,'寅':0.84,'卯':0.84,'辰':0.742,'巳':0.84,'午':0.84,'未':0.798,'申':0.70,'酉':0.70,'戌':0.728,'亥':0.742}},
    '午': {'丁': {'子':1.00,'丑':1.00,'寅':1.20,'卯':1.20,'辰':1.06,'巳':1.14,'午':1.20,'未':1.10,'申':1.00,'酉':1.00,'戌':1.04,'亥':1.06}},
    '未': {'丁': {'子':0.30,'丑':0.30,'寅':0.36,'卯':0.36,'辰':0.318,'巳':0.342,'午':0.36,'未':0.33,'申':0.30,'酉':0.30,'戌':0.312,'亥':0.318},
           '乙': {'子':0.24,'丑':0.212,'寅':0.228,'卯':0.24,'辰':0.22,'巳':0.20,'午':0.20,'未':0.208,'申':0.212,'酉':0.20,'戌':0.20,'亥':0.24},
           '己': {'子':0.50,'丑':0.55,'寅':0.53,'卯':0.50,'辰':0.55,'巳':0.57,'午':0.60,'未':0.58,'申':0.50,'酉':0.50,'戌':0.57,'亥':0.50}},
    '申': {'壬': {'子':0.36,'丑':0.33,'寅':0.30,'卯':0.30,'辰':0.312,'巳':0.318,'午':0.30,'未':0.30,'申':0.36,'酉':0.36,'戌':0.318,'亥':0.342},
           '庚': {'子':0.70,'丑':0.798,'寅':0.70,'卯':0.70,'辰':0.77,'巳':0.742,'午':0.70,'未':0.77,'申':0.798,'酉':0.84,'戌':0.812,'亥':0.70}},
    '酉': {'辛': {'子':1.00,'丑':1.14,'寅':1.00,'卯':1.00,'辰':1.10,'巳':1.06,'午':1.00,'未':1.10,'申':1.14,'酉':1.20,'戌':1.16,'亥':1.00}},
    '戌': {'辛': {'子':0.30,'丑':0.342,'寅':0.30,'卯':0.30,'辰':0.33,'巳':0.318,'午':0.30,'未':0.33,'申':0.342,'酉':0.36,'戌':0.348,'亥':0.30},
           '丁': {'子':0.20,'丑':0.20,'寅':0.24,'卯':0.24,'辰':0.212,'巳':0.228,'午':0.24,'未':0.22,'申':0.20,'酉':0.20,'戌':0.208,'亥':0.212},
           '戊': {'子':0.50,'丑':0.55,'寅':0.53,'卯':0.50,'辰':0.55,'巳':0.57,'午':0.60,'未':0.58,'申':0.50,'酉':0.50,'戌':0.57,'亥':0.50}},
    '亥': {'甲': {'子':0.36,'丑':0.318,'寅':0.342,'卯':0.36,'辰':0.33,'巳':0.30,'午':0.30,'未':0.312,'申':0.318,'酉':0.30,'戌':0.30,'亥':0.36},
           '壬': {'子':0.84,'丑':0.77,'寅':0.70,'卯':0.70,'辰':0.728,'巳':0.742,'午':0.70,'未':0.70,'申':0.84,'酉':0.84,'戌':0.724,'亥':0.798}},
}

# Vitality classification
SAME_KIND  = {'金':['金','土'], '木':['木','水'], '水':['水','金'], '火':['火','木'], '土':['土','火']}
DIFF_KIND  = {'金':['木','水','火'],'木':['金','火','土'],'水':['木','火','土'],'火':['金','水','土'],'土':['金','木','水']}
STEM_EL    = {'甲':'木','乙':'木','丙':'火','丁':'火','戊':'土','己':'土','庚':'金','辛':'金','壬':'水','癸':'水'}


def _calc_wuxing_strength(yr_gan, yr_zhi, mo_gan, mo_zhi,
                          dy_gan, dy_zhi, hr_gan, hr_zhi) -> Dict[str, float]:
    """Compute five-element strength scores."""
    strength = {'木':0.0, '火':0.0, '土':0.0, '金':0.0, '水':0.0}
    for stem in [yr_gan, mo_gan, dy_gan, hr_gan]:
        strength[STEM_EL[stem]] += STEM_STRENGTH[stem][mo_zhi]
    for branch in [yr_zhi, mo_zhi, dy_zhi, hr_zhi]:
        if branch in BRANCH_HIDDEN_STRENGTH:
            for h, table in BRANCH_HIDDEN_STRENGTH[branch].items():
                strength[STEM_EL[h]] += table[mo_zhi]
    return {k: round(v, 3) for k, v in strength.items()}


def _assess_vitality_and_favorable(yr_gan, yr_zhi, mo_gan, mo_zhi,
                                    dy_gan, dy_zhi, hr_gan, hr_zhi):
    """
    Assess body-strength (旺衰) and favorable elements (喜用).
    same_kind (self + birth mother) > diff_kind (conqueror + child + exhaustor)
      → strong body → drain (取异类泄)
    same_kind < diff_kind
      → weak body → support (取同类扶)
    Returns: (vitality_label, favorable_string, strength_dict)
    """
    day_el = STEM_EL[dy_gan]
    strength = _calc_wuxing_strength(yr_gan, yr_zhi, mo_gan, mo_zhi,
                                      dy_gan, dy_zhi, hr_gan, hr_zhi)
    same = SAME_KIND[day_el]
    diff = DIFF_KIND[day_el]
    score_same = sum(strength[el] for el in same)
    score_diff = sum(strength[el] for el in diff)

    if score_same > score_diff:
        vitality = '强'
        favorable = diff
    elif score_same < score_diff:
        vitality = '弱'
        favorable = same
    else:
        vitality = '中'
        favorable = same + diff

    return vitality, ' '.join(favorable), strength


def _collect_afflictions(yr_zhi, mo_zhi, dy_zhi, all_branches, dy_gan) -> List[str]:
    """Collect affliction list (神煞)."""
    result = []

    for b in [yr_zhi, dy_zhi]:
        target = PEACH_BLOSSOM.get(b, '')
        if target in all_branches and '桃花' not in result:
            result.append('桃花')

    for nb in TIANYI_NOBLE.get(dy_gan, []):
        if nb in all_branches:
            if '天乙贵人' not in result:
                result.append('天乙贵人')
            break

    for b in [yr_zhi, mo_zhi]:
        horse = YIMA_HORSE.get(b, '')
        if horse and horse in all_branches and '驿马' not in result:
            result.append('驿马')

    for b in [yr_zhi, dy_zhi]:
        general = JIANGXING.get(b, '')
        if general and general in all_branches and '将星' not in result:
            result.append('将星')

    for b in [yr_zhi, mo_zhi]:
        robbery = JIESHA.get(b, '')
        if robbery and robbery in all_branches and '劫煞' not in result:
            result.append('劫煞')

    return result


def _build_pillar(stem, branch, na_yin_val, hidden_list, vacant_val,
                   dy_gan, pillar_name) -> Pillar:
    """Build a Pillar dataclass."""
    return Pillar(
        stem      = stem,
        branch    = branch,
        stem_el   = stem_element(stem),
        branch_el = branch_element(branch),
        yinyang   = stem_yinyang(stem),
        ten_god   = '日主' if pillar_name == 'day' else TEN_GOD.get(dy_gan, {}).get(stem, ''),
        na_yin    = na_yin_val,
        hidden    = hidden_list,
        vacant    = vacant_val,
    )


# ════════════════════════════════════════════════════════════════════════════════
# Layer 5: Public API
# ════════════════════════════════════════════════════════════════════════════════

def _get_lunar_data(year, month, day, hour, minute):
    """Fetch raw lunar data from the local lunar_core module."""
    solar = Solar.fromYmdHms(int(year), int(month), int(day), int(hour), int(minute), 0)
    lunar = solar.getLunar()
    ba    = lunar.getEightChar()
    return solar, lunar, ba


def _compute_fate(name: str, year, month, day, hour, minute, gender) -> Fate:
    """Compute full baZi, return Fate object."""
    solar, lunar, ba = _get_lunar_data(year, month, day, hour, minute)
    gender_num  = 1 if gender in (1, '1', '男') else 0
    gender_str  = '男' if gender_num == 1 else '女'

    yr_stem, yr_zhi   = ba.getYearGan(),  ba.getYearZhi()
    mo_stem, mo_zhi   = ba.getMonthGan(), ba.getMonthZhi()
    dy_stem, dy_zhi   = ba.getDayGan(),   ba.getDayZhi()
    hr_stem, hr_zhi   = ba.getTimeGan(),  ba.getTimeZhi()
    dy_gan = dy_stem

    all_branches = [yr_zhi, mo_zhi, dy_zhi, hr_zhi]

    vitality, favorable, wuxing_strength = _assess_vitality_and_favorable(
        yr_stem, yr_zhi, mo_stem, mo_zhi, dy_stem, dy_zhi, hr_stem, hr_zhi)

    afflictions = _collect_afflictions(yr_zhi, mo_zhi, dy_zhi, all_branches, dy_gan)

    day_ganzhi   = dy_stem + dy_zhi
    void_branches = lookup_xunkong(dy_stem, dy_zhi)

    yr_p = _build_pillar(yr_stem, yr_zhi, ba.getYearNaYin(),
                         ba.getYearHideGan(), ba.getYearXunKong(), dy_gan, 'year')
    mo_p = _build_pillar(mo_stem, mo_zhi, ba.getMonthNaYin(),
                         ba.getMonthHideGan(), ba.getMonthXunKong(), dy_gan, 'month')
    dy_p = _build_pillar(dy_stem, dy_zhi, ba.getDayNaYin(),
                         ba.getDayHideGan(), ba.getDayXunKong(), dy_gan, 'day')
    hr_p = _build_pillar(hr_stem, hr_zhi, ba.getTimeNaYin(),
                         ba.getTimeHideGan(), ba.getTimeXunKong(), dy_gan, 'hour')

    try:
        yun      = ba.getYun(gender_num)
        qiyun_yr = yun.getStartYear()
        qiyun_mo = yun.getStartMonth()
        dayun_list = yun.getDaYun()
        dayun_data = [
            {"ganzhi": d.getGanZhi(), "start_age": d.getStartAge(),
             "end_age": d.getEndAge(), "start_year": d.getStartYear(),
             "vacant": d.getXunKong()}
            for d in dayun_list[:10]
        ]
    except Exception:
        qiyun_yr = qiyun_mo = 0
        dayun_data = []

    return Fate(
        name          = name,
        gender        = gender_str,
        birth         = {"year":int(year),"month":int(month),"day":int(day),
                        "hour":int(hour),"minute":int(minute)},
        year_pillar   = yr_p,
        month_pillar  = mo_p,
        day_pillar    = dy_p,
        hour_pillar   = hr_p,
        bazi_str      = f"{yr_stem}{yr_zhi} {mo_stem}{mo_zhi} {dy_stem}{dy_zhi} {hr_stem}{hr_zhi}",
        lunar_str     = lunar.toString(),
        shengxiao     = lunar.getYearShengXiao(),
        wuxing_count  = wuxing_strength,
        vitality      = vitality,
        favorable     = favorable,
        xiyong        = favorable,
        mingge        = calc_mingge(dy_stem, mo_stem),
        day_stem      = dy_stem,
        afflictions   = afflictions,
        void          = void_branches,
        vacant        = void_branches,
        taiyuan       = ba.getTaiYuan(),
        taixi         = ba.getTaiXi(),
        minggong      = ba.getMingGong(),
        shengong      = ba.getShenGong(),
        qiyun         = {"year": qiyun_yr, "month": qiyun_mo},
        dayun         = dayun_data,
        _lunar        = lunar,
    )


def _fate_to_dict(f: Fate) -> dict:
    """Serialize Fate → dict for JSON storage."""
    return {
        "name":   f.name, "gender": f.gender, "birth": f.birth,
        "bazi": {
            "year":  f.year_pillar.stem  + f.year_pillar.branch,
            "month": f.month_pillar.stem + f.month_pillar.branch,
            "day":   f.day_pillar.stem   + f.day_pillar.branch,
            "hour":  f.hour_pillar.stem  + f.hour_pillar.branch,
        },
        "bazi_str":   f.bazi_str,
        "lunar_str":  f.lunar_str,
        "shengxiao":  f.shengxiao,
        "wuxing": ' '.join([
            f.year_pillar.stem_el  + f.year_pillar.branch_el,
            f.month_pillar.stem_el + f.month_pillar.branch_el,
            f.day_pillar.stem_el   + f.day_pillar.branch_el,
            f.hour_pillar.stem_el  + f.hour_pillar.branch_el,
        ]),
        "wuxing_count": f.wuxing_count,
        "mingge":      f.mingge,
        "wangshuai":   f.vitality,
        "xiyong":      f.favorable,
        "vitality":    f.vitality,
        "favorable":   f.favorable,
        "yinyang":     f.day_pillar.yinyang,
        "nayin": {
            "year":  f.year_pillar.na_yin,
            "month": f.month_pillar.na_yin,
            "day":   f.day_pillar.na_yin,
            "hour":  f.hour_pillar.na_yin,
        },
        "kongwang":  f.void,
        "shensha":   f.afflictions,
        "taiyuan":   f.taiyuan,
        "taixi":     f.taixi,
        "minggong":  f.minggong,
        "shengong":  f.shengong,
        "qiyun":     f.qiyun,
        "dayun":     f.dayun,
        "rizhu":     f.day_pillar.stem + f.day_pillar.branch,
        "me":        f.day_stem,
        "detail": {
            p_key: {
                "gan":      getattr(f, f'{p_key}_pillar').stem,
                "zhi":      getattr(f, f'{p_key}_pillar').branch,
                "wuxing":   getattr(f, f'{p_key}_pillar').stem_el,
                "yinyang":  getattr(f, f'{p_key}_pillar').yinyang,
                "shishen":  getattr(f, f'{p_key}_pillar').ten_god,
                "nayin":    getattr(f, f'{p_key}_pillar').na_yin,
                "canggan":  getattr(f, f'{p_key}_pillar').hidden,
                "xunkong":  getattr(f, f'{p_key}_pillar').vacant,
            }
            for p_key in ('year', 'month', 'day', 'hour')
        },
    }


def dict_to_fate(bazi_dict: dict, birth: dict, gender) -> Fate:
    """Reconstruct Fate from stored dict (for view/joint commands)."""
    det = bazi_dict.get('detail', {})

    def _rebuild(p_key: str) -> Pillar:
        d = det.get(p_key, {})
        return Pillar(
            stem      = d.get('gan', ''),
            branch    = d.get('zhi', ''),
            stem_el   = d.get('wuxing', ''),
            branch_el = '',
            yinyang   = d.get('yinyang', ''),
            ten_god   = d.get('shishen', ''),
            na_yin    = d.get('nayin', ''),
            hidden    = d.get('canggan', []),
            vacant    = d.get('xunkong', ''),
        )

    gender_str = '男' if gender in (1, '1', '男') else '女'
    bz = bazi_dict.get('bazi', {})
    bazi_str = ' '.join(bz.values()) if isinstance(bz, dict) else str(bz)

    return Fate(
        name          = bazi_dict.get('name', ''),
        gender        = gender_str,
        birth         = birth,
        year_pillar   = _rebuild('year'),
        month_pillar  = _rebuild('month'),
        day_pillar    = _rebuild('day'),
        hour_pillar   = _rebuild('hour'),
        bazi_str      = bazi_str,
        lunar_str     = bazi_dict.get('lunar_str', ''),
        shengxiao     = bazi_dict.get('shengxiao', ''),
        wuxing_count  = bazi_dict.get('wuxing_count', {}),
        vitality      = bazi_dict.get('wangshuai', ''),
        favorable     = bazi_dict.get('xiyong', ''),
        xiyong        = bazi_dict.get('xiyong', ''),
        mingge        = bazi_dict.get('mingge', ''),
        day_stem      = bazi_dict.get('me', ''),
        afflictions   = bazi_dict.get('shensha', []),
        void          = bazi_dict.get('kongwang', ''),
        vacant        = bazi_dict.get('kongwang', ''),
        taiyuan       = bazi_dict.get('taiyuan', ''),
        taixi         = bazi_dict.get('taixi', ''),
        minggong      = bazi_dict.get('minggong', ''),
        shengong      = bazi_dict.get('shengong', ''),
        qiyun         = bazi_dict.get('qiyun', {}),
        dayun         = bazi_dict.get('dayun', []),
        _lunar        = None,
    )


def calculate_fate(name: str, year, month, day, hour, minute, gender) -> dict:
    """
    Public entry point. Returns dict for JSON serialization.
    For in-memory Fate object, use dict_to_fate() to reconstruct.
    """
    fate_obj = _compute_fate(name, year, month, day, hour, minute, gender)
    return _fate_to_dict(fate_obj)


path: scripts/main.py
content: #!/usr/bin/env python3
"""
八字算命 CLI 入口
使用方式:
  python3 scripts/main.py add    <name> <year> <month> <day> <time> <gender>
  python3 scripts/main.py update <name> <year> <month> <day> <time> <gender>
  python3 scripts/main.py list
  python3 scripts/main.py view   [name]
  python3 scripts/main.py remove <name>
  python3 scripts/main.py active [name1,name2,...|none]
  python3 scripts/main.py status
  python3 scripts/main.py joint  [name1,name2,...]
"""
import sys, re
from calc import calculate_fate, dict_to_fate
from report import view_single, view_joint
from store import (load_users, save_users, load_active, save_active, now_iso,
                   ensure_dir)


# ── 时间解析 ─────────────────────────────────────────────────────────────────

def parse_time(time_str):
    """解析自然时间字符串，返回 (hour, minute)"""
    t = time_str.strip()
    t = re.sub(r'[点时]', '', t)

    is_pm = False
    if '下午' in t or '晚' in t or 'PM' in t.upper():
        is_pm = True
        t = re.sub(r'下午|晚|PM', '', t, flags=re.IGNORECASE).strip()
    if '上午' in t or '早' in t or 'AM' in t.upper():
        t = re.sub(r'上午|早|AM', '', t, flags=re.IGNORECASE).strip()

    if ':' in t:
        parts = t.split(':')
        h = int(parts[0])
        m = int(parts[1]) if len(parts) > 1 else 0
    else:
        h = int(t) if t else 0
        m = 0

    if is_pm and h < 12:
        h += 12

    return h, m


# ── 命令实现 ─────────────────────────────────────────────────────────────────

def cmd_add():
    args = sys.argv[2:]
    if len(args) < 5:
        print("用法: python3 scripts/main.py add <name> <year> <month> <day> <time> <gender>")
        sys.exit(1)

    name = args[0]
    year, month, day = args[1], args[2], args[3]
    time_str = args[4]
    gender = args[-1]
    hour, minute = parse_time(time_str)

    users = load_users()
    if name in users:
        print(f"'{name}' 已存在，用 update 命令更新。")
        sys.exit(1)

    fate_dict = calculate_fate(name, year, month, day, hour, minute, gender)
    users[name] = {
        "birth": {"year": int(year), "month": int(month), "day": int(day),
                  "hour": int(hour), "minute": int(minute), "gender": gender},
        "bazi": fate_dict,
        "fetched_at": now_iso(),
    }
    save_users(users)
    bazi_s = " ".join(fate_dict['bazi'].values()) if isinstance(fate_dict.get('bazi'), dict) else str(fate_dict.get('bazi', ''))
    print(f"✅ 已添加：{name}")
    print(f"   八字：{bazi_s}")
    print(f"   命格：{fate_dict['mingge']} | {fate_dict['wangshuai']} | 喜用：{fate_dict['xiyong']}")


def cmd_update():
    args = sys.argv[2:]
    if len(args) < 5:
        print("用法: python3 scripts/main.py update <name> <year> <month> <day> <time> <gender>")
        sys.exit(1)

    name = args[0]
    year, month, day = args[1], args[2], args[3]
    time_str = args[4]
    gender = args[-1]
    hour, minute = parse_time(time_str)

    users = load_users()
    fate_dict = calculate_fate(name, year, month, day, hour, minute, gender)
    users[name] = {
        "birth": {"year": int(year), "month": int(month), "day": int(day),
                  "hour": int(hour), "minute": int(minute), "gender": gender},
        "bazi": fate_dict,
        "fetched_at": now_iso(),
    }
    save_users(users)
    bazi_s = " ".join(fate_dict['bazi'].values()) if isinstance(fate_dict.get('bazi'), dict) else str(fate_dict.get('bazi', ''))
    print(f"✅ 已更新：{name}")
    print(f"   八字：{bazi_s}")


def cmd_list():
    users = load_users()
    if not users:
        print("还没有保存任何八字。")
        return
    for name, data in users.items():
        bazi = data.get("bazi", {})
        if isinstance(bazi, dict):
            raw = bazi.get("bazi", {})
            bazi_str = " ".join(raw.values()) if isinstance(raw, dict) else str(raw)
        else:
            bazi_str = "?"
        birth = data["birth"]
        print(f"• {name}：{birth['year']}年{birth['month']}月{birth['day']}日 {bazi_str} ({birth['gender']})")


def cmd_view():
    users = load_users()
    args = sys.argv[2:]

    if args:
        name = args[0]
    else:
        active = load_active()
        if not active.get("names"):
            print("未指定姓名，且无激活用户。")
            sys.exit(1)
        name = active["names"][0]

    if name not in users:
        print(f"未找到：{name}")
        sys.exit(1)

    # dict → Fate dataclass（供 report.py 使用）
    user_data = users[name]
    fate_obj  = dict_to_fate(user_data, user_data["birth"], user_data.get("gender", "男"))
    print(view_single(name, {"birth": user_data["birth"], "bazi": fate_obj}))


def cmd_remove():
    users = load_users()
    args = sys.argv[2:]
    if not args:
        print("用法: python3 scripts/main.py remove <name>")
        sys.exit(1)

    name = args[0]
    if name not in users:
        print(f"未找到：{name}")
        sys.exit(1)

    del users[name]
    save_users(users)

    active = load_active()
    if name in active.get("names", []):
        active["names"] = [n for n in active["names"] if n != name]
        save_active(active)

    print(f"✅ 已删除：{name}")


def cmd_active():
    users = load_users()
    args = sys.argv[2:]

    if not args:
        active = load_active()
        if active.get("names"):
            print("当前激活：" + ", ".join(active["names"]))
        else:
            print("当前无激活用户")
        return

    names_input = args[0]
    if names_input.lower() == "none":
        names = []
    else:
        names = [n.strip() for n in names_input.split(",")]
        for n in names:
            if n not in users:
                print(f"错误：'{n}' 未找到，请先用 add 添加。")
                sys.exit(1)

    save_active({"active": True, "names": names})
    if names:
        print(f"✅ 已激活：{', '.join(names)}")
    else:
        print("✅ 已取消激活")


def cmd_status():
    active = load_active()
    users = load_users()
    if not users:
        print("NOT_FOUND:无任何用户")
        return
    if not active.get("active") or not active.get("names"):
        print("NOT_ACTIVE")
        return
    for n in active["names"]:
        if n not in users:
            print(f"NOT_ACTIVE:{n}已删除")
            return
    print(f"ACTIVE:{','.join(active['names'])}")


def cmd_joint():
    users = load_users()
    args = sys.argv[2:]

    if args:
        names = [n.strip() for n in args[0].split(",")]
    else:
        active = load_active()
        names = active.get("names", [])
        if not names:
            print("未指定用户，且无激活用户。请用 active 或 joint name1,name2")
            sys.exit(1)

    if len(names) < 2:
        print("联合分析至少需要2个人。")
        sys.exit(1)

    data_list = []
    for name in names:
        if name not in users:
            print(f"未找到：{name}")
            sys.exit(1)
        ud = users[name]
        fate_obj  = dict_to_fate(ud, ud["birth"], ud.get("gender", "男"))
        data_list.append({"name": name, "birth": ud["birth"], "bazi": fate_obj})

    print(view_joint(data_list))


# ── 入口 ─────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python3 scripts/main.py <add|update|list|view|remove|active|status|joint>")
        sys.exit(1)

    cmd_map = {
        "add":    cmd_add,
        "update": cmd_update,
        "list":   cmd_list,
        "view":   cmd_view,
        "remove": cmd_remove,
        "active": cmd_active,
        "status": cmd_status,
        "joint":  cmd_joint,
    }

    cmd = sys.argv[1]
    if cmd in cmd_map:
        cmd_map[cmd]()
    else:
        print(f"未知命令：{cmd}")
        sys.exit(1)


path: scripts/report.py
content: #!/usr/bin/env python3
"""
八字展示报告模块

与 scripts/calc.py 的 Fate dataclass 解耦：
  - 接收 Fate 对象或 dict（兼容旧数据）
  - 输出格式化字符串

命名映射（scripts/calc.py → scripts/report.py）：
  stem/stem_el    = 天干/天干五行
  branch/branch_el = 地支/地支五行
  ten_god         = 十神
  na_yin          = 纳音
  hidden_stems    = 藏干
  void            = 旬空
  vitality        = 旺衰
  favorable       = 喜用
  afflictions     = 神煞
"""
from typing import List
from calc import Fate


def _safe_get(obj, key, default=''):
    """兼容 Fate dataclass 和 dict"""
    if isinstance(obj, dict):
        return obj.get(key, default)
    return getattr(obj, key, default)


def _get_pillar_detail(fate, pillar_key, is_fate=True):
    """
    取某柱的完整数据
    is_fate=True  → Fate dataclass
    is_fate=False → dict（旧格式）
    """
    if is_fate:
        pillar = getattr(fate, f"{pillar_key}_pillar", None)
        if pillar is None:
            return {}
        return {
            'stem':        pillar.stem,
            'branch':      pillar.branch,
            'stem_el':     pillar.stem_el,
            'branch_el':   pillar.branch_el,
            'yinyang':     pillar.yinyang,
            'ten_god':     pillar.ten_god,
            'na_yin':      pillar.na_yin,
            'hidden':      pillar.hidden,
            'vacant':      pillar.vacant,
        }
    # dict 旧格式
    detail = fate.get('detail', {})
    return detail.get(pillar_key, {})


def view_single(name, user_data):
    """
    输出单人完整八字报告
    user_data: dict with 'birth' + 'bazi' keys
    bazi 可能是 dict（旧格式）或 Fate dataclass（新格式）
    """
    birth   = user_data["birth"]
    bazi    = user_data.get("bazi", {})
    is_fate = isinstance(bazi, Fate)

    gender_d = "男" if str(birth.get("gender", birth.get("gender_d", 1))) in ('1', '男') else "女"

    hour_str = (
        f"{birth['hour']}:{int(birth.get('minute', 0)):02d}"
        if birth.get('minute') is not None
        else f"{birth['hour']}点"
    )

    # ── 从 bazi 取字段（统一接口） ────────────────────────────────────────────
    if is_fate:
        f         = bazi
        bazi_str  = f.bazi_str
        lunar_str = f.lunar_str
        shengxiao = f.shengxiao
        mingge    = f.mingge
        vitality  = f.vitality
        favorable = f.favorable
        void      = f.void
        vacant    = f.void
        afflictions = f.afflictions
        taiyuan   = f.taiyuan
        taixi     = f.taixi
        life_palace = f.minggong    # 命宫
        destiny_palace = f.shengong  # 身宫
        dayun     = f.dayun
        day_stem  = f.day_stem
        elem_counts = f.wuxing_count

        # 四柱干支
        bazi_ganzhi = {
            "year":  f.year_pillar.stem  + f.year_pillar.branch,
            "month": f.month_pillar.stem + f.month_pillar.branch,
            "day":   f.day_pillar.stem   + f.day_pillar.branch,
            "hour":  f.hour_pillar.stem  + f.hour_pillar.branch,
        }

        # 五行字符串
        wx_str = ' '.join([
            f.year_pillar.stem_el  + f.year_pillar.branch_el,
            f.month_pillar.stem_el + f.month_pillar.branch_el,
            f.day_pillar.stem_el   + f.day_pillar.branch_el,
            f.hour_pillar.stem_el  + f.hour_pillar.branch_el,
        ])

        # 纳音
        nayin_y = f.year_pillar.na_yin
        nayin_m = f.month_pillar.na_yin
        nayin_d = f.day_pillar.na_yin
        nayin_h = f.hour_pillar.na_yin
        yinyang = f.day_pillar.yinyang

    else:
        # 旧 dict 格式（兼容）
        bazi_ganzhi_raw = bazi.get("bazi", {})
        if isinstance(bazi_ganzhi_raw, dict):
            bazi_str = " ".join(bazi_ganzhi_raw.values())
            bazi_ganzhi = bazi_ganzhi_raw
        else:
            bazi_str = str(bazi_ganzhi_raw)
            bazi_ganzhi = {}

        lunar_str     = bazi.get('lunar_str', '')
        shengxiao     = bazi.get('shengxiao', '')
        mingge        = bazi.get('mingge', '')
        vitality      = bazi.get('wangshuai', '')
        favorable     = bazi.get('xiyong', '')
        vacant        = bazi.get('kongwang', '')
        afflictions   = bazi.get('shensha', [])
        taiyuan       = bazi.get('taiyuan', '')
        taixi         = bazi.get('taixi', '')
        life_palace   = bazi.get('minggong', '')
        destiny_palace= bazi.get('shengong', '')
        dayun         = bazi.get('dayun', [])
        day_stem      = bazi.get('me', '')
        elem_counts   = bazi.get('wuxing_count', {})

        wx_str        = bazi.get('wuxing', '')
        nayin_y       = bazi.get('nayin', {}).get('year', '')
        nayin_m       = bazi.get('nayin', {}).get('month', '')
        nayin_d       = bazi.get('nayin', {}).get('day', '')
        nayin_h       = bazi.get('nayin', {}).get('hour', '')
        yinyang       = bazi.get('yinyang', '')

    # ── 逐柱输出 ──────────────────────────────────────────────────────────────
    lines = [
        f"━━━ {name} 八字详情 ━━━",
        f"性别：{gender_d}",
        f"公历：{birth['year']}年{birth['month']}月{birth['day']}日{hour_str}",
        f"农历：{lunar_str}",
        f"生肖：{shengxiao}",
        f"━━━ 四柱 ━━━",
    ]

    for p_key, label in [('year','年柱'),('month','月柱'),('day','日柱'),('hour','时柱')]:
        d = _get_pillar_detail(bazi, p_key, is_fate)

        stem_v     = d.get('stem', '')
        branch_v    = d.get('branch', '')
        stem_el_v   = d.get('stem_el', '')
        branch_el_v = d.get('branch_el', '')
        yinyang_v   = d.get('yinyang', '')
        ten_god_v   = d.get('ten_god', '日主' if p_key == 'day' else '')
        na_yin_v    = d.get('na_yin', '')
        hidden_v    = d.get('hidden', [])
        vacant_v    = d.get('vacant', '')
        ganzhi_v    = bazi_ganzhi.get(p_key, '') if isinstance(bazi_ganzhi, dict) else ''

        lines.append(f"{label}：{ganzhi_v}（{na_yin_v}）")
        lines.append(f"  天干：{stem_v} {yinyang_v} {stem_el_v} 【{ten_god_v}】")
        lines.append(f"  地支：{branch_v} {yinyang_v} 藏干：{','.join(hidden_v)} 旬空：{vacant_v}")

    lines += [
        f"━━━ 命理 ━━━",
        f"日主：{day_stem}（{yinyang}）",
        f"五行：{wx_str}",
        f"  统计：{elem_counts}",
        f"纳音：{nayin_y} / {nayin_m} / {nayin_d} / {nayin_h}",
        f"命格：{mingge}",
        f"旺衰：{vitality}",
        f"喜用：{favorable}",
        f"旬空：{vacant}",
        f"胎元：{taiyuan}",
        f"胎息：{taixi}",
        f"命宫：{life_palace}",
        f"身宫：{destiny_palace}",
        f"神煞：{', '.join(afflictions) if afflictions else '无'}",
        f"━━━ 大运 ━━━",
    ]

    if dayun:
        for i, d in enumerate(dayun[:8]):
            lines.append(
                f"  {i+1}. {d.get('ganzhi','')} "
                f"{d.get('start_age','')}-{d.get('end_age','')}岁"
                f"（{d.get('start_year','')}年）旬空：{d.get('vacant', d.get('void',''))}"
            )
    else:
        lines.append("  （大运数据不可用）")

    return '\n'.join(lines)


# ════════════════════════════════════════════════════════════════════════════════
# 联合分析
# ════════════════════════════════════════════════════════════════════════════════

def _get_branch(bazi, pillar_key, is_fate):
    """取某柱地支"""
    if is_fate:
        pillar = getattr(bazi, f"{pillar_key}_pillar", None)
        return pillar.branch if pillar else ''
    raw = bazi.get("bazi", {})
    if isinstance(raw, dict):
        v = raw.get(pillar_key, '')
        return v[-1:] if v else ''
    return ''


def _get_stems(bazi, is_fate):
    """取四柱天干列表"""
    if is_fate:
        return [
            bazi.year_pillar.stem, bazi.month_pillar.stem,
            bazi.day_pillar.stem,  bazi.hour_pillar.stem,
        ]
    raw = bazi.get("bazi", {})
    if isinstance(raw, dict):
        return [v[:-1] if v else '' for v in raw.values()]
    return []


def _branch_interaction(branches: List[str]) -> str:
    """
    判断地支间合冲刑害关系
    branches: 地支列表
    """
    from calc import BranchRelation

    rels, seen = [], set()
    for i in range(len(branches)):
        for j in range(i + 1, len(branches)):
            a, b = branches[i], branches[j]
            if not a or not b:
                continue
            key = (a, b)
            if key in BranchRelation.HEX:
                r = f"{a}{BranchRelation.HEX[key][-1]}{b}"
                if r not in seen:
                    seen.add(r); rels.append(f"{r}（吉）")
            elif key in BranchRelation.CLASH:
                r = f"{a}{BranchRelation.CLASH[key][-1]}{b}"
                if r not in seen:
                    seen.add(r); rels.append(f"{r}（冲）")
            elif key in BranchRelation.PUNISHMENT:
                r = BranchRelation.PUNISHMENT[key]
                if r not in seen:
                    seen.add(r); rels.append(f"{r}（刑）")
    return '无明显合冲刑害' if not rels else ' / '.join(rels)


def _wuxing_affinity(char_sets: List[str]) -> str:
    """
    五行相生相克分析
    char_sets: 每个八字的所有字符（干支混合）
    """
    from calc import Stem

    def char_to_element(c: str) -> str:
        if c in Stem.ELEMENT:
            return Stem.ELEMENT[c]
        from calc import Branch
        if c in Branch.ELEMENT:
            return Branch.ELEMENT[c]
        return ''

    sheng_pairs = {('木','火'), ('火','土'), ('土','金'), ('金','水'), ('水','木')}

    elem_sets = []
    for chars in char_sets:
        elems = set()
        for c in chars:
            e = char_to_element(c)
            if e:
                elems.add(e)
        elem_sets.append(elems)

    results = []
    for i in range(len(elem_sets)):
        for j in range(i + 1, len(elem_sets)):
            for e1 in elem_sets[i]:
                for e2 in elem_sets[j]:
                    if (e1, e2) in sheng_pairs:
                        results.append(f"{e1}→{e2}")
                    if (e2, e1) in sheng_pairs:
                        results.append(f"{e2}→{e1}")

    if not results:
        return "五行无明显相生关系，较为独立"
    seen = list(dict.fromkeys(results))
    return "五行相生：" + '、'.join(seen)


def _marriage_analysis(d1: dict, d2: dict) -> str:
    """婚配分析（两人）"""
    from calc import BranchRelation

    def zhi_rel(zhis):
        rels, seen = [], set()
        for i in range(len(zhis)):
            for j in range(i + 1, len(zhis)):
                a, b = zhis[i], zhis[j]
                if not a or not b:
                    continue
                key = (a, b)
                if key in BranchRelation.HEX:
                    r = f"{a}{BranchRelation.HEX[key][-1]}{b}"
                    if r not in seen: seen.add(r); rels.append(f"{r}（吉）")
                elif key in BranchRelation.CLASH:
                    r = f"{a}{BranchRelation.CLASH[key][-1]}{b}"
                    if r not in seen: seen.add(r); rels.append(f"{r}（冲）")
                elif key in BranchRelation.PUNISHMENT:
                    r = BranchRelation.PUNISHMENT[key]
                    if r not in seen: seen.add(r); rels.append(f"{r}（刑）")
        return '无明显合冲刑害' if not rels else ' / '.join(rels)

    def get_key(bazi, key, default=''):
        if isinstance(bazi, Fate):
            val = getattr(bazi, key, None)
            if val is not None and val != '':
                return val
            # xiyong fallback → favorable（兼容 xiyong 为空的 Fate 对象）
            if key == 'xiyong':
                return getattr(bazi, 'favorable', '')
            return default
        return bazi.get(key, default)

    def get_branch(bazi, pillar_key):
        return _get_branch(bazi, pillar_key, isinstance(bazi, Fate))

    bazi1, bazi2 = d1["bazi"], d2["bazi"]

    yz1 = get_branch(bazi1, 'year')
    yz2 = get_branch(bazi2, 'year')
    dz1 = get_branch(bazi1, 'day')
    dz2 = get_branch(bazi2, 'day')

    favorable1 = get_key(bazi1, 'xiyong', '')
    favorable2 = get_key(bazi2, 'xiyong', '')

    shensha1 = set(get_key(bazi1, 'shensha', []))
    shensha2 = set(get_key(bazi2, 'shensha', []))

    lines = [
        f"日支关系：{zhi_rel([dz1, dz2])}（{d1['name']}:{dz1} / {d2['name']}:{dz2}）",
        f"年支关系：{zhi_rel([yz1, yz2])}（{d1['name']}:{yz1} / {d2['name']}:{yz2}）",
        f"喜用互补：{favorable1[:30]} | {favorable2[:30]}",
    ]

    if "桃花" in shensha1 and "桃花" in shensha2:
        lines.append("⚠️ 双方均带桃花，感情需注意经营")
    if "天乙贵人" in shensha1 and "天乙贵人" in shensha2:
        lines.append("✅ 双方均有天乙贵人，互助有缘")

    return '\n'.join(lines)


def view_joint(data_list):
    """
    输出多人联合分析报告
    data_list: [{"name": str, "birth": dict, "bazi": Fate/dict}, ...]
    """
    lines = ["━━━ 联合分析 ━━━"]

    for d in data_list:
        bazi    = d["bazi"]
        is_fate = isinstance(bazi, Fate)
        gender_d = "男" if str(d["birth"].get("gender", '1')) in ('1', '男') else "女"

        if is_fate:
            bazi_str = bazi.bazi_str
            mingge_v = bazi.mingge
        else:
            raw = bazi.get("bazi", {})
            bazi_str = " ".join(raw.values()) if isinstance(raw, dict) else str(raw)
            mingge_v = bazi.get("mingge", "")

        lines.append(f"【{d['name']}】{gender_d} | {bazi_str} | {mingge_v}")

    lines.append("━━━ 合冲刑害 ━━━")

    # 年支关系
    year_rels = [_get_branch(d["bazi"], "year", isinstance(d["bazi"], Fate)) for d in data_list]
    year_rel_label = _branch_interaction(year_rels)
    year_zhis_label = ', '.join([f"{d['name']}{z}" for d, z in zip(data_list, year_rels)])
    lines.append(f"年支关系：{year_rel_label}（{year_zhis_label}）")

    # 日支关系
    day_rels = [_get_branch(d["bazi"], "day", isinstance(d["bazi"], Fate)) for d in data_list]
    day_rel_label = _branch_interaction(day_rels)
    day_zhis_label = ', '.join([f"{d['name']}{z}" for d, z in zip(data_list, day_rels)])
    lines.append(f"日支关系：{day_rel_label}（{day_zhis_label}）")

    # 月支关系
    month_rels = [_get_branch(d["bazi"], "month", isinstance(d["bazi"], Fate)) for d in data_list]
    lines.append(
        f"月支关系：{_branch_interaction(month_rels)}"
    )

    # 五行契合
    if len(data_list) >= 2:
        char_sets = []
        for d in data_list:
            bazi = d["bazi"]
            if isinstance(bazi, Fate):
                chars = (
                    bazi.year_pillar.stem  + bazi.year_pillar.branch  +
                    bazi.month_pillar.stem + bazi.month_pillar.branch +
                    bazi.day_pillar.stem   + bazi.day_pillar.branch   +
                    bazi.hour_pillar.stem  + bazi.hour_pillar.branch
                )
            else:
                raw = bazi.get("bazi", {})
                chars = ''.join(raw.values()) if isinstance(raw, dict) else ''
            char_sets.append(chars)
        lines += ["", "━━━ 五行契合 ━━━", _wuxing_affinity(char_sets)]

    # 婚配分析（两人）
    if len(data_list) == 2:
        lines += ["", "━━━ 婚配分析 ━━━", _marriage_analysis(data_list[0], data_list[1])]

    return '\n'.join(lines)


path: scripts/lunar_core.py
content: #!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
lunar_core — Self-contained Solar->Lunar->EightChar engine.
Zero external dependencies beyond this file.
"""
from __future__ import annotations

LY_DATA = {
  1899: (0, 443, 2414637, [(11, 2414637, 30, False), (12, 2414667, 29, False), (1, 2414696, 30, False), (2, 2414726, 29, False), (3, 2414755, 30, False), (4, 2414785, 29, False), (5, 2414814, 30, False), (6, 2414844, 29, False), (7, 2414873, 30, False), (8, 2414903, 30, False), (9, 2414933, 29, False), (10, 2414962, 30, False), (11, 2414992, 29, False), (12, 2415021, 30, False), (1, 2415051, 29, False)]),
  1900: (8, 443, 2414992, [(11, 2414992, 29, False), (12, 2415021, 30, False), (1, 2415051, 29, False), (2, 2415080, 30, False), (3, 2415110, 29, False), (4, 2415139, 29, False), (5, 2415168, 30, False), (6, 2415198, 29, False), (7, 2415227, 30, False), (8, 2415257, 30, False), (8, 2415287, 29, True), (9, 2415316, 30, False), (10, 2415346, 30, False), (11, 2415376, 29, False), (12, 2415405, 30, False)]),
  1901: (0, 443, 2415376, [(11, 2415376, 29, False), (12, 2415405, 30, False), (1, 2415435, 29, False), (2, 2415464, 30, False), (3, 2415494, 29, False), (4, 2415523, 29, False), (5, 2415552, 30, False), (6, 2415582, 29, False), (7, 2415611, 30, False), (8, 2415641, 29, False), (9, 2415670, 30, False), (10, 2415700, 30, False), (11, 2415730, 30, False), (12, 2415760, 29, False), (1, 2415789, 30, False)]),
  1902: (0, 443, 2415730, [(11, 2415730, 30, False), (12, 2415760, 29, False), (1, 2415789, 30, False), (2, 2415819, 29, False), (3, 2415848, 30, False), (4, 2415878, 29, False), (5, 2415907, 29, False), (6, 2415936, 30, False), (7, 2415966, 29, False), (8, 2415995, 30, False), (9, 2416025, 29, False), (10, 2416054, 30, False), (11, 2416084, 30, False), (12, 2416114, 30, False), (1, 2416144, 29, False)]),
  1903: (5, 443, 2416084, [(11, 2416084, 30, False), (12, 2416114, 30, False), (1, 2416144, 29, False), (2, 2416173, 30, False), (3, 2416203, 29, False), (4, 2416232, 30, False), (5, 2416262, 29, False), (5, 2416291, 29, True), (6, 2416320, 30, False), (7, 2416350, 29, False), (8, 2416379, 29, False), (9, 2416408, 30, False), (10, 2416438, 30, False), (11, 2416468, 29, False), (12, 2416497, 30, False)]),
  1904: (0, 443, 2416468, [(11, 2416468, 29, False), (12, 2416497, 30, False), (1, 2416527, 30, False), (2, 2416557, 30, False), (3, 2416587, 29, False), (4, 2416616, 30, False), (5, 2416646, 29, False), (6, 2416675, 29, False), (7, 2416704, 30, False), (8, 2416734, 29, False), (9, 2416763, 29, False), (10, 2416792, 30, False), (11, 2416822, 30, False), (12, 2416852, 29, False), (1, 2416881, 30, False)]),
  1905: (0, 443, 2416822, [(11, 2416822, 30, False), (12, 2416852, 29, False), (1, 2416881, 30, False), (2, 2416911, 30, False), (3, 2416941, 29, False), (4, 2416970, 30, False), (5, 2417000, 30, False), (6, 2417030, 29, False), (7, 2417059, 29, False), (8, 2417088, 30, False), (9, 2417118, 29, False), (10, 2417147, 30, False), (11, 2417177, 29, False), (12, 2417206, 30, False), (1, 2417236, 29, False)]),
  1906: (4, 443, 2417177, [(11, 2417177, 29, False), (12, 2417206, 30, False), (1, 2417236, 29, False), (2, 2417265, 30, False), (3, 2417295, 30, False), (4, 2417325, 29, False), (4, 2417354, 30, True), (5, 2417384, 29, False), (6, 2417413, 30, False), (7, 2417443, 29, False), (8, 2417472, 30, False), (9, 2417502, 29, False), (10, 2417531, 30, False), (11, 2417561, 29, False), (12, 2417590, 30, False)]),
  1907: (0, 443, 2417561, [(11, 2417561, 29, False), (12, 2417590, 30, False), (1, 2417620, 29, False), (2, 2417649, 30, False), (3, 2417679, 29, False), (4, 2417708, 30, False), (5, 2417738, 29, False), (6, 2417767, 30, False), (7, 2417797, 30, False), (8, 2417827, 29, False), (9, 2417856, 30, False), (10, 2417886, 29, False), (11, 2417915, 30, False), (12, 2417945, 29, False), (1, 2417974, 30, False)]),
  1908: (0, 443, 2417915, [(11, 2417915, 30, False), (12, 2417945, 29, False), (1, 2417974, 30, False), (2, 2418004, 29, False), (3, 2418033, 29, False), (4, 2418062, 30, False), (5, 2418092, 30, False), (6, 2418122, 29, False), (7, 2418151, 30, False), (8, 2418181, 29, False), (9, 2418210, 30, False), (10, 2418240, 30, False), (11, 2418270, 29, False), (12, 2418299, 30, False), (1, 2418329, 29, False)]),
  1909: (2, 443, 2418270, [(11, 2418270, 29, False), (12, 2418299, 30, False), (1, 2418329, 29, False), (2, 2418358, 30, False), (2, 2418388, 29, True), (3, 2418417, 29, False), (4, 2418446, 30, False), (5, 2418476, 29, False), (6, 2418505, 30, False), (7, 2418535, 29, False), (8, 2418564, 30, False), (9, 2418594, 30, False), (10, 2418624, 30, False), (11, 2418654, 29, False), (12, 2418683, 30, False)]),
  1910: (0, 443, 2418654, [(11, 2418654, 29, False), (12, 2418683, 30, False), (1, 2418713, 29, False), (2, 2418742, 30, False), (3, 2418772, 29, False), (4, 2418801, 29, False), (5, 2418830, 30, False), (6, 2418860, 29, False), (7, 2418889, 30, False), (8, 2418919, 29, False), (9, 2418948, 30, False), (10, 2418978, 30, False), (11, 2419008, 30, False), (12, 2419038, 29, False), (1, 2419067, 30, False)]),
  1911: (6, 443, 2419008, [(11, 2419008, 30, False), (12, 2419038, 29, False), (1, 2419067, 30, False), (2, 2419097, 29, False), (3, 2419126, 30, False), (4, 2419156, 29, False), (5, 2419185, 29, False), (6, 2419214, 30, False), (6, 2419244, 29, True), (7, 2419273, 29, False), (8, 2419302, 30, False), (9, 2419332, 30, False), (10, 2419362, 29, False), (11, 2419391, 30, False), (12, 2419421, 30, False)]),
  1912: (0, 444, 2419391, [(11, 2419391, 30, False), (12, 2419421, 30, False), (1, 2419451, 30, False), (2, 2419481, 29, False), (3, 2419510, 30, False), (4, 2419540, 29, False), (5, 2419569, 29, False), (6, 2419598, 30, False), (7, 2419628, 29, False), (8, 2419657, 29, False), (9, 2419686, 30, False), (10, 2419716, 30, False), (11, 2419746, 29, False), (12, 2419775, 30, False), (1, 2419805, 30, False)]),
  1913: (0, 443, 2419746, [(11, 2419746, 29, False), (12, 2419775, 30, False), (1, 2419805, 30, False), (2, 2419835, 30, False), (3, 2419865, 29, False), (4, 2419894, 30, False), (5, 2419924, 29, False), (6, 2419953, 29, False), (7, 2419982, 30, False), (8, 2420012, 29, False), (9, 2420041, 29, False), (10, 2420070, 30, False), (11, 2420100, 29, False), (12, 2420129, 30, False), (1, 2420159, 30, False)]),
  1914: (5, 443, 2420100, [(11, 2420100, 29, False), (12, 2420129, 30, False), (1, 2420159, 30, False), (2, 2420189, 30, False), (3, 2420219, 29, False), (4, 2420248, 30, False), (5, 2420278, 29, False), (5, 2420307, 30, True), (6, 2420337, 29, False), (7, 2420366, 30, False), (8, 2420396, 29, False), (9, 2420425, 29, False), (10, 2420454, 30, False), (11, 2420484, 29, False), (12, 2420513, 30, False)]),
  1915: (0, 443, 2420484, [(11, 2420484, 29, False), (12, 2420513, 30, False), (1, 2420543, 30, False), (2, 2420573, 29, False), (3, 2420602, 30, False), (4, 2420632, 30, False), (5, 2420662, 29, False), (6, 2420691, 30, False), (7, 2420721, 29, False), (8, 2420750, 30, False), (9, 2420780, 29, False), (10, 2420809, 30, False), (11, 2420839, 29, False), (12, 2420868, 29, False), (1, 2420897, 30, False)]),
  1916: (0, 443, 2420839, [(11, 2420839, 29, False), (12, 2420868, 29, False), (1, 2420897, 30, False), (2, 2420927, 30, False), (3, 2420957, 29, False), (4, 2420986, 30, False), (5, 2421016, 29, False), (6, 2421045, 30, False), (7, 2421075, 30, False), (8, 2421105, 29, False), (9, 2421134, 30, False), (10, 2421164, 29, False), (11, 2421193, 30, False), (12, 2421223, 29, False), (1, 2421252, 30, False)]),
  1917: (2, 443, 2421193, [(11, 2421193, 30, False), (12, 2421223, 29, False), (1, 2421252, 30, False), (2, 2421282, 29, False), (2, 2421311, 29, True), (3, 2421340, 30, False), (4, 2421370, 29, False), (5, 2421399, 30, False), (6, 2421429, 30, False), (7, 2421459, 29, False), (8, 2421488, 30, False), (9, 2421518, 30, False), (10, 2421548, 29, False), (11, 2421577, 30, False), (12, 2421607, 29, False)]),
  1918: (0, 443, 2421577, [(11, 2421577, 30, False), (12, 2421607, 29, False), (1, 2421636, 30, False), (2, 2421666, 29, False), (3, 2421695, 29, False), (4, 2421724, 30, False), (5, 2421754, 29, False), (6, 2421783, 30, False), (7, 2421813, 29, False), (8, 2421842, 30, False), (9, 2421872, 30, False), (10, 2421902, 29, False), (11, 2421931, 30, False), (12, 2421961, 30, False), (1, 2421991, 29, False)]),
  1919: (7, 444, 2421931, [(11, 2421931, 30, False), (12, 2421961, 30, False), (1, 2421991, 29, False), (2, 2422020, 30, False), (3, 2422050, 29, False), (4, 2422079, 29, False), (5, 2422108, 30, False), (6, 2422138, 29, False), (7, 2422167, 29, False), (7, 2422196, 30, True), (8, 2422226, 30, False), (9, 2422256, 29, False), (10, 2422285, 30, False), (11, 2422315, 30, False), (12, 2422345, 30, False)]),
  1920: (0, 444, 2422315, [(11, 2422315, 30, False), (12, 2422345, 30, False), (1, 2422375, 29, False), (2, 2422404, 30, False), (3, 2422434, 29, False), (4, 2422463, 29, False), (5, 2422492, 30, False), (6, 2422522, 29, False), (7, 2422551, 29, False), (8, 2422580, 30, False), (9, 2422610, 29, False), (10, 2422639, 30, False), (11, 2422669, 30, False), (12, 2422699, 30, False), (1, 2422729, 30, False)]),
  1921: (0, 444, 2422669, [(11, 2422669, 30, False), (12, 2422699, 30, False), (1, 2422729, 30, False), (2, 2422759, 29, False), (3, 2422788, 30, False), (4, 2422818, 29, False), (5, 2422847, 29, False), (6, 2422876, 30, False), (7, 2422906, 29, False), (8, 2422935, 29, False), (9, 2422964, 30, False), (10, 2422994, 29, False), (11, 2423023, 30, False), (12, 2423053, 30, False), (1, 2423083, 30, False)]),
  1922: (5, 444, 2423023, [(11, 2423023, 30, False), (12, 2423053, 30, False), (1, 2423083, 30, False), (2, 2423113, 29, False), (3, 2423142, 30, False), (4, 2423172, 30, False), (5, 2423202, 29, False), (5, 2423231, 29, True), (6, 2423260, 30, False), (7, 2423290, 29, False), (8, 2423319, 29, False), (9, 2423348, 30, False), (10, 2423378, 29, False), (11, 2423407, 30, False), (12, 2423437, 30, False)]),
  1923: (0, 443, 2423407, [(11, 2423407, 30, False), (12, 2423437, 30, False), (1, 2423467, 29, False), (2, 2423496, 30, False), (3, 2423526, 30, False), (4, 2423556, 29, False), (5, 2423585, 30, False), (6, 2423615, 29, False), (7, 2423644, 30, False), (8, 2423674, 29, False), (9, 2423703, 29, False), (10, 2423732, 30, False), (11, 2423762, 29, False), (12, 2423791, 30, False), (1, 2423821, 29, False)]),
  1924: (0, 443, 2423762, [(11, 2423762, 29, False), (12, 2423791, 30, False), (1, 2423821, 29, False), (2, 2423850, 30, False), (3, 2423880, 30, False), (4, 2423910, 29, False), (5, 2423939, 30, False), (6, 2423969, 30, False), (7, 2423999, 29, False), (8, 2424028, 30, False), (9, 2424058, 29, False), (10, 2424087, 30, False), (11, 2424117, 29, False), (12, 2424146, 29, False), (1, 2424175, 30, False)]),
  1925: (4, 443, 2424117, [(11, 2424117, 29, False), (12, 2424146, 29, False), (1, 2424175, 30, False), (2, 2424205, 29, False), (3, 2424234, 30, False), (4, 2424264, 29, False), (4, 2424293, 30, True), (5, 2424323, 30, False), (6, 2424353, 29, False), (7, 2424382, 30, False), (8, 2424412, 30, False), (9, 2424442, 29, False), (10, 2424471, 30, False), (11, 2424501, 29, False), (12, 2424530, 30, False)]),
  1926: (0, 443, 2424501, [(11, 2424501, 29, False), (12, 2424530, 30, False), (1, 2424560, 29, False), (2, 2424589, 29, False), (3, 2424618, 30, False), (4, 2424648, 29, False), (5, 2424677, 30, False), (6, 2424707, 29, False), (7, 2424736, 30, False), (8, 2424766, 30, False), (9, 2424796, 29, False), (10, 2424825, 30, False), (11, 2424855, 30, False), (12, 2424885, 29, False), (1, 2424914, 30, False)]),
  1927: (0, 443, 2424855, [(11, 2424855, 30, False), (12, 2424885, 29, False), (1, 2424914, 30, False), (2, 2424944, 29, False), (3, 2424973, 29, False), (4, 2425002, 30, False), (5, 2425032, 29, False), (6, 2425061, 30, False), (7, 2425091, 29, False), (8, 2425120, 30, False), (9, 2425150, 29, False), (10, 2425179, 30, False), (11, 2425209, 30, False), (12, 2425239, 30, False), (1, 2425269, 29, False)]),
  1928: (2, 444, 2425209, [(11, 2425209, 30, False), (12, 2425239, 30, False), (1, 2425269, 29, False), (2, 2425298, 30, False), (2, 2425328, 29, True), (3, 2425357, 29, False), (4, 2425386, 30, False), (5, 2425416, 29, False), (6, 2425445, 29, False), (7, 2425474, 30, False), (8, 2425504, 29, False), (9, 2425533, 30, False), (10, 2425563, 30, False), (11, 2425593, 30, False), (12, 2425623, 30, False)]),
  1929: (0, 443, 2425593, [(11, 2425593, 30, False), (12, 2425623, 30, False), (1, 2425653, 29, False), (2, 2425682, 30, False), (3, 2425712, 29, False), (4, 2425741, 29, False), (5, 2425770, 30, False), (6, 2425800, 29, False), (7, 2425829, 29, False), (8, 2425858, 30, False), (9, 2425888, 29, False), (10, 2425917, 30, False), (11, 2425947, 30, False), (12, 2425977, 30, False), (1, 2426007, 29, False)]),
  1930: (6, 443, 2425947, [(11, 2425947, 30, False), (12, 2425977, 30, False), (1, 2426007, 29, False), (2, 2426036, 30, False), (3, 2426066, 30, False), (4, 2426096, 29, False), (5, 2426125, 29, False), (6, 2426154, 30, False), (6, 2426184, 29, True), (7, 2426213, 29, False), (8, 2426242, 30, False), (9, 2426272, 29, False), (10, 2426301, 30, False), (11, 2426331, 30, False), (12, 2426361, 29, False)]),
  1931: (0, 443, 2426331, [(11, 2426331, 30, False), (12, 2426361, 29, False), (1, 2426390, 30, False), (2, 2426420, 30, False), (3, 2426450, 29, False), (4, 2426479, 30, False), (5, 2426509, 29, False), (6, 2426538, 30, False), (7, 2426568, 29, False), (8, 2426597, 29, False), (9, 2426626, 30, False), (10, 2426656, 29, False), (11, 2426685, 30, False), (12, 2426715, 29, False), (1, 2426744, 30, False)]),
  1932: (0, 443, 2426685, [(11, 2426685, 30, False), (12, 2426715, 29, False), (1, 2426744, 30, False), (2, 2426774, 30, False), (3, 2426804, 30, False), (4, 2426834, 29, False), (5, 2426863, 30, False), (6, 2426893, 29, False), (7, 2426922, 30, False), (8, 2426952, 29, False), (9, 2426981, 29, False), (10, 2427010, 30, False), (11, 2427040, 29, False), (12, 2427069, 30, False), (1, 2427099, 29, False)]),
  1933: (5, 443, 2427040, [(11, 2427040, 29, False), (12, 2427069, 30, False), (1, 2427099, 29, False), (2, 2427128, 30, False), (3, 2427158, 30, False), (4, 2427188, 29, False), (5, 2427217, 30, False), (5, 2427247, 30, True), (6, 2427277, 29, False), (7, 2427306, 30, False), (8, 2427336, 29, False), (9, 2427365, 30, False), (10, 2427395, 29, False), (11, 2427424, 29, False), (12, 2427453, 30, False)]),
  1934: (0, 443, 2427424, [(11, 2427424, 29, False), (12, 2427453, 30, False), (1, 2427483, 29, False), (2, 2427512, 30, False), (3, 2427542, 29, False), (4, 2427571, 30, False), (5, 2427601, 30, False), (6, 2427631, 29, False), (7, 2427660, 30, False), (8, 2427690, 29, False), (9, 2427719, 30, False), (10, 2427749, 30, False), (11, 2427779, 29, False), (12, 2427808, 30, False), (1, 2427838, 29, False)]),
  1935: (0, 443, 2427779, [(11, 2427779, 29, False), (12, 2427808, 30, False), (1, 2427838, 29, False), (2, 2427867, 29, False), (3, 2427896, 30, False), (4, 2427926, 29, False), (5, 2427955, 30, False), (6, 2427985, 29, False), (7, 2428014, 30, False), (8, 2428044, 30, False), (9, 2428074, 29, False), (10, 2428103, 30, False), (11, 2428133, 30, False), (12, 2428163, 29, False), (1, 2428192, 30, False)]),
  1936: (3, 443, 2428133, [(11, 2428133, 30, False), (12, 2428163, 29, False), (1, 2428192, 30, False), (2, 2428222, 29, False), (3, 2428251, 29, False), (3, 2428280, 30, True), (4, 2428310, 29, False), (5, 2428339, 29, False), (6, 2428368, 30, False), (7, 2428398, 30, False), (8, 2428428, 29, False), (9, 2428457, 30, False), (10, 2428487, 30, False), (11, 2428517, 30, False), (12, 2428547, 29, False)]),
  1937: (0, 443, 2428517, [(11, 2428517, 30, False), (12, 2428547, 29, False), (1, 2428576, 30, False), (2, 2428606, 29, False), (3, 2428635, 29, False), (4, 2428664, 30, False), (5, 2428694, 29, False), (6, 2428723, 29, False), (7, 2428752, 30, False), (8, 2428782, 29, False), (9, 2428811, 30, False), (10, 2428841, 30, False), (11, 2428871, 30, False), (12, 2428901, 29, False), (1, 2428930, 30, False)]),
  1938: (7, 443, 2428871, [(11, 2428871, 30, False), (12, 2428901, 29, False), (1, 2428930, 30, False), (2, 2428960, 30, False), (3, 2428990, 29, False), (4, 2429019, 29, False), (5, 2429048, 30, False), (6, 2429078, 29, False), (7, 2429107, 29, False), (7, 2429136, 30, True), (8, 2429166, 29, False), (9, 2429195, 30, False), (10, 2429225, 30, False), (11, 2429255, 29, False), (12, 2429284, 30, False)]),
  1939: (0, 443, 2429255, [(11, 2429255, 29, False), (12, 2429284, 30, False), (1, 2429314, 30, False), (2, 2429344, 30, False), (3, 2429374, 29, False), (4, 2429403, 29, False), (5, 2429432, 30, False), (6, 2429462, 29, False), (7, 2429491, 29, False), (8, 2429520, 30, False), (9, 2429550, 29, False), (10, 2429579, 30, False), (11, 2429609, 29, False), (12, 2429638, 30, False), (1, 2429668, 30, False)]),
  1940: (0, 443, 2429609, [(11, 2429609, 29, False), (12, 2429638, 30, False), (1, 2429668, 30, False), (2, 2429698, 30, False), (3, 2429728, 29, False), (4, 2429757, 30, False), (5, 2429787, 29, False), (6, 2429816, 30, False), (7, 2429846, 29, False), (8, 2429875, 29, False), (9, 2429904, 30, False), (10, 2429934, 29, False), (11, 2429963, 30, False), (12, 2429993, 29, False), (1, 2430022, 30, False)]),
  1941: (6, 443, 2429963, [(11, 2429963, 30, False), (12, 2429993, 29, False), (1, 2430022, 30, False), (2, 2430052, 30, False), (3, 2430082, 29, False), (4, 2430111, 30, False), (5, 2430141, 30, False), (6, 2430171, 29, False), (6, 2430200, 30, True), (7, 2430230, 29, False), (8, 2430259, 29, False), (9, 2430288, 30, False), (10, 2430318, 29, False), (11, 2430347, 30, False), (12, 2430377, 29, False)]),
  1942: (0, 443, 2430347, [(11, 2430347, 30, False), (12, 2430377, 29, False), (1, 2430406, 30, False), (2, 2430436, 29, False), (3, 2430465, 30, False), (4, 2430495, 30, False), (5, 2430525, 29, False), (6, 2430554, 30, False), (7, 2430584, 29, False), (8, 2430613, 30, False), (9, 2430643, 29, False), (10, 2430672, 30, False), (11, 2430702, 29, False), (12, 2430731, 30, False), (1, 2430761, 29, False)]),
  1943: (0, 443, 2430702, [(11, 2430702, 29, False), (12, 2430731, 30, False), (1, 2430761, 29, False), (2, 2430790, 30, False), (3, 2430820, 29, False), (4, 2430849, 30, False), (5, 2430879, 29, False), (6, 2430908, 30, False), (7, 2430938, 30, False), (8, 2430968, 29, False), (9, 2430997, 30, False), (10, 2431027, 29, False), (11, 2431056, 30, False), (12, 2431086, 29, False), (1, 2431115, 30, False)]),
  1944: (4, 444, 2431056, [(11, 2431056, 30, False), (12, 2431086, 29, False), (1, 2431115, 30, False), (2, 2431145, 29, False), (3, 2431174, 30, False), (4, 2431204, 29, False), (4, 2431233, 30, True), (5, 2431263, 29, False), (6, 2431292, 30, False), (7, 2431322, 29, False), (8, 2431351, 30, False), (9, 2431381, 30, False), (10, 2431411, 29, False), (11, 2431440, 30, False), (12, 2431470, 30, False)]),
  1945: (0, 444, 2431440, [(11, 2431440, 30, False), (12, 2431470, 30, False), (1, 2431500, 29, False), (2, 2431529, 29, False), (3, 2431558, 30, False), (4, 2431588, 29, False), (5, 2431617, 29, False), (6, 2431646, 30, False), (7, 2431676, 29, False), (8, 2431705, 30, False), (9, 2431735, 30, False), (10, 2431765, 30, False), (11, 2431795, 29, False), (12, 2431824, 30, False), (1, 2431854, 30, False)]),
  1946: (0, 443, 2431795, [(11, 2431795, 29, False), (12, 2431824, 30, False), (1, 2431854, 30, False), (2, 2431884, 29, False), (3, 2431913, 29, False), (4, 2431942, 30, False), (5, 2431972, 29, False), (6, 2432001, 29, False), (7, 2432030, 30, False), (8, 2432060, 29, False), (9, 2432089, 30, False), (10, 2432119, 30, False), (11, 2432149, 29, False), (12, 2432178, 30, False), (1, 2432208, 30, False)]),
  1947: (2, 443, 2432149, [(11, 2432149, 29, False), (12, 2432178, 30, False), (1, 2432208, 30, False), (2, 2432238, 30, False), (2, 2432268, 29, True), (3, 2432297, 29, False), (4, 2432326, 30, False), (5, 2432356, 29, False), (6, 2432385, 29, False), (7, 2432414, 30, False), (8, 2432444, 29, False), (9, 2432473, 30, False), (10, 2432503, 29, False), (11, 2432532, 30, False), (12, 2432562, 30, False)]),
  1948: (0, 444, 2432532, [(11, 2432532, 30, False), (12, 2432562, 30, False), (1, 2432592, 30, False), (2, 2432622, 29, False), (3, 2432651, 30, False), (4, 2432681, 29, False), (5, 2432710, 30, False), (6, 2432740, 29, False), (7, 2432769, 29, False), (8, 2432798, 30, False), (9, 2432828, 29, False), (10, 2432857, 30, False), (11, 2432887, 29, False), (12, 2432916, 30, False), (1, 2432946, 30, False)]),
  1949: (7, 443, 2432887, [(11, 2432887, 29, False), (12, 2432916, 30, False), (1, 2432946, 30, False), (2, 2432976, 29, False), (3, 2433005, 30, False), (4, 2433035, 30, False), (5, 2433065, 29, False), (6, 2433094, 30, False), (7, 2433124, 29, False), (7, 2433153, 29, True), (8, 2433182, 30, False), (9, 2433212, 29, False), (10, 2433241, 30, False), (11, 2433271, 29, False), (12, 2433300, 30, False)]),
  1950: (0, 443, 2433271, [(11, 2433271, 29, False), (12, 2433300, 30, False), (1, 2433330, 29, False), (2, 2433359, 30, False), (3, 2433389, 30, False), (4, 2433419, 29, False), (5, 2433448, 30, False), (6, 2433478, 30, False), (7, 2433508, 29, False), (8, 2433537, 29, False), (9, 2433566, 30, False), (10, 2433596, 29, False), (11, 2433625, 30, False), (12, 2433655, 29, False), (1, 2433684, 30, False)]),
  1951: (0, 443, 2433625, [(11, 2433625, 30, False), (12, 2433655, 29, False), (1, 2433684, 30, False), (2, 2433714, 29, False), (3, 2433743, 30, False), (4, 2433773, 30, False), (5, 2433803, 29, False), (6, 2433832, 30, False), (7, 2433862, 29, False), (8, 2433891, 30, False), (9, 2433921, 29, False), (10, 2433950, 30, False), (11, 2433980, 29, False), (12, 2434009, 30, False), (1, 2434039, 29, False)]),
  1952: (5, 443, 2433980, [(11, 2433980, 29, False), (12, 2434009, 30, False), (1, 2434039, 29, False), (2, 2434068, 30, False), (3, 2434098, 29, False), (4, 2434127, 30, False), (5, 2434157, 29, False), (5, 2434186, 30, True), (6, 2434216, 29, False), (7, 2434245, 30, False), (8, 2434275, 30, False), (9, 2434305, 29, False), (10, 2434334, 30, False), (11, 2434364, 29, False), (12, 2434393, 30, False)]),
  1953: (0, 443, 2434364, [(11, 2434364, 29, False), (12, 2434393, 30, False), (1, 2434423, 29, False), (2, 2434452, 30, False), (3, 2434482, 29, False), (4, 2434511, 29, False), (5, 2434540, 30, False), (6, 2434570, 30, False), (7, 2434600, 29, False), (8, 2434629, 30, False), (9, 2434659, 30, False), (10, 2434689, 29, False), (11, 2434718, 30, False), (12, 2434748, 29, False), (1, 2434777, 30, False)]),
  1954: (0, 443, 2434718, [(11, 2434718, 30, False), (12, 2434748, 29, False), (1, 2434777, 30, False), (2, 2434807, 29, False), (3, 2434836, 30, False), (4, 2434866, 29, False), (5, 2434895, 29, False), (6, 2434924, 30, False), (7, 2434954, 29, False), (8, 2434983, 30, False), (9, 2435013, 30, False), (10, 2435043, 29, False), (11, 2435072, 30, False), (12, 2435102, 30, False), (1, 2435132, 29, False)]),
  1955: (3, 444, 2435072, [(11, 2435072, 30, False), (12, 2435102, 30, False), (1, 2435132, 29, False), (2, 2435161, 30, False), (3, 2435191, 29, False), (3, 2435220, 30, True), (4, 2435250, 29, False), (5, 2435279, 29, False), (6, 2435308, 30, False), (7, 2435338, 29, False), (8, 2435367, 30, False), (9, 2435397, 29, False), (10, 2435426, 30, False), (11, 2435456, 30, False), (12, 2435486, 30, False)]),
  1956: (0, 444, 2435456, [(11, 2435456, 30, False), (12, 2435486, 30, False), (1, 2435516, 29, False), (2, 2435545, 30, False), (3, 2435575, 29, False), (4, 2435604, 30, False), (5, 2435634, 29, False), (6, 2435663, 29, False), (7, 2435692, 30, False), (8, 2435722, 29, False), (9, 2435751, 30, False), (10, 2435781, 29, False), (11, 2435810, 30, False), (12, 2435840, 30, False), (1, 2435870, 30, False)]),
  1957: (8, 443, 2435810, [(11, 2435810, 30, False), (12, 2435840, 30, False), (1, 2435870, 30, False), (2, 2435900, 29, False), (3, 2435929, 30, False), (4, 2435959, 29, False), (5, 2435988, 30, False), (6, 2436018, 29, False), (7, 2436047, 29, False), (8, 2436076, 30, False), (8, 2436106, 29, True), (9, 2436135, 30, False), (10, 2436165, 29, False), (11, 2436194, 30, False), (12, 2436224, 29, False)]),
  1958: (0, 443, 2436194, [(11, 2436194, 30, False), (12, 2436224, 29, False), (1, 2436253, 30, False), (2, 2436283, 30, False), (3, 2436313, 30, False), (4, 2436343, 29, False), (5, 2436372, 30, False), (6, 2436402, 29, False), (7, 2436431, 29, False), (8, 2436460, 30, False), (9, 2436490, 29, False), (10, 2436519, 30, False), (11, 2436549, 29, False), (12, 2436578, 30, False), (1, 2436608, 29, False)]),
  1959: (0, 443, 2436549, [(11, 2436549, 29, False), (12, 2436578, 30, False), (1, 2436608, 29, False), (2, 2436637, 30, False), (3, 2436667, 30, False), (4, 2436697, 29, False), (5, 2436726, 30, False), (6, 2436756, 29, False), (7, 2436785, 30, False), (8, 2436815, 29, False), (9, 2436844, 30, False), (10, 2436874, 29, False), (11, 2436903, 30, False), (12, 2436933, 29, False), (1, 2436962, 30, False)]),
  1960: (6, 443, 2436903, [(11, 2436903, 30, False), (12, 2436933, 29, False), (1, 2436962, 30, False), (2, 2436992, 29, False), (3, 2437021, 30, False), (4, 2437051, 29, False), (5, 2437080, 30, False), (6, 2437110, 30, False), (6, 2437140, 29, True), (7, 2437169, 30, False), (8, 2437199, 29, False), (9, 2437228, 30, False), (10, 2437258, 29, False), (11, 2437287, 30, False), (12, 2437317, 29, False)]),
  1961: (0, 443, 2437287, [(11, 2437287, 30, False), (12, 2437317, 29, False), (1, 2437346, 30, False), (2, 2437376, 29, False), (3, 2437405, 30, False), (4, 2437435, 29, False), (5, 2437464, 30, False), (6, 2437494, 29, False), (7, 2437523, 30, False), (8, 2437553, 30, False), (9, 2437583, 29, False), (10, 2437612, 30, False), (11, 2437642, 29, False), (12, 2437671, 30, False), (1, 2437701, 29, False)]),
  1962: (0, 443, 2437642, [(11, 2437642, 29, False), (12, 2437671, 30, False), (1, 2437701, 29, False), (2, 2437730, 30, False), (3, 2437760, 29, False), (4, 2437789, 29, False), (5, 2437818, 30, False), (6, 2437848, 29, False), (7, 2437877, 30, False), (8, 2437907, 30, False), (9, 2437937, 29, False), (10, 2437966, 30, False), (11, 2437996, 30, False), (12, 2438026, 29, False), (1, 2438055, 30, False)]),
  1963: (4, 443, 2437996, [(11, 2437996, 30, False), (12, 2438026, 29, False), (1, 2438055, 30, False), (2, 2438085, 29, False), (3, 2438114, 30, False), (4, 2438144, 29, False), (4, 2438173, 29, True), (5, 2438202, 30, False), (6, 2438232, 29, False), (7, 2438261, 30, False), (8, 2438291, 29, False), (9, 2438320, 30, False), (10, 2438350, 30, False), (11, 2438380, 30, False), (12, 2438410, 29, False)]),
  1964: (0, 443, 2438380, [(11, 2438380, 30, False), (12, 2438410, 29, False), (1, 2438439, 30, False), (2, 2438469, 29, False), (3, 2438498, 30, False), (4, 2438528, 29, False), (5, 2438557, 29, False), (6, 2438586, 30, False), (7, 2438616, 29, False), (8, 2438645, 30, False), (9, 2438675, 29, False), (10, 2438704, 30, False), (11, 2438734, 30, False), (12, 2438764, 30, False), (1, 2438794, 29, False)]),
  1965: (0, 443, 2438734, [(11, 2438734, 30, False), (12, 2438764, 30, False), (1, 2438794, 29, False), (2, 2438823, 30, False), (3, 2438853, 29, False), (4, 2438882, 30, False), (5, 2438912, 29, False), (6, 2438941, 29, False), (7, 2438970, 30, False), (8, 2439000, 29, False), (9, 2439029, 29, False), (10, 2439058, 30, False), (11, 2439088, 30, False), (12, 2439118, 29, False), (1, 2439147, 30, False)]),
  1966: (3, 443, 2439088, [(11, 2439088, 30, False), (12, 2439118, 29, False), (1, 2439147, 30, False), (2, 2439177, 30, False), (3, 2439207, 30, False), (3, 2439237, 29, True), (4, 2439266, 30, False), (5, 2439296, 29, False), (6, 2439325, 29, False), (7, 2439354, 30, False), (8, 2439384, 29, False), (9, 2439413, 29, False), (10, 2439442, 30, False), (11, 2439472, 30, False), (12, 2439502, 29, False)]),
  1967: (0, 443, 2439472, [(11, 2439472, 30, False), (12, 2439502, 29, False), (1, 2439531, 30, False), (2, 2439561, 30, False), (3, 2439591, 29, False), (4, 2439620, 30, False), (5, 2439650, 30, False), (6, 2439680, 29, False), (7, 2439709, 29, False), (8, 2439738, 30, False), (9, 2439768, 29, False), (10, 2439797, 30, False), (11, 2439827, 29, False), (12, 2439856, 30, False), (1, 2439886, 29, False)]),
  1968: (7, 443, 2439827, [(11, 2439827, 29, False), (12, 2439856, 30, False), (1, 2439886, 29, False), (2, 2439915, 30, False), (3, 2439945, 29, False), (4, 2439974, 30, False), (5, 2440004, 30, False), (6, 2440034, 29, False), (7, 2440063, 30, False), (7, 2440093, 29, True), (8, 2440122, 30, False), (9, 2440152, 29, False), (10, 2440181, 30, False), (11, 2440211, 29, False), (12, 2440240, 30, False)]),
  1969: (0, 443, 2440211, [(11, 2440211, 29, False), (12, 2440240, 30, False), (1, 2440270, 29, False), (2, 2440299, 30, False), (3, 2440329, 29, False), (4, 2440358, 30, False), (5, 2440388, 29, False), (6, 2440417, 30, False), (7, 2440447, 30, False), (8, 2440477, 29, False), (9, 2440506, 30, False), (10, 2440536, 29, False), (11, 2440565, 30, False), (12, 2440595, 29, False), (1, 2440624, 30, False)]),
  1970: (0, 443, 2440565, [(11, 2440565, 30, False), (12, 2440595, 29, False), (1, 2440624, 30, False), (2, 2440654, 29, False), (3, 2440683, 29, False), (4, 2440712, 30, False), (5, 2440742, 29, False), (6, 2440771, 30, False), (7, 2440801, 30, False), (8, 2440831, 29, False), (9, 2440860, 30, False), (10, 2440890, 30, False), (11, 2440920, 29, False), (12, 2440949, 30, False), (1, 2440979, 29, False)]),
  1971: (5, 443, 2440920, [(11, 2440920, 29, False), (12, 2440949, 30, False), (1, 2440979, 29, False), (2, 2441008, 30, False), (3, 2441038, 29, False), (4, 2441067, 29, False), (5, 2441096, 30, False), (5, 2441126, 29, True), (6, 2441155, 30, False), (7, 2441185, 29, False), (8, 2441214, 30, False), (9, 2441244, 30, False), (10, 2441274, 30, False), (11, 2441304, 29, False), (12, 2441333, 30, False)]),
  1972: (0, 443, 2441304, [(11, 2441304, 29, False), (12, 2441333, 30, False), (1, 2441363, 29, False), (2, 2441392, 30, False), (3, 2441422, 29, False), (4, 2441451, 29, False), (5, 2441480, 30, False), (6, 2441510, 29, False), (7, 2441539, 30, False), (8, 2441569, 29, False), (9, 2441598, 30, False), (10, 2441628, 30, False), (11, 2441658, 29, False), (12, 2441687, 30, False), (1, 2441717, 30, False)]),
  1973: (0, 443, 2441658, [(11, 2441658, 29, False), (12, 2441687, 30, False), (1, 2441717, 30, False), (2, 2441747, 29, False), (3, 2441776, 30, False), (4, 2441806, 29, False), (5, 2441835, 29, False), (6, 2441864, 30, False), (7, 2441894, 29, False), (8, 2441923, 29, False), (9, 2441952, 30, False), (10, 2441982, 30, False), (11, 2442012, 29, False), (12, 2442041, 30, False), (1, 2442071, 30, False)]),
  1974: (4, 443, 2442012, [(11, 2442012, 29, False), (12, 2442041, 30, False), (1, 2442071, 30, False), (2, 2442101, 30, False), (3, 2442131, 29, False), (4, 2442160, 30, False), (4, 2442190, 29, True), (5, 2442219, 29, False), (6, 2442248, 30, False), (7, 2442278, 29, False), (8, 2442307, 29, False), (9, 2442336, 30, False), (10, 2442366, 30, False), (11, 2442396, 29, False), (12, 2442425, 30, False)]),
  1975: (0, 443, 2442396, [(11, 2442396, 29, False), (12, 2442425, 30, False), (1, 2442455, 30, False), (2, 2442485, 30, False), (3, 2442515, 29, False), (4, 2442544, 30, False), (5, 2442574, 29, False), (6, 2442603, 29, False), (7, 2442632, 30, False), (8, 2442662, 29, False), (9, 2442691, 29, False), (10, 2442720, 30, False), (11, 2442750, 29, False), (12, 2442779, 30, False), (1, 2442809, 30, False)]),
  1976: (8, 443, 2442750, [(11, 2442750, 29, False), (12, 2442779, 30, False), (1, 2442809, 30, False), (2, 2442839, 30, False), (3, 2442869, 29, False), (4, 2442898, 30, False), (5, 2442928, 29, False), (6, 2442957, 30, False), (7, 2442987, 29, False), (8, 2443016, 30, False), (8, 2443046, 29, True), (9, 2443075, 29, False), (10, 2443104, 30, False), (11, 2443134, 29, False), (12, 2443163, 30, False)]),
  1977: (0, 443, 2443134, [(11, 2443134, 29, False), (12, 2443163, 30, False), (1, 2443193, 30, False), (2, 2443223, 29, False), (3, 2443252, 30, False), (4, 2443282, 30, False), (5, 2443312, 29, False), (6, 2443341, 30, False), (7, 2443371, 29, False), (8, 2443400, 30, False), (9, 2443430, 29, False), (10, 2443459, 30, False), (11, 2443489, 29, False), (12, 2443518, 29, False), (1, 2443547, 30, False)]),
  1978: (0, 443, 2443489, [(11, 2443489, 29, False), (12, 2443518, 29, False), (1, 2443547, 30, False), (2, 2443577, 29, False), (3, 2443606, 30, False), (4, 2443636, 30, False), (5, 2443666, 29, False), (6, 2443695, 30, False), (7, 2443725, 30, False), (8, 2443755, 29, False), (9, 2443784, 30, False), (10, 2443814, 29, False), (11, 2443843, 30, False), (12, 2443873, 29, False), (1, 2443902, 30, False)]),
  1979: (6, 443, 2443843, [(11, 2443843, 30, False), (12, 2443873, 29, False), (1, 2443902, 30, False), (2, 2443932, 29, False), (3, 2443961, 29, False), (4, 2443990, 30, False), (5, 2444020, 29, False), (6, 2444049, 30, False), (6, 2444079, 30, True), (7, 2444109, 29, False), (8, 2444138, 30, False), (9, 2444168, 30, False), (10, 2444198, 29, False), (11, 2444227, 30, False), (12, 2444257, 29, False)]),
  1980: (0, 443, 2444227, [(11, 2444227, 30, False), (12, 2444257, 29, False), (1, 2444286, 30, False), (2, 2444316, 29, False), (3, 2444345, 29, False), (4, 2444374, 30, False), (5, 2444404, 29, False), (6, 2444433, 30, False), (7, 2444463, 29, False), (8, 2444492, 30, False), (9, 2444522, 30, False), (10, 2444552, 29, False), (11, 2444581, 30, False), (12, 2444611, 30, False), (1, 2444641, 29, False)]),
  1981: (0, 444, 2444581, [(11, 2444581, 30, False), (12, 2444611, 30, False), (1, 2444641, 29, False), (2, 2444670, 30, False), (3, 2444700, 29, False), (4, 2444729, 29, False), (5, 2444758, 30, False), (6, 2444788, 29, False), (7, 2444817, 29, False), (8, 2444846, 30, False), (9, 2444876, 30, False), (10, 2444906, 29, False), (11, 2444935, 30, False), (12, 2444965, 30, False), (1, 2444995, 30, False)]),
  1982: (4, 444, 2444935, [(11, 2444935, 30, False), (12, 2444965, 30, False), (1, 2444995, 30, False), (2, 2445025, 29, False), (3, 2445054, 30, False), (4, 2445084, 29, False), (4, 2445113, 29, True), (5, 2445142, 30, False), (6, 2445172, 29, False), (7, 2445201, 29, False), (8, 2445230, 30, False), (9, 2445260, 29, False), (10, 2445289, 30, False), (11, 2445319, 30, False), (12, 2445349, 30, False)]),
  1983: (0, 444, 2445319, [(11, 2445319, 30, False), (12, 2445349, 30, False), (1, 2445379, 30, False), (2, 2445409, 29, False), (3, 2445438, 30, False), (4, 2445468, 29, False), (5, 2445497, 29, False), (6, 2445526, 30, False), (7, 2445556, 29, False), (8, 2445585, 29, False), (9, 2445614, 30, False), (10, 2445644, 29, False), (11, 2445673, 30, False), (12, 2445703, 30, False), (1, 2445733, 30, False)]),
  1984: (10, 444, 2445673, [(11, 2445673, 30, False), (12, 2445703, 30, False), (1, 2445733, 30, False), (2, 2445763, 29, False), (3, 2445792, 30, False), (4, 2445822, 30, False), (5, 2445852, 29, False), (6, 2445881, 29, False), (7, 2445910, 30, False), (8, 2445940, 29, False), (9, 2445969, 29, False), (10, 2445998, 30, False), (10, 2446028, 29, True), (11, 2446057, 30, False), (12, 2446087, 30, False)]),
  1985: (0, 443, 2446057, [(11, 2446057, 30, False), (12, 2446087, 30, False), (1, 2446117, 29, False), (2, 2446146, 30, False), (3, 2446176, 30, False), (4, 2446206, 29, False), (5, 2446235, 30, False), (6, 2446265, 29, False), (7, 2446294, 30, False), (8, 2446324, 29, False), (9, 2446353, 29, False), (10, 2446382, 30, False), (11, 2446412, 29, False), (12, 2446441, 30, False), (1, 2446471, 29, False)]),
  1986: (0, 443, 2446412, [(11, 2446412, 29, False), (12, 2446441, 30, False), (1, 2446471, 29, False), (2, 2446500, 30, False), (3, 2446530, 30, False), (4, 2446560, 29, False), (5, 2446589, 30, False), (6, 2446619, 30, False), (7, 2446649, 29, False), (8, 2446678, 30, False), (9, 2446708, 29, False), (10, 2446737, 30, False), (11, 2446767, 29, False), (12, 2446796, 29, False), (1, 2446825, 30, False)]),
  1987: (6, 442, 2446767, [(11, 2446767, 29, False), (12, 2446796, 29, False), (1, 2446825, 30, False), (2, 2446855, 29, False), (3, 2446884, 30, False), (4, 2446914, 29, False), (5, 2446943, 30, False), (6, 2446973, 30, False), (6, 2447003, 29, True), (7, 2447032, 30, False), (8, 2447062, 30, False), (9, 2447092, 29, False), (10, 2447121, 30, False), (11, 2447151, 29, False), (12, 2447180, 29, False)]),
  1988: (0, 443, 2447151, [(11, 2447151, 29, False), (12, 2447180, 29, False), (1, 2447209, 30, False), (2, 2447239, 29, False), (3, 2447268, 30, False), (4, 2447298, 29, False), (5, 2447327, 30, False), (6, 2447357, 29, False), (7, 2447386, 30, False), (8, 2447416, 30, False), (9, 2447446, 29, False), (10, 2447475, 30, False), (11, 2447505, 30, False), (12, 2447535, 29, False), (1, 2447564, 30, False)]),
  1989: (0, 443, 2447505, [(11, 2447505, 30, False), (12, 2447535, 29, False), (1, 2447564, 30, False), (2, 2447594, 29, False), (3, 2447623, 29, False), (4, 2447652, 30, False), (5, 2447682, 29, False), (6, 2447711, 30, False), (7, 2447741, 29, False), (8, 2447770, 30, False), (9, 2447800, 29, False), (10, 2447829, 30, False), (11, 2447859, 30, False), (12, 2447889, 30, False), (1, 2447919, 29, False)]),
  1990: (5, 444, 2447859, [(11, 2447859, 30, False), (12, 2447889, 30, False), (1, 2447919, 29, False), (2, 2447948, 30, False), (3, 2447978, 29, False), (4, 2448007, 29, False), (5, 2448036, 30, False), (5, 2448066, 29, True), (6, 2448095, 29, False), (7, 2448124, 30, False), (8, 2448154, 29, False), (9, 2448183, 30, False), (10, 2448213, 30, False), (11, 2448243, 30, False), (12, 2448273, 30, False)]),
  1991: (0, 443, 2448243, [(11, 2448243, 30, False), (12, 2448273, 30, False), (1, 2448303, 29, False), (2, 2448332, 30, False), (3, 2448362, 29, False), (4, 2448391, 29, False), (5, 2448420, 30, False), (6, 2448450, 29, False), (7, 2448479, 29, False), (8, 2448508, 30, False), (9, 2448538, 29, False), (10, 2448567, 30, False), (11, 2448597, 30, False), (12, 2448627, 30, False), (1, 2448657, 29, False)]),
  1992: (0, 443, 2448597, [(11, 2448597, 30, False), (12, 2448627, 30, False), (1, 2448657, 29, False), (2, 2448686, 30, False), (3, 2448716, 30, False), (4, 2448746, 29, False), (5, 2448775, 29, False), (6, 2448804, 30, False), (7, 2448834, 29, False), (8, 2448863, 29, False), (9, 2448892, 30, False), (10, 2448922, 29, False), (11, 2448951, 30, False), (12, 2448981, 30, False), (1, 2449011, 29, False)]),
  1993: (3, 443, 2448951, [(11, 2448951, 30, False), (12, 2448981, 30, False), (1, 2449011, 29, False), (2, 2449040, 30, False), (3, 2449070, 30, False), (3, 2449100, 29, True), (4, 2449129, 30, False), (5, 2449159, 29, False), (6, 2449188, 30, False), (7, 2449218, 29, False), (8, 2449247, 29, False), (9, 2449276, 30, False), (10, 2449306, 29, False), (11, 2449335, 30, False), (12, 2449365, 29, False)]),
  1994: (0, 443, 2449335, [(11, 2449335, 30, False), (12, 2449365, 29, False), (1, 2449394, 30, False), (2, 2449424, 30, False), (3, 2449454, 30, False), (4, 2449484, 29, False), (5, 2449513, 30, False), (6, 2449543, 29, False), (7, 2449572, 30, False), (8, 2449602, 29, False), (9, 2449631, 29, False), (10, 2449660, 30, False), (11, 2449690, 29, False), (12, 2449719, 30, False), (1, 2449749, 29, False)]),
  1995: (8, 443, 2449690, [(11, 2449690, 29, False), (12, 2449719, 30, False), (1, 2449749, 29, False), (2, 2449778, 30, False), (3, 2449808, 30, False), (4, 2449838, 29, False), (5, 2449867, 30, False), (6, 2449897, 29, False), (7, 2449926, 30, False), (8, 2449956, 30, False), (8, 2449986, 29, True), (9, 2450015, 29, False), (10, 2450044, 30, False), (11, 2450074, 29, False), (12, 2450103, 30, False)]),
  1996: (0, 443, 2450074, [(11, 2450074, 29, False), (12, 2450103, 30, False), (1, 2450133, 29, False), (2, 2450162, 30, False), (3, 2450192, 29, False), (4, 2450221, 30, False), (5, 2450251, 30, False), (6, 2450281, 29, False), (7, 2450310, 30, False), (8, 2450340, 29, False), (9, 2450369, 30, False), (10, 2450399, 30, False), (11, 2450429, 29, False), (12, 2450458, 29, False), (1, 2450487, 30, False)]),
  1997: (0, 443, 2450429, [(11, 2450429, 29, False), (12, 2450458, 29, False), (1, 2450487, 30, False), (2, 2450517, 29, False), (3, 2450546, 30, False), (4, 2450576, 29, False), (5, 2450605, 30, False), (6, 2450635, 29, False), (7, 2450664, 30, False), (8, 2450694, 30, False), (9, 2450724, 29, False), (10, 2450753, 30, False), (11, 2450783, 30, False), (12, 2450813, 29, False), (1, 2450842, 30, False)]),
  1998: (5, 443, 2450783, [(11, 2450783, 30, False), (12, 2450813, 29, False), (1, 2450842, 30, False), (2, 2450872, 29, False), (3, 2450901, 29, False), (4, 2450930, 30, False), (5, 2450960, 29, False), (5, 2450989, 29, True), (6, 2451018, 30, False), (7, 2451048, 30, False), (8, 2451078, 29, False), (9, 2451107, 30, False), (10, 2451137, 30, False), (11, 2451167, 29, False), (12, 2451196, 30, False)]),
  1999: (0, 443, 2451167, [(11, 2451167, 29, False), (12, 2451196, 30, False), (1, 2451226, 30, False), (2, 2451256, 29, False), (3, 2451285, 29, False), (4, 2451314, 30, False), (5, 2451344, 29, False), (6, 2451373, 29, False), (7, 2451402, 30, False), (8, 2451432, 29, False), (9, 2451461, 30, False), (10, 2451491, 30, False), (11, 2451521, 30, False), (12, 2451551, 29, False), (1, 2451580, 30, False)]),
  2000: (0, 443, 2451521, [(11, 2451521, 30, False), (12, 2451551, 29, False), (1, 2451580, 30, False), (2, 2451610, 30, False), (3, 2451640, 29, False), (4, 2451669, 29, False), (5, 2451698, 30, False), (6, 2451728, 29, False), (7, 2451757, 29, False), (8, 2451786, 30, False), (9, 2451816, 29, False), (10, 2451845, 30, False), (11, 2451875, 30, False), (12, 2451905, 29, False), (1, 2451934, 30, False)]),
  2001: (4, 443, 2451875, [(11, 2451875, 30, False), (12, 2451905, 29, False), (1, 2451934, 30, False), (2, 2451964, 30, False), (3, 2451994, 29, False), (4, 2452023, 30, False), (4, 2452053, 29, True), (5, 2452082, 30, False), (6, 2452112, 29, False), (7, 2452141, 29, False), (8, 2452170, 30, False), (9, 2452200, 29, False), (10, 2452229, 30, False), (11, 2452259, 29, False), (12, 2452288, 30, False)]),
  2002: (0, 443, 2452259, [(11, 2452259, 29, False), (12, 2452288, 30, False), (1, 2452318, 30, False), (2, 2452348, 30, False), (3, 2452378, 29, False), (4, 2452407, 30, False), (5, 2452437, 29, False), (6, 2452466, 30, False), (7, 2452496, 29, False), (8, 2452525, 29, False), (9, 2452554, 30, False), (10, 2452584, 29, False), (11, 2452613, 30, False), (12, 2452643, 29, False), (1, 2452672, 30, False)]),
  2003: (0, 443, 2452613, [(11, 2452613, 30, False), (12, 2452643, 29, False), (1, 2452672, 30, False), (2, 2452702, 30, False), (3, 2452732, 29, False), (4, 2452761, 30, False), (5, 2452791, 30, False), (6, 2452821, 29, False), (7, 2452850, 30, False), (8, 2452880, 29, False), (9, 2452909, 29, False), (10, 2452938, 30, False), (11, 2452968, 29, False), (12, 2452997, 30, False), (1, 2453027, 29, False)]),
  2004: (2, 443, 2452968, [(11, 2452968, 29, False), (12, 2452997, 30, False), (1, 2453027, 29, False), (2, 2453056, 30, False), (2, 2453086, 29, True), (3, 2453115, 30, False), (4, 2453145, 30, False), (5, 2453175, 29, False), (6, 2453204, 30, False), (7, 2453234, 29, False), (8, 2453263, 30, False), (9, 2453293, 29, False), (10, 2453322, 30, False), (11, 2453352, 29, False), (12, 2453381, 30, False)]),
  2005: (0, 443, 2453352, [(11, 2453352, 29, False), (12, 2453381, 30, False), (1, 2453411, 29, False), (2, 2453440, 30, False), (3, 2453470, 29, False), (4, 2453499, 30, False), (5, 2453529, 29, False), (6, 2453558, 30, False), (7, 2453588, 30, False), (8, 2453618, 29, False), (9, 2453647, 30, False), (10, 2453677, 29, False), (11, 2453706, 30, False), (12, 2453736, 29, False), (1, 2453765, 30, False)]),
  2006: (7, 444, 2453706, [(11, 2453706, 30, False), (12, 2453736, 29, False), (1, 2453765, 30, False), (2, 2453795, 29, False), (3, 2453824, 30, False), (4, 2453854, 29, False), (5, 2453883, 30, False), (6, 2453913, 29, False), (7, 2453942, 30, False), (7, 2453972, 29, True), (8, 2454001, 30, False), (9, 2454031, 30, False), (10, 2454061, 29, False), (11, 2454090, 30, False), (12, 2454120, 30, False)]),
  2007: (0, 444, 2454090, [(11, 2454090, 30, False), (12, 2454120, 30, False), (1, 2454150, 29, False), (2, 2454179, 29, False), (3, 2454208, 30, False), (4, 2454238, 29, False), (5, 2454267, 29, False), (6, 2454296, 30, False), (7, 2454326, 29, False), (8, 2454355, 30, False), (9, 2454385, 30, False), (10, 2454415, 30, False), (11, 2454445, 29, False), (12, 2454474, 30, False), (1, 2454504, 30, False)]),
  2008: (0, 443, 2454445, [(11, 2454445, 29, False), (12, 2454474, 30, False), (1, 2454504, 30, False), (2, 2454534, 29, False), (3, 2454563, 29, False), (4, 2454592, 30, False), (5, 2454622, 29, False), (6, 2454651, 29, False), (7, 2454680, 30, False), (8, 2454710, 29, False), (9, 2454739, 30, False), (10, 2454769, 30, False), (11, 2454799, 29, False), (12, 2454828, 30, False), (1, 2454858, 30, False)]),
  2009: (5, 443, 2454799, [(11, 2454799, 29, False), (12, 2454828, 30, False), (1, 2454858, 30, False), (2, 2454888, 30, False), (3, 2454918, 29, False), (4, 2454947, 29, False), (5, 2454976, 30, False), (5, 2455006, 29, True), (6, 2455035, 29, False), (7, 2455064, 30, False), (8, 2455094, 29, False), (9, 2455123, 30, False), (10, 2455153, 29, False), (11, 2455182, 30, False), (12, 2455212, 30, False)]),
  2010: (0, 444, 2455182, [(11, 2455182, 30, False), (12, 2455212, 30, False), (1, 2455242, 30, False), (2, 2455272, 29, False), (3, 2455301, 30, False), (4, 2455331, 29, False), (5, 2455360, 30, False), (6, 2455390, 29, False), (7, 2455419, 29, False), (8, 2455448, 30, False), (9, 2455478, 29, False), (10, 2455507, 30, False), (11, 2455537, 29, False), (12, 2455566, 30, False), (1, 2455596, 30, False)]),
  2011: (0, 443, 2455537, [(11, 2455537, 29, False), (12, 2455566, 30, False), (1, 2455596, 30, False), (2, 2455626, 29, False), (3, 2455655, 30, False), (4, 2455685, 30, False), (5, 2455715, 29, False), (6, 2455744, 30, False), (7, 2455774, 29, False), (8, 2455803, 29, False), (9, 2455832, 30, False), (10, 2455862, 29, False), (11, 2455891, 30, False), (12, 2455921, 29, False), (1, 2455950, 30, False)]),
  2012: (4, 443, 2455891, [(11, 2455891, 30, False), (12, 2455921, 29, False), (1, 2455950, 30, False), (2, 2455980, 29, False), (3, 2456009, 30, False), (4, 2456039, 30, False), (4, 2456069, 29, True), (5, 2456098, 30, False), (6, 2456128, 29, False), (7, 2456157, 30, False), (8, 2456187, 29, False), (9, 2456216, 30, False), (10, 2456246, 29, False), (11, 2456275, 30, False), (12, 2456305, 29, False)]),
  2013: (0, 443, 2456275, [(11, 2456275, 30, False), (12, 2456305, 29, False), (1, 2456334, 30, False), (2, 2456364, 29, False), (3, 2456393, 30, False), (4, 2456423, 29, False), (5, 2456452, 30, False), (6, 2456482, 30, False), (7, 2456512, 29, False), (8, 2456541, 30, False), (9, 2456571, 29, False), (10, 2456600, 30, False), (11, 2456630, 29, False), (12, 2456659, 30, False), (1, 2456689, 29, False)]),
  2014: (9, 443, 2456630, [(11, 2456630, 29, False), (12, 2456659, 30, False), (1, 2456689, 29, False), (2, 2456718, 30, False), (3, 2456748, 29, False), (4, 2456777, 30, False), (5, 2456807, 29, False), (6, 2456836, 30, False), (7, 2456866, 29, False), (8, 2456895, 30, False), (9, 2456925, 30, False), (9, 2456955, 29, True), (10, 2456984, 30, False), (11, 2457014, 29, False), (12, 2457043, 30, False)]),
  2015: (0, 443, 2457014, [(11, 2457014, 29, False), (12, 2457043, 30, False), (1, 2457073, 29, False), (2, 2457102, 30, False), (3, 2457132, 29, False), (4, 2457161, 29, False), (5, 2457190, 30, False), (6, 2457220, 29, False), (7, 2457249, 30, False), (8, 2457279, 30, False), (9, 2457309, 30, False), (10, 2457339, 29, False), (11, 2457368, 30, False), (12, 2457398, 29, False), (1, 2457427, 30, False)]),
  2016: (0, 443, 2457368, [(11, 2457368, 30, False), (12, 2457398, 29, False), (1, 2457427, 30, False), (2, 2457457, 29, False), (3, 2457486, 30, False), (4, 2457516, 29, False), (5, 2457545, 29, False), (6, 2457574, 30, False), (7, 2457604, 29, False), (8, 2457633, 30, False), (9, 2457663, 30, False), (10, 2457693, 29, False), (11, 2457722, 30, False), (12, 2457752, 30, False), (1, 2457782, 29, False)]),
  2017: (6, 444, 2457722, [(11, 2457722, 30, False), (12, 2457752, 30, False), (1, 2457782, 29, False), (2, 2457811, 30, False), (3, 2457841, 29, False), (4, 2457870, 30, False), (5, 2457900, 29, False), (6, 2457929, 29, False), (6, 2457958, 30, True), (7, 2457988, 29, False), (8, 2458017, 30, False), (9, 2458047, 29, False), (10, 2458076, 30, False), (11, 2458106, 30, False), (12, 2458136, 30, False)]),
  2018: (0, 444, 2458106, [(11, 2458106, 30, False), (12, 2458136, 30, False), (1, 2458166, 29, False), (2, 2458195, 30, False), (3, 2458225, 29, False), (4, 2458254, 30, False), (5, 2458284, 29, False), (6, 2458313, 29, False), (7, 2458342, 30, False), (8, 2458372, 29, False), (9, 2458401, 30, False), (10, 2458431, 29, False), (11, 2458460, 30, False), (12, 2458490, 30, False), (1, 2458520, 30, False)]),
  2019: (0, 443, 2458460, [(11, 2458460, 30, False), (12, 2458490, 30, False), (1, 2458520, 30, False), (2, 2458550, 29, False), (3, 2458579, 30, False), (4, 2458609, 29, False), (5, 2458638, 30, False), (6, 2458668, 29, False), (7, 2458697, 29, False), (8, 2458726, 30, False), (9, 2458756, 29, False), (10, 2458785, 29, False), (11, 2458814, 30, False), (12, 2458844, 30, False), (1, 2458874, 29, False)]),
  2020: (4, 444, 2458814, [(11, 2458814, 30, False), (12, 2458844, 30, False), (1, 2458874, 29, False), (2, 2458903, 30, False), (3, 2458933, 30, False), (4, 2458963, 30, False), (4, 2458993, 29, True), (5, 2459022, 30, False), (6, 2459052, 29, False), (7, 2459081, 29, False), (8, 2459110, 30, False), (9, 2459140, 29, False), (10, 2459169, 30, False), (11, 2459199, 29, False), (12, 2459228, 30, False)]),
  2021: (0, 443, 2459199, [(11, 2459199, 29, False), (12, 2459228, 30, False), (1, 2459258, 29, False), (2, 2459287, 30, False), (3, 2459317, 30, False), (4, 2459347, 29, False), (5, 2459376, 30, False), (6, 2459406, 29, False), (7, 2459435, 30, False), (8, 2459465, 29, False), (9, 2459494, 30, False), (10, 2459524, 29, False), (11, 2459553, 30, False), (12, 2459583, 29, False), (1, 2459612, 30, False)]),
  2022: (0, 443, 2459553, [(11, 2459553, 30, False), (12, 2459583, 29, False), (1, 2459612, 30, False), (2, 2459642, 29, False), (3, 2459671, 30, False), (4, 2459701, 29, False), (5, 2459730, 30, False), (6, 2459760, 30, False), (7, 2459790, 29, False), (8, 2459819, 30, False), (9, 2459849, 29, False), (10, 2459878, 30, False), (11, 2459908, 29, False), (12, 2459937, 30, False), (1, 2459967, 29, False)]),
  2023: (2, 443, 2459908, [(11, 2459908, 29, False), (12, 2459937, 30, False), (1, 2459967, 29, False), (2, 2459996, 30, False), (2, 2460026, 29, True), (3, 2460055, 29, False), (4, 2460084, 30, False), (5, 2460114, 30, False), (6, 2460144, 29, False), (7, 2460173, 30, False), (8, 2460203, 30, False), (9, 2460233, 29, False), (10, 2460262, 30, False), (11, 2460292, 29, False), (12, 2460321, 30, False)]),
  2024: (0, 443, 2460292, [(11, 2460292, 29, False), (12, 2460321, 30, False), (1, 2460351, 29, False), (2, 2460380, 30, False), (3, 2460410, 29, False), (4, 2460439, 29, False), (5, 2460468, 30, False), (6, 2460498, 29, False), (7, 2460527, 30, False), (8, 2460557, 30, False), (9, 2460587, 29, False), (10, 2460616, 30, False), (11, 2460646, 30, False), (12, 2460676, 29, False), (1, 2460705, 30, False)]),
  2025: (6, 443, 2460646, [(11, 2460646, 30, False), (12, 2460676, 29, False), (1, 2460705, 30, False), (2, 2460735, 29, False), (3, 2460764, 30, False), (4, 2460794, 29, False), (5, 2460823, 29, False), (6, 2460852, 30, False), (6, 2460882, 29, True), (7, 2460911, 30, False), (8, 2460941, 29, False), (9, 2460970, 30, False), (10, 2461000, 30, False), (11, 2461030, 30, False), (12, 2461060, 29, False)]),
  2026: (0, 443, 2461030, [(11, 2461030, 30, False), (12, 2461060, 29, False), (1, 2461089, 30, False), (2, 2461119, 29, False), (3, 2461148, 30, False), (4, 2461178, 29, False), (5, 2461207, 29, False), (6, 2461236, 30, False), (7, 2461266, 29, False), (8, 2461295, 29, False), (9, 2461324, 30, False), (10, 2461354, 30, False), (11, 2461384, 30, False), (12, 2461414, 29, False), (1, 2461443, 30, False)]),
  2027: (0, 443, 2461384, [(11, 2461384, 30, False), (12, 2461414, 29, False), (1, 2461443, 30, False), (2, 2461473, 30, False), (3, 2461503, 29, False), (4, 2461532, 30, False), (5, 2461562, 29, False), (6, 2461591, 29, False), (7, 2461620, 30, False), (8, 2461650, 29, False), (9, 2461679, 29, False), (10, 2461708, 30, False), (11, 2461738, 30, False), (12, 2461768, 29, False), (1, 2461797, 30, False)]),
  2028: (5, 443, 2461738, [(11, 2461738, 30, False), (12, 2461768, 29, False), (1, 2461797, 30, False), (2, 2461827, 30, False), (3, 2461857, 30, False), (4, 2461887, 29, False), (5, 2461916, 30, False), (5, 2461946, 29, True), (6, 2461975, 29, False), (7, 2462004, 30, False), (8, 2462034, 29, False), (9, 2462063, 29, False), (10, 2462092, 30, False), (11, 2462122, 30, False), (12, 2462152, 29, False)]),
  2029: (0, 443, 2462122, [(11, 2462122, 30, False), (12, 2462152, 29, False), (1, 2462181, 30, False), (2, 2462211, 30, False), (3, 2462241, 29, False), (4, 2462270, 30, False), (5, 2462300, 29, False), (6, 2462329, 30, False), (7, 2462359, 29, False), (8, 2462388, 30, False), (9, 2462418, 29, False), (10, 2462447, 29, False), (11, 2462476, 30, False), (12, 2462506, 30, False), (1, 2462536, 29, False)]),
  2030: (0, 443, 2462476, [(11, 2462476, 30, False), (12, 2462506, 30, False), (1, 2462536, 29, False), (2, 2462565, 30, False), (3, 2462595, 29, False), (4, 2462624, 30, False), (5, 2462654, 30, False), (6, 2462684, 29, False), (7, 2462713, 30, False), (8, 2462743, 29, False), (9, 2462772, 30, False), (10, 2462802, 29, False), (11, 2462831, 30, False), (12, 2462861, 29, False), (1, 2462890, 29, False)]),
  2031: (3, 443, 2462831, [(11, 2462831, 30, False), (12, 2462861, 29, False), (1, 2462890, 29, False), (2, 2462919, 30, False), (3, 2462949, 30, False), (3, 2462979, 29, True), (4, 2463008, 30, False), (5, 2463038, 29, False), (6, 2463067, 30, False), (7, 2463097, 30, False), (8, 2463127, 29, False), (9, 2463156, 30, False), (10, 2463186, 29, False), (11, 2463215, 30, False), (12, 2463245, 29, False)]),
  2032: (0, 443, 2463215, [(11, 2463215, 30, False), (12, 2463245, 29, False), (1, 2463274, 30, False), (2, 2463304, 29, False), (3, 2463333, 29, False), (4, 2463362, 30, False), (5, 2463392, 29, False), (6, 2463421, 30, False), (7, 2463451, 30, False), (8, 2463481, 29, False), (9, 2463510, 30, False), (10, 2463540, 30, False), (11, 2463570, 29, False), (12, 2463599, 30, False), (1, 2463629, 29, False)]),
  2033: (11, 443, 2463570, [(11, 2463570, 29, False), (12, 2463599, 30, False), (1, 2463629, 29, False), (2, 2463658, 30, False), (3, 2463688, 29, False), (4, 2463717, 29, False), (5, 2463746, 30, False), (6, 2463776, 29, False), (7, 2463805, 30, False), (8, 2463835, 29, False), (9, 2463864, 30, False), (10, 2463894, 30, False), (11, 2463924, 30, False), (11, 2463954, 29, True), (12, 2463983, 30, False)]),
  2034: (0, 443, 2463924, [(11, 2463924, 30, False), (11, 2463954, 29, True), (12, 2463983, 30, False), (1, 2464013, 29, False), (2, 2464042, 30, False), (3, 2464072, 29, False), (4, 2464101, 29, False), (5, 2464130, 30, False), (6, 2464160, 29, False), (7, 2464189, 30, False), (8, 2464219, 29, False), (9, 2464248, 30, False), (10, 2464278, 30, False), (11, 2464308, 29, False), (12, 2464337, 30, False)]),
  2035: (0, 443, 2464308, [(11, 2464308, 29, False), (12, 2464337, 30, False), (1, 2464367, 30, False), (2, 2464397, 29, False), (3, 2464426, 30, False), (4, 2464456, 29, False), (5, 2464485, 29, False), (6, 2464514, 30, False), (7, 2464544, 29, False), (8, 2464573, 29, False), (9, 2464602, 30, False), (10, 2464632, 30, False), (11, 2464662, 29, False), (12, 2464691, 30, False), (1, 2464721, 30, False)]),
  2036: (6, 443, 2464662, [(11, 2464662, 29, False), (12, 2464691, 30, False), (1, 2464721, 30, False), (2, 2464751, 30, False), (3, 2464781, 29, False), (4, 2464810, 30, False), (5, 2464840, 29, False), (6, 2464869, 29, False), (6, 2464898, 30, True), (7, 2464928, 29, False), (8, 2464957, 29, False), (9, 2464986, 30, False), (10, 2465016, 29, False), (11, 2465045, 30, False), (12, 2465075, 30, False)]),
  2037: (0, 444, 2465045, [(11, 2465045, 30, False), (12, 2465075, 30, False), (1, 2465105, 30, False), (2, 2465135, 30, False), (3, 2465165, 29, False), (4, 2465194, 30, False), (5, 2465224, 29, False), (6, 2465253, 29, False), (7, 2465282, 30, False), (8, 2465312, 29, False), (9, 2465341, 29, False), (10, 2465370, 30, False), (11, 2465400, 29, False), (12, 2465429, 30, False), (1, 2465459, 30, False)]),
  2038: (0, 443, 2465400, [(11, 2465400, 29, False), (12, 2465429, 30, False), (1, 2465459, 30, False), (2, 2465489, 30, False), (3, 2465519, 29, False), (4, 2465548, 30, False), (5, 2465578, 29, False), (6, 2465607, 30, False), (7, 2465637, 29, False), (8, 2465666, 30, False), (9, 2465696, 29, False), (10, 2465725, 29, False), (11, 2465754, 30, False), (12, 2465784, 29, False), (1, 2465813, 30, False)]),
  2039: (5, 443, 2465754, [(11, 2465754, 30, False), (12, 2465784, 29, False), (1, 2465813, 30, False), (2, 2465843, 30, False), (3, 2465873, 29, False), (4, 2465902, 30, False), (5, 2465932, 30, False), (5, 2465962, 29, True), (6, 2465991, 30, False), (7, 2466021, 29, False), (8, 2466050, 30, False), (9, 2466080, 29, False), (10, 2466109, 30, False), (11, 2466139, 29, False), (12, 2466168, 29, False)]),
  2040: (0, 442, 2466139, [(11, 2466139, 29, False), (12, 2466168, 29, False), (1, 2466197, 30, False), (2, 2466227, 29, False), (3, 2466256, 30, False), (4, 2466286, 30, False), (5, 2466316, 29, False), (6, 2466345, 30, False), (7, 2466375, 29, False), (8, 2466404, 30, False), (9, 2466434, 30, False), (10, 2466464, 29, False), (11, 2466493, 30, False), (12, 2466523, 29, False), (1, 2466552, 29, False)]),
  2041: (0, 443, 2466493, [(11, 2466493, 30, False), (12, 2466523, 29, False), (1, 2466552, 29, False), (2, 2466581, 30, False), (3, 2466611, 29, False), (4, 2466640, 30, False), (5, 2466670, 29, False), (6, 2466699, 30, False), (7, 2466729, 30, False), (8, 2466759, 29, False), (9, 2466788, 30, False), (10, 2466818, 30, False), (11, 2466848, 29, False), (12, 2466877, 30, False), (1, 2466907, 29, False)]),
  2042: (2, 443, 2466848, [(11, 2466848, 29, False), (12, 2466877, 30, False), (1, 2466907, 29, False), (2, 2466936, 30, False), (2, 2466966, 29, True), (3, 2466995, 29, False), (4, 2467024, 30, False), (5, 2467054, 29, False), (6, 2467083, 30, False), (7, 2467113, 29, False), (8, 2467142, 30, False), (9, 2467172, 30, False), (10, 2467202, 29, False), (11, 2467231, 30, False), (12, 2467261, 30, False)]),
  2043: (0, 444, 2467231, [(11, 2467231, 30, False), (12, 2467261, 30, False), (1, 2467291, 29, False), (2, 2467320, 30, False), (3, 2467350, 29, False), (4, 2467379, 29, False), (5, 2467408, 30, False), (6, 2467438, 29, False), (7, 2467467, 29, False), (8, 2467496, 30, False), (9, 2467526, 30, False), (10, 2467556, 29, False), (11, 2467585, 30, False), (12, 2467615, 30, False), (1, 2467645, 30, False)]),
  2044: (7, 444, 2467585, [(11, 2467585, 30, False), (12, 2467615, 30, False), (1, 2467645, 30, False), (2, 2467675, 29, False), (3, 2467704, 30, False), (4, 2467734, 29, False), (5, 2467763, 29, False), (6, 2467792, 30, False), (7, 2467822, 29, False), (7, 2467851, 29, True), (8, 2467880, 30, False), (9, 2467910, 29, False), (10, 2467939, 30, False), (11, 2467969, 30, False), (12, 2467999, 30, False)]),
  2045: (0, 444, 2467969, [(11, 2467969, 30, False), (12, 2467999, 30, False), (1, 2468029, 30, False), (2, 2468059, 29, False), (3, 2468088, 30, False), (4, 2468118, 29, False), (5, 2468147, 29, False), (6, 2468176, 30, False), (7, 2468206, 29, False), (8, 2468235, 29, False), (9, 2468264, 30, False), (10, 2468294, 29, False), (11, 2468323, 30, False), (12, 2468353, 30, False), (1, 2468383, 30, False)]),
  2046: (0, 444, 2468323, [(11, 2468323, 30, False), (12, 2468353, 30, False), (1, 2468383, 30, False), (2, 2468413, 29, False), (3, 2468442, 30, False), (4, 2468472, 29, False), (5, 2468501, 30, False), (6, 2468531, 29, False), (7, 2468560, 30, False), (8, 2468590, 29, False), (9, 2468619, 29, False), (10, 2468648, 30, False), (11, 2468678, 29, False), (12, 2468707, 30, False), (1, 2468737, 30, False)]),
  2047: (5, 443, 2468678, [(11, 2468678, 29, False), (12, 2468707, 30, False), (1, 2468737, 30, False), (2, 2468767, 29, False), (3, 2468796, 30, False), (4, 2468826, 30, False), (5, 2468856, 29, False), (5, 2468885, 30, True), (6, 2468915, 29, False), (7, 2468944, 30, False), (8, 2468974, 29, False), (9, 2469003, 29, False), (10, 2469032, 30, False), (11, 2469062, 29, False), (12, 2469091, 30, False)]),
  2048: (0, 443, 2469062, [(11, 2469062, 29, False), (12, 2469091, 30, False), (1, 2469121, 29, False), (2, 2469150, 30, False), (3, 2469180, 30, False), (4, 2469210, 29, False), (5, 2469239, 30, False), (6, 2469269, 30, False), (7, 2469299, 29, False), (8, 2469328, 30, False), (9, 2469358, 29, False), (10, 2469387, 29, False), (11, 2469416, 30, False), (12, 2469446, 29, False), (1, 2469475, 30, False)]),
  2049: (0, 443, 2469416, [(11, 2469416, 30, False), (12, 2469446, 29, False), (1, 2469475, 30, False), (2, 2469505, 29, False), (3, 2469534, 30, False), (4, 2469564, 29, False), (5, 2469593, 30, False), (6, 2469623, 30, False), (7, 2469653, 29, False), (8, 2469682, 30, False), (9, 2469712, 30, False), (10, 2469742, 29, False), (11, 2469771, 30, False), (12, 2469801, 29, False), (1, 2469830, 29, False)]),
  2050: (3, 443, 2469771, [(11, 2469771, 30, False), (12, 2469801, 29, False), (1, 2469830, 29, False), (2, 2469859, 30, False), (3, 2469889, 29, False), (3, 2469918, 30, True), (4, 2469948, 29, False), (5, 2469977, 30, False), (6, 2470007, 29, False), (7, 2470036, 30, False), (8, 2470066, 30, False), (9, 2470096, 29, False), (10, 2470125, 30, False), (11, 2470155, 30, False), (12, 2470185, 29, False)]),
  2051: (0, 443, 2470155, [(11, 2470155, 30, False), (12, 2470185, 29, False), (1, 2470214, 30, False), (2, 2470244, 29, False), (3, 2470273, 29, False), (4, 2470302, 30, False), (5, 2470332, 29, False), (6, 2470361, 29, False), (7, 2470390, 30, False), (8, 2470420, 30, False), (9, 2470450, 29, False), (10, 2470479, 30, False), (11, 2470509, 30, False), (12, 2470539, 30, False), (1, 2470569, 29, False)]),
  2052: (8, 444, 2470509, [(11, 2470509, 30, False), (12, 2470539, 30, False), (1, 2470569, 29, False), (2, 2470598, 30, False), (3, 2470628, 29, False), (4, 2470657, 29, False), (5, 2470686, 30, False), (6, 2470716, 29, False), (7, 2470745, 29, False), (8, 2470774, 30, False), (8, 2470804, 29, True), (9, 2470833, 30, False), (10, 2470863, 30, False), (11, 2470893, 30, False), (12, 2470923, 30, False)]),
  2053: (0, 443, 2470893, [(11, 2470893, 30, False), (12, 2470923, 30, False), (1, 2470953, 29, False), (2, 2470982, 30, False), (3, 2471012, 29, False), (4, 2471041, 29, False), (5, 2471070, 30, False), (6, 2471100, 29, False), (7, 2471129, 29, False), (8, 2471158, 30, False), (9, 2471188, 29, False), (10, 2471217, 30, False), (11, 2471247, 30, False), (12, 2471277, 30, False), (1, 2471307, 29, False)]),
  2054: (0, 443, 2471247, [(11, 2471247, 30, False), (12, 2471277, 30, False), (1, 2471307, 29, False), (2, 2471336, 30, False), (3, 2471366, 30, False), (4, 2471396, 29, False), (5, 2471425, 29, False), (6, 2471454, 30, False), (7, 2471484, 29, False), (8, 2471513, 29, False), (9, 2471542, 30, False), (10, 2471572, 29, False), (11, 2471601, 30, False), (12, 2471631, 30, False), (1, 2471661, 29, False)]),
  2055: (6, 443, 2471601, [(11, 2471601, 30, False), (12, 2471631, 30, False), (1, 2471661, 29, False), (2, 2471690, 30, False), (3, 2471720, 30, False), (4, 2471750, 29, False), (5, 2471779, 30, False), (6, 2471809, 29, False), (6, 2471838, 30, True), (7, 2471868, 29, False), (8, 2471897, 29, False), (9, 2471926, 30, False), (10, 2471956, 29, False), (11, 2471985, 30, False), (12, 2472015, 29, False)]),
  2056: (0, 443, 2471985, [(11, 2471985, 30, False), (12, 2472015, 29, False), (1, 2472044, 30, False), (2, 2472074, 30, False), (3, 2472104, 30, False), (4, 2472134, 29, False), (5, 2472163, 30, False), (6, 2472193, 29, False), (7, 2472222, 30, False), (8, 2472252, 29, False), (9, 2472281, 29, False), (10, 2472310, 30, False), (11, 2472340, 29, False), (12, 2472369, 30, False), (1, 2472399, 29, False)]),
  2057: (0, 443, 2472340, [(11, 2472340, 29, False), (12, 2472369, 30, False), (1, 2472399, 29, False), (2, 2472428, 30, False), (3, 2472458, 30, False), (4, 2472488, 29, False), (5, 2472517, 30, False), (6, 2472547, 29, False), (7, 2472576, 30, False), (8, 2472606, 30, False), (9, 2472636, 29, False), (10, 2472665, 29, False), (11, 2472694, 30, False), (12, 2472724, 29, False), (1, 2472753, 30, False)]),
  2058: (4, 443, 2472694, [(11, 2472694, 30, False), (12, 2472724, 29, False), (1, 2472753, 30, False), (2, 2472783, 29, False), (3, 2472812, 30, False), (4, 2472842, 29, False), (4, 2472871, 30, True), (5, 2472901, 29, False), (6, 2472930, 30, False), (7, 2472960, 30, False), (8, 2472990, 29, False), (9, 2473019, 30, False), (10, 2473049, 30, False), (11, 2473079, 29, False), (12, 2473108, 29, False)]),
  2059: (0, 443, 2473079, [(11, 2473079, 29, False), (12, 2473108, 29, False), (1, 2473137, 30, False), (2, 2473167, 29, False), (3, 2473196, 30, False), (4, 2473226, 29, False), (5, 2473255, 30, False), (6, 2473285, 29, False), (7, 2473314, 30, False), (8, 2473344, 29, False), (9, 2473373, 30, False), (10, 2473403, 30, False), (11, 2473433, 30, False), (12, 2473463, 29, False), (1, 2473492, 30, False)]),
  2060: (0, 443, 2473433, [(11, 2473433, 30, False), (12, 2473463, 29, False), (1, 2473492, 30, False), (2, 2473522, 29, False), (3, 2473551, 29, False), (4, 2473580, 30, False), (5, 2473610, 29, False), (6, 2473639, 29, False), (7, 2473668, 30, False), (8, 2473698, 29, False), (9, 2473727, 30, False), (10, 2473757, 30, False), (11, 2473787, 30, False), (12, 2473817, 29, False), (1, 2473846, 30, False)]),
}

STEMS = ("甲","乙","丙","丁","戊","己","庚","辛","壬","癸")
ZHI   = ("子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥")
SI    = {s:i for i,s in enumerate(STEMS)}
ZI    = {z:i for i,z in enumerate(ZHI)}

ZHI_HIDE = {
    "子":["癸"],"丑":["己","癸","辛"],"寅":["甲","丙","戊"],"卯":["乙"],
    "辰":["戊","乙","癸"],"巳":["丙","庚","戊"],"午":["丁","己"],
    "未":["丁","乙","己"],"申":["庚","壬","戊"],"酉":["辛"],
    "戌":["戊","辛","丁"],"亥":["壬","甲"],
}
ZHI_ZOO = {
    "子":"鼠","丑":"牛","寅":"虎","卯":"兔","辰":"龙","巳":"蛇",
    "午":"马","未":"羊","申":"猴","酉":"鸡","戌":"狗","亥":"猪",
}
NAYIN = {
    "甲子":"海中金","乙丑":"海中金","甲午":"海中金","乙午":"海中金",
    "丙寅":"炉中火","丁卯":"炉中火","丙申":"炉中火","丁酉":"炉中火",
    "戊辰":"木大林","己巳":"木大林","戊戌":"木大林","己亥":"木大林",
    "庚午":"路旁土","辛未":"路旁土","庚子":"路旁土","辛丑":"路旁土",
    "壬申":"剑锋金","癸酉":"剑锋金","壬寅":"剑锋金","癸卯":"剑锋金",
    "甲戌":"山头火","乙亥":"山头火","甲辰":"山头火","乙巳":"山头火",
    "丙子":"涧下水","丁丑":"涧下水","丙午":"涧下水","丁未":"涧下水",
    "戊寅":"城墙土","己卯":"城墙土","戊申":"城墙土","己酉":"城墙土",
    "庚辰":"白蜡金","辛巳":"白蜡金","庚戌":"白蜡金","辛亥":"白蜡金",
    "壬午":"杨柳木","癸未":"杨柳木","壬子":"杨柳木","癸丑":"杨柳木",
    "甲申":"井泉水","乙酉":"井泉水","甲寅":"井泉水","乙卯":"井泉水",
    "丙戌":"屋上土","丁亥":"屋上土","丙辰":"屋上土","丁巳":"屋上土",
    "戊子":"霹雳火","己丑":"霹雳火","戊午":"霹雳火","己未":"霹雳火",
    "庚寅":"松柏木","辛卯":"松柏木","庚申":"松柏木","辛酉":"松柏木",
    "壬辰":"长流水","癸巳":"长流水","壬戌":"长流水","癸亥":"长流水",
    "甲辰":"覆灯火","乙巳":"覆灯火","甲戌":"覆灯火","乙亥":"覆灯火",
    "丙午":"天河水","丁未":"天河水","丙子":"天河水","丁丑":"天河水",
    "戊申":"大驿土","己酉":"大驿土","戊寅":"大驿土","己卯":"大驿土",
    "庚戌":"石榴木","辛亥":"石榴木","庚辰":"石榴木","辛巳":"石榴木",
    "壬子":"桑柘木","癸丑":"桑柘木","壬午":"桑柘木","癸未":"桑柘木",
    "甲寅":"大溪水","乙酉":"大溪水","甲申":"大溪水","乙卯":"大溪水",
    "丙辰":"砂石土","丁亥":"砂石土","丙戌":"砂石土","丁巳":"砂石土",
    "戊午":"天上火","己未":"天上火","戊子":"天上火","己丑":"天上火",
    "庚申":"城头土","辛酉":"城头土","庚寅":"城头土","辛卯":"城头土",
    "壬戌":"大海水","癸亥":"大海水","壬辰":"大海水","癸巳":"大海水",
}

DAY_EPOCH_JD = 11

LICHUN_JD = {
 1900:2415055.077,1901:2415420.319,1902:2415785.568,1903:2416150.813,1904:2416516.058,
 1905:2416881.303,1906:2417246.544,1907:2417611.791,1908:2417977.033,1909:2418342.273,
 1910:2418707.519,1911:2419072.757,1912:2419438.000,1913:2419803.238,1914:2420168.479,
 1915:2420533.726,1916:2420898.968,1917:2421264.207,1918:2421629.454,1919:2421994.694,
 1920:2422359.935,1921:2422725.181,1922:2423090.421,1923:2423455.667,1924:2423820.909,
 1925:2424186.151,1926:2424551.402,1927:2424916.646,1928:2425281.886,1929:2425647.131,
 1930:2426012.369,1931:2426377.612,1932:2426742.854,1933:2427108.090,1934:2427473.336,
 1935:2427838.575,1936:2428203.812,1937:2428569.059,1938:2428934.302,1939:2429299.549,
 1940:2429664.797,1941:2430030.035,1942:2430395.284,1943:2430760.528,1944:2431125.766,
 1945:2431491.013,1946:2431856.253,1947:2432221.493,1948:2432586.738,1949:2432951.974,
 1950:2433317.223,1951:2433682.468,1952:2434047.703,1953:2434412.949,1954:2434778.188,
 1955:2435143.429,1956:2435508.675,1957:2435873.913,1958:2436239.159,1959:2436604.404,
 1960:2436969.641,1961:2437334.891,1962:2437700.137,1963:2438065.380,1964:2438430.628,
 1965:2438795.865,1966:2439161.110,1967:2439526.355,1968:2439891.588,1969:2440256.833,
 1970:2440622.073,1971:2440987.309,1972:2441352.556,1973:2441717.795,1974:2442083.042,
 1975:2442448.291,1976:2442813.527,1977:2443178.773,1978:2443544.019,1979:2443909.259,
 1980:2444274.507,1981:2444639.747,1982:2445004.990,1983:2445370.236,1984:2445735.471,
 1985:2446100.717,1986:2446465.964,1987:2446831.203,1988:2447196.446,1989:2447561.686,
 1990:2447926.926,1991:2448292.173,1992:2448657.409,1993:2449022.651,1994:2449387.896,
 1995:2449753.134,1996:2450118.380,1997:2450483.626,1998:2450848.873,1999:2451214.123,
 2000:2451579.361,2001:2451944.603,2002:2452309.850,2003:2452675.087,2004:2453040.331,
 2005:2453405.572,2006:2453770.811,2007:2454136.054,2008:2454501.292,2009:2454866.535,
 2010:2455231.783,2011:2455597.023,2012:2455962.266,2013:2456327.509,2014:2456692.752,
 2015:2457058.000,2016:2457423.240,2017:2457788.482,2018:2458153.728,2019:2458518.968,
 2020:2458884.211,2021:2459249.458,2022:2459614.702,2023:2459979.946,2024:2460345.185,
 2025:2460710.424,2026:2461075.668,2027:2461440.907,2028:2461806.147,2029:2462171.389,
 2030:2462536.631,2031:2462901.874,2032:2463267.117,2033:2463632.362,2034:2463997.612,
 2035:2464362.855,2036:2464728.097,2037:2465093.341,2038:2465458.586,2039:2465823.828,
 2040:2466189.069,2041:2466554.309,2042:2466919.551,2043:2467284.791,2044:2467650.031,
 2045:2468015.275,2046:2468380.522,2047:2468745.763,2048:2469111.003,2049:2469476.245,
 2050:2469841.489,2051:2470206.733,2052:2470571.974,2053:2470937.217,2054:2471302.464,
 2055:2471667.705,2056:2472032.949,2057:2472398.196,2058:2472763.441,2059:2473128.683,
}

def greg_jd(y,m,d):
    if m<=2: y-=1; m+=12
    A=y//100; B=2-A+A//4
    return int(365.25*(y+4716))+int(30.6001*(m+1))+d+B-1524

def jd_greg(jd):
    a=int(jd)+32044
    b=(4*a+3)//146097
    c=a-(146097*b)//4
    d=(4*c+3)//1461
    e=c-(1461*d)//4
    m=(5*e+2)//153
    day=e-(153*m+2)//5+1
    month=m+3-12*(m//10)
    year=100*b+d-4800+(m//10)
    return year,month,day

def iter_lunar_months(ly_num):
    current_year=ly_num-1
    for mo,start,days,is_l in LY_DATA[ly_num][3]:
        if mo==1 and not is_l:
            current_year+=1
        yield current_year,mo,start,days,is_l

class Solar:
    __slots__=("year","month","day","hour","minute","second","jd")
    def __init__(self,y,mo,d,h=0,mi=0,s=0):
        self.year=y; self.month=mo; self.day=d
        self.hour=h; self.minute=mi; self.second=s
        self.jd=greg_jd(y,mo,d)
    @staticmethod
    def fromYmdHms(y,mo,d,h,mi,s): return Solar(y,mo,d,h,mi,s)
    def getLunar(self): return Lunar._from_solar(self)
    def getYear(self): return self.year
    def getMonth(self): return self.month
    def getDay(self): return self.day
    def getHour(self): return self.hour
    def getMinute(self): return self.minute
    def getSecond(self): return self.second
    def getJulianDay(self): return float(self.jd)
    def toYmd(self): return f"{self.year:04d}{self.month:02d}{self.day:02d}"
    def subtract(self,o): return self.jd-o.jd
    def next(self,n): y,m,d=jd_greg(self.jd+n); return Solar(y,m,d,self.hour,self.minute,self.second)

class Lunar:
    __slots__=("year","month","day","hour","minute","second","isLeap","_s","_yg","_yz","_yg_ex","_yz_ex","_mg","_mz","_mg_ex","_mz_ex","_dg","_dz","_dg_ex","_dz_ex","_tg","_tz","_yh","_mh","_dh","_th","_yn","_mn","_dn","_tn","_yxk","_mxk","_dxk","_txk","_sx","_ty","_tx","_mgong","_sgong")
    def __init__(self,ly,lm,ld,h=0,mi=0,s=0,leap=False):
        self.year=ly; self.month=lm; self.day=ld
        self.hour=h; self.minute=mi; self.second=s; self.isLeap=leap
        self._s=self._to_solar()
        self._compute()
    def _to_solar(self):
        for lunar_year,mo,start,days,is_l in iter_lunar_months(self.year):
            if lunar_year==self.year and mo==self.month and is_l==self.isLeap:
                y,m,d=jd_greg(int(start)+self.day-1)
                return Solar(y,m,d,self.hour,self.minute,self.second)
        y,m,d=jd_greg(int(LY_DATA[self.year][2]))
        return Solar(y,m,d,self.hour,self.minute,self.second)
    @staticmethod
    def _from_solar(s):
        jd=s.jd
        for ly_num in (s.year, s.year+1, s.year-1):
            if ly_num not in LY_DATA: continue
            for lunar_year,mo,start,days,is_l in iter_lunar_months(ly_num):
                if start <= jd < start+days:
                    day=int(jd-start)+1
                    return Lunar(lunar_year,mo,day,s.hour,s.minute,s.second,is_l)
        return Lunar(s.year,1,1,s.hour,s.minute,s.second,False)
    def _compute(self):
        s=self._s; ly=self.year
        lc_ymd=int(LICHUN_YMD.get(s.year,"20000204"))
        lc_jd=LICHUN_JD.get(s.year,2446831.0)
        s_ymd=int(s.toYmd())
        s_jd=s.jd-0.5+(s.hour*3600+s.minute*60+s.second)/86400
        before_lichun=s_ymd<lc_ymd or (s_ymd==lc_ymd and s_jd<lc_jd)
        yg=(ly-4)%10; yz=(ly-4)%12
        g_ex=yg; z_ex=yz
        if ly==s.year:
            if before_lichun: g_ex-=1; z_ex-=1
        elif ly<s.year:
            if not before_lichun: g_ex+=1; z_ex+=1
        def m10(x): return (x+10)%10
        def m12(x): return (x+12)%12
        self._yg=STEMS[yg%10]; self._yz=ZHI[yz%12]
        self._yg_ex=STEMS[m10(g_ex)]; self._yz_ex=ZHI[m12(z_ex)]
        gi=SI[self._yg_ex]; g_off=(((gi%5)+1)*2)%10; mi0=11 if self.month==1 and before_lichun else self.month-1
        self._mg=STEMS[(g_off+mi0)%10]; self._mz=ZHI[(2+mi0)%12]
        self._mg_ex=STEMS[(g_off+mi0)%10]; self._mz_ex=ZHI[(2+mi0)%12]
        noon=Solar(s.year,s.month,s.day,12,0,0); di=noon.jd-DAY_EPOCH_JD
        dg=int(di)%10; dz=int(di)%12
        self._dg=STEMS[dg]; self._dz=ZHI[dz]
        dg_ex=dg; dz_ex=dz
        hm=f"{self.hour:02d}:{self.minute:02d}"
        if "23:00"<=hm<="23:59": dg_ex=(dg_ex+1)%10; dz_ex=(dz_ex+1)%12
        self._dg_ex=STEMS[dg_ex]; self._dz_ex=ZHI[dz_ex]
        tzi=0 if self.hour==23 else (self.hour+1)//2
        self._tz=ZHI[tzi]; self._tg=STEMS[(dg_ex*2+tzi)%10]
        self._yh=ZHI_HIDE.get(self._yz,[]); self._mh=ZHI_HIDE.get(self._mz,[])
        self._dh=ZHI_HIDE.get(self._dz,[]); self._th=ZHI_HIDE.get(self._tz,[])
        def nay(g,z): k=g+z; return NAYIN.get(k,NAYIN.get(z+g,"土"))
        self._yn=nay(self._yg,self._yz); self._mn=nay(self._mg,self._mz)
        self._dn=nay(self._dg,self._dz); self._tn=nay(self._tg,self._tz)
        def xk(g,z):
            xs={"甲":0,"乙":0,"丙":2,"丁":2,"戊":4,"己":4,"庚":6,"辛":6,"壬":8,"癸":8}
            xi=xs.get(g,0); zi2=ZI[z]; diff=(zi2-xi)%12
            return ZHI[(zi2+10-diff)%12]+ZHI[(zi2+11-diff)%12]
        xk_v=xk(self._dg,self._dz)
        self._yxk=xk_v; self._mxk=xk_v; self._dxk=xk_v; self._txk=xk_v
        self._sx=ZHI_ZOO.get(self._yz,"鼠")
        self._ty=STEMS[(SI[self._mg]+1)%10]+ZHI[(ZI[self._mz]+3)%12]
        self._tx=STEMS[(SI[self._dg]+1)%10]+ZHI[(ZI[self._dz]+3)%12]
        mg_zi=(14-ZI[self._mz]-ZI[self._tz])%12
        self._mgong=STEMS[(yg*2+mg_zi)%10]+ZHI[mg_zi]
        sg_zi=(ZI[self._mz]+ZI[self._tz]+2)%12
        self._sgong=STEMS[(ZI[self._mz]+sg_zi)%10]+ZHI[sg_zi]
    # Public API
    def getYearGan(self): return self._yg
    def getYearZhi(self): return self._yz
    def getYearGanExact(self): return self._yg_ex
    def getYearZhiExact(self): return self._yz_ex
    def getYearInGanZhi(self): return self._yg+self._yz
    def getYearInGanZhiExact(self): return self._yg_ex+self._yz_ex
    def getMonthGan(self): return self._mg
    def getMonthZhi(self): return self._mz
    def getMonthGanExact(self): return self._mg_ex
    def getMonthZhiExact(self): return self._mz_ex
    def getMonthInGanZhi(self): return self._mg+self._mz
    def getMonthInGanZhiExact(self): return self._mg_ex+self._mz_ex
    def getDayGan(self): return self._dg
    def getDayZhi(self): return self._dz
    def getDayGanExact(self): return self._dg_ex
    def getDayZhiExact(self): return self._dz_ex
    def getDayInGanZhi(self): return self._dg+self._dz
    def getDayInGanZhiExact(self): return self._dg_ex+self._dz_ex
    def getTimeGan(self): return self._tg
    def getTimeZhi(self): return self._tz
    def getTimeInGanZhi(self): return self._tg+self._tz
    def getYearShengXiao(self): return self._sx
    def getYearHideGan(self): return self._yh
    def getMonthHideGan(self): return self._mh
    def getDayHideGan(self): return self._dh
    def getTimeHideGan(self): return self._th
    def getYearNaYin(self): return self._yn
    def getMonthNaYin(self): return self._mn
    def getDayNaYin(self): return self._dn
    def getTimeNaYin(self): return self._tn
    def getYearXunKong(self): return self._yxk
    def getMonthXunKong(self): return self._mxk
    def getDayXunKong(self): return self._dxk
    def getTimeXunKong(self): return self._txk
    def getSolar(self): return self._s
    def getEightChar(self): return EightChar(self)
    def toString(self): leap_str="闰" if self.isLeap else ""; return f"{self.year}年{leap_str}{self.month}月{self.day}日"

LICHUN_YMD = {
 1900:"19000204",1901:"19010204",1902:"19020205",1903:"19030205",1904:"19040205",
 1905:"19050204",1906:"19060205",1907:"19070205",1908:"19080205",1909:"19090204",
 1910:"19100205",1911:"19110205",1912:"19120205",1913:"19130204",1914:"19140204",
 1915:"19150205",1916:"19160205",1917:"19170204",1918:"19180204",1919:"19190205",
 1920:"19200205",1921:"19210204",1922:"19220204",1923:"19230205",1924:"19240205",
 1925:"19250204",1926:"19260204",1927:"19270205",1928:"19280205",1929:"19290204",
 1930:"19300204",1931:"19310205",1932:"19320205",1933:"19330204",1934:"19340204",
 1935:"19350205",1936:"19360205",1937:"19370204",1938:"19380204",1939:"19390205",
 1940:"19400205",1941:"19410204",1942:"19420204",1943:"19430205",1944:"19440205",
 1945:"19450204",1946:"19460204",1947:"19470204",1948:"19480205",1949:"19490204",
 1950:"19500204",1951:"19510204",1952:"19520205",1953:"19530204",1954:"19540204",
 1955:"19550204",1956:"19560205",1957:"19570204",1958:"19580204",1959:"19590204",
 1960:"19600205",1961:"19610204",1962:"19620204",1963:"19630204",1964:"19640205",
 1965:"19650204",1966:"19660204",1967:"19670204",1968:"19680205",1969:"19690204",
 1970:"19700204",1971:"19710204",1972:"19720205",1973:"19730204",1974:"19740204",
 1975:"19750204",1976:"19760205",1977:"19770204",1978:"19780204",1979:"19790204",
 1980:"19800205",1981:"19810204",1982:"19820204",1983:"19830204",1984:"19840204",
 1985:"19850204",1986:"19860204",1987:"19870204",1988:"19880204",1989:"19890204",
 1990:"19900204",1991:"19910204",1992:"19920204",1993:"19930204",1994:"19940204",
 1995:"19950204",1996:"19960204",1997:"19970204",1998:"19980204",1999:"19990204",
 2000:"20000204",2001:"20010204",2002:"20020204",2003:"20030204",2004:"20040204",
 2005:"20050204",2006:"20060204",2007:"20070204",2008:"20080204",2009:"20090204",
 2010:"20100204",2011:"20110204",2012:"20120204",2013:"20130204",2014:"20140204",
 2015:"20150204",2016:"20160204",2017:"20170203",2018:"20180204",2019:"20190204",
 2020:"20200204",2021:"20210203",2022:"20220204",2023:"20230204",2024:"20240204",
 2025:"20250203",2026:"20260204",2027:"20270204",2028:"20280204",2029:"20290203",
 2030:"20300204",2031:"20310204",2032:"20320204",2033:"20330203",2034:"20340204",
 2035:"20350204",2036:"20360204",2037:"20370203",2038:"20380204",2039:"20390204",
 2040:"20400204",2041:"20410203",2042:"20420204",2043:"20430204",2044:"20440204",
 2045:"20450203",2046:"20460204",2047:"20470204",2048:"20480204",2049:"20490203",
 2050:"20500203",2051:"20510204",2052:"20520204",2053:"20530203",2054:"20540203",
 2055:"20550204",2056:"20560204",2057:"20570203",2058:"20580203",2059:"20590204",
}

class EightChar:
    __slots__=("_l",)
    def __init__(self,l): self._l=l
    def getYearGan(self): return self._l.getYearGanExact()
    def getYearZhi(self): return self._l.getYearZhiExact()
    def getMonthGan(self): return self._l.getMonthGanExact()
    def getMonthZhi(self): return self._l.getMonthZhiExact()
    def getDayGan(self): return self._l.getDayGan()
    def getDayZhi(self): return self._l.getDayZhi()
    def getTimeGan(self): return self._l.getTimeGan()
    def getTimeZhi(self): return self._l.getTimeZhi()
    def getYearHideGan(self): return self._l.getYearHideGan()
    def getMonthHideGan(self): return self._l.getMonthHideGan()
    def getDayHideGan(self): return self._l.getDayHideGan()
    def getTimeHideGan(self): return self._l.getTimeHideGan()
    def getYearNaYin(self): return self._l.getYearNaYin()
    def getMonthNaYin(self): return self._l.getMonthNaYin()
    def getDayNaYin(self): return self._l.getDayNaYin()
    def getTimeNaYin(self): return self._l.getTimeNaYin()
    def getYearXunKong(self): return self._l.getYearXunKong()
    def getMonthXunKong(self): return self._l.getMonthXunKong()
    def getDayXunKong(self): return self._l.getDayXunKong()
    def getTimeXunKong(self): return self._l.getTimeXunKong()
    def getTaiYuan(self): return self._l._ty
    def getTaiXi(self): return self._l._tx
    def getMingGong(self): return self._l._mgong
    def getShenGong(self): return self._l._sgong
    def getYun(self,gender): return Yun(self._l,gender)

def _ec(self): return EightChar(self)
Lunar.getEightChar=_ec

class Yun:
    __slots__=("_l","_g","_f","_sy","_sm")
    def __init__(self,l,g):
        self._l=l; self._g=g
        dg=l.getDayGanExact(); yang=SI[dg]%2==0; man=(g==1)
        self._f=(yang and man)or(not yang and not man)
        self._sy=l.year+1; self._sm=l.month
    def getStartYear(self): return self._sy
    def getStartMonth(self): return self._sm
    def getDaYun(self,n=10): return [DaYun(self,i) for i in range(n)]

class DaYun:
    __slots__=("_y","_i","_gz","_sy","_ea","_ey","_xk")
    def __init__(self,y,i):
        self._y=y; self._i=i
        l=y._l; dg=l.getDayGanExact(); dz=l.getDayZhiExact()
        s=1 if y._f else -1; gi=SI[dg]; zi=ZI[dz]; n=i+1
        self._gz=STEMS[(gi+s*n)%10]+ZHI[(zi+s*n)%12]
        self._sy=l.year+1+i*10; self._ea=1+i*10; self._ey=self._sy+9
        xs={"甲":0,"乙":0,"丙":2,"丁":2,"戊":4,"己":4,"庚":6,"辛":6,"壬":8,"癸":8}
        xi=xs.get(dg,0); zi2=ZI[dz]; diff=(zi2-xi)%12
        self._xk=ZHI[(zi2+10-diff)%12]+ZHI[(zi2+11-diff)%12]
    def getGanZhi(self): return self._gz
    def getStartYear(self): return self._sy
    def getEndYear(self): return self._ey
    def getStartAge(self): return self._ea
    def getEndAge(self): return self._ea+9
    def getXunKong(self): return self._xk
    def getIndex(self): return self._i


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)