Files
ZiPingEightChar/scripts/chart/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

325 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""子平通行神煞求值(文档:rules/94_神煞细节.md)。
纯查表,零命理判断:输入四柱干支、性别、年柱纳音,输出按柱分组的神煞清单。
查法口径与 rules/94_神煞细节.md 各 R-煞 条目一一对应;条目注明出处为
《三命通会》者,查法表见本模块顶部各常量。
本命盘与岁运查询共用同一求值器:
- 本命:evaluate_natal() → {year/month/day/hour/combo: [条目文字]}
- 岁运:evaluate_target() → 目标干支(流年/流月/大运/流日)相对本命锚点所逢之神煞
"""
from __future__ import annotations
GAN = "甲乙丙丁戊己庚辛壬癸"
ZHI = "子丑寅卯辰巳午未申酉戌亥"
YANG_GAN = set("甲丙戊庚壬")
# 六十甲子纳音( pairwise ),末字即五行
_NAYIN_PAIRS = [
"海中金", "炉中火", "大林木", "路旁土", "剑锋金",
"山头火", "涧下水", "城头土", "白蜡金", "杨柳木",
"泉中水", "屋上土", "霹雳火", "松柏木", "长流水",
"沙中金", "山下火", "平地木", "壁上土", "金箔金",
"覆灯火", "天河水", "大驿土", "钗钏金", "桑柘木",
"大溪水", "沙中土", "天上火", "石榴木", "大海水",
]
_JIAZI = [GAN[i % 10] + ZHI[i % 12] for i in range(60)]
NAYIN = {gz: _NAYIN_PAIRS[i // 2] for i, gz in enumerate(_JIAZI)}
# —— 日干查地支 ——
TIANYI = {"甲": "丑未", "戊": "丑未", "庚": "丑未",
"乙": "子申", "己": "子申", "丙": "亥酉", "丁": "亥酉",
"壬": "卯巳", "癸": "卯巳", "辛": "午寅"} # 天乙贵人
WENCHANG = {"甲": "巳", "乙": "午", "丙": "申", "戊": "申", "丁": "酉",
"己": "酉", "庚": "亥", "辛": "子", "壬": "寅", "癸": "卯"} # 文昌
LU = {"甲": "寅", "乙": "卯", "丙": "巳", "丁": "午", "戊": "巳",
"己": "午", "庚": "申", "辛": "酉", "壬": "亥", "癸": "子"} # 禄神
YANGREN = {"甲": "卯", "丙": "午", "戊": "午", "庚": "酉", "壬": "子"} # 羊刃(五阳干)
JINYU = {"甲": "辰", "乙": "巳", "丙": "未", "丁": "申", "戊": "未",
"己": "申", "庚": "戌", "辛": "亥", "壬": "丑", "癸": "寅"} # 金舆(禄前二辰)
TAIJI = {"甲": "子午", "乙": "子午", "丙": "卯酉", "丁": "卯酉",
"戊": "辰戌丑未", "己": "辰戌丑未", "庚": "寅亥", "辛": "寅亥",
"壬": "巳申", "癸": "巳申"} # 太极贵人
FUXING = {"甲": "寅子", "乙": "丑卯", "丙": "寅子", "丁": "亥", "戊": "申",
"己": "未", "庚": "午", "辛": "巳", "壬": "辰", "癸": "丑卯"} # 福星贵人
HONGYAN = {"甲": "午", "乙": "午", "丙": "寅", "丁": "未", "戊": "子",
"己": "辰", "庚": "戌", "辛": "酉", "壬": "巳", "癸": "申"} # 红艳(三命通会)
# —— 月令查 ——
TIANDE = {"寅": "丁", "卯": "申", "辰": "壬", "巳": "辛", "午": "亥", "未": "甲",
"申": "癸", "酉": "寅", "戌": "丙", "亥": "乙", "子": "巳", "丑": "庚"} # 天德
YUEDE_GROUPS = {"寅": "丙", "午": "丙", "戌": "丙", "申": "壬", "子": "壬", "辰": "壬",
"亥": "甲", "卯": "甲", "未": "甲", "巳": "庚", "酉": "庚", "丑": "庚"} # 月德
STEM_HE = {"甲": "己", "己": "甲", "乙": "庚", "庚": "乙", "丙": "辛", "辛": "丙",
"丁": "壬", "壬": "丁", "戊": "癸", "癸": "戊"}
BRANCH_HE = {"子": "丑", "丑": "子", "寅": "亥", "亥": "寅", "卯": "戌", "戌": "卯",
"辰": "酉", "酉": "辰", "巳": "申", "申": "巳", "午": "未", "未": "午"}
# —— 三合局查(以年支或日支)——
def _sanhe(key: str, table: tuple[str, str, str, str]) -> str:
"""key 支所属三合局 → 对应目标支。table 序:申子辰、寅午戌、巳酉丑、亥卯未。"""
for group, target in zip(("申子辰", "寅午戌", "巳酉丑", "亥卯未"), table):
if key in group:
return target
raise ValueError(key)
YIMA = ("寅", "申", "亥", "巳") # 驿马
TAOHUA = ("酉", "卯", "午", "子") # 桃花(咸池)
HUAGAI = ("辰", "戌", "丑", "未") # 华盖
JIANGXING = ("子", "午", "酉", "卯") # 将星
JIESHA = ("巳", "亥", "寅", "申") # 劫煞
WANGSHEN = ("亥", "巳", "申", "寅") # 亡神
ZAISHA = ("午", "子", "卯", "酉") # 灾煞
# —— 年支查 ——
GUCHEN = {"亥": "寅", "子": "寅", "丑": "寅", "寅": "巳", "卯": "巳", "辰": "巳",
"巳": "申", "午": "申", "未": "申", "申": "亥", "酉": "亥", "戌": "亥"} # 孤辰
GUASU = {"亥": "戌", "子": "戌", "丑": "戌", "寅": "丑", "卯": "丑", "辰": "丑",
"巳": "辰", "午": "辰", "未": "辰", "申": "未", "酉": "未", "戌": "未"} # 寡宿
CHONG = {z: ZHI[(ZHI.index(z) + 6) % 12] for z in ZHI}
# —— 年柱纳音查 ——
# 天罗地网:火命见戌亥为天罗,水土命见辰巳为地网,金木命无(三命通会·卷三·论天罗地网)
# 童子煞:春秋寅子贵,冬夏卯未辰;金木马卯合,水火鸡犬多;土命逢辰巳(通行口诀,无书证)
SEASON_OF_MONTH = {**{m: "春" for m in "寅卯辰"}, **{m: "夏" for m in "巳午未"},
**{m: "秋" for m in "申酉戌"}, **{m: "冬" for m in "亥子丑"}}
TONGZI_SEASON = {"春": "寅子", "秋": "寅子", "冬": "卯未辰", "夏": "卯未辰"}
TONGZI_NAYIN = {"金": "午卯", "木": "午卯", "水": "酉戌", "火": "酉戌", "土": "辰巳"}
# —— 日柱干支固定组 ——
YINCHA_YANGCUO = {"丙子", "丁丑", "戊寅", "辛卯", "壬辰", "癸巳",
"丙午", "丁未", "戊申", "辛酉", "壬戌", "癸亥"} # 阴差阳错
SHILING = {"甲辰", "乙亥", "丙辰", "丁酉", "戊午",
"庚戌", "庚寅", "辛亥", "壬寅", "癸未"} # 十灵日
KUIGANG = {"庚辰", "庚戌", "壬辰", "戊戌"} # 魁罡
SHIE_DABAI = {"甲辰", "乙巳", "丙申", "丁亥", "戊戌",
"己丑", "庚辰", "辛巳", "壬申", "癸亥"} # 十恶大败
TIANSHE = {"春": "戊寅", "夏": "甲午", "秋": "戊申", "冬": "甲子"} # 天赦日
# —— 三奇(天干顺布相连;三组归属诸书异说,不冠名,只判顺逆)——
SANQI = ("乙丙丁", "甲戊庚", "辛壬癸")
# 输出排序(每柱内部按此序)
STAR_ORDER = [
"天德", "天德合", "月德", "月德合", "天乙贵人", "太极贵人", "福星贵人",
"文昌", "禄神", "羊刃", "金舆", "驿马", "桃花", "红艳", "华盖", "将星",
"劫煞", "灾煞", "亡神", "元辰", "勾煞", "绞煞", "孤辰", "寡宿",
"天罗", "地网", "童子煞", "天赦", "魁罡", "阴差阳错", "十灵日", "十恶大败",
]
PILLAR_KEYS = ("year", "month", "day", "hour")
PILLAR_LABELS = {"year": "年柱", "month": "月柱", "day": "日柱", "hour": "时柱"}
_PILLAR_SHORT = {"year": "年", "month": "月", "day": "日", "hour": "时"}
def _shift(zhi: str, n: int) -> str:
return ZHI[(ZHI.index(zhi) + n) % 12]
def _entry(star: str, note: str) -> tuple[str, str]:
return (star, note)
class ShenShaContext:
"""本命锚点:日干、年支、日支、月令、年柱纳音、阴阳男女。"""
def __init__(self, pillars: dict[str, str], gender: str):
"""pillars: {year/month/day/hour: 干支两字符};gender: 「男」/「女」"""
self.pillars = pillars
self.gender = gender
self.day_gan = pillars["day"][0]
self.year_zhi = pillars["year"][1]
self.day_zhi = pillars["day"][1]
self.month_zhi = pillars["month"][1]
self.year_ganzhi = pillars["year"]
self.day_ganzhi = pillars["day"]
self.nayin_wuxing = NAYIN[self.year_ganzhi][-1]
year_gan = pillars["year"][0]
yang_year = year_gan in YANG_GAN
male = gender == "男"
# 阳男阴女为顺,阴男阳女为逆
self.shun = (yang_year and male) or (not yang_year and not male)
self.season = SEASON_OF_MONTH[self.month_zhi]
def anchor_hits(self, star: str, targets: str, anchor_desc: str,
out: dict[str, list]) -> None:
"""targets 为若干目标支;命中某柱地支则在该柱登记。"""
for key in PILLAR_KEYS:
zhi = self.pillars[key][1]
if zhi in targets:
out[key].append(_entry(
star, f"以{anchor_desc}查,见于{_PILLAR_SHORT[key]}支{zhi}"))
def evaluate_natal(pillars: dict[str, str], gender: str) -> dict[str, list]:
"""本命神煞:{year/month/day/hour: [(星名, 注)], combo: [...]}"""
ctx = ShenShaContext(pillars, gender)
out: dict[str, list] = {k: [] for k in PILLAR_KEYS}
out["combo"] = []
# —— 日干查 ——
ctx.anchor_hits("天乙贵人", TIANYI[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("文昌", WENCHANG[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("禄神", LU[ctx.day_gan], f"日干{ctx.day_gan}", out)
if ctx.day_gan in YANGREN:
ctx.anchor_hits("羊刃", YANGREN[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("金舆", JINYU[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("太极贵人", TAIJI[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("福星贵人", FUXING[ctx.day_gan], f"日干{ctx.day_gan}", out)
ctx.anchor_hits("红艳", HONGYAN[ctx.day_gan], f"日干{ctx.day_gan}", out)
# —— 月令查(天德、月德及其合,可应天干或地支)——
for star, target in (("天德", TIANDE[ctx.month_zhi]),
("月德", YUEDE_GROUPS[ctx.month_zhi])):
he = STEM_HE.get(target) or BRANCH_HE.get(target)
for key in PILLAR_KEYS:
gan, zhi = ctx.pillars[key]
if gan == target:
out[key].append(_entry(
star, f"以月令{ctx.month_zhi}查,见于{_PILLAR_SHORT[key]}干{gan}"))
elif zhi == target:
out[key].append(_entry(
star, f"以月令{ctx.month_zhi}查,见于{_PILLAR_SHORT[key]}支{zhi}"))
if gan == he:
out[key].append(_entry(
f"{star}合", f"以月令{ctx.month_zhi}查,见于{_PILLAR_SHORT[key]}干{gan}"))
elif zhi == he:
out[key].append(_entry(
f"{star}合", f"以月令{ctx.month_zhi}查,见于{_PILLAR_SHORT[key]}支{zhi}"))
# —— 年支、日支并查(三合局诸煞)——
for star, table in (("驿马", YIMA), ("桃花", TAOHUA), ("华盖", HUAGAI),
("将星", JIANGXING), ("劫煞", JIESHA),
("灾煞", ZAISHA), ("亡神", WANGSHEN)):
for anchor_zhi, anchor_desc in ((ctx.year_zhi, f"年支{ctx.year_zhi}"),
(ctx.day_zhi, f"日支{ctx.day_zhi}")):
ctx.anchor_hits(star, _sanhe(anchor_zhi, table), anchor_desc, out)
# —— 年支查(孤辰寡宿、元辰、勾绞)——
ctx.anchor_hits("孤辰", GUCHEN[ctx.year_zhi], f"年支{ctx.year_zhi}", out)
ctx.anchor_hits("寡宿", GUASU[ctx.year_zhi], f"年支{ctx.year_zhi}", out)
yz = ctx.year_zhi
if ctx.shun: # 阳男阴女
yuan_chen = _shift(CHONG[yz], 1) # 冲前一位
gou, jiao = _shift(yz, 3), _shift(yz, -3) # 命前三辰为勾,后三辰为绞
else: # 阴男阳女
yuan_chen = _shift(CHONG[yz], -1) # 冲后一位
jiao, gou = _shift(yz, 3), _shift(yz, -3)
ctx.anchor_hits("元辰", yuan_chen, f"年支{yz}", out)
ctx.anchor_hits("勾煞", gou, f"年支{yz}", out)
ctx.anchor_hits("绞煞", jiao, f"年支{yz}", out)
# —— 年柱纳音查 ——
wx = ctx.nayin_wuxing
if wx == "火":
ctx.anchor_hits("天罗", "戌亥", f"年柱纳音{wx}命", out)
elif wx in "水土":
ctx.anchor_hits("地网", "辰巳", f"年柱纳音{wx}命", out)
for key in ("day", "hour"): # 童子煞查日、时二支
zhi = ctx.pillars[key][1]
if zhi in TONGZI_SEASON[ctx.season]:
out[key].append(_entry(
"童子煞", f"以月令{ctx.month_zhi}季节查,见于{_PILLAR_SHORT[key]}支{zhi}"))
elif zhi in TONGZI_NAYIN[wx]:
out[key].append(_entry(
"童子煞", f"以年命纳音{wx}查,见于{_PILLAR_SHORT[key]}支{zhi}"))
# —— 日柱干支固定组 ——
fixed = (("阴差阳错", YINCHA_YANGCUO), ("十灵日", SHILING),
("魁罡", KUIGANG), ("十恶大败", SHIE_DABAI))
for star, group in fixed:
if ctx.day_ganzhi in group:
out["day"].append(_entry(star, "日柱干支直查"))
if ctx.day_ganzhi == TIANSHE[ctx.season]:
out["day"].append(_entry("天赦", f"{ctx.season}季生,日柱干支直查"))
# —— 三奇(天干顺布相连)——
stems = "".join(ctx.pillars[k][0] for k in PILLAR_KEYS)
for seq in SANQI:
if seq in stems:
i = stems.index(seq)
span = "年月日" if i == 0 else "月日时"
out["combo"].append(_entry("三奇贵人", f"{seq}顺布,见于{span}天干"))
elif seq[::-1] in stems:
i = stems.index(seq[::-1])
span = "年月日" if i == 0 else "月日时"
out["combo"].append(_entry("三奇贵人", f"{seq}逆布,见于{span}天干"))
return out
def evaluate_target(pillars: dict[str, str], gender: str,
target_ganzhi: str) -> dict[str, list]:
"""岁运查询:目标干支(流年/流月/大运/流日)相对本命锚点所逢神煞。
返回 {anchored: [(星名, 注)], own: [(星名, 注)]}:
anchored —— 以本命日干/年支/日支/月令/年命纳音查得,目标干支逢之;
own —— 目标干支自身所属之日柱固定组(阴差阳错等,供流日查询参看)。
"""
ctx = ShenShaContext(pillars, gender)
t_gan, t_zhi = target_ganzhi[0], target_ganzhi[1]
anchored: list[tuple[str, str]] = []
own: list[tuple[str, str]] = []
def hit(star: str, targets: str, anchor_desc: str) -> None:
if t_zhi in targets:
anchored.append(_entry(star, f"以{anchor_desc}查,见于{target_ganzhi}之支"))
hit("天乙贵人", TIANYI[ctx.day_gan], f"日干{ctx.day_gan}")
hit("文昌", WENCHANG[ctx.day_gan], f"日干{ctx.day_gan}")
hit("禄神", LU[ctx.day_gan], f"日干{ctx.day_gan}")
if ctx.day_gan in YANGREN:
hit("羊刃", YANGREN[ctx.day_gan], f"日干{ctx.day_gan}")
hit("金舆", JINYU[ctx.day_gan], f"日干{ctx.day_gan}")
hit("太极贵人", TAIJI[ctx.day_gan], f"日干{ctx.day_gan}")
hit("福星贵人", FUXING[ctx.day_gan], f"日干{ctx.day_gan}")
hit("红艳", HONGYAN[ctx.day_gan], f"日干{ctx.day_gan}")
for star, target in (("天德", TIANDE[ctx.month_zhi]),
("月德", YUEDE_GROUPS[ctx.month_zhi])):
he = STEM_HE.get(target) or BRANCH_HE.get(target)
if t_gan == target or t_zhi == target:
anchored.append(_entry(star, f"以月令{ctx.month_zhi}查,逢{target_ganzhi}"))
if t_gan == he or t_zhi == he:
anchored.append(_entry(f"{star}合", f"以月令{ctx.month_zhi}查,逢{target_ganzhi}"))
for star, table in (("驿马", YIMA), ("桃花", TAOHUA), ("华盖", HUAGAI),
("将星", JIANGXING), ("劫煞", JIESHA),
("灾煞", ZAISHA), ("亡神", WANGSHEN)):
for anchor_zhi, anchor_desc in ((ctx.year_zhi, f"年支{ctx.year_zhi}"),
(ctx.day_zhi, f"日支{ctx.day_zhi}")):
hit(star, _sanhe(anchor_zhi, table), anchor_desc)
hit("孤辰", GUCHEN[ctx.year_zhi], f"年支{ctx.year_zhi}")
hit("寡宿", GUASU[ctx.year_zhi], f"年支{ctx.year_zhi}")
yz = ctx.year_zhi
if ctx.shun:
yuan_chen, gou, jiao = _shift(CHONG[yz], 1), _shift(yz, 3), _shift(yz, -3)
else:
yuan_chen, jiao, gou = _shift(CHONG[yz], -1), _shift(yz, 3), _shift(yz, -3)
hit("元辰", yuan_chen, f"年支{yz}")
hit("勾煞", gou, f"年支{yz}")
hit("绞煞", jiao, f"年支{yz}")
wx = ctx.nayin_wuxing
if wx == "火":
hit("天罗", "戌亥", f"年柱纳音{wx}命")
elif wx in "水土":
hit("地网", "辰巳", f"年柱纳音{wx}命")
if t_zhi in TONGZI_SEASON[ctx.season]:
anchored.append(_entry("童子煞", f"以月令{ctx.month_zhi}季节查,逢{target_ganzhi}"))
elif t_zhi in TONGZI_NAYIN[wx]:
anchored.append(_entry("童子煞", f"以年命纳音{wx}查,逢{target_ganzhi}"))
for star, group in (("阴差阳错", YINCHA_YANGCUO), ("十灵日", SHILING),
("魁罡", KUIGANG), ("十恶大败", SHIE_DABAI)):
if target_ganzhi in group:
own.append(_entry(star, "目标干支自身所属"))
if target_ganzhi == TIANSHE[ctx.season]:
own.append(_entry("天赦", f"{ctx.season}季之目标干支自身所属"))
order = {name: i for i, name in enumerate(STAR_ORDER)}
anchored.sort(key=lambda e: order[e[0]])
own.sort(key=lambda e: order[e[0]])
return {"anchored": anchored, "own": own}