排盘输出 schema 1.1→1.2:
- ## 流年 改为出生年至今+10 年全量,逐年标注所属区间(童限/某运/交运年)
- 新增 ## 小运(童限各虚岁干支,自时柱起、一位一岁、顺逆同大运)
- 生于交节前后 12 小时内出 ⚠️ 节气边界提示(月令敏感性)
- 神煞改由脚本按查法直接计算并列锚点;新增 scripts/query_shensha.py 查岁运神煞
规则库 380→541(16→20 文件,版本 3.4):
- 新增 89_旺衰扶抑 / 91_何知事类 / 92_调候 / 93_性情(滴天髓、穷通宝鉴参看层)
- 90_行运.md 新增 §10 童限(R-运-36~39,蒸馏《命理探源》《三命通会》小运诸法)
- R-运-33 改写为核对前置:intake 一问定场景,可核对走锚点、不可核对直推,输出只出现一次
- R-运-32 增前提挑战、直答收敛(一个主答案+置信度)、同机制证伪降权
- R-运-34 增救应回查(合绊解冲折减引动并回写排序);R-运-10 补「缓急非早晚」之辨
- 94 神煞扩展 19 条(三命通会书证);规则编号记录移调试层
- sources 按书归档五书全文;黄金集扩至 291 例(真诠 92 + 滴天髓 199)
SKILL.md 输出契约 v2.1→v3.0:
- 骨架改为 排盘口径→直答→现实对应→分析推理→存疑与边界→免责
- 说人话段改现实对应段(问题域优先);编号默认不进用户面输出
- Intake 增场景判定一问(命主本人/替人看/能否核对)
测试:新增 test_luck.py 9 项,全套 65 项通过
167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
"""起运与大运(文档 4.8 节)。
|
|
|
|
规则(真诠体系):
|
|
- 阳男阴女顺排,阴男阳女逆排
|
|
- 起运:顺排取出生时刻到下一节、逆排取到上一节的时间差
|
|
- 折算:3 天 = 1 年(1 年 = 12 个月 = 360 天)
|
|
- 大运每步 10 年,从月柱干支起顺/逆推
|
|
|
|
纯 Python 历法推算,不 import tyme4py。
|
|
"""
|
|
from datetime import datetime
|
|
|
|
from .model import LuckPillar
|
|
|
|
GAN = "甲乙丙丁戊己庚辛壬癸"
|
|
ZHI = "子丑寅卯辰巳午未申酉戌亥"
|
|
|
|
YANG_GAN = frozenset("甲丙戊庚壬")
|
|
|
|
# 年上起月法(五虎遁):年干 → 寅月天干
|
|
# 甲己之年丙作首,乙庚之年戊为头,丙辛之年寻庚起,丁壬壬寅顺水流,戊癸甲寅
|
|
_MONTH_HEAD_GAN = {
|
|
"甲": "丙", "己": "丙",
|
|
"乙": "戊", "庚": "戊",
|
|
"丙": "庚", "辛": "庚",
|
|
"丁": "壬", "壬": "壬",
|
|
"戊": "甲", "癸": "甲",
|
|
}
|
|
|
|
# 节气月:月支从寅起(立春为寅月),(月支, 起始节气)
|
|
_SOLAR_MONTHS = (
|
|
("寅", "立春"), ("卯", "惊蛰"), ("辰", "清明"), ("巳", "立夏"),
|
|
("午", "芒种"), ("未", "小暑"), ("申", "立秋"), ("酉", "白露"),
|
|
("戌", "寒露"), ("亥", "立冬"), ("子", "大雪"), ("丑", "小寒"),
|
|
)
|
|
|
|
SECONDS_PER_DAY = 86400
|
|
DAYS_PER_LUCK_YEAR = 3.0 # 3 天折 1 年
|
|
|
|
|
|
def ganzhi_of_year(y: int) -> str:
|
|
"""公元 4 年为甲子,(y-4)%60 定位。"""
|
|
return GAN[(y - 4) % 10] + ZHI[(y - 4) % 12]
|
|
|
|
|
|
def ganzhi_cycle_next(gan: str, zhi: str, n: int) -> tuple[str, str]:
|
|
"""干支同步推进 n 步(n 可为负)。"""
|
|
return GAN[(GAN.index(gan) + n) % 10], ZHI[(ZHI.index(zhi) + n) % 12]
|
|
|
|
|
|
def is_forward(gender: str, year_gan: str) -> bool:
|
|
"""阳年干(甲丙戊庚壬)+ 男 → 顺;阴年干 + 女 → 顺。"""
|
|
return (year_gan in YANG_GAN) == (gender == "男")
|
|
|
|
|
|
def compute_luck(
|
|
birth_tst: datetime,
|
|
prev_jie_time: datetime,
|
|
next_jie_time: datetime,
|
|
forward: bool,
|
|
) -> dict:
|
|
"""起运:顺排取下一节减出生,逆排取出生减上一节;3 天折 1 年。
|
|
|
|
返回 {"is_forward", "start_age_years", "start_age_text"}。
|
|
start_age_years 保留 4 位小数;起运年龄精度要到「天」级,否则起运年份会差一年。
|
|
"""
|
|
if forward:
|
|
delta = next_jie_time - birth_tst
|
|
else:
|
|
delta = birth_tst - prev_jie_time
|
|
|
|
start_age_years = delta.total_seconds() / SECONDS_PER_DAY / DAYS_PER_LUCK_YEAR
|
|
|
|
return {
|
|
"is_forward": forward,
|
|
"start_age_years": round(start_age_years, 4),
|
|
"start_age_text": _age_to_text(start_age_years),
|
|
}
|
|
|
|
|
|
def _age_to_text(years: float) -> str:
|
|
"""年数 → 「X 岁 Y 个月 Z 天」。
|
|
|
|
按 1 年 = 12 个月 = 360 天折算(0.5 年 = 6 个月),四舍五入到天。
|
|
"""
|
|
total_days = round(years * 360)
|
|
y = total_days // 360
|
|
m = (total_days % 360) // 30
|
|
d = total_days % 30
|
|
return f"{y} 岁 {m} 个月 {d} 天"
|
|
|
|
|
|
def decade_pillars(
|
|
month_gan: str,
|
|
month_zhi: str,
|
|
forward: bool,
|
|
start_age_years: float,
|
|
birth_year: int,
|
|
n: int = 8,
|
|
) -> list[LuckPillar]:
|
|
"""从月柱起顺/逆推 n 步大运(第 1 步为月柱 ±1)。"""
|
|
step = 1 if forward else -1
|
|
pillars = []
|
|
for index in range(n):
|
|
gan, zhi = ganzhi_cycle_next(month_gan, month_zhi, step * (index + 1))
|
|
start_age = start_age_years + 10 * index
|
|
pillars.append(LuckPillar(
|
|
index=index + 1,
|
|
gan=gan,
|
|
zhi=zhi,
|
|
start_age=round(start_age, 4),
|
|
start_year=birth_year + round(start_age),
|
|
))
|
|
return pillars
|
|
|
|
|
|
def flow_years(birth_year: int, current_year: int, n: int = 10) -> list[tuple[int, str]]:
|
|
"""出生年至今 +n 个流年干支(回溯断运需要过往流年)。"""
|
|
return [(y, ganzhi_of_year(y)) for y in range(birth_year, current_year + n)]
|
|
|
|
|
|
def childhood_luck(
|
|
hour_gan: str,
|
|
hour_zhi: str,
|
|
forward: bool,
|
|
start_age_years: float,
|
|
birth_year: int,
|
|
) -> list[tuple[int, int, str]]:
|
|
"""小运(童限):自时柱起,一位一岁,顺逆与大运同(阳男阴女顺、阴男阳女逆)。
|
|
|
|
覆盖虚岁 1 岁至起运前;起运不足一岁者返回空表。
|
|
返回 (虚岁, 公元年, 干支)。
|
|
"""
|
|
step = 1 if forward else -1
|
|
last_age = int(start_age_years)
|
|
out = []
|
|
for age in range(1, last_age + 1):
|
|
gan, zhi = ganzhi_cycle_next(hour_gan, hour_zhi, step * age)
|
|
out.append((age, birth_year + age - 1, gan + zhi))
|
|
return out
|
|
|
|
|
|
def luck_label_for_year(year: int, luck_pillars: list[LuckPillar]) -> str:
|
|
"""该流年所属区间标签(按起运整数年归属):童限 / 某运干支 / 交运年。"""
|
|
if not luck_pillars or year < luck_pillars[0].start_year:
|
|
return "童限"
|
|
label = f"{luck_pillars[0].gan}{luck_pillars[0].zhi}运"
|
|
for lp in luck_pillars:
|
|
if year >= lp.start_year:
|
|
label = f"{lp.gan}{lp.zhi}运"
|
|
if year == lp.start_year:
|
|
label = f"交运年·入{lp.gan}{lp.zhi}运"
|
|
return label
|
|
|
|
|
|
def flow_months(year: int) -> list[tuple[str, str]]:
|
|
"""该年 12 个节气月,返回 (「X月(节气起)」, 干支)。
|
|
|
|
月支从寅起(立春为寅月);月干按年上起月法(五虎遁)。
|
|
"""
|
|
year_gan = GAN[(year - 4) % 10]
|
|
head = GAN.index(_MONTH_HEAD_GAN[year_gan])
|
|
return [
|
|
(f"{zhi}月({jie}起)", GAN[(head + i) % 10] + zhi)
|
|
for i, (zhi, jie) in enumerate(_SOLAR_MONTHS)
|
|
]
|