Files
ZiPingEightChar/tests/test_shensha.py
T
moxiaonian 618bf8dcab v3.4:童限小运、全量流年与节气边界提示;解释输出契约 v3.0 直答先行
排盘输出 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 项通过
2026-10-08 13:07:23 +08:00

272 lines
11 KiB
Python

"""神煞求值单元测试(chart/shensha.py)。
对拍基准:rules/94_神煞细节.md 各条目查法 + 《三命通会》卷三诸神煞篇。
整盘向量取「丁亥/乙巳/庚子/辛巳(女)」,与人工按 94 篇逐条手推的结果一致。
"""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
from chart.shensha import evaluate_natal, evaluate_target # noqa: E402
def natal(year, month, day, hour, gender="男"):
return evaluate_natal(
{"year": year, "month": month, "day": day, "hour": hour}, gender)
def stars(result, key):
return {s for s, _ in result[key]}
class WholeChartVectorTest(unittest.TestCase):
"""丁亥/乙巳/庚子/辛巳(女)整盘对拍。"""
@classmethod
def setUpClass(cls):
cls.r = natal("丁亥", "乙巳", "庚子", "辛巳", gender="女")
def test_year(self):
self.assertEqual(stars(self.r, "year"), {"文昌", "太极贵人", "亡神"})
def test_month(self):
self.assertEqual(stars(self.r, "month"),
{"月德合", "驿马", "劫煞", "地网"})
def test_day(self):
self.assertEqual(stars(self.r, "day"), {"月德", "桃花", "将星"})
def test_hour(self):
self.assertEqual(stars(self.r, "hour"),
{"天德", "驿马", "劫煞", "地网", "童子煞"})
def test_combo_empty(self):
self.assertEqual(self.r["combo"], [])
class GanLookupTest(unittest.TestCase):
"""日干查支诸星。"""
def test_tianyi(self):
r = natal("甲子", "丙寅", "甲午", "辛未") # 甲日 → 丑未
self.assertIn("天乙贵人", stars(r, "hour"))
self.assertNotIn("天乙贵人", stars(r, "year"))
def test_lu_ren_jinyu(self):
r = natal("丙子", "庚寅", "甲申", "己巳") # 甲禄在寅
self.assertIn("禄神", stars(r, "month"))
all_stars = stars(r, "month") | stars(r, "day") | stars(r, "year") | stars(r, "hour")
self.assertNotIn("羊刃", all_stars) # 盘无卯,甲日亦无刃可取
r2 = natal("丙子", "庚寅", "甲辰", "己巳") # 甲日金舆在辰
self.assertIn("金舆", stars(r2, "day"))
def test_yangren_yang_gan(self):
r = natal("丙子", "辛卯", "甲戌", "己巳") # 甲刃在卯
self.assertIn("羊刃", stars(r, "month"))
def test_yangren_yin_gan_none(self):
"""阴干无刃:乙日见卯只取禄,不取刃。"""
r = natal("丙子", "庚寅", "乙卯", "己巳")
self.assertNotIn("羊刃", stars(r, "day"))
self.assertIn("禄神", stars(r, "day"))
def test_taiji_fuxing_hongyan(self):
r = natal("丙子", "庚寅", "庚午", "丁亥") # 庚日:太极寅亥、福星午、红艳戌
self.assertIn("太极贵人", stars(r, "hour"))
self.assertIn("福星贵人", stars(r, "day"))
self.assertNotIn("红艳", stars(r, "day") | stars(r, "hour"))
def test_wenchang(self):
r = natal("丙子", "庚寅", "乙巳", "丁午") # 乙日文昌在午
self.assertIn("文昌", stars(r, "hour"))
class MonthLingTest(unittest.TestCase):
"""月令查天德、月德及其合(可应天干或地支)。"""
def test_tiande_stem_and_branch(self):
r = natal("辛亥", "庚寅", "丁子", "辛巳") # 寅月天德丁(日干)、天德合壬
self.assertIn("天德", stars(r, "day"))
r2 = natal("辛亥", "庚寅", "壬子", "辛巳")
self.assertIn("天德合", stars(r2, "day"))
def test_tiande_branch_hit(self):
r = natal("甲子", "庚午", "丙子", "己亥") # 午月天德亥(时支)、月德丙(日干)
self.assertIn("天德", stars(r, "hour"))
self.assertIn("月德", stars(r, "day"))
def test_yuede(self):
r = natal("戊申", "壬子", "甲子", "丙寅") # 子月(申子辰)月德壬、月德合丁
self.assertIn("月德", stars(r, "month"))
r2 = natal("戊申", "丁丑", "甲子", "丙寅") # 丑月(巳酉丑)月德庚
self.assertNotIn("月德", stars(r2, "month"))
class SanheLookupTest(unittest.TestCase):
"""三合局诸煞:年支、日支并查。"""
def test_yima_double_anchor(self):
# 年支巳(巳酉丑→亥)、日支午(寅午戌→申)
r = natal("辛巳", "庚寅", "戊午", "壬申")
self.assertIn("驿马", stars(r, "hour")) # 以日支午查
r2 = natal("辛巳", "庚寅", "戊午", "乙亥")
self.assertIn("驿马", stars(r2, "hour")) # 以年支巳查
def test_taohua_huagai_jiangxing(self):
# 日支辰(申子辰):桃花酉、华盖辰、将星子
r = natal("甲子", "辛酉", "丙辰", "戊子")
self.assertIn("桃花", stars(r, "month"))
self.assertIn("华盖", stars(r, "day"))
self.assertIn("将星", stars(r, "year"))
self.assertIn("将星", stars(r, "hour"))
def test_jiesha_wangshen_zaisha(self):
# 年支卯(亥卯未):劫煞申、亡神寅、灾煞酉
r = natal("丁卯", "壬寅", "甲午", "壬申")
self.assertIn("亡神", stars(r, "month"))
self.assertIn("劫煞", stars(r, "hour"))
r2 = natal("丁卯", "己酉", "甲午", "壬申")
self.assertIn("灾煞", stars(r2, "month"))
class YearBranchAndGenderTest(unittest.TestCase):
"""孤辰寡宿、元辰、勾绞——与阴阳男女有关。"""
def test_guchen_guasu(self):
r = natal("戊寅", "乙巳", "甲午", "戊辰") # 年支寅(寅卯辰):孤辰巳、寡宿丑
self.assertIn("孤辰", stars(r, "month"))
self.assertNotIn("寡宿", stars(r, "month"))
def test_yuanchen_shun(self):
"""阳男:冲前一位。子年冲午,午前一位 = 未。"""
r = natal("甲子", "辛未", "丙午", "戊子", gender="男")
self.assertIn("元辰", stars(r, "month"))
def test_yuanchen_ni(self):
"""阴女同阳男顺查;阴男阳女逆查。子年阴男:冲后一位 = 巳。"""
r_male = natal("乙丑", "辛巳", "丙午", "戊子", gender="男")
# 乙丑年支丑,阴男:冲后一位。丑冲未,未后一位 = 午
self.assertIn("元辰", stars(r_male, "day") | stars(r_male, "hour"))
r_female = natal("乙丑", "辛巳", "丙午", "戊子", gender="女")
# 阴女顺查:冲前一位 = 申,盘无申 → 无元辰
self.assertNotIn("元辰", stars(r_female, "month") | stars(r_female, "day") |
stars(r_female, "year") | stars(r_female, "hour"))
def test_goujiao(self):
"""甲子阳男:命前三辰卯为勾、后三辰酉为绞。"""
r = natal("甲子", "丁卯", "辛酉", "戊子", gender="男")
self.assertIn("勾煞", stars(r, "month"))
self.assertIn("绞煞", stars(r, "day"))
r2 = natal("甲子", "丁卯", "辛酉", "戊子", gender="女")
self.assertIn("绞煞", stars(r2, "month"))
self.assertIn("勾煞", stars(r2, "day"))
class NayinTest(unittest.TestCase):
"""年柱纳音查:天罗地网、童子煞。"""
def test_tianluo_fire(self):
r = natal("丙寅", "壬辰", "甲戌", "乙亥") # 炉中火 → 天罗戌亥
self.assertIn("天罗", stars(r, "day"))
self.assertIn("天罗", stars(r, "hour"))
def test_diwang_water_earth(self):
r = natal("丙子", "壬辰", "甲辰", "己巳") # 涧下水 → 地网辰巳
self.assertIn("地网", stars(r, "month"))
self.assertIn("地网", stars(r, "day"))
self.assertIn("地网", stars(r, "hour"))
def test_luowang_metal_wood_none(self):
r = natal("甲子", "壬辰", "甲戌", "乙亥") # 海中金 → 无罗网
for k in ("year", "month", "day", "hour"):
self.assertNotIn("天罗", stars(r, k))
self.assertNotIn("地网", stars(r, k))
def test_tongzi_season(self):
r = natal("甲子", "丙寅", "戊寅", "甲寅") # 春生见寅(日支、时支皆中)
self.assertIn("童子煞", stars(r, "day"))
self.assertIn("童子煞", stars(r, "hour"))
def test_tongzi_nayin(self):
# 庚午路旁土 → 辰巳;秋月生(寅子),日时支午卯两不涉 → 须无
r = natal("庚午", "甲申", "甲午", "丁卯")
self.assertNotIn("童子煞", stars(r, "day") | stars(r, "hour"))
# 甲子海中金 → 午卯;秋月生不涉季节组,纯验纳音组
r2 = natal("甲子", "壬申", "甲午", "丁卯")
self.assertIn("童子煞", stars(r2, "day"))
self.assertIn("童子煞", stars(r2, "hour"))
class FixedDayGroupTest(unittest.TestCase):
"""日柱干支固定组。"""
def test_yincha_yangcuo(self):
r = natal("甲子", "丙寅", "丙子", "戊子")
self.assertIn("阴差阳错", stars(r, "day"))
r2 = natal("甲子", "丙寅", "丙戌", "戊子")
self.assertNotIn("阴差阳错", stars(r2, "day"))
def test_shiling_kuigang(self):
r = natal("甲子", "丙寅", "庚戌", "戊子")
self.assertIn("十灵日", stars(r, "day"))
self.assertIn("魁罡", stars(r, "day"))
def test_shie_dabai_jichou(self):
"""己丑为十恶大败(甲申旬空午未、己禄在午);epub 作乙丑系讹字。"""
r = natal("甲子", "丙寅", "己丑", "戊子")
self.assertIn("十恶大败", stars(r, "day"))
r2 = natal("甲子", "丙寅", "乙丑", "戊子")
self.assertNotIn("十恶大败", stars(r2, "day"))
def test_tianshe(self):
r = natal("甲子", "丙寅", "戊寅", "戊午") # 春月戊寅日
self.assertIn("天赦", stars(r, "day"))
r2 = natal("甲子", "壬申", "戊寅", "戊午") # 秋月戊寅日 → 非
self.assertNotIn("天赦", stars(r2, "day"))
class SanqiTest(unittest.TestCase):
def test_shunbu(self):
r = natal("乙巳", "丙戌", "丁亥", "辛丑")
self.assertEqual(len(r["combo"]), 1)
star, note = r["combo"][0]
self.assertEqual(star, "三奇贵人")
self.assertIn("顺布", note)
def test_nibu(self):
r = natal("丁巳", "丙戌", "乙亥", "辛丑")
self.assertEqual(len(r["combo"]), 1)
self.assertIn("逆布", r["combo"][0][1])
def test_not_adjacent_no_sanqi(self):
r = natal("乙巳", "甲戌", "丙子", "丁丑") # 乙丙丁不相连
self.assertEqual(r["combo"], [])
class TargetQueryTest(unittest.TestCase):
"""岁运查询(evaluate_target)。"""
P = {"year": "丁亥", "month": "乙巳", "day": "庚子", "hour": "辛巳"}
def test_anchored(self):
"""丙午 对 丁亥/乙巳/庚子/辛巳(女):福星(庚见午)、天德合(丙)、
灾煞(日支子见午)、元辰(阴女顺查,亥冲巳前一位为午);丙午自身属阴差阳错。"""
r = evaluate_target(self.P, "女", "丙午")
names = {s for s, _ in r["anchored"]}
self.assertEqual(names, {"福星贵人", "天德合", "灾煞", "元辰"})
own = {s for s, _ in r["own"]}
self.assertEqual(own, {"阴差阳错"})
def test_anchored_yisi(self):
r = evaluate_target(self.P, "女", "乙巳")
names = {s for s, _ in r["anchored"]}
self.assertEqual(names, {"月德合", "驿马", "劫煞", "地网", "童子煞"})
own = {s for s, _ in r["own"]}
self.assertEqual(own, {"十恶大败"})
if __name__ == "__main__":
unittest.main()