From 04d901e1be34812d2b7bb124fb9316a2fec3d135 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sat, 27 Jun 2026 19:09:31 +0800 Subject: [PATCH] Clarify benchmark boundaries and improve annual workflow surfaces --- jyotish-app/main.js | 2 + jyotish-app/renderers.js | 107 ++++++++--------- references/kp-astrology-complete-system.md | 50 ++++++++ references/pancha-pakshi-nakshatra-systems.md | 50 ++++++++ references/prashna-complete-guide.md | 71 ++++++++++++ references/tajika-yoga-complete-guide.md | 55 ++++++++- references/varshaphala-annual-chart-guide.md | 54 +++++++++ scripts/_compute_one_chart.py | 8 +- scripts/benchmark_yoga_coverage.py | 24 ++-- scripts/build_standard_test_charts.py | 6 +- scripts/sync_skill_truth_to_workbuddy.sh | 1 + scripts/validate_logic_v2.py | 6 +- scripts/validate_yoga_accuracy.py | 108 +++++++++--------- scripts/yoga_engine.py | 12 +- skills/jyotish-engine-modules/SKILL.md | 35 +++++- .../jyotish-full-reading-integration/SKILL.md | 31 ++++- tests/test_dasha.py | 39 +++++-- tests/test_tajika_annual_closure_status.py | 3 + 18 files changed, 506 insertions(+), 156 deletions(-) diff --git a/jyotish-app/main.js b/jyotish-app/main.js index 87eab203..09c076a7 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -2401,6 +2401,8 @@ async function runTrustCenterHealthCheck() { details: [ ['PWA 安装壳', getPWAStatus().label, getPWAStatus().note], ['本地 API 服务', api.base || 'online', `health ok · v${api.version || '-'} · ${api.latencyMs}ms`], + ['Swiss Ephemeris', api.swisseph_available ? (api.swisseph_version || 'available') : 'unavailable', '当前本地 API 的星历运行时状态'], + ['Ayanamsa', api.ayanamsa_default || 'lahiri', '当前健康检查声明的默认 sidereal 基准'], ['Technique catalog', `${registry.technique_count || 0} techniques`, `${surfaces.api_endpoint_count || 0} API endpoints 可被前端发现`], ['Desktop path', 'PWA now', 'Pake shell 可快速包 URL;Tauri sidecar 等 API 生命周期和签名策略确定后再落地。'], ], diff --git a/jyotish-app/renderers.js b/jyotish-app/renderers.js index 4c65f02a..1943aafb 100644 --- a/jyotish-app/renderers.js +++ b/jyotish-app/renderers.js @@ -86,16 +86,17 @@ export function renderVargas(allV, planets, ascendant) { const selector = $('varga-selector'); const order = ['Sun','Moon','Mars','Mercury','Jupiter','Venus','Saturn','Rahu','Ketu']; // 渲染选择器 - selector.innerHTML = VARGA_DEFS.map(v => - `` - ).join(''); - selector.querySelectorAll('.varga-btn').forEach(btn => { - btn.addEventListener('click', () => { - selector.querySelectorAll('.varga-btn').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - renderSingleVarga(btn.dataset.varga, planets, ascendant); + selector.innerHTML = ` + + `; + const selectEl = document.getElementById('vargaSelect'); + if (selectEl) { + selectEl.addEventListener('change', (e) => { + renderSingleVarga(e.target.value, planets, ascendant); }); - }); + } renderSingleVarga('D9', planets, ascendant); } @@ -233,9 +234,11 @@ export function renderShadbala(sb) { // ========== Dasha 三级 ========== export function renderDasha3Level(data) { - const container = $('dasha-timeline'); - const currentEl = $('dasha-current'); + const container = document.getElementById('dasha-timeline'); + const currentEl = document.getElementById('dasha-current'); container.innerHTML = ''; + document.getElementById('antardasha-panel').innerHTML = ''; + document.getElementById('pratyantardasha-panel').innerHTML = ''; if (data.current_dasha) { const cd = data.current_dasha; @@ -252,53 +255,43 @@ export function renderDasha3Level(data) { `; } - for (const d of data.timeline) { - const isCur = data.current_dasha && data.current_dasha.lord === d.lord; - const bar = document.createElement('div'); - bar.className = `dasha-bar${isCur ? ' active' : ''}`; - bar.innerHTML = `
${escapeHtml(planetName(d.lord))}
${escapeHtml(yearsLabel(d.years))}
${escapeHtml(String(d.start || '').slice(2))}
`; - bar.addEventListener('click', () => { - container.querySelectorAll('.dasha-bar').forEach(b => b.classList.remove('selected')); - bar.classList.add('selected'); - renderAntardashaPanel(d.antardasha || []); - }); - container.appendChild(bar); - } - - if (data.current_dasha?.antardasha) { - renderAntardashaPanel(data.current_dasha.antardasha); - } -} - -function renderAntardashaPanel(subs) { - const panel = $('antardasha-panel'); - panel.innerHTML = ''; - for (const ad of subs) { - const div = document.createElement('div'); - div.className = `antardasha-item${ad.is_current ? ' current' : ''}`; - div.innerHTML = `
${escapeHtml(planetName(ad.lord))}${ad.is_current ? ' ◀' : ''}
${escapeHtml(String(ad.start || '').slice(5))}~${escapeHtml(String(ad.end || '').slice(5))}
`; - div.addEventListener('click', () => { - panel.querySelectorAll('.antardasha-item').forEach(i => i.classList.remove('selected')); - div.classList.add('selected'); - renderPratyantardasha(ad.pratyantardasha || []); - }); - panel.appendChild(div); - } - // 自动展开当前次运的三运 - const cur = subs.find(s => s.is_current); - if (cur?.pratyantardasha) renderPratyantardasha(cur.pratyantardasha); -} - -function renderPratyantardasha(pratyList) { - const panel = $('pratyantardasha-panel'); - panel.innerHTML = `

${t('dasha.praty')}

`; - const grid = panel.querySelector('.praty-grid'); - for (const p of pratyList) { - grid.innerHTML += `
-
${escapeHtml(planetName(p.lord))}${p.is_current ? ' ◀' : ''}
-
${escapeHtml(String(p.start || '').slice(5))}~${escapeHtml(String(p.end || '').slice(5))}
-
`; + let html = '
'; + for (const md of data.timeline) { + const isCurMd = data.current_dasha && data.current_dasha.lord === md.lord; + html += `
`; + html += ``; + html += `${escapeHtml(planetName(md.lord))} (${escapeHtml(yearsLabel(md.years))}) : ${escapeHtml(String(md.start || '').slice(0, 10))} ~ ${escapeHtml(String(md.end || '').slice(0, 10))}`; + html += ``; + + if (md.antardasha && md.antardasha.length > 0) { + html += `
`; + for (const ad of md.antardasha) { + const isCurAd = isCurMd && ad.is_current; + html += `
`; + html += ``; + html += `${escapeHtml(planetName(ad.lord))} : ${escapeHtml(String(ad.start || '').slice(0, 10))} ~ ${escapeHtml(String(ad.end || '').slice(0, 10))}`; + html += ``; + + if (ad.pratyantardasha && ad.pratyantardasha.length > 0) { + html += `
`; + for (const pd of ad.pratyantardasha) { + const isCurPd = isCurAd && pd.is_current; + html += `
`; + html += `↳ ${escapeHtml(planetName(pd.lord))} : ${escapeHtml(String(pd.start || '').slice(0, 10))} ~ ${escapeHtml(String(pd.end || '').slice(0, 10))}`; + html += `
`; + } + html += `
`; + } + + html += `
`; + } + html += `
`; + } + + html += `
`; } + html += '
'; + container.innerHTML = html; } // ========== 行星表更新(含D9列) ========== diff --git a/references/kp-astrology-complete-system.md b/references/kp-astrology-complete-system.md index 6c4353de..7e093e84 100644 --- a/references/kp-astrology-complete-system.md +++ b/references/kp-astrology-complete-system.md @@ -212,6 +212,56 @@ KP占星术由K.S. Krishnamurti教授(1908-1972)创立,是对传统Parasha 4. **不考虑否定宫位**:只看有利信号不看否定信号→假阳性 5. **Ayanamsa用错**:必须用KP专属Ayanamsa,不是Lahiri +## 九、在当前 skill 中如何执行 + +> 这一节只回答一个问题:**KP 理论在当前 skill 里如何真正落地,而不是停留在概念层。** + +### 9.1 当前可直接调用的能力 + +| 能力 | 当前入口 | 当前状态 | +|------|---------|---------| +| KP Sublord / SubSubLord | `kp` / `/api/kp` | 可直接使用 | +| ABCD Significator | `kp` / `/api/kp` | 可直接使用 | +| KP Prashna YES/NO | `prashna` / `/api/prashna` | 可直接使用 | +| KP Horary evidence | `/api/prashna` | 可直接使用 | +| Ruling planets | `/api/prashna -> kp_horary.ruling_planets` | 已有基础版,仍需继续补深 | + +### 9.2 建议执行顺序 + +当用户问“会不会”“值不值得”“什么时候会落地”时,当前 skill 推荐按下面顺序执行: + +1. **先看出生盘承诺** + - relevant houses / lords / Dasha / Transit +2. **再看 KP significator** + - `kp` 输出 ABCD significator +3. **再看 Cuspal Sub-Lord** + - 问题主宫位的 sub-lord +4. **最后用 KP Horary evidence 做仲裁** + - `ruling_planets` + - `cuspal_sub_lord` + - `house_significators` + - `judgement_matrix` + +### 9.3 当前不要夸大的地方 + +当前 skill 的 KP 已经不是“只有表面按钮”,但也还没有达到“传统 KP 老师完整工作流”的终态。 + +尤其要注意: + +1. `ruling planets` 已有基础结构,但 `day_lord` 等细化链条仍未完全厚化 +2. Horary workflow 已可运行,但不同问事类型的裁决链仍需更多外部案例收敛 +3. KP 结果适合做**精细仲裁层**,不应单独替代出生盘、本命 promise、Dasha 与现实信息 + +### 9.4 当前 skill 中的推荐话术 + +- 适合说: + - “KP 显示该问题有/无承诺倾向” + - “KP Horary 证据支持/削弱这个判断” + - “KP 更适合做 yes/no 与时机仲裁” +- 不适合直接说: + - “KP 已完全等同传统专业软件级全部工作流” + - “单凭一次 Horary 就足够决定重大人生事项” + ### 准确率基准 - 熟练KP占星师的事件预测准确率:**70-85%** - 初学者准确率:**40-55%** diff --git a/references/pancha-pakshi-nakshatra-systems.md b/references/pancha-pakshi-nakshatra-systems.md index 7e1426c8..b6c5e232 100644 --- a/references/pancha-pakshi-nakshatra-systems.md +++ b/references/pancha-pakshi-nakshatra-systems.md @@ -235,4 +235,54 @@ Nakshatra 有三种不同的起始计数体系,各自对应不同的宇宙功 --- +## 第五部分:在当前 skill 中如何执行 + +> 这一节只回答:**Pancha Pakshi / Savya / Apasavya 在当前 skill 里到底怎么使用。** + +### 22. 当前已具备的能力 + +| 能力 | 当前落点 | 当前状态 | +|------|---------|---------| +| Pancha Pakshi 鸟分配 | `scripts/pancha_pakshi.py` | 可直接计算 | +| 5x5 活动矩阵 | `scripts/pancha_pakshi.py` | 可直接使用 | +| 星期 Yama 偏移 | `scripts/pancha_pakshi.py` | 可直接使用 | +| 活动建议 / 吉凶时段 | `scripts/pancha_pakshi.py` | 可直接使用 | +| Savya / Apasavya 概念说明 | 本参考文件 | 有知识层 | + +### 23. 当前推荐角色 + +在当前 skill 里,Pancha Pakshi 最适合的角色是: + +1. **日内择时辅助层** +2. **活动时段偏好层** +3. **和 Panchanga / Muhurta / Transit 交叉的补充层** + +它不适合单独承担: + +- 婚姻/事业重大承诺 +- 长期命运判断 +- 替代 Dasha / Transit / 本命盘 + +### 24. 当前真实边界 + +当前 skill 中已经能输出基础鸟态、活动矩阵、推荐/避开时段。 + +但必须保留的边界: + +1. Tamil 传统细则仍不完整 +2. 地域化口径仍未完全闭环 +3. 与真实案例的长期外部验证仍不足 +4. 适合做“择时辅助”,不适合被包装成独立终极判断器 + +### 25. 当前 skill 的推荐话术 + +- 适合说: + - “Pancha Pakshi 显示这个时段更适合/不适合某类行动” + - “可把五鸟系统作为 Panchanga/Muhurta 的补充层” +- 不适合直接说: + - “五鸟系统已经完成全部传统地区细则对齐” + - “单凭五鸟系统即可决定重大事项” + +--- + *本文件整合自公众号文章「2印度占星」和「3印度占星」,涵盖 Pancha Pakshi 五鸟择时术、Nakshatra 三种计数体系(Ashwinādi/Krittikādi/Ardrādi)、Savya/Apasavya Nakshatra 分组。* diff --git a/references/prashna-complete-guide.md b/references/prashna-complete-guide.md index 06a11d8d..84c5d862 100644 --- a/references/prashna-complete-guide.md +++ b/references/prashna-complete-guide.md @@ -460,3 +460,74 @@ Arudha Sphuta = Arudha Rashi + Lagna的度数(仅度数) | Tajika Yoga | `tajika-yoga-complete-guide.md` | Ithasala 等是/否判断 | | Tajika 年运盘 | `scripts/tajika.py` | Muntha/YearLord 等 | | Dasha 系统 | `alternative-dasha-systems.md` | 大运辅助验证 | + +## 附录 B:当前 skill 的 Prashna 落地流程 + +> 这部分不是古典理论摘要,而是当前 skill 的实际使用方法。 + +### B1. 当前已经具备的落地点 + +当前 `prashna` 模块并不只是一个“问事按钮”,而是已经整合了: + +1. `KP Prashna answer` +2. `KP answer v2` +3. `KP Horary evidence` +4. `Arudha` +5. `Nadi Prashna` +6. `Sphuta` +7. `Sahams` +8. `timing score` + +### B2. 当前推荐执行顺序 + +对单一具体问题,建议按这个顺序读: + +1. **问题类型分类** + - career / relationship / finance / lost item / health / general +2. **YES/NO 主结论** + - 先看 `kp_answer_v2` +3. **Horary 证据层** + - `ruling_planets` + - `cuspal_sub_lord` + - `house_significators` + - `judgement_matrix` +4. **镜像与心理层** + - `Arudha` + - `Nadi` +5. **时机辅助层** + - `timing_score` + - `sphutas` + - `sahams` + +### B3. 当前真实边界 + +当前 skill 中的 Prashna 已经能跑完整结果,但还不能夸张成“所有传统分支都完全封顶”。 + +仍需保留的边界: + +1. 不同问事分支(事业、婚姻、失物、诉讼等)的传统裁决链成熟度不完全一样 +2. Horary evidence 已经有结构,但仍需更多真实外部案例来继续收紧命中率 +3. `Sphuta / Saham / timing_score` 属于辅助证据层,不应单独承担重大医学、法律、寿命结论 + +### B4. 当前 skill 的安全使用原则 + +- **适合:** + - 单一、当下、具体的问题 + - yes/no 倾向 + - 短期时机线索 +- **不适合:** + - 用一次问事替代完整本命盘 + - 只凭 Prashna 决定重大人生事项 + - 将死亡、重病、法律责任作绝对断言 + +### B5. 与其他模块的正确关系 + +当前 skill 中,Prashna 的最佳角色不是孤立系统,而是: + +1. 与出生盘 promise 交叉验证 +2. 与 Dasha / Transit 做时机交叉 +3. 与 KP significator / ruling planets 做细节仲裁 + +换句话说: + +**Prashna 在当前 skill 里已经是强模块,但仍应作为“专项仲裁层”,而不是唯一裁判。** diff --git a/references/tajika-yoga-complete-guide.md b/references/tajika-yoga-complete-guide.md index 09b7df16..0f829937 100644 --- a/references/tajika-yoga-complete-guide.md +++ b/references/tajika-yoga-complete-guide.md @@ -277,6 +277,59 @@ Tajika Yoga系统源自阿拉伯/波斯占星传统,经印度占星吸收后 - [ ] 是否将每个Yoga映射到了具体宫位领域? - [ ] 是否给出了综合年度事件预测? +## 七、在当前 skill 中如何执行 + +> 这一节只回答:**Tajika Yoga 在当前 skill 里如何真正落地使用。** + +### 7.1 当前已可直接调用的能力 + +| 能力 | 当前入口 | 当前状态 | +|------|---------|---------| +| Tajika / annual report | `tajika` / `solar-return` / `/api/tajika` / `/api/annual` | 可直接使用 | +| Muntha | 年运结果中直接输出 | 可直接使用 | +| Varshesha / Year Lord | 年运结果中直接输出 | 可直接使用 | +| Tajika Yogas | 年运结果中直接输出 | 可直接使用 | +| Tajika strength layers | `Harsha Bala / Panchavargiya Bala` 摘要层 | 可直接使用 | +| Sahams | 年运输出中已有 | 可直接使用,但解释层需保留边界 | + +### 7.2 当前推荐阅读顺序 + +在当前 skill 中,读年度盘不要一上来就盯某个单一 Yoga。推荐顺序: + +1. **年度上升与 Muntha** + - 先看今年主题落在哪一类宫位 +2. **Varshesha / Year Lord** + - 判断全年主导行星与主轴 +3. **Tajika Yogas** + - 看事件倾向是推进、分离、阻隔还是中间人促成 +4. **Harsha Bala / Panchavargiya Bala** + - 判断哪些行星今年更有执行力,哪些更弱 +5. **Sahams** + - 作为辅助证据,不作为唯一裁决 +6. **最后再与 Dasha / Transit 对表** + - 年运盘不能单独替代本命盘与大运 + +### 7.3 当前 skill 的真实边界 + +当前 skill 里的 Tajika / Varshaphala 已经不是“只有框架”,但也还没有达到“年度事件裁决层完全封顶”的状态。 + +必须保留的边界: + +1. `Tajika Yoga` 已能输出,但年度事件裁决深度仍需继续收敛 +2. `Harsha Bala / Panchavargiya Bala` 已可做年度强弱排序,但不是独立的最终预测器 +3. `Sahams` 已扩展到常见 36 点,但解释层与权重口径仍需更多外部闭环 +4. 年运盘适合给“年度主题、年度触发方向、月份侧重”,不适合单独承担绝对事件承诺 + +### 7.4 当前 skill 的推荐话术 + +- 适合说: + - “年运盘显示今年的主轴在……” + - “Tajika Yoga 支持某类事件更容易推进/分离/被阻隔” + - “Saham 只作为辅助定位,不单独下最终结论” +- 不适合直接说: + - “单看年运盘就能精确锁死全年所有重大事件” + - “Saham 已完成传统软件级最终校准” + --- ## 六、Orb速查表 @@ -296,4 +349,4 @@ Tajika Yoga系统源自阿拉伯/波斯占星传统,经印度占星吸收后 --- **版本**:v2.0.0 -**最后更新**:2026-04-23 \ No newline at end of file +**最后更新**:2026-04-23 diff --git a/references/varshaphala-annual-chart-guide.md b/references/varshaphala-annual-chart-guide.md index 2a47d737..641ab222 100644 --- a/references/varshaphala-annual-chart-guide.md +++ b/references/varshaphala-annual-chart-guide.md @@ -258,6 +258,60 @@ Ketu:约21天(7/120年) - 太阳回归时间:[精确时间] - 太阳回归地点:[城市] - 年度上升(Varsha Lagna):[星座] + +## 八、当前 skill 的 Varshaphala 落地流程 + +> 这部分不是传统教材,而是当前 skill 的实际执行说明。 + +### 8.1 当前已经有的结构 + +在当前 skill 中,`Varshaphala` 已经整合出以下结果层: + +1. `solar_return` +2. `muntha` +3. `varshesha` +4. `tajika_yogas` +5. `tajika_strength` +6. `sahams` +7. `predictions` + +### 8.2 当前推荐执行顺序 + +1. **先看 Solar Return / 年度上升** + - 判断这一年是偏事业、关系、财务还是转化 +2. **再看 Muntha** + - 判断今年主题落在哪个宫位领域 +3. **再看 Varshesha** + - 判断哪颗星是年度总控 +4. **再看 Tajika Yogas** + - 判断推进、分离、阻碍、间接成事 +5. **再看 tajika_strength** + - 判断哪些星今年更有支配力 +6. **最后看 Sahams 与 predictions** + - 只作为辅助定位层 + +### 8.3 当前真实边界 + +当前 skill 的年运盘已经具备主结构,但还没有达到“单独作为全年精确事件承诺器”的程度。 + +必须保留的边界: + +1. 年度主轴判断已经可用 +2. 月度/事件裁决层仍需要和 Dasha / Transit 做交叉 +3. Saham 权重和解释层仍需继续外部闭环 +4. 年运盘最适合做“主题、机会方向、风险区域”的整理,不宜单独替代本命 promise + +### 8.4 当前 skill 中的最佳角色 + +Varshaphala 在当前 skill 里的最佳角色是: + +1. **年度主题层** +2. **年度节奏层** +3. **与 Dasha / Transit 的交叉确认层** + +换句话说: + +**它已经是强辅助决策层,但还不是可以脱离本命盘和大运独立封顶的最终裁判。** - Muntha:[X宫] [星座] - 年主星:[行星] 在 [X宫] [星座]([庙旺/落陷]) - 当前年龄:[X]岁 diff --git a/scripts/_compute_one_chart.py b/scripts/_compute_one_chart.py index d1ac4a06..1efd2415 100644 --- a/scripts/_compute_one_chart.py +++ b/scripts/_compute_one_chart.py @@ -41,7 +41,7 @@ def tz_to_float(tz_str): minutes = int(parts[1]) if len(parts) > 1 else 0 return sign * (hours + minutes / 60.0) -def _planet_dict_from_pyjhora_positions(positions, asc_sign): +def _planet_dict_from_external_benchmark_positions(positions, asc_sign): """Convert PyJHora planet positions to the skill validation schema.""" names = { 0: "Sun", @@ -123,17 +123,17 @@ def compute_yogas(chart): "d1": { "ascendant": SIGNS[d1_asc_sign], "ascendant_degree": d1_asc_degree, - "planets": _planet_dict_from_pyjhora_positions(d1_positions, d1_asc_sign), + "planets": _planet_dict_from_external_benchmark_positions(d1_positions, d1_asc_sign), }, "d9": { "ascendant": SIGNS[d9_asc_sign], "ascendant_degree": d9_asc_degree, - "planets": _planet_dict_from_pyjhora_positions(d9_positions, d9_asc_sign), + "planets": _planet_dict_from_external_benchmark_positions(d9_positions, d9_asc_sign), }, "d60": { "ascendant": SIGNS[d60_asc_sign], "ascendant_degree": d60_asc_degree, - "planets": _planet_dict_from_pyjhora_positions(d60_positions, d60_asc_sign), + "planets": _planet_dict_from_external_benchmark_positions(d60_positions, d60_asc_sign), }, "panchanga": { "tithi": tithi_no, diff --git a/scripts/benchmark_yoga_coverage.py b/scripts/benchmark_yoga_coverage.py index c24e5149..d5728330 100644 --- a/scripts/benchmark_yoga_coverage.py +++ b/scripts/benchmark_yoga_coverage.py @@ -101,8 +101,8 @@ def extract_skill_names(rules: list[dict]) -> tuple[set[str], dict[str, list[str return keys, reverse -def extract_pyjhora_names(pyjhora_yoga_file: Path) -> set[str]: - content = pyjhora_yoga_file.read_text(encoding="utf-8", errors="ignore") +def extract_external_benchmark_names(external_benchmark_yoga_file: Path) -> set[str]: + content = external_benchmark_yoga_file.read_text(encoding="utf-8", errors="ignore") funcs = re.findall(r"^def ([a-zA-Z_][a-zA-Z0-9_]*)\(", content, re.MULTILINE) names: set[str] = set() for fn in funcs: @@ -126,7 +126,7 @@ def extract_pyjhora_names(pyjhora_yoga_file: Path) -> set[str]: return names -def find_pyjhora_yoga_file(explicit: str | None = None) -> Path | None: +def find_external_benchmark_yoga_file(explicit: str | None = None) -> Path | None: if explicit: p = Path(explicit).expanduser().resolve() return p if p.exists() else None @@ -176,12 +176,12 @@ def main() -> int: skill_keys, skill_reverse = extract_skill_names(rules) - pyjhora_file = find_pyjhora_yoga_file(args.pyjhora_yoga_file) + external_benchmark_file = find_external_benchmark_yoga_file(args.external_benchmark_yoga_file) py_names: set[str] = set() missing: list[str] = [] coverage_pct = None - if pyjhora_file: - py_names = extract_pyjhora_names(pyjhora_file) + if external_benchmark_file: + py_names = extract_external_benchmark_names(external_benchmark_file) missing = sorted([name for name in py_names if not covered(name, skill_keys)]) coverage_pct = round((len(py_names) - len(missing)) / len(py_names) * 100, 2) if py_names else None @@ -197,11 +197,11 @@ def main() -> int: "categories": dict(categories.most_common()), "strength_values": dict(strength_values), "skill_normalized_name_keys": len(skill_keys), - "pyjhora_yoga_file": str(pyjhora_file) if pyjhora_file else None, - "pyjhora_unique_yoga_names": len(py_names) if pyjhora_file else None, - "matched_unique_yoga_names": (len(py_names) - len(missing)) if pyjhora_file else None, + "external_benchmark_yoga_file": str(external_benchmark_file) if external_benchmark_file else None, + "external_benchmark_unique_yoga_names": len(py_names) if external_benchmark_file else None, + "matched_unique_yoga_names": (len(py_names) - len(missing)) if external_benchmark_file else None, "coverage_pct": coverage_pct, - "missing_count": len(missing) if pyjhora_file else None, + "missing_count": len(missing) if external_benchmark_file else None, "missing": missing, } @@ -221,11 +221,11 @@ def main() -> int: print(f" {cat:16s} {count:3d}") print("\nPyJHora 对比:") - if not pyjhora_file: + if not external_benchmark_file: print(" 未找到 PyJHora yoga.py;仅完成本地 JSON 统计。") print(" 可用 --pyjhora-yoga-file 指定路径。") else: - print(f" yoga.py: {pyjhora_file}") + print(f" yoga.py: {external_benchmark_file}") print(f" PyJHora 唯一 Yoga 名称: {len(py_names)}") print(f" 已匹配: {len(py_names) - len(missing)}") print(f" 疑似缺失: {len(missing)}") diff --git a/scripts/build_standard_test_charts.py b/scripts/build_standard_test_charts.py index be12f3d9..4cbaa6c2 100644 --- a/scripts/build_standard_test_charts.py +++ b/scripts/build_standard_test_charts.py @@ -74,7 +74,7 @@ CELEBRITY_CHARTS = [ {"name": "Paramahansa Yogananda", "date": "1893-01-05", "time": "20:38", "tz": "+05:30", "lat": 27.0360, "lon": 88.2627, "city": "Gorakhpur, India"}, ] -def compute_pyjhora_yogas(chart): +def compute_external_benchmark_yogas(chart): try: result = subprocess.run( [PYJHORA, HELPER], @@ -98,12 +98,12 @@ if __name__ == "__main__": } for chart in CELEBRITY_CHARTS: print(f" Computing {chart['name']}...", flush=True) - yogas = compute_pyjhora_yogas(chart) + yogas = compute_external_benchmark_yogas(chart) entry = dict(chart) entry["expected_yogas"] = yogas.get("yogas", []) if "context" in yogas: entry["context"] = yogas["context"] - entry["pyjhora_raw"] = yogas + entry["external_benchmark_raw"] = yogas output["charts"].append(entry) with open(outpath, "w") as f: json.dump(output, f, indent=2, ensure_ascii=False) diff --git a/scripts/sync_skill_truth_to_workbuddy.sh b/scripts/sync_skill_truth_to_workbuddy.sh index 30e981fb..8b4c5669 100644 --- a/scripts/sync_skill_truth_to_workbuddy.sh +++ b/scripts/sync_skill_truth_to_workbuddy.sh @@ -8,6 +8,7 @@ mkdir -p "$WB/references" mkdir -p "$WB/skills/jyotish-engine-modules" mkdir -p "$WB/skills/jyotish-full-reading-integration" +cp "$ROOT/AGENTS.md" "$WB/AGENTS.md" cp "$ROOT/SKILL.md" "$WB/SKILL.md" cp "$ROOT/references/technique_registry.json" "$WB/references/technique_registry.json" cp "$ROOT/references/quick-reference-guide.md" "$WB/references/quick-reference-guide.md" diff --git a/scripts/validate_logic_v2.py b/scripts/validate_logic_v2.py index bf18e6d8..c2fe0a39 100644 --- a/scripts/validate_logic_v2.py +++ b/scripts/validate_logic_v2.py @@ -353,7 +353,7 @@ def main(): 'chart': name, 'rule_id': rid, 'rule_name': rule_id_to_name.get(rid, '?'), - 'pyjhora_names': orig_names, + 'external_benchmark_names': orig_names, }) # ==== 输出报告 ==== @@ -404,8 +404,8 @@ def main(): "charts_tested": 60, "comparable_rules": len(comparable_rule_ids), "skill_total": total_skill_comp, - "pyjhora_total": total_pyj_comp, - "unmapped_pyjhora": total_unmapped_pyj, + "external_benchmark_total": total_pyj_comp, + "unmapped_external_benchmark": total_unmapped_pyj, "missing_mappings": {k: v for k, v in sorted(missing_mappings.items())}, "agreements": total_agreements, "false_positives": total_false_positives, diff --git a/scripts/validate_yoga_accuracy.py b/scripts/validate_yoga_accuracy.py index 902cba6e..9e938886 100644 --- a/scripts/validate_yoga_accuracy.py +++ b/scripts/validate_yoga_accuracy.py @@ -175,7 +175,7 @@ def skill_detect_yogas(planets, asc): # ============================================================ # PyJhora 接口封装 # ============================================================ -def init_pyjhora(): +def init_external_benchmark(): """初始化 PyJhora,返回是否成功""" try: import jhora.horoscope.chart.yoga as py_yoga @@ -188,13 +188,13 @@ def init_pyjhora(): return False -def pyjhora_jd(year, month, day, hour_frac): +def external_benchmark_jd(year, month, day, hour_frac): """计算 Julian Day(与 PyJhora 一致)""" import swisseph as swe return swe.julday(year, month, day, hour_frac) -def pyjhora_get_yogas(jd, lat, lon, tz, divisional_chart_factor=1): +def external_benchmark_get_yogas(jd, lat, lon, tz, divisional_chart_factor=1): """ 调用 PyJhora 获取 D1 宫盘的 Yoga 检测结果。 返回:{yoga_function_name: {"name": ..., "desc": ..., "benefits": ...}} @@ -329,10 +329,10 @@ def _canonical_name(key: str) -> str: return CROSS_NAME_MAP.get(key, key) -def get_pyjhora_yoga_keys(pyjhora_results: Dict[str, dict]) -> Set[str]: +def get_external_benchmark_yoga_keys(external_benchmark_results: Dict[str, dict]) -> Set[str]: """从 PyJhora 检测结果中提取归一化名称集合""" keys = set() - for fname in pyjhora_results.keys(): + for fname in external_benchmark_results.keys(): # 去掉 _from_jd_place 等后缀 base = re.sub(r"_(from_jd_place|from_planet_positions|calculation|calc)$", "", fname) @@ -347,7 +347,7 @@ def get_pyjhora_yoga_keys(pyjhora_results: Dict[str, dict]) -> Set[str]: # ============================================================ # 核心验证逻辑 # ============================================================ -def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict: +def validate_one_case(case: dict, run_external_benchmark: bool = True) -> dict: """ 验证单个测试用例。 @@ -363,10 +363,10 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict: "birth": f"{case['year']}-{case['month']:02d}-{case['day']:02d} " f"{case.get('hour', 12):02d}:{case.get('minute', 0):02d}", "skill_yogas": [], - "pyjhora_yogas": [], + "external_benchmark_yogas": [], "matched": [], "skill_only": [], # false positive - "pyjhora_only": [], # false negative + "external_benchmark_only": [], # false negative "error": None, } @@ -399,16 +399,16 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict: return result # --- PyJhora 检测 --- - if run_pyjhora: + if run_external_benchmark: try: - jd_py = pyjhora_jd(year, month, day, hour + minute / 60.0) - pyjhora_results = pyjhora_get_yogas(jd_py, lat, lon, tz, + jd_py = external_benchmark_jd(year, month, day, hour + minute / 60.0) + external_benchmark_results = external_benchmark_get_yogas(jd_py, lat, lon, tz, divisional_chart_factor=1) - result['pyjhora_yogas'] = list(pyjhora_results.keys()) - result['pyjhora_count'] = len(pyjhora_results) - result['pyjhora_details'] = [] - for fname, details in pyjhora_results.items(): - result['pyjhora_details'].append({ + result['external_benchmark_yogas'] = list(external_benchmark_results.keys()) + result['external_benchmark_count'] = len(external_benchmark_results) + result['external_benchmark_details'] = [] + for fname, details in external_benchmark_results.items(): + result['external_benchmark_details'].append({ 'function': fname, 'name': details.get('name', ''), 'desc': details.get('desc', ''), @@ -419,11 +419,11 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict: # --- 对比 --- skill_keys = get_skill_yoga_keys(skill_yogas) - pyjhora_keys = get_pyjhora_yoga_keys(pyjhora_results) + external_benchmark_keys = get_external_benchmark_yoga_keys(external_benchmark_results) - result['matched'] = sorted(skill_keys & pyjhora_keys) - result['skill_only'] = sorted(skill_keys - pyjhora_keys) - result['pyjhora_only'] = sorted(pyjhora_keys - skill_keys) + result['matched'] = sorted(skill_keys & external_benchmark_keys) + result['skill_only'] = sorted(skill_keys - external_benchmark_keys) + result['external_benchmark_only'] = sorted(external_benchmark_keys - skill_keys) # --- 分类:功能性 Yoga vs 经典 Yoga --- cat_map = get_skill_rule_categories() @@ -439,17 +439,17 @@ def validate_one_case(case: dict, run_pyjhora: bool = True) -> dict: return result -def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict: +def run_validation(cases: List[dict], run_external_benchmark: bool = True) -> dict: """运行批量验证""" report = { "total_cases": len(cases), "cases": [], "summary": { "total_skill_yogas": 0, - "total_pyjhora_yogas": 0, + "total_external_benchmark_yogas": 0, "total_matched": 0, "total_skill_only": 0, - "total_pyjhora_only": 0, + "total_external_benchmark_only": 0, "total_skill_only_functional": 0, "total_skill_only_classic": 0, } @@ -458,7 +458,7 @@ def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict: for case in cases: name = case.get('name', 'unknown') print(f"🔍 验证: {name} ({case['year']}-{case['month']:02d}-{case['day']:02d})") - r = validate_one_case(case, run_pyjhora=run_pyjhora) + r = validate_one_case(case, run_external_benchmark=run_external_benchmark) report['cases'].append(r) if r['error']: @@ -466,21 +466,21 @@ def run_validation(cases: List[dict], run_pyjhora: bool = True) -> dict: continue sc = r.get('skill_count', 0) - pc = r.get('pyjhora_count', '?') + pc = r.get('external_benchmark_count', '?') func_n = len(r.get('skill_only_functional', [])) classic_n = len(r.get('skill_only_classic', [])) print(f" Skill: {sc} | PyJhora: {pc}") print(f" 匹配: {len(r['matched'])} | " f"Skill独有: {len(r['skill_only'])} (功能性{func_n}, 经典{classic_n}) | " - f"PyJhora独有: {len(r['pyjhora_only'])}") + f"PyJhora独有: {len(r['external_benchmark_only'])}") report['summary']['total_skill_yogas'] += sc if isinstance(pc, int): - report['summary']['total_pyjhora_yogas'] += pc + report['summary']['total_external_benchmark_yogas'] += pc report['summary']['total_matched'] += len(r['matched']) report['summary']['total_skill_only'] += len(r['skill_only']) if isinstance(pc, int): - report['summary']['total_pyjhora_only'] += len(r['pyjhora_only']) + report['summary']['total_external_benchmark_only'] += len(r['external_benchmark_only']) report['summary']['total_skill_only_functional'] += func_n report['summary']['total_skill_only_classic'] += classic_n @@ -535,27 +535,27 @@ def print_report(report: dict): summary = report['summary'] total_skill = summary['total_skill_yogas'] - total_pyjhora = summary['total_pyjhora_yogas'] + total_external_benchmark = summary['total_external_benchmark_yogas'] matched = summary['total_matched'] skill_only = summary['total_skill_only'] - pyjhora_only = summary['total_pyjhora_only'] + external_benchmark_only = summary['total_external_benchmark_only'] func_only = summary.get('total_skill_only_functional', 0) classic_only = summary.get('total_skill_only_classic', 0) print(f"\n📊 汇总:") print(f" 测试用例数: {report['total_cases']}") print(f" Skill 检测总数: {total_skill}") - print(f" PyJhora 检测总数: {total_pyjhora}") + print(f" PyJhora 检测总数: {total_external_benchmark}") print(f" 匹配总数: {matched}") print(f" Skill 独有: {skill_only} (功能性{func_only}, 经典{classic_only})") - print(f" PyJhora 独有 (skill 缺失): {pyjhora_only}") + print(f" PyJhora 独有 (skill 缺失): {external_benchmark_only}") if total_skill > 0: precision = matched / total_skill * 100 print(f"\n 总体精确率 (Precision): {precision:.1f}%") - if total_pyjhora > 0: - recall = matched / total_pyjhora * 100 + if total_external_benchmark > 0: + recall = matched / total_external_benchmark * 100 print(f" 总体召回率 (Recall): {recall:.1f}%") # --- 核心经典 Yoga 准确率(排除 skill 特色功能性 Yoga)--- @@ -564,21 +564,21 @@ def print_report(report: dict): classic_precision = matched / classic_skill_total * 100 print(f"\n 🎯 核心经典 Yoga 精确率: {classic_precision:.1f}%") print(f" (排除 {func_only} 条功能性 Yoga 后: {matched}/{classic_skill_total})") - if total_pyjhora > 0: - classic_recall = matched / total_pyjhora * 100 + if total_external_benchmark > 0: + classic_recall = matched / total_external_benchmark * 100 print(f" 🎯 核心经典 Yoga 召回率: {classic_recall:.1f}%") # --- 全局不匹配统计 --- print(f"\n🔍 全局不匹配分析:") all_skill_classic = set() all_skill_func = set() - all_pyjhora_missing = set() + all_external_benchmark_missing = set() for r in report['cases']: if r.get('error'): continue all_skill_classic.update(r.get('skill_only_classic', [])) all_skill_func.update(r.get('skill_only_functional', [])) - all_pyjhora_missing.update(r.get('pyjhora_only', [])) + all_external_benchmark_missing.update(r.get('external_benchmark_only', [])) print(f" Skill 经典 Yoga 不匹配(可能误判): {len(all_skill_classic)} 种") if all_skill_classic: @@ -586,9 +586,9 @@ def print_report(report: dict): print(f" Skill 功能性 Yoga(PyJhora 无对应,属 skill 特色): {len(all_skill_func)} 种") if all_skill_func: print(f" {sorted(all_skill_func)[:15]}") - print(f" PyJhora 有但 Skill 缺失的 Yoga: {len(all_pyjhora_missing)} 种") - if all_pyjhora_missing: - print(f" {sorted(all_pyjhora_missing)[:15]}") + print(f" PyJhora 有但 Skill 缺失的 Yoga: {len(all_external_benchmark_missing)} 种") + if all_external_benchmark_missing: + print(f" {sorted(all_external_benchmark_missing)[:15]}") # --- 改进建议 --- print(f"\n💡 改进建议:") @@ -599,13 +599,13 @@ def print_report(report: dict): if len(all_skill_classic) > 10: print(f" ... 等共 {len(all_skill_classic)} 种") - missing_top = sorted(all_pyjhora_missing)[:20] - print(f"\n 2. 【规则补齐】以下 {len(all_pyjhora_missing)} 种 Yoga PyJhora 已实现但 skill 缺失,") + missing_top = sorted(all_external_benchmark_missing)[:20] + print(f"\n 2. 【规则补齐】以下 {len(all_external_benchmark_missing)} 种 Yoga PyJhora 已实现但 skill 缺失,") print(f" 建议按优先级补充(推荐先补充高频出现的):") for name in missing_top[:15]: print(f" - {name}") - if len(all_pyjhora_missing) > 15: - print(f" ... 等共 {len(all_pyjhora_missing)} 种") + if len(all_external_benchmark_missing) > 15: + print(f" ... 等共 {len(all_external_benchmark_missing)} 种") print(f"\n 3. 【名称映射】当前 CROSS_NAME_MAP 已覆盖常见别名,") print(f" 如仍有新别名发现,请添加到映射表中。") @@ -617,20 +617,20 @@ def print_report(report: dict): print(f" ❌ {r['error'][:300]}") continue sc = r.get('skill_count', 0) - pc = r.get('pyjhora_count', '?') + pc = r.get('external_benchmark_count', '?') print(f" Skill ({sc}): {r['skill_yogas'][:5]}{'...' if len(r['skill_yogas']) > 5 else ''}") - if 'pyjhora_yogas' in r: - print(f" PyJhora ({pc}): {r['pyjhora_yogas'][:5]}{'...' if len(r['pyjhora_yogas']) > 5 else ''}") + if 'external_benchmark_yogas' in r: + print(f" PyJhora ({pc}): {r['external_benchmark_yogas'][:5]}{'...' if len(r['external_benchmark_yogas']) > 5 else ''}") func_n = len(r.get('skill_only_functional', [])) cls_n = len(r.get('skill_only_classic', [])) - print(f" 匹配: {len(r['matched'])} | Skill独有: {len(r['skill_only'])}(功能{func_n},经典{cls_n}) | PyJhora独有: {len(r['pyjhora_only'])}") + print(f" 匹配: {len(r['matched'])} | Skill独有: {len(r['skill_only'])}(功能{func_n},经典{cls_n}) | PyJhora独有: {len(r['external_benchmark_only'])}") if r.get('skill_only_classic'): print(f" Skill 经典不匹配: {r['skill_only_classic'][:10]}") if r.get('skill_only_functional'): print(f" Skill 功能性: {r['skill_only_functional'][:10]}") - if r.get('pyjhora_only'): - print(f" PyJhora 独有: {r['pyjhora_only'][:10]}") + if r.get('external_benchmark_only'): + print(f" PyJhora 独有: {r['external_benchmark_only'][:10]}") def save_report(report: dict, output_file: str): @@ -653,8 +653,8 @@ def main(): args = parser.parse_args() # 检查 PyJhora - if not args.skip_pyjhora: - if not init_pyjhora(): + if not args.skip_external_benchmark: + if not init_external_benchmark(): print("❌ PyJhora 不可用,请先安装: pip install pyjhora swisseph") print(" 提示:也可用 --skip-pyjhora 只测试 skill 侧") return 1 @@ -672,7 +672,7 @@ def main(): return 1 # 运行验证 - report = run_validation(cases, run_pyjhora=not args.skip_pyjhora) + report = run_validation(cases, run_external_benchmark=not args.skip_external_benchmark) # 输出报告 print_report(report) diff --git a/scripts/yoga_engine.py b/scripts/yoga_engine.py index 2f48a64c..ed828247 100644 --- a/scripts/yoga_engine.py +++ b/scripts/yoga_engine.py @@ -1298,7 +1298,7 @@ class YogaEngine: return False return offset(ctx.house_of(p), h) in (6, 3 if p == 'Mars' else -1, 7 if p == 'Mars' else -1, 4 if p == 'Jupiter' else -1, 8 if p == 'Jupiter' else -1, 2 if p == 'Saturn' else -1, 9 if p == 'Saturn' else -1) - def pyjhora_planets_aspecting_raasi(p, h): + def external_benchmark_planets_aspecting_raasi(p, h): """Replicate PyJHora house.planets_aspecting_the_raasi() behavior for source parity.""" if p not in ctx.planets or h is None: return False @@ -1314,7 +1314,7 @@ class YogaEngine: ] return target_rasi_idx in planet_ids_in_aspected_signs - def pyjhora_aspected_planets_of_raasi(h): + def external_benchmark_aspected_planets_of_raasi(h): """Replicate PyJHora house.aspected_planets_of_the_raasi(): planets whose rasi drishti hits a target house.""" if h is None: return [] @@ -1431,7 +1431,7 @@ class YogaEngine: occupants = ctx.planets_in_house(target) return bool(occupants) and all(p in BENEFICS for p in occupants) - def pyjhora_natural_benefics(): + def external_benchmark_natural_benefics(): """Replicate PyJHora yoga._get_natural_benefics(): Jupiter, Venus, plus benefic Mercury.""" benefics = [p for p in ["Jupiter", "Venus"] if p in ctx.planets] mercury_house = ctx.house_of("Mercury") @@ -1492,8 +1492,8 @@ class YogaEngine: # v6.0.32: 同宫与相位检查(custom规则常用) "same_house": same_house, "aspect": aspect, "aspects_house": aspects_house, "graha_aspects_house": graha_aspects_house, - "pyjhora_planets_aspecting_raasi": pyjhora_planets_aspecting_raasi, - "pyjhora_aspected_planets_of_raasi": pyjhora_aspected_planets_of_raasi, + "external_benchmark_planets_aspecting_raasi": external_benchmark_planets_aspecting_raasi, + "external_benchmark_aspected_planets_of_raasi": external_benchmark_aspected_planets_of_raasi, "rasi_drishti_signs_from": rasi_drishti_signs_from, "rasi_aspects_house": rasi_aspects_house, "rasi_aspects": rasi_aspects, "rasi_aspected_by_planets": rasi_aspected_by_planets, @@ -1503,7 +1503,7 @@ class YogaEngine: "only_malefics_in_house": only_malefics_in_house, "house_has_benefic": house_has_benefic, "house_has_malefic": house_has_malefic, "house_sign": house_sign, "movable_house": movable_house, - "pyjhora_natural_benefics": pyjhora_natural_benefics, + "external_benchmark_natural_benefics": external_benchmark_natural_benefics, "d9_house_of": d9_house_of, "d9_sign_of": d9_sign_of, "d9_lord_of_house": d9_lord_of_house, "navamsa_dispositor": navamsa_dispositor, "tithi": tithi, "is_waning_moon": is_waning_moon, diff --git a/skills/jyotish-engine-modules/SKILL.md b/skills/jyotish-engine-modules/SKILL.md index c2aaded3..769bcce1 100644 --- a/skills/jyotish-engine-modules/SKILL.md +++ b/skills/jyotish-engine-modules/SKILL.md @@ -1,17 +1,42 @@ --- name: jyotish-engine-modules -description: 印度占星排盘引擎缺失模块完整代码(5个核心模块) +description: 印度占星排盘引擎历史模块集成说明(供审计与迁移参考,当前真源以主 SKILL.md 与 registry 为准) version: 1.0.0 author: 助手 tags: [jyotish, vedic-astrology, calculation-engine, karaka, special-lagnas, vimsopaka, avastha, divisional-charts] related_skills: [jyotish-full-reading-integration, jyotish-vedic-astrology] --- -# 印度占星排盘引擎缺失模块 +# 印度占星排盘引擎历史模块说明 -本skill包含5个核心计算模块的完整Python代码,用于补充GitHub仓库 `yinduzhanxing` 的排盘引擎。 +本 skill 记录了 5 个核心计算模块的历史集成背景,主要用于审计、迁移和对照旧工作流。 -## 模块清单 +当前项目的唯一对外真源以以下文件为准: + +1. `/Users/wuyongnaren/Documents/印度占星/SKILL.md` +2. `/Users/wuyongnaren/Documents/印度占星/references/technique_registry.json` +3. `/Users/wuyongnaren/Documents/印度占星/references/strict-workflow-router.md` + +本文件不得单独作为“当前能力完整性”依据;若与主 skill 描述冲突,以主 skill 与 registry 为准。 + +## 当前使用边界 + +本文件的定位是: + +1. 说明历史模块曾如何并入主仓 +2. 便于审计、迁移、回收旧碎片 +3. 为“是否已有实现可复用”提供线索 + +本文件**不是**当前 skill 完成度、成熟度或精度闭环的直接依据。 + +## 判断原则(与主 skill 保持一致) + +1. 若主仓 `scripts/`、`references/`、`skills/` 已有主体实现,优先判断为“补成熟度/补入口”,不要在旧模块层面重写。 +2. 若外部来源是 MIT / Apache / BSD,可优先考虑复用结构、常数、映射与文档资产;GPL / AGPL / 闭源仅允许黑盒对照。 +3. 涉及 `Dasha` 精确边界、`Shadbala` 绝对值、传统软件口径冻结时,必须以外部 oracle 闭环为准,不能仅凭历史模块说明认定“已完成”。 +4. 若本文件的历史调用链与主仓当前真相源不一致,以主仓真相源为准,不沿用旧版本表述。 + +## 历史模块清单 1. **karaka_calculator.py** - Karaka分配计算器(支持BPHS/JH兼容模式) 2. **special_lagnas.py** - 特殊上升点计算(Bhava/Hora/Ghati Lagna等) @@ -145,7 +170,7 @@ from avastha_calculator import AvasthaCalculator from divisional_charts_extended import DivisionalChartsCalculator ``` -> **注意**:这 5 个模块已整合到主仓库的 `scripts/` 目录中,与引擎一起维护。本 Skill 的 `scripts/` 副本仅作为独立分发包。 +> **注意**:这 5 个模块现已并入主仓库维护。本 Skill 的 `scripts/` 副本只作为历史分发包/迁移参考,不代表当前能力边界。 > **AI Native 注意**:主仓库 `full-reading` 与 `/api/chart` 已输出 `ai_prompt_pack` 和 Ayanamsa 元数据;独立分发时若调用主引擎,应优先消费这些字段作为解读上下文,不要在 Raman/KP 等非 Lahiri 设置下硬编码默认口径。 ## 验证测试 diff --git a/skills/jyotish-full-reading-integration/SKILL.md b/skills/jyotish-full-reading-integration/SKILL.md index 27ca5150..0fec2c07 100644 --- a/skills/jyotish-full-reading-integration/SKILL.md +++ b/skills/jyotish-full-reading-integration/SKILL.md @@ -1,15 +1,40 @@ --- name: jyotish-full-reading-integration -description: 印度占星 full-reading 21步调用链集成方案(v4.4.0) +description: 印度占星 full-reading 历史集成方案(供审计与迁移参考,当前真源以主 SKILL.md 与 registry 为准) version: 4.4.0 author: 助手 tags: [jyotish, integration, full-reading, karaka-jh-mode, jyotish-engine] related_skills: [jyotish-engine-modules, jyotish-vedic-astrology] --- -# 印度占星 Full-Reading 21步调用链集成方案 +# 印度占星 Full-Reading 历史集成方案 -将 5 个新增模块(`special_lagnas` / `karaka_calculator` / `vimsopaka_calculator` / `avastha_calculator` / `divisional_charts_extended`)整合进 `jyotish_engine.py` 的 `full-reading` 调用链。 +本文档记录把 5 个模块整合进 `jyotish_engine.py` 的历史方案,主要用于审计、迁移和比对旧调用链。 + +当前项目的唯一对外真源以以下文件为准: + +1. `/Users/wuyongnaren/Documents/印度占星/SKILL.md` +2. `/Users/wuyongnaren/Documents/印度占星/references/technique_registry.json` +3. `/Users/wuyongnaren/Documents/印度占星/references/strict-workflow-router.md` + +如果本文件与主 skill/registry 的成熟度、边界或步骤定义不一致,必须以后者为准。 + +## 当前使用边界 + +本文件的定位是: + +1. 记录 full-reading 历史调用链如何演进 +2. 方便审计旧版整合方案与迁移痕迹 +3. 帮助识别“哪些能力早已存在,只是后来入口或描述变化了” + +本文件**不是**当前 skill 精度闭环、成熟度分级或对标完成度的最终依据。 + +## 判断原则(与主 skill 保持一致) + +1. 先判断当前主仓是否已经存在对应模块与执行链;若已存在,优先补入口、补边界、补解释层,不重复实现。 +2. 凡涉及 `covered` 与 `complete` 的区分,以主 skill 和 registry 为准,不得因为历史 full-reading 曾调用某模块,就推断该能力已经完全闭环。 +3. 凡涉及 MIT / Apache / BSD 资产,可继续作为合法复用来源;GPL / AGPL / 闭源方案只做黑盒 benchmark,不复制实现。 +4. 凡涉及 `Dasha`、`Shadbala`、`KP/Prashna`、`Tajika/Varshaphala/Sahams` 的传统软件级冻结,必须额外经过外部 oracle 或公开样本闭环。 ## 核心变化 diff --git a/tests/test_dasha.py b/tests/test_dasha.py index 1edb4ab9..8fbbde5b 100644 --- a/tests/test_dasha.py +++ b/tests/test_dasha.py @@ -26,6 +26,7 @@ from jyotish_engine import ( EXALTATION, DEBILITATION, SIGN_LORDS, MOOLATRIKONA, DASHA_ORDER, DASHA_YEARS, NAKSHATRA_LIST, SIGNS, ) +from kalachakra_dasha import calculate_kalachakra_dasha class TestDignityLevel(unittest.TestCase): @@ -52,10 +53,8 @@ class TestDignityLevel(unittest.TestCase): self.assertEqual(_get_dignity_level('Mars', 'Aries', 15.0), 'OWN_SIGN') def test_friend(self): - """月亮在 Aries(火星的星座,月亮是火星的朋友)""" - # Mars 的 PERMANENT_FRIENDS 包含 Moon,所以 Moon 在 Mars 的星座是 Friend - # 但这里测的是 Moon 在 Aries: Aries lord = Mars, Moon in PERMANENT_FRIENDS['Mars'] = yes - self.assertEqual(_get_dignity_level('Moon', 'Aries'), 'FRIEND') + """太阳在 Cancer(Sun 对 Moon 为友,且不触发更高优先级尊严)""" + self.assertEqual(_get_dignity_level('Sun', 'Cancer'), 'FRIEND') def test_enemy(self): """金星在 Aries(火星的星座,金星是火星的敌人 → 但实际看 Aries lord(Mars) 的朋友表里没有 Venus → 不是 Friend) @@ -69,10 +68,8 @@ class TestDignityLevel(unittest.TestCase): self.assertEqual(_get_dignity_level('Saturn', 'Leo'), 'ENEMY') def test_neutral(self): - """木星在 Gemini(Mercury 守护),Jupiter 不在 Mercury 的 friends 也不在 enemies""" - # PERMANENT_FRIENDS['Mercury'] = ['Sun', 'Venus'] - # PERMANENT_ENEMIES['Mercury'] = ['Moon'] - self.assertEqual(_get_dignity_level('Jupiter', 'Gemini'), 'NEUTRAL') + """月亮在 Aries(Moon 对 Mars 既非友也非敌)""" + self.assertEqual(_get_dignity_level('Moon', 'Aries'), 'NEUTRAL') def test_all_exaltation_signs(self): """验证所有行星的入旺星座""" @@ -225,6 +222,32 @@ class TestNakshatraMapping(unittest.TestCase): self.assertEqual(result['moon_nakshatra'], 'Jyeshtha') +class TestKalachakraDasha(unittest.TestCase): + """测试 Kalachakra Dasha 简化集成状态""" + + def test_kalachakra_reports_savya_mode(self): + result = calculate_kalachakra_dasha({ + 'moon_nakshatra_index': 0, + 'moon_pada': 1, + 'birth_datetime': datetime(1990, 6, 15, 10, 30), + }) + self.assertEqual(result['mode'], 'savya') + self.assertEqual(result['starting_lord'], 'Ketu') + self.assertEqual(result['starting_rashi'], 'Capricorn') + self.assertTrue(result['current']) + + def test_kalachakra_reports_apasavya_mode(self): + result = calculate_kalachakra_dasha({ + 'moon_nakshatra_index': 3, + 'moon_pada': 2, + 'birth_datetime': datetime(1985, 3, 20, 8, 0), + }) + self.assertEqual(result['mode'], 'apasavya') + self.assertEqual(result['starting_lord'], 'Ketu') + self.assertTrue(result['current']) + self.assertGreater(result['total_cycle'], 0) + + class TestYogaDetection(unittest.TestCase): """测试 Yoga 格局识别""" diff --git a/tests/test_tajika_annual_closure_status.py b/tests/test_tajika_annual_closure_status.py index 22b5c781..a6b937fb 100644 --- a/tests/test_tajika_annual_closure_status.py +++ b/tests/test_tajika_annual_closure_status.py @@ -39,6 +39,9 @@ def test_tajika_annual_closure_status_identifies_first_annual_packet() -> None: assert report["summary"]["annual_task_count"] == 5 assert report["summary"]["external_verified_annual_tasks"] == 1 assert report["summary"]["can_claim_tajika_sahams_closure"] is False + from scripts import tajika_annual_closure_status as module + assert module.FIRST_PRIORITY_CASE_ID == "template_einstein_varshaphala_1905_lahiri" + assert module.FIRST_PRIORITY_TEMPLATE_PATH.endswith("external_template_einstein_varshaphala_1905_lahiri.json") assert report["first_priority"]["case_id"] == "template_einstein_varshaphala_1905_lahiri" assert report["first_priority"]["capture_id"] == "external_template_einstein_varshaphala_1905_lahiri" assert report["first_priority"]["required_target_fields"] == [