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 项通过
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""流年/小运/大运标签单元测试(schema 1.2)。"""
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
|
||||
from chart.luck import ( # noqa: E402
|
||||
childhood_luck, decade_pillars, flow_years, ganzhi_of_year,
|
||||
luck_label_for_year,
|
||||
)
|
||||
from chart.model import LuckPillar # noqa: E402
|
||||
|
||||
|
||||
class FlowYearsTest(unittest.TestCase):
|
||||
def test_ganzhi_of_year(self):
|
||||
self.assertEqual(ganzhi_of_year(2007), "丁亥")
|
||||
self.assertEqual(ganzhi_of_year(2026), "丙午")
|
||||
|
||||
def test_full_range_from_birth_year(self):
|
||||
years = flow_years(2007, 2026, 10)
|
||||
self.assertEqual(years[0], (2007, "丁亥"))
|
||||
self.assertEqual(years[-1], (2035, "乙卯"))
|
||||
self.assertEqual(len(years), 29)
|
||||
self.assertIn((2013, "癸巳"), years)
|
||||
|
||||
|
||||
class ChildhoodLuckTest(unittest.TestCase):
|
||||
def test_forward(self):
|
||||
# 阴年女命顺行:时柱辛巳 → 1 岁壬午、2 岁癸未……10 岁辛卯
|
||||
rows = childhood_luck("辛", "巳", True, 10.34, 2007)
|
||||
self.assertEqual(rows[0], (1, 2007, "壬午"))
|
||||
self.assertEqual(rows[1], (2, 2008, "癸未"))
|
||||
self.assertEqual(rows[-1], (10, 2016, "辛卯"))
|
||||
self.assertEqual(len(rows), 10)
|
||||
|
||||
def test_backward(self):
|
||||
rows = childhood_luck("辛", "巳", False, 3.0, 2000)
|
||||
self.assertEqual(rows[0], (1, 2000, "庚辰"))
|
||||
self.assertEqual(rows[-1], (3, 2002, "戊寅"))
|
||||
|
||||
def test_empty_when_luck_starts_under_one(self):
|
||||
self.assertEqual(childhood_luck("辛", "巳", True, 0.34, 2007), [])
|
||||
|
||||
|
||||
class LuckLabelTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.pillars = [
|
||||
LuckPillar(index=1, gan="丙", zhi="午", start_age=10.34, start_year=2017),
|
||||
LuckPillar(index=2, gan="丁", zhi="未", start_age=20.34, start_year=2027),
|
||||
]
|
||||
|
||||
def test_childhood(self):
|
||||
self.assertEqual(luck_label_for_year(2013, self.pillars), "童限")
|
||||
|
||||
def test_boundary_year(self):
|
||||
self.assertEqual(luck_label_for_year(2017, self.pillars), "交运年·入丙午运")
|
||||
|
||||
def test_inside_pillar(self):
|
||||
self.assertEqual(luck_label_for_year(2020, self.pillars), "丙午运")
|
||||
self.assertEqual(luck_label_for_year(2030, self.pillars), "丁未运")
|
||||
|
||||
def test_no_pillars(self):
|
||||
self.assertEqual(luck_label_for_year(2020, []), "童限")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,271 @@
|
||||
"""神煞求值单元测试(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()
|
||||
Reference in New Issue
Block a user