排盘输出 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 项通过
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""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", ""),
|
||
)
|