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:
+19
-6
@@ -54,7 +54,8 @@ def main() -> int:
|
||||
from astro.day_boundary import resolve_calendar_day
|
||||
from chart.tyme_adapter import cast
|
||||
from chart.luck import (
|
||||
compute_luck, decade_pillars, flow_months, flow_years, is_forward,
|
||||
childhood_luck, compute_luck, decade_pillars, flow_months,
|
||||
flow_years, is_forward, luck_label_for_year,
|
||||
)
|
||||
from chart.model import build_chart_data
|
||||
from render.markdown import render
|
||||
@@ -89,7 +90,7 @@ def main() -> int:
|
||||
# L1-a 排盘(tyme4py 唯一接触面)
|
||||
raw = cast(cast_dt, a.gender)
|
||||
|
||||
# L1-b 起运、大运、流年、流月
|
||||
# L1-b 起运、大运、流年、流月、小运
|
||||
forward = is_forward(a.gender, raw["year"]["gan"])
|
||||
luck = compute_luck(
|
||||
birth_tst=tst,
|
||||
@@ -97,20 +98,32 @@ def main() -> int:
|
||||
next_jie_time=raw["next_jie"]["time"],
|
||||
forward=forward,
|
||||
)
|
||||
this_year = datetime.now().year
|
||||
# 大运步数须覆盖流年末端(默认 8 步;年长者自动加步)
|
||||
n_pillars = max(
|
||||
8,
|
||||
int((this_year + 9 - cast_dt.year - luck["start_age_years"]) / 10) + 2,
|
||||
)
|
||||
decade = decade_pillars(
|
||||
raw["month"]["gan"], raw["month"]["zhi"], forward,
|
||||
luck["start_age_years"], cast_dt.year, n=n_pillars,
|
||||
)
|
||||
years = [
|
||||
(y, gz, luck_label_for_year(y, decade))
|
||||
for y, gz in flow_years(cast_dt.year, this_year, 10)
|
||||
]
|
||||
months = flow_months(this_year)
|
||||
childhood = childhood_luck(
|
||||
raw["hour"]["gan"], raw["hour"]["zhi"], forward,
|
||||
luck["start_age_years"], cast_dt.year,
|
||||
)
|
||||
this_year = datetime.now().year
|
||||
years = flow_years(this_year, 10)
|
||||
months = flow_months(this_year)
|
||||
|
||||
# L1-c 组装 ChartData
|
||||
chart = build_chart_data(
|
||||
name=a.name, gender=a.gender, place=a.place, clock=a.birth,
|
||||
loc=loc, tz_audit=tz_audit, st_audit=st_audit, bd_audit=bd_audit,
|
||||
raw=raw, luck=luck, luck_pillars=decade,
|
||||
flow_years=years, flow_months=months,
|
||||
flow_years=years, flow_months=months, childhood_luck=childhood,
|
||||
)
|
||||
|
||||
# L2 渲染:stdout 只出 Markdown
|
||||
|
||||
+37
-3
@@ -114,9 +114,43 @@ def decade_pillars(
|
||||
return pillars
|
||||
|
||||
|
||||
def flow_years(current_year: int, n: int = 10) -> list[tuple[int, str]]:
|
||||
"""从今年起 n 个流年干支。"""
|
||||
return [(y, ganzhi_of_year(y)) for y in range(current_year, current_year + n)]
|
||||
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]]:
|
||||
|
||||
+24
-5
@@ -60,15 +60,17 @@ class ChartData:
|
||||
start_age_text: str = "" # 起运年龄,如「7 岁 7 个月 19 天」
|
||||
terrain: dict[str, str] = field(default_factory=dict) # 四柱键 → 十二长生
|
||||
void: dict[str, str] = field(default_factory=dict) # {"day": .., "hour": ..}
|
||||
shen_sha: list[str] = field(default_factory=list) # 神煞
|
||||
flow_years: list[tuple[int, str]] = field(default_factory=list) # 流年 (年份, 干支)
|
||||
shen_sha: dict[str, list[str]] = field(default_factory=dict) # 神煞:year/month/day/hour/combo → 条目
|
||||
flow_years: list[tuple[int, str, str]] = field(default_factory=list) # 流年 (年份, 干支, 所属大运标签)
|
||||
flow_months: list[tuple[str, str]] = field(default_factory=list) # 流月 (标签, 干支)
|
||||
childhood_luck: list[tuple[int, int, str]] = field(default_factory=list) # 小运 (虚岁, 公元年, 干支)
|
||||
tai_yuan: str = "" # 胎元
|
||||
ming_gong: str = "" # 命宫
|
||||
jie_gap_warning: str = "" # 节气边界提示(空串 = 无)
|
||||
|
||||
# —— 溯源 ——
|
||||
library_version: str = "" # tyme4py 版本,写入输出
|
||||
schema_version: str = "1.0"
|
||||
schema_version: str = "1.2"
|
||||
|
||||
|
||||
def _fmt_dt(dt: datetime) -> str:
|
||||
@@ -93,6 +95,19 @@ def _build_pillar(key: str, raw: dict) -> Pillar:
|
||||
)
|
||||
|
||||
|
||||
def _jie_gap_warning(tst: datetime, prev_jie: dict, next_jie: dict,
|
||||
threshold_hours: float = 12.0) -> str:
|
||||
"""出生真太阳时距交节不足 threshold_hours 时给出月令敏感性提示(空串 = 不提示)。"""
|
||||
for term in (prev_jie, next_jie):
|
||||
gap_h = abs((tst - term["time"]).total_seconds()) / 3600
|
||||
if gap_h < threshold_hours:
|
||||
flip = "前一月" if tst >= term["time"] else "后一月"
|
||||
return (f"⚠️ 节气边界:出生距 {term['name']}({_fmt_dt(term['time'])})"
|
||||
f"仅 {gap_h:.1f} 小时;若出生记录误差超过此值,"
|
||||
f"月令将变为{flip},四柱面目全变")
|
||||
return ""
|
||||
|
||||
|
||||
def build_chart_data(
|
||||
name: str,
|
||||
gender: str,
|
||||
@@ -105,8 +120,9 @@ def build_chart_data(
|
||||
raw: dict,
|
||||
luck: dict,
|
||||
luck_pillars: list[LuckPillar],
|
||||
flow_years: list[tuple[int, str]],
|
||||
flow_years: list[tuple[int, str, str]],
|
||||
flow_months: list[tuple[str, str]],
|
||||
childhood_luck: list[tuple[int, int, str]],
|
||||
) -> ChartData:
|
||||
"""把 tyme_adapter 的原始 dict、各层 audit 与 luck 结果组装成 ChartData。
|
||||
|
||||
@@ -141,11 +157,14 @@ def build_chart_data(
|
||||
start_age_text=luck["start_age_text"],
|
||||
terrain=raw.get("terrain", {}),
|
||||
void=raw.get("void", {}),
|
||||
shen_sha=raw.get("shen_sha", []),
|
||||
shen_sha=raw.get("shen_sha", {}),
|
||||
flow_years=flow_years,
|
||||
flow_months=flow_months,
|
||||
childhood_luck=childhood_luck,
|
||||
tai_yuan=raw.get("tai_yuan", ""),
|
||||
ming_gong=raw.get("ming_gong", ""),
|
||||
jie_gap_warning=_jie_gap_warning(
|
||||
st_audit["tst"], raw["prev_jie"], raw["next_jie"]),
|
||||
# 溯源
|
||||
library_version=raw.get("library_version", ""),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
"""子平通行神煞求值(文档: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}
|
||||
@@ -42,11 +42,17 @@ def _hidden(me, earth_branch) -> list:
|
||||
return items
|
||||
|
||||
|
||||
def _shen_sha(solar: SolarTime) -> list:
|
||||
try:
|
||||
return [g.get_name() for g in solar.get_lunar_hour().get_lunar_day().get_gods()]
|
||||
except Exception:
|
||||
return []
|
||||
def _shen_sha(ganzhi: dict, gender: str) -> dict:
|
||||
"""子平通行神煞(查法见 chart/shensha.py 与 rules/94_神煞细节.md)。"""
|
||||
from chart.shensha import evaluate_natal, STAR_ORDER
|
||||
|
||||
raw = evaluate_natal(ganzhi, gender)
|
||||
order = {name: i for i, name in enumerate(STAR_ORDER)}
|
||||
return {
|
||||
key: [f"{star}({note})"
|
||||
for star, note in sorted(entries, key=lambda e: order[e[0]])]
|
||||
for key, entries in raw.items()
|
||||
}
|
||||
|
||||
|
||||
def cast(cast_dt: datetime, gender: str) -> dict:
|
||||
@@ -59,9 +65,12 @@ def cast(cast_dt: datetime, gender: str) -> dict:
|
||||
|
||||
prev_jie, next_jie = _jie_bounds(cast_dt)
|
||||
|
||||
base = {k: {"gan": p.get_heaven_stem().get_name(),
|
||||
"zhi": p.get_earth_branch().get_name()} for k, p in pillars.items()}
|
||||
ganzhi = {k: v["gan"] + v["zhi"] for k, v in base.items()}
|
||||
|
||||
return {
|
||||
**{k: {"gan": p.get_heaven_stem().get_name(),
|
||||
"zhi": p.get_earth_branch().get_name()} for k, p in pillars.items()},
|
||||
**base,
|
||||
"day_master": me.get_name(),
|
||||
"ten_god": {k: me.get_ten_star(pillars[k].get_heaven_stem()).get_name()
|
||||
for k in ("year", "month", "hour")},
|
||||
@@ -71,7 +80,7 @@ def cast(cast_dt: datetime, gender: str) -> dict:
|
||||
"na_yin": {k: pillars[k].get_sound().get_name() for k in _PILLARS},
|
||||
"void": {k: "".join(b.get_name() for b in pillars[k].get_extra_earth_branches())
|
||||
for k in ("day", "hour")},
|
||||
"shen_sha": _shen_sha(solar),
|
||||
"shen_sha": _shen_sha(ganzhi, gender),
|
||||
"prev_jie": {"name": prev_jie[0].get_name(), "time": prev_jie[1]},
|
||||
"next_jie": {"name": next_jie[0].get_name(), "time": next_jie[1]},
|
||||
"tai_yuan": ec.get_fetal_origin().get_name(),
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""岁运神煞实时查询 —— 模型按需调用。
|
||||
|
||||
用法:
|
||||
python query_shensha.py --gender 女 \
|
||||
--pillars "丁亥 乙巳 庚子 辛巳" --target 丙午 [--label "2026 流年"]
|
||||
|
||||
输出(stdout):
|
||||
- 以本命锚点(日干、年支、日支、月令、年柱纳音、阴阳男女)查得、目标干支所逢之神煞;
|
||||
- 目标干支自身所属之日柱固定组(阴差阳错、十灵日、魁罡、十恶大败、天赦,供流日参看)。
|
||||
查法口径与排盘输出 `## 神煞` 一致(chart/shensha.py,规则见 rules/94_神煞细节.md)。
|
||||
|
||||
退出码:0 成功;2 输入不可用;3 内部错误。
|
||||
"""
|
||||
import argparse
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser(
|
||||
prog="query_shensha.py",
|
||||
description="查询某一干支(流年/流月/大运/流日)相对本命四柱所逢的神煞",
|
||||
)
|
||||
ap.add_argument("--gender", required=True, choices=["男", "女"], help="性别")
|
||||
ap.add_argument("--pillars", required=True,
|
||||
help='本命四柱干支,年月日时顺序,空格分隔,如 "丁亥 乙巳 庚子 辛巳"')
|
||||
ap.add_argument("--target", required=True,
|
||||
help="目标干支(流年/流月/大运/流日),如 丙午")
|
||||
ap.add_argument("--label", default="", help='目标说明,如 "2026 流年",仅用于输出标题')
|
||||
a = ap.parse_args()
|
||||
|
||||
try:
|
||||
from chart.shensha import evaluate_target, GAN, ZHI
|
||||
|
||||
parts = a.pillars.split()
|
||||
if len(parts) != 4 or any(len(p) != 2 or p[0] not in GAN or p[1] not in ZHI
|
||||
for p in parts):
|
||||
print("ERROR: --pillars 应为年月日时四组干支,如 \"丁亥 乙巳 庚子 辛巳\"。",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
target = a.target.strip()
|
||||
if len(target) != 2 or target[0] not in GAN or target[1] not in ZHI:
|
||||
print("ERROR: --target 应为一组干支,如 丙午。", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
pillars = dict(zip(("year", "month", "day", "hour"), parts))
|
||||
result = evaluate_target(pillars, a.gender, target)
|
||||
|
||||
title = f"# 神煞查询:{target}"
|
||||
if a.label:
|
||||
title += f"({a.label})"
|
||||
title += f" 对 {' / '.join(parts)}({a.gender})"
|
||||
print(title)
|
||||
print()
|
||||
print("## 本命锚点查得(岁运逢)")
|
||||
print()
|
||||
if result["anchored"]:
|
||||
for star, note in result["anchored"]:
|
||||
print(f"- {star}({note})")
|
||||
else:
|
||||
print("- (无)")
|
||||
print()
|
||||
print("## 目标干支自身所属")
|
||||
print()
|
||||
if result["own"]:
|
||||
for star, note in result["own"]:
|
||||
print(f"- {star}({note})")
|
||||
else:
|
||||
print("- (无)")
|
||||
return 0
|
||||
except Exception:
|
||||
print("ERROR: 查询内部错误。", file=sys.stderr)
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
return 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -20,6 +20,8 @@ def render(chart) -> str:
|
||||
"已按「子初换日」归入次日。另存「子正换日」一派,"
|
||||
"日柱与时柱将不同,结论需谨慎。"
|
||||
)
|
||||
if chart.jie_gap_warning:
|
||||
L.append(f"- {chart.jie_gap_warning}")
|
||||
L.append("")
|
||||
|
||||
L.append("## 四柱")
|
||||
@@ -63,11 +65,23 @@ def render(chart) -> str:
|
||||
|
||||
L.append("## 神煞")
|
||||
L.append("")
|
||||
if chart.shen_sha:
|
||||
for s in chart.shen_sha:
|
||||
for key, label in (("year", "年柱"), ("month", "月柱"),
|
||||
("day", "日柱"), ("hour", "时柱")):
|
||||
L.append(f"### {label}神煞")
|
||||
entries = chart.shen_sha.get(key) or []
|
||||
if entries:
|
||||
for s in entries:
|
||||
L.append(f"- {s}")
|
||||
else:
|
||||
L.append("- (无)")
|
||||
L.append("")
|
||||
L.append("### 四柱组合")
|
||||
combo = chart.shen_sha.get("combo") or []
|
||||
if combo:
|
||||
for s in combo:
|
||||
L.append(f"- {s}")
|
||||
else:
|
||||
L.append("- (本造未取神煞)")
|
||||
L.append("- (无)")
|
||||
L.append("")
|
||||
|
||||
L.append("## 节令")
|
||||
@@ -90,10 +104,20 @@ def render(chart) -> str:
|
||||
f"({lp.start_age:.2f} 岁起,{lp.start_year} 年)")
|
||||
L.append("")
|
||||
|
||||
L.append("## 小运")
|
||||
L.append("")
|
||||
if chart.childhood_luck:
|
||||
L.append("- 自时柱起,一位一岁;童限(未交大运)参用")
|
||||
for age, year, gz in chart.childhood_luck:
|
||||
L.append(f"- {age} 岁({year} 年):{gz}")
|
||||
else:
|
||||
L.append("- (无:起运不足一岁)")
|
||||
L.append("")
|
||||
|
||||
L.append("## 流年")
|
||||
L.append("")
|
||||
for year, gz in chart.flow_years:
|
||||
L.append(f"- {year} {gz}")
|
||||
for year, gz, label in chart.flow_years:
|
||||
L.append(f"- {year} {gz}({label})")
|
||||
L.append("")
|
||||
|
||||
L.append("## 流月")
|
||||
|
||||
Reference in New Issue
Block a user