Add Rahu Ketu node mode option

This commit is contained in:
732642856
2026-06-03 19:55:22 +08:00
parent 7bd91b3b76
commit 9fd26dbea5
3 changed files with 56 additions and 26 deletions
+13
View File
@@ -1,5 +1,18 @@
# 印度占星 Skill 更新日志
## v6.0.7-node-mode-arbitration2026-06-03)— Rahu/Ketu 节点口径仲裁与参数冻结
> **触发原因**:可信度 benchmark 第四轮确认第三轮 PyJHora 中 Rahu/Ketu 的剩余差异主要来自 Mean Node / True Node 口径差异,而非 D9/D10 计算 bug。
### 修复内容
- `scripts/jyotish_engine.py` 的出生数据类子命令新增 `--node-mode mean|true` 参数,默认 `mean`,可选 `true` 用于对齐 PyJHora 默认口径。
- `compute_chart_data()` 输出 `birth_info.node_mode``node_mode_note`,把 Rahu/Ketu 节点模式纳入参数冻结证据。
- `SKILL.md` 新增“Rahu/Ketu 节点口径冻结”强制规则,要求 benchmark 与解盘输出显式声明 Mean/True Node。
- 第四轮报告确认:当前 skill vs Swiss Mean Node 80/80 匹配;vs Swiss True Node 与 PyJHora 默认均为 54/80,差异模式完全一致。
---
## v6.0.6-d10-benchmark-fix2026-06-03)— PyJHora benchmark 暴露的 D10 口径修复
> **触发原因**:可信度 benchmark 第三轮接入 PyJHora 后,D10 出现系统性错配;交叉查证 D10 规则为“奇数星座从本宫起算,偶数星座从第九宫起算(含本宫计数)”。
+13 -4
View File
@@ -1,6 +1,6 @@
---
name: jyotish-vedic-astrology
version: 6.0.6
version: 6.0.7
description: 印度占星(Jyotish)专业解盘与推运系统。核心能力:PDF星盘输入→严谨解盘→精确推运应期输出。触发词:印度占星、吠陀占星、Jyotish、解盘、推运、星盘分析、Dasha、Transit、Nakshatra、Yoga、出生时间矫正、PDF星盘、读取PDF、分析PDF星盘、现代解读、误判纠错、Varga分盘、综合分析、过境分析、合盘、婚姻匹配、年运盘、Prashna、Argala、Jaimini、KP系统、Shadbala、Ashtakavarga、HTML报告、深度解盘。
---
@@ -10,7 +10,7 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
> **严格路由**`references/strict-workflow-router.md`(⭐涉及事业/婚恋/财务/应期/技法验证时必须优先读取)
> **覆盖矩阵**`references/technique-capability-matrix.md`(⭐判断技法 covered/partial/missing 时必须参考)
> **机器注册表**`references/technique_registry.json` + `scripts/audit_capabilities.py`(⭐用于自动审计与CI门禁)
> **版本**v6.0.6-d10-benchmark-fix | **详细变更**`CHANGELOG.md`
> **版本**v6.0.7-node-mode-arbitration | **详细变更**`CHANGELOG.md`
---
@@ -88,6 +88,15 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
→ 完整规范:`references/transit-actionable-output-guide.md`
### Rahu/Ketu 节点口径冻结(v6.0.7-node-mode
**所有 benchmark 与解盘输出必须显式声明 Rahu/Ketu 使用 Mean Node 还是 True Node。**
- 当前 skill 默认:`--node-mode mean`Swiss Ephemeris Mean Node)。
- 可选:`--node-mode true`Swiss Ephemeris True Node,用于对齐 PyJHora 默认口径)。
- PyJHora 4.8.6 的 `rasi_chart()` 默认使用 True Node;第三轮 benchmark 的 Rahu/Ketu 差异已由第四轮仲裁确认为 Mean/True Node 口径差异,不应再误判为 D9/D10 计算 bug。
- 输出 `birth_info.node_mode``node_mode_note` 必须保留,作为参数冻结证据。
---
## 核心能力速查
@@ -255,9 +264,9 @@ $PYTHON $SCRIPT <子命令> [参数]
---
**版本**v6.0.6-d10-benchmark-fix
**版本**v6.0.7-node-mode-arbitration
**创建日期**2026-04-20
**最后更新**2026-06-03v6.0.6 修正 D10 偶数星座起算与非D9分盘度数缩放口径
**最后更新**2026-06-03v6.0.7 新增 Rahu/Ketu Mean/True Node 口径冻结与 `--node-mode` 参数
---
+30 -22
View File
@@ -242,7 +242,8 @@ def _get_dignity_level(planet, sign, deg_in_sign=None):
return 'ENEMY'
return 'NEUTRAL'
PLANET_CN = {"Ketu": "南交点Ketu", "Venus": "金星Venus", "Sun": "太阳Sun", "Moon": "月亮Moon", "Mars": "火星Mars", "Rahu": "北交点Rahu", "Jupiter": "木星Jupiter", "Saturn": "土星Saturn", "Mercury": "水星Mercury"}
PLANETS_SWE = {'Sun': swe.SUN, 'Moon': swe.MOON, 'Mars': swe.MARS, 'Mercury': swe.MERCURY, 'Jupiter': swe.JUPITER, 'Venus': swe.VENUS, 'Saturn': swe.SATURN, 'Rahu': swe.MEAN_NODE} if HAS_SWE else {}
BASE_PLANETS_SWE = {'Sun': swe.SUN, 'Moon': swe.MOON, 'Mars': swe.MARS, 'Mercury': swe.MERCURY, 'Jupiter': swe.JUPITER, 'Venus': swe.VENUS, 'Saturn': swe.SATURN} if HAS_SWE else {}
PLANETS_SWE = {**BASE_PLANETS_SWE, 'Rahu': swe.MEAN_NODE} if HAS_SWE else {}
def output_json(data):
@@ -260,13 +261,14 @@ def _add_chart_args(p):
p.add_argument('--lat', type=float, required=True)
p.add_argument('--lon', type=float, required=True)
p.add_argument('--tz', type=float, default=0)
p.add_argument('--node-mode', default='mean', choices=['mean', 'true'], help='Rahu/Ketu节点口径:mean=Mean Node(默认,JHora常用/Swiss direct baseline),true=True NodePyJHora默认)')
# ============================================================================
# 公共星盘计算(供 chart/shadbala/ashtakavarga 共用,v3.4提取)
# ============================================================================
def compute_chart_data(year, month, day, hour, minute, lat, lon, tz):
"""计算星盘核心数据,返回 (result_dict, asc_idx, jd, ayanamsa)"""
def compute_chart_data(year, month, day, hour, minute, lat, lon, tz, node_mode='mean'):
"""计算星盘核心数据,返回 (result_dict, asc_idx, jd, ayanamsa)。node_mode: mean|true。"""
if not HAS_SWE:
return None, None, None, None
swe.set_ephe_path('')
@@ -274,10 +276,15 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz):
jd = swe.julday(year, month, day, hour_decimal)
ayanamsa = swe.get_ayanamsa(jd)
node_mode = (node_mode or 'mean').lower()
if node_mode not in ('mean', 'true'):
node_mode = 'mean'
node_pid = swe.TRUE_NODE if node_mode == 'true' else swe.MEAN_NODE
result = {"birth_info": {
"date": f"{year}-{month:02d}-{day:02d}", "time": f"{hour:02d}:{minute:02d}",
"tz": f"UTC{'+' if tz >= 0 else ''}{tz}", "lat": lat, "lon": lon,
"julian_day": round(jd, 6), "ayanamsa": round(ayanamsa, 4)
"julian_day": round(jd, 6), "ayanamsa": round(ayanamsa, 4),
"node_mode": node_mode, "node_mode_note": "mean=Mean Node; true=True Node. PyJHora默认true,本skill默认mean。"
}, "ascendant": None, "planets": {}, "houses": {}}
asc_lon, _ = swe.houses(jd, lat, lon, b'A')
@@ -296,7 +303,8 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz):
"lord": SIGN_LORDS[SIGNS[si]]}
nak_span = 360.0 / 27
for pname, pid in PLANETS_SWE.items():
planets_swe = {**BASE_PLANETS_SWE, 'Rahu': node_pid}
for pname, pid in planets_swe.items():
try:
pos, _ = swe.calc_ut(jd, pid)
lon_p = (pos[0] - ayanamsa) % 360; lat_p = pos[1]; spd = pos[3]
@@ -336,7 +344,7 @@ def compute_chart_data(year, month, day, hour, minute, lat, lon, tz):
# 1. 星盘计算
# ============================================================================
def cmd_chart(args):
result, asc_idx, jd, ayanamsa = compute_chart_data(args.year, args.month, args.day, args.hour, args.minute, args.lat, args.lon, args.tz)
result, asc_idx, jd, ayanamsa = compute_chart_data(args.year, args.month, args.day, args.hour, args.minute, args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if result is None:
return {"error": "swisseph未安装"}
# v3.5: --validate 触发 R1-R10 校验
@@ -872,7 +880,7 @@ def cmd_predict(args):
if getattr(args, 'past_verify', False) and args.year:
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
return _past_event_verify(chart, asc_idx, args)
@@ -1435,7 +1443,7 @@ def cmd_double_transit_pac(args):
# 1. 计算本命星盘
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
@@ -1679,7 +1687,7 @@ def cmd_transit_ll7l(args):
"""Transit LL/7L 连接 + 互换检测"""
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
@@ -1764,7 +1772,7 @@ def cmd_planetary_congregation(args):
"""行星聚集检测"""
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
@@ -1844,7 +1852,7 @@ def cmd_vivah_saham(args):
"""Vivah Saham 计算 + Transit 激活"""
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
@@ -1908,7 +1916,7 @@ def cmd_vivah_saham(args):
def cmd_shadbala(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -1930,7 +1938,7 @@ def cmd_shadbala(args):
def cmd_ashtakavarga(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -1990,7 +1998,7 @@ def cmd_memory(args):
def cmd_validate(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2119,7 +2127,7 @@ def cmd_audit(args):
"""P1-P12 行星审计:调用 chart→shadbala→ashtakavarga→yoga,输出统一审计报告"""
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
@@ -2384,7 +2392,7 @@ def cmd_report(args):
def cmd_varga_full(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2405,7 +2413,7 @@ def cmd_varga_full(args):
def cmd_aspects(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2428,7 +2436,7 @@ def cmd_aspects(args):
def cmd_jaimini(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2475,7 +2483,7 @@ def cmd_jaimini(args):
def cmd_nakshatra_adv(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2513,7 +2521,7 @@ def cmd_nakshatra_adv(args):
def cmd_argala(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -2537,7 +2545,7 @@ def cmd_argala(args):
def cmd_tajika(args):
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装"}
try:
@@ -3065,7 +3073,7 @@ def cmd_full_reading(args):
# ── Step 1: 核心星盘 ──
chart, asc_idx, jd, ayanamsa = compute_chart_data(
args.year, args.month, args.day, args.hour, args.minute,
args.lat, args.lon, args.tz)
args.lat, args.lon, args.tz, getattr(args, 'node_mode', 'mean'))
if chart is None:
return {"error": "swisseph未安装,无法计算星盘"}