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

171 lines
5.7 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.
"""L1→L2 数据契约(文档 4.7 节)。
改动本文件等于改契约版本(schema_version)。
本模块只做数据定义与组装,不含任何命理判断(P1)。
"""
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Pillar:
gan: str
zhi: str
gan_ten_god: str | None = None # 该柱天干相对日干的十神
hidden_stems: list[str] = field(default_factory=list) # 藏干
hidden_ten_gods: list[str] = field(default_factory=list)
na_yin: str | None = None
@dataclass
class LuckPillar:
index: int
gan: str
zhi: str
start_age: float
start_year: int
@dataclass
class ChartData:
# —— 输入 ——
name: str
gender: str
birthplace: str
birth_clock: str # 用户原始输入
# —— 时间归算审计(P3)——
tz_used: str
is_dst: bool
lon_correction_min: float
eot_minutes: float
true_solar_time: str
is_late_zi: bool
# —— 四柱 ——
year: Pillar
month: Pillar
day: Pillar
hour: Pillar
# —— 派生 ——
day_master: str # 日干
month_ling: str # 月令地支(取格之锚)
solar_term_before: str # 出生前一个节气
solar_term_after: str # 出生后一个节气
luck_pillars: list[LuckPillar] = field(default_factory=list)
# —— 派生(渲染层补充字段)——
luck_is_forward: bool = False # 大运顺排/逆排
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: 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.2"
def _fmt_dt(dt: datetime) -> str:
return dt.strftime("%Y-%m-%d %H:%M")
def _fmt_term(term: dict) -> str:
"""{"name", "time"} → 「节气名 YYYY-MM-DD HH:MM」"""
return f"{term['name']} {_fmt_dt(term['time'])}"
def _build_pillar(key: str, raw: dict) -> Pillar:
gz = raw[key]
hidden_pairs = raw.get("hidden", {}).get(key, [])
return Pillar(
gan=gz["gan"],
zhi=gz["zhi"],
gan_ten_god=raw.get("ten_god", {}).get(key),
hidden_stems=[pair[0] for pair in hidden_pairs],
hidden_ten_gods=[pair[1] for pair in hidden_pairs],
na_yin=raw.get("na_yin", {}).get(key),
)
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,
place: str,
clock: str,
loc,
tz_audit: dict,
st_audit: dict,
bd_audit: dict,
raw: dict,
luck: dict,
luck_pillars: list[LuckPillar],
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。
纯组装,零命理判断。
"""
return ChartData(
# 输入
name=name,
gender=gender,
birthplace=place,
birth_clock=clock,
# 时间归算审计
tz_used=tz_audit["tz_used"],
is_dst=tz_audit["is_dst"],
lon_correction_min=st_audit["lon_correction_min"],
eot_minutes=st_audit["eot_minutes"],
true_solar_time=_fmt_dt(st_audit["tst"]),
is_late_zi=bd_audit["is_late_zi"],
# 四柱
year=_build_pillar("year", raw),
month=_build_pillar("month", raw),
day=_build_pillar("day", raw),
hour=_build_pillar("hour", raw),
# 派生
day_master=raw["day_master"],
month_ling=raw["month"]["zhi"],
solar_term_before=_fmt_term(raw["prev_jie"]),
solar_term_after=_fmt_term(raw["next_jie"]),
luck_pillars=luck_pillars,
# 派生(补充字段)
luck_is_forward=luck["is_forward"],
start_age_text=luck["start_age_text"],
terrain=raw.get("terrain", {}),
void=raw.get("void", {}),
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", ""),
)