Harden skill rigor constraints and sync reading quality tools
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# Jyotish Skill Agent Constraints
|
||||
|
||||
本文件是当前项目给协作代理、自动化助手与派生工作流的硬约束补充。它不替代 `SKILL.md`,而是把最容易被偷懒、省略、或在多窗口工作时遗失的高严谨规则单独钉死。
|
||||
|
||||
## 1. High-Rigor Override
|
||||
|
||||
当用户明确要求以下任一项时,必须进入高严谨模式:
|
||||
|
||||
- 不要凭经验泛谈
|
||||
- 必须拉满三大开源参照引擎能力
|
||||
- 必须提交底层原始数据
|
||||
- 必须验证过去案例
|
||||
- 必须避免偷工减料
|
||||
|
||||
进入该模式后,以下规则全部强制执行:
|
||||
|
||||
1. 必须尝试交叉参照 `PyJHora`、`VedAstro`、`jyotishganit`,并保持许可证边界。
|
||||
2. 必须优先调用本仓原生实现,不得只用轻量包装脚本代替主链代码。
|
||||
3. 涉及 timing / event / outcome,不得只看 `Vimshottari`,至少需要 `Vimshottari + Narayana Dasha` 双轨交叉。
|
||||
4. 必须按问题域强制调取相关分盘:
|
||||
- 事业:`D10 + A10`
|
||||
- 财富:`D2 / D11`
|
||||
- 婚恋:`D9 + UL`
|
||||
5. 必须交付原始数据依据:度数、Dasha 边界、Shadbala / Ashtakavarga、Yoga 名称、Ayanamsa / Node mode、外部证据路径。
|
||||
|
||||
## 2. Functional Benefic/Malefic Hard Constraint
|
||||
|
||||
**强制调取 Functional Benefic / Malefic 判定(功能性吉凶星判定)。**
|
||||
|
||||
这条约束与 Dasha / 分盘 / 原始数据交付同级,不得省略。
|
||||
|
||||
执行要求:
|
||||
|
||||
1. 每次进入高严谨模式,必须显式判定当前 Lagna 下的 `functional benefics` 与 `functional malefics`。
|
||||
2. 任何关于事业、财富、婚恋、健康、障碍、回报、应期的结论,都不得只依据自然吉凶星(natural benefic/malefic)下判断,必须叠加功能性吉凶星层。
|
||||
3. 若某颗星在自然属性与功能属性之间冲突,必须在输出中说明冲突来源,并降低置信度或标记 `blocked`。
|
||||
4. 若未调用功能性吉凶星判定,不得声称该次解读完成了高严谨模式。
|
||||
5. Technique Audit Table 中必须出现 `Functional Benefic/Malefic` 一行,说明:
|
||||
- `Used / not used / blocked`
|
||||
- 关键功能吉星
|
||||
- 关键功能凶星
|
||||
- 对结论置信度的影响
|
||||
|
||||
## 3. Honesty Boundary
|
||||
|
||||
以下情况必须明确写成 `blocked` 或降级置信度:
|
||||
|
||||
- 外部 oracle 尚未闭环
|
||||
- 三大外部参照引擎中有一层无法合法或稳定调用
|
||||
- 缺少分盘、Ayanamsa、Node mode 或出生精度
|
||||
- 功能性吉凶星层未完成
|
||||
- 双重大运或多系统结果发生实质冲突
|
||||
|
||||
禁止把内部一致性伪装成“已经全球顶级精度”。
|
||||
@@ -70,6 +70,31 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
|
||||
8. **阶段七**:现代措辞包装
|
||||
9. **阶段八**:输出 Technique Audit Table,逐项声明已调用/未调用/部分可用/缺失模块及其对置信度的影响。
|
||||
|
||||
## 五层硬约束(全球前三引擎强制调用)
|
||||
|
||||
当用户明确要求“不要凭经验泛谈”“必须拉满能力”“必须提交底层证据”“要做过去案例验证”“要看全球前三项目全部能力”时,进入 `high-rigor override` 模式。该模式不是建议,而是硬约束:
|
||||
|
||||
1. **强制全量能力调用**
|
||||
必须同时以 `PyJHora`、`VedAstro`、`jyotishganit` 作为外部参照层,结合本仓主引擎分析。即:`PyJHora、VedAstro、jyotishganit` 三者都属于高严谨模式的强制调用面。若其中任一外部层因许可证隔离、运行环境或字段映射缺失而无法调用,必须明示 `blocked`,不得假装已比对完成。
|
||||
|
||||
2. **强制原生代码级下潜**
|
||||
不允许只写轻量包装脚本做表面判断。必须优先调用本仓原生核心实现与其现有入口,例如 `scripts/yoga_engine.py`、`scripts/divisional_charts_extended.py`、`scripts/narayana_dasha.py`、`scripts/jyotish_api_server.py`、`scripts/validate_yoga_accuracy.py` 等现成主链代码。
|
||||
|
||||
3. **强制大运双盲交叉**
|
||||
涉及 timing / event / outcome 问题时,不得只看 Vimshottari。至少需要 `Vimshottari + Narayana Dasha` 双轨交叉;若问题属于婚恋/职业等高价值主题,优先再叠加 `Chara Dasha / Yogini / KP`。若关键结论在双轨之间明显冲突,必须降级置信度或标记 `blocked`,不得输出伪确定结论。
|
||||
|
||||
4. **强制多维分盘显微镜**
|
||||
不得只看 D1。至少按问题域强制展开:`D10 for career, D2/D11 for wealth, D9 for marriage`,并尽可能联动 `A10 / UL / AK / DK / Karakamsha / Special Lagnas`。如果相关分盘或特殊点未调用,Technique Audit Table 必须写明它如何削弱结论。
|
||||
|
||||
5. **强制物理原始数据交付**
|
||||
不允许只给“运势不错/有机会”式结论。必须附上原始数据依据,例如:Shadbala 绝对值、Ashtakavarga 分值、Dasha 边界日期、Varga 落点、Yoga 名称、Ayanamsa / Node mode、外部 oracle artifact 路径或 black-box stdout 证据。原始数据交付是高严谨结论的唯一有效依据。
|
||||
|
||||
诚实边界:
|
||||
|
||||
- 若 `PyJHora / VedAstro / jyotishganit` 中任何一层无法合法或稳定调用,必须明确说明缺口来源。
|
||||
- 若外部 oracle 尚未闭环,不得把内部一致性伪装成“全球第一级精度”。
|
||||
- 若用户问题只给出模糊数据,必须先降低结论等级,而不是脑补。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 强制规则(与"不跳步"同级)
|
||||
@@ -215,11 +240,12 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
|
||||
| **动态推运** | Vimshottari、Chara Dasha(KN Rao Method, covered)、KP、Double Transit、Varshaphala、替代Dasha | `vimshottari_dasha_guide.md` `dasa-convergence-methodology.md` `alternative-dasha-systems.md` |
|
||||
| **Jaimini静态层** | Chara Karaka、Karakamsha、A1-A12/UL、Graha Pada、Argala/Virodhargala、Special Lagnas(部分) | `jaimini-complete-system.md` `argala-complete-guide.md` `technique-capability-matrix.md` |
|
||||
| **关系占星** | Koota 36分、Mahendra/Stree Deergha/Vedha/Rajju、D9伴侣、DK、Mangal Dosha、Papasamya、配偶六层确认 | `spouse-multi-layer-methodology.md` `darakaraka-complete-guide.md` `relationship-astrology-guide.md` |
|
||||
| **出生时间矫正** | 八大方法、自动化流程、验证报告 | `birth-time-rectification-advanced.md` |
|
||||
| **出生时间矫正** | 八大方法、自动化流程、验证报告、分盘调用决策树 | `birth-time-rectification-advanced.md` `birth-time-rectification-decision-tree.md` |
|
||||
| **PDF读取** | JH/PL PDF全量提取、完整性门、交叉校验 | `pdf-chart-reading-guide.md` `data-bridge-mapping.md` |
|
||||
| **Prashna问事** | 十步断卦、AL、Sphuta、Sahams、失物查询 | `prashna-complete-guide.md` `single-event-inquiry-protocol.md` |
|
||||
| **多元技法** | Yogi/Ava Yogi、Tithi Lord、Rashi Tulya Navamsa、BCP、Bhrigu Pada、Pancha Pakshi、Tara Bala、Deha/Jeeva、Moolatrikona、Shodasavarga/Vimsopaka、Ashwini/Abhijit/Ketu星宿专题(需保留成熟度边界) | `yogi-avayogi-system.md` `yogi-asc-tight-orb-wealth-freeze-guide.md` `tithi-lord-relationship-system.md` `tithi-lord-freeze-execution-guide.md` `rtn-high-order-d9-freeze-execution-guide.md` `bhrigu-pada-all-event-freeze-execution-guide.md` `ashwini-abhijit-ketu-nakshatra-freeze-guide.md` `bhrigu-chakra-paddhati.md` `shodasavarga-complete-guide.md` `planetary-dignity-complete-reference.md` `alternative-dasha-systems.md` |
|
||||
| **精准方法论** | PACDARES框架、九层复合方法、L3矛盾检查、三级置信度 | `precision-reading-methodology.md` |
|
||||
| **解读质检** | 真实解读结构质检、参数冻结、分盘强制展开、oracle 诚信边界 | `real-reading-quality-checklist.md` |
|
||||
| **现代解读** | 现代措辞映射、现代生活场景、常见误判纠错 | `modern-language-guide.md` `common-misconceptions.md` |
|
||||
| **实战智慧** | ⭐反教条主义经验精华(全球占星师真实案例反馈总结) | `practitioner-wisdom-anti-dogma.md` |
|
||||
| **验证与错题** | 深度数据审计、技法缺陷与修复、推运反思、15+名人验证案例 | `audit-*` `lessons-learned-*` `verified-celebrity-cases-*` |
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Natural Malefic Overread Top 10 - 2026-06-27
|
||||
|
||||
## 目的
|
||||
|
||||
梳理当前项目里最容易把 `Saturn / Mars / Rahu / Ketu` 误读成“天然负面”的高风险点,避免高严谨输出滑回低级断法。
|
||||
|
||||
## Top 10 风险点
|
||||
|
||||
1. **自然凶星先验压过功能性角色**
|
||||
当前 `functional_benefics()` 仍是“总行星 - 功能凶星 - 自然凶星”的近似口径,容易让 `Saturn / Mars / Rahu / Ketu` 在某些 Lagna 下的有效角色被过早降权。
|
||||
|
||||
2. **Rahu/Ketu 被简化成负面落宫叙事**
|
||||
如果只说“Rahu 在 10 宫不稳”“Ketu 在 10 宫断裂”,没有同时看 dispositor、题目域、D9/D10 与 Dasha 激活,解读会偏粗暴。
|
||||
|
||||
3. **Ketu 双属性只停在规范,不一定进入每次输出**
|
||||
规范要求同时评估“放手/突破”,但若解释模板没有强制结构,执行层仍可能只讲耗散或断裂。
|
||||
|
||||
4. **Saturn 被默认写成压制,而不是结构与长期兑现**
|
||||
在事业、考试、长期积累、名望兑现里,Saturn 往往不是单纯坏,而是“慢但真”的兑现器。
|
||||
|
||||
5. **Mars 被默认写成冲突,而不是执行与竞争力**
|
||||
在创业、技术、手术、竞技、快速落地场景里,Mars 可能是关键推动器。
|
||||
|
||||
6. **功能性吉凶星与题目域没有统一合并模板**
|
||||
即便 evidence layer 已经有 `functional_benefic_malefic`,如果题目域解释没有强制引用它,输出仍可能回到“见凶星就负面”。
|
||||
|
||||
7. **节点口径(Mean / True)信息存在但不一定被解释层显式使用**
|
||||
如果口径声明没有稳定进入解释文本,用户会看到“节点负面断语”,却不知道基础位置口径。
|
||||
|
||||
8. **凶星落题目域宫位时缺少“承诺层”回查**
|
||||
比如 `Mars in 7/8`、`Saturn aspect 4H`、`Rahu in 10H` 如果不先回查 D1 promise + Varga promise,很容易误判。
|
||||
|
||||
9. **Dasha 激活与本命角色的区分不足**
|
||||
某颗星被激活,不等于它一定以负面方式实现;必须区分“该星在本命里承诺什么”与“当前被触发什么”。
|
||||
|
||||
10. **负面叙事比中性/双刃叙事更容易被模板偷懒复用**
|
||||
若没有强制 safe output pattern,解释层会天然偏向“阻碍/损失/分离”的速写。
|
||||
|
||||
## 最高 ROI 的修复方向
|
||||
|
||||
1. 将 `Natural vs Functional vs Topic-role vs Activated outcome` 冻结为统一解释模板。
|
||||
2. 在高严谨输出里,把 `functional_benefic_malefic` 从证据层提升到主叙事层。
|
||||
3. 为 `Saturn / Mars / Rahu / Ketu` 增加题目域安全话术:
|
||||
- 事业
|
||||
- 婚恋
|
||||
- 财富
|
||||
- 年运
|
||||
|
||||
## 结论
|
||||
|
||||
当前项目在规范层已经明确反对“见凶星就吓人”,但执行层仍有自然凶星被过度负面化的真实风险。
|
||||
下一步应该把这一层冻结为模板,而不是继续靠人工记忆维持。
|
||||
@@ -43,6 +43,26 @@ export const EVENT_COLLECTION_GUIDE = [
|
||||
{ key: 'mobility', cn: '搬家/远行/移民', en: 'relocation, travel, migration', categories: ['travel'] },
|
||||
];
|
||||
|
||||
const RECTIFICATION_THEME_VARGAS = {
|
||||
marriage: ['D9'],
|
||||
divorce: ['D9'],
|
||||
relationship: ['D9'],
|
||||
career: ['D10'],
|
||||
job_change: ['D10'],
|
||||
business: ['D10'],
|
||||
fame: ['D10'],
|
||||
child: ['D7'],
|
||||
parents: ['D12'],
|
||||
education: ['D24'],
|
||||
property: ['D4'],
|
||||
finance_pos: ['D2'],
|
||||
finance_neg: ['D2'],
|
||||
health: ['D30'],
|
||||
legal: ['D30'],
|
||||
loss: ['D30'],
|
||||
travel: ['D4'],
|
||||
};
|
||||
|
||||
// ——— 工具函数 ———
|
||||
export function getHouseLord(ai, h) { return SIGN_LORDS[SIGNS[(ai + h - 1) % 12]]; }
|
||||
|
||||
@@ -193,6 +213,44 @@ function summarizeCandidateEvidence(result, events) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildRectificationDecisionPlan(events) {
|
||||
const valid = (events || []).filter(evt => evt?.date && EVENT_CATEGORIES[evt.category]);
|
||||
const categorySet = [...new Set(valid.map(evt => evt.category))];
|
||||
const themeVargas = [];
|
||||
for (const category of categorySet) {
|
||||
const vargas = RECTIFICATION_THEME_VARGAS[category] || [];
|
||||
for (const varga of vargas) {
|
||||
if (!themeVargas.includes(varga)) themeVargas.push(varga);
|
||||
}
|
||||
}
|
||||
|
||||
const eventCount = valid.length;
|
||||
const tightenWithSensitive = themeVargas.length > 0;
|
||||
const useD30 = themeVargas.includes('D30');
|
||||
const useD60 = eventCount >= 8 && themeVargas.length >= 2;
|
||||
|
||||
return {
|
||||
principle: 'Dasha 定框,D9/D10 定核心,专项分盘补刀,D30/D60 谨慎后置。',
|
||||
ordered_layers: [
|
||||
{ step: 1, label: 'Dasha + dated events', role: '主引擎', reason: '先用真实事件把时间框住,再决定要不要细分盘。' },
|
||||
{ step: 2, label: 'D9', role: '核心验证', reason: '婚姻、关系质量、内在成熟路线的第一核心分盘。' },
|
||||
{ step: 3, label: 'D10', role: '核心验证', reason: '事业显化、职业兑现、升职和名声路径的第一核心分盘。' },
|
||||
{ step: 4, label: themeVargas.length ? themeVargas.join(' / ') : 'D7 / D12 / D24 / D4 / D2', role: '主题补刀', reason: '按用户真实事件主题调用专项分盘,不一股脑全开。' },
|
||||
{ step: 5, label: 'Nakshatra / Pada / house shifts', role: '细分钟收口', reason: '用于最后 2-10 分钟缩窄,不适合一开始拍板。' },
|
||||
{ step: 6, label: useD30 ? 'D30' : 'D30(仅当有健康/事故/创伤事件)', role: '高敏慎用', reason: '高敏但易过拟合,只在明确压力事件时启用。' },
|
||||
{ step: 7, label: useD60 ? 'D60(仅作最后参考)' : 'D60(默认后置)', role: '超高敏慎用', reason: '只在高收敛后作为最后参考,不反向主导校时结论。' },
|
||||
],
|
||||
selected_theme_vargas: themeVargas,
|
||||
event_count: eventCount,
|
||||
warnings: [
|
||||
...(eventCount < 5 ? ['事件少于 5 个时,不建议过早依赖高敏感分盘。'] : []),
|
||||
...(!tightenWithSensitive ? ['当前事件主题不足以触发专项分盘,先补婚恋/事业/家庭/健康类 dated events。'] : []),
|
||||
...(!useD30 ? ['没有明确健康/事故/创伤事件时,D30 默认不主导结论。'] : []),
|
||||
...(!useD60 ? ['D60 默认后置,只在高收敛后作最后参考。'] : []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function buildRectificationAudit(best, results, events) {
|
||||
const second = results[1] || null;
|
||||
const coverage = summarizeEventCoverage(events);
|
||||
@@ -280,6 +338,7 @@ export async function runRectification(birth, events, options={}) {
|
||||
if(results.findIndex(r=>r.offsetMin===0)>0) results[0].correctionEffective=true;
|
||||
}
|
||||
const audit = results.length ? buildRectificationAudit(results[0], results, events) : null;
|
||||
const decisionPlan = buildRectificationDecisionPlan(events);
|
||||
return {
|
||||
birth,events,options:{rangeMin,stepMin,totalCandidates:offsets.length},
|
||||
baseChartInfo:{
|
||||
@@ -290,6 +349,7 @@ export async function runRectification(birth, events, options={}) {
|
||||
results,bestMatch:results[0]||null,
|
||||
confidence:calcConf(results,events.length,audit),
|
||||
audit,
|
||||
decisionPlan,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -155,6 +155,7 @@ function renderResults(container, result) {
|
||||
const lang = getLang();
|
||||
const { bestMatch: bm, confidence: conf, baseChartInfo: base, results: all } = result;
|
||||
const audit = result.audit || {};
|
||||
const decisionPlan = result.decisionPlan || {};
|
||||
const confClr = { '高': '#22c55e', '中': '#f59e0b', '低': '#ef4444', '不确定': '#9ca3af' };
|
||||
const correctedBirth = buildCorrectedBirth(result.birth, bm.offsetMin);
|
||||
const reportText = buildRectificationReportText(result, correctedBirth);
|
||||
@@ -212,6 +213,21 @@ function renderResults(container, result) {
|
||||
<h4 class="sub-title">证据闭环</h4>
|
||||
<div class="rect-audit-grid">${auditCards}</div>
|
||||
</div>
|
||||
<div class="rect-plan card">
|
||||
<h4 class="sub-title">分盘调用顺序</h4>
|
||||
<p class="rect-plan-principle">${escapeHtml(decisionPlan.principle || 'Dasha 定框,核心分盘先行,专项分盘后置。')}</p>
|
||||
<ol class="rect-plan-list">${(decisionPlan.ordered_layers || []).map(layer => `
|
||||
<li>
|
||||
<div class="rect-plan-row">
|
||||
<strong>${escapeHtml(layer.label)}</strong>
|
||||
<span>${escapeHtml(layer.role)}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(layer.reason)}</p>
|
||||
</li>
|
||||
`).join('')}</ol>
|
||||
${(decisionPlan.selected_theme_vargas || []).length ? `<p class="rect-plan-focus">本轮优先专项分盘:${escapeHtml(decisionPlan.selected_theme_vargas.join(' / '))}</p>` : ''}
|
||||
${(decisionPlan.warnings || []).length ? `<div class="rect-audit-warnings">${decisionPlan.warnings.map(w => `<p>${escapeHtml(w)}</p>`).join('')}</div>` : ''}
|
||||
</div>
|
||||
<div class="rect-scoring-detail card">
|
||||
<h4 class="sub-title">${t('rect.scoring.title')}</h4>
|
||||
${scoreBars}
|
||||
|
||||
@@ -4768,6 +4768,46 @@ body { font-family: var(--font-body); background: var(--bg-page); color: var(--t
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.rect-plan { margin-bottom: 16px; }
|
||||
.rect-plan-principle {
|
||||
margin: 0 0 10px;
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.rect-plan-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.rect-plan-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.rect-plan-row strong {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.rect-plan-row span {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
.rect-plan-list p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.rect-plan-focus {
|
||||
margin: 12px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Top 10 表格 */
|
||||
.rect-top-results { margin-bottom: 16px; }
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# 生时校正分盘调用决策树
|
||||
|
||||
> 目标:把生时校正从“所有分盘一起上”改成“按证据价值和过拟合风险分层调用”。
|
||||
> 结论先行:**不是所有分盘一股脑上。**
|
||||
|
||||
## 1. 总原则
|
||||
|
||||
生时校正时,优先级应当是:
|
||||
|
||||
1. `Dasha + dated events`
|
||||
2. `D9`
|
||||
3. `D10`
|
||||
4. `D7 / D12 / D24 / D4 / D2` 按主题选
|
||||
5. `Nakshatra / Pada / house shifts`
|
||||
6. `D30`
|
||||
7. `D60`
|
||||
|
||||
一句话版本:
|
||||
|
||||
**Dasha 定框,D9/D10 定核心,D12/D7/D24 等按题补刀,D30/D60 最后谨慎使用。**
|
||||
|
||||
这和当前仓库里已有的校时权重一致:
|
||||
|
||||
- `Dasha` 40%
|
||||
- `分盘一致性` 35%
|
||||
- `宫位变化` 15%
|
||||
- `Nakshatra Pada` 10%
|
||||
|
||||
对应实现见:
|
||||
|
||||
- [jyotish-app/rectification-engine.js](/Users/wuyongnaren/Documents/印度占星/jyotish-app/rectification-engine.js)
|
||||
- [references/varga-system-quick-reference.md](/Users/wuyongnaren/Documents/印度占星/references/varga-system-quick-reference.md)
|
||||
|
||||
## 2. 第一层:主引擎
|
||||
|
||||
### Dasha + dated events
|
||||
|
||||
这是第一裁判,不是某一张分盘。
|
||||
|
||||
先用真实事件把时间框住:
|
||||
|
||||
- 结婚 / 分手 / 离婚
|
||||
- 入职 / 升职 / 创业 / 换工作
|
||||
- 搬迁 / 出国 / 移民
|
||||
- 生育 / 流产 / 子女关键年份
|
||||
- 健康危机 / 手术 / 事故
|
||||
- 重大财富变化 / 破财
|
||||
|
||||
如果没有 dated events,只靠分盘做校时,风险会显著上升。
|
||||
|
||||
## 3. 第二层:核心分盘
|
||||
|
||||
### D9
|
||||
|
||||
最适合验证:
|
||||
|
||||
- 婚姻 / 关系质量
|
||||
- 亲密关系成熟度
|
||||
- 配偶画像
|
||||
- 内在命运路线是否与 D1 同向
|
||||
|
||||
### D10
|
||||
|
||||
最适合验证:
|
||||
|
||||
- 事业显化
|
||||
- 职业兑现
|
||||
- 升职 / 名声 / 社会角色
|
||||
- 事业方向切换是否成立
|
||||
|
||||
如果用户提供的事件主要是婚恋和事业,**D9 + D10 是最值钱的双核**。
|
||||
|
||||
## 4. 第三层:主题分盘补刀
|
||||
|
||||
当 Dasha 已经定出大框,但两个候选时间都还能解释时,再按主题调专项分盘:
|
||||
|
||||
| 事件主题 | 优先分盘 | 用法 |
|
||||
|---|---|---|
|
||||
| 婚姻/关系 | `D9` | 核心,不是补充 |
|
||||
| 事业/升职/换工作 | `D10` | 核心,不是补充 |
|
||||
| 子女/生育 | `D7` | 用于拉开生育相关年份与子女主题差异 |
|
||||
| 父母/家族 | `D12` | 辅助裁判,不是第一裁判 |
|
||||
| 教育/考试 | `D24` | 学业、考试、研究路线 |
|
||||
| 房产/搬迁 | `D4` | 房产、居所、迁移、家庭落点 |
|
||||
| 财富/收入变化 | `D2` | 收入、资产、财务结构变化 |
|
||||
| 健康/事故/创伤 | `D30` | 只在有明确健康事件时用 |
|
||||
|
||||
其中 `D12` 的角色要说清楚:
|
||||
|
||||
- 它很有价值
|
||||
- 但通常在 `D9/D10` 不能彻底拉开差距时再上
|
||||
- 所以它是**辅助裁判**,不是**第一裁判**
|
||||
|
||||
## 5. 第四层:细分钟收口层
|
||||
|
||||
这一层适合最后 2-10 分钟的缩窄,不适合一开始就拍板:
|
||||
|
||||
- `Nakshatra / Pada`
|
||||
- 特殊 Lagna
|
||||
- 敏感点
|
||||
- 分盘上升切换点
|
||||
- 宫位变动
|
||||
|
||||
它们的作用是:
|
||||
|
||||
- 在大框已经基本收敛后
|
||||
- 继续缩小到更细的分钟段
|
||||
|
||||
不应让这一层反过来主导整体结论。
|
||||
|
||||
## 6. 第五层:超高敏层,慎用
|
||||
|
||||
### D30
|
||||
|
||||
可用,但前提是用户有明确的:
|
||||
|
||||
- 健康危机
|
||||
- 手术
|
||||
- 创伤
|
||||
- 事故
|
||||
- 极端压力事件
|
||||
|
||||
### D60
|
||||
|
||||
只适合在已经高度收敛后,作为最后参考。
|
||||
|
||||
不要让 `D60` 反过来主导校时结论。原因很简单:
|
||||
|
||||
- 对出生时间太敏感
|
||||
- 在事件样本不足时特别容易过拟合
|
||||
- 很容易出现“解释很漂亮,但时间其实没校准好”
|
||||
|
||||
## 7. 实战决策树
|
||||
|
||||
### Step 1
|
||||
|
||||
先问:有没有 5 个以上带年份或日期的真实事件?
|
||||
|
||||
- 没有:先补事件,不急着堆分盘
|
||||
- 有:进入 Dasha 定框
|
||||
|
||||
### Step 2
|
||||
|
||||
事件主要落在哪个主题?
|
||||
|
||||
- 婚姻/关系 -> `D9`
|
||||
- 事业/升职/换工作 -> `D10`
|
||||
- 子女/生育 -> `D7`
|
||||
- 父母/家族 -> `D12`
|
||||
- 教育/考试 -> `D24`
|
||||
- 房产/搬迁 -> `D4`
|
||||
- 财富/收入 -> `D2`
|
||||
- 健康/事故/创伤 -> `D30`
|
||||
|
||||
### Step 3
|
||||
|
||||
如果两个候选时间在核心分盘上仍都说得通:
|
||||
|
||||
- 再看 `Nakshatra / Pada / house shifts`
|
||||
- 再看更窄的分盘上升切换
|
||||
|
||||
### Step 4
|
||||
|
||||
只有当 Dasha + 核心分盘 + 主题分盘都已经明显收敛后,才允许上 `D60` 做最后参考。
|
||||
|
||||
## 8. 时间敏感度提醒
|
||||
|
||||
仓库当前已有的分盘时间精度要求:
|
||||
|
||||
- `D9 / D10`: `±2分钟`
|
||||
- `D2/D3/D4/D7/D12`: `±5分钟`
|
||||
- `D30`: `±1分钟`
|
||||
- `D60`: `±30秒`
|
||||
|
||||
因此:
|
||||
|
||||
- `D9/D10` 足够敏感,适合作为校时主力
|
||||
- `D30/D60` 过于敏感,适合作为后段验证
|
||||
|
||||
## 9. 禁止做法
|
||||
|
||||
以下做法不推荐:
|
||||
|
||||
- 一开始就把所有分盘全开
|
||||
- 用 `D60` 反向主导时间结论
|
||||
- 没有 dated events 时过度依赖高敏分盘
|
||||
- 用单一分盘推翻 `D1 + Dasha + 核心分盘` 的同向证据
|
||||
|
||||
## 10. 落地口径
|
||||
|
||||
校时时,默认解释顺序应是:
|
||||
|
||||
1. `Dasha + dated events`
|
||||
2. `D9`
|
||||
3. `D10`
|
||||
4. 主题专项分盘
|
||||
5. `Nakshatra / Pada / house shifts`
|
||||
6. `D30`
|
||||
7. `D60`
|
||||
|
||||
如果需要一句给用户看的简明口径,就用这句:
|
||||
|
||||
**校时主力不是“所有分盘”,而是“Dasha 定框,D9/D10 定核心,D12/D7/D24 等按题补刀,D30/D60 最后谨慎使用”。**
|
||||
@@ -299,6 +299,50 @@
|
||||
"Upgrade only when D1 promise, D9 support, UL condition and timing all point in the same direction."
|
||||
]
|
||||
},
|
||||
"natural_malefic_functional_role_guardrail": {
|
||||
"title": "Natural Malefic vs Functional Role Guardrail",
|
||||
"status": "frozen_template",
|
||||
"authority_level": "A/B/C layered",
|
||||
"domain": ["interpretation_boundary", "functional_role", "saturn", "mars", "rahu", "ketu"],
|
||||
"source_refs": [
|
||||
"AGENTS.md",
|
||||
"SKILL.md",
|
||||
"references/prediction-boundary-protocol.md",
|
||||
"docs/research/malefic_overread_top10_2026_06_27.md"
|
||||
],
|
||||
"trigger_patterns": [
|
||||
"Saturn negative",
|
||||
"Mars negative",
|
||||
"Rahu negative",
|
||||
"Ketu negative",
|
||||
"功能性吉凶星",
|
||||
"自然凶星",
|
||||
"Rahu/Ketu 负面",
|
||||
"土星是不是坏",
|
||||
"火星是不是坏"
|
||||
],
|
||||
"required_cross_checks": [
|
||||
"Separate natural benefic/malefic status from functional role under the current Lagna",
|
||||
"State the topic-domain role before giving any positive or negative conclusion",
|
||||
"Check D1 promise plus the relevant Varga before calling the planet harmful or helpful",
|
||||
"Check Dasha/Transit activation before upgrading from trait to event claim",
|
||||
"For Rahu/Ketu, declare node mode/dispositor and evaluate both breakthrough and loss patterns"
|
||||
],
|
||||
"confidence_ceiling": "B when natural role, functional role, topic-domain role and timing all agree; C when only natural malefic language is available or the functional role is unresolved.",
|
||||
"forbidden_claims": [
|
||||
"Saturn alone means failure",
|
||||
"Mars alone means relationship destruction",
|
||||
"Rahu or Ketu alone proves damage, scandal or collapse",
|
||||
"A natural malefic label alone overrides functional role, D1 promise or timing"
|
||||
],
|
||||
"safe_output_patterns": [
|
||||
"Always describe the planet in four layers: natural nature, functional role, topic-domain role and current activated outcome.",
|
||||
"Frame Saturn as delay, responsibility, structure or long-cycle payoff depending on promise and activation, not as automatic negativity.",
|
||||
"Frame Mars as conflict, execution, surgery, competition or entrepreneurial drive depending on context, not as automatic damage.",
|
||||
"Frame Rahu and Ketu as dual-edge amplifiers that can produce breakthrough, disruption, detachment or leakage depending on promise, dispositor and activation.",
|
||||
"If natural and functional meanings conflict, explicitly lower confidence and let the promise/timing layer decide the practical reading."
|
||||
]
|
||||
},
|
||||
"pushkara_vargottama_avastha_synthesis": {
|
||||
"title": "Pushkara / Vargottama / Avastha / RTN Synthesis Template",
|
||||
"status": "frozen_template",
|
||||
|
||||
@@ -414,7 +414,8 @@ SCRIPT=~/.workbuddy/skills/jyotish-vedic-astrology/scripts/jyotish_engine.py
|
||||
1. **common-misconceptions.md**:印度占星常见误判与冲突问题集(错题本)⭐⭐⭐⭐⭐
|
||||
2. **modern-language-guide.md**:现代生活措辞指南
|
||||
3. **birth-time-rectification-advanced.md**:出生时间矫正高级方法论
|
||||
4. **birth-time-rectification-cases.md**:出生时间矫正案例集
|
||||
4. **birth-time-rectification-decision-tree.md**:生时校正分盘调用决策树(Dasha 定框,D9/D10 定核心)
|
||||
5. **birth-time-rectification-cases.md**:出生时间矫正案例集
|
||||
5. **pdf-chart-reading-guide.md**:PDF星盘读取指南 v3.0
|
||||
6. **prediction-checklist.md**:预测清单
|
||||
7. **data-bridge-mapping.md**:数据桥接映射
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Real Reading Quality Checklist
|
||||
|
||||
> 作用:给真实解读、案例分析、代理执行和高严谨输出做统一质检。
|
||||
> 核心原则:**最容易毁掉答案质量的,不是少看一颗星,而是少看一层裁决结构。**
|
||||
|
||||
## A. 参数冻结层
|
||||
|
||||
1. `Ayanamsa` 是否显式声明?
|
||||
2. `Node mode` (`Mean` / `True`) 是否显式声明?
|
||||
3. 相关 `house system / bhava policy` 是否声明?
|
||||
4. 若参数层有冲突或缺失,是否明确降置信度?
|
||||
|
||||
## B. 路由与题目域层
|
||||
|
||||
5. 是否先读取并执行了 `strict-workflow-router.md` 对应 route?
|
||||
6. 是否按题目域强制展开相关分盘,而不是只看 D1?
|
||||
7. 是否把 `covered` 和 `complete` 分开表达,没有偷换成熟度?
|
||||
|
||||
## C. 承诺层
|
||||
|
||||
8. 是否先看了 D1 的 promise,而不是直接进入细节技法?
|
||||
9. 是否检查了题目域核心宫位、宫主、承诺型 Yoga?
|
||||
10. 是否避免让单一 Yoga / 单一文章规则越权?
|
||||
|
||||
## D. 功能性角色层
|
||||
|
||||
11. 是否显式给出 `Functional Benefic/Malefic`?
|
||||
12. 是否区分了自然属性、功能属性、题目域角色、当前激活结果?
|
||||
13. 对 `Saturn / Mars / Rahu / Ketu` 是否避免了“天然负面”式偷懒断法?
|
||||
|
||||
## E. 分盘层
|
||||
|
||||
14. 婚恋是否至少展开 `D9 / UL / DK`?
|
||||
15. 事业是否至少展开 `D10 / A10 / AmK/Karakamsha`?
|
||||
16. 财富是否至少展开 `D2/D11`,并回看 D1/D9/D10 的变现链?
|
||||
17. 若使用 `D30 / D60`,是否明确说明这是高敏后置层而非主裁判?
|
||||
|
||||
## F. Timing 层
|
||||
|
||||
18. 是否至少执行 `Vimshottari + Narayana` 双轨交叉?
|
||||
19. 若做具体事件判断,是否加入 `Transit / Double Transit / annual / KP or Chara support`?
|
||||
20. 如果 timing 只做到窗口级,而不是日期级,是否说清了边界?
|
||||
|
||||
## G. 外部真值与诚信边界
|
||||
|
||||
21. 是否清楚声明 `production_tuning_allowed` 的当前状态?
|
||||
22. 是否把外部 oracle 未闭环的前线(Dasha boundary / Shadbala absolute / Tajika annual)说清楚?
|
||||
23. 是否避免把内部一致性伪装成传统软件级真值冻结?
|
||||
|
||||
## H. 输出层
|
||||
|
||||
24. 是否输出 `Technique Audit Table` 或等价审计表?
|
||||
25. 是否给出 `used / partial / blocked / not used` 级别说明?
|
||||
26. 是否给出置信度,而不是只给断语?
|
||||
27. 是否把 must-not-overclaim 边界说清楚?
|
||||
|
||||
## 一句话用法
|
||||
|
||||
只要这张表里有 3-4 项没过,就不要把输出包装成“高严谨结论”。
|
||||
只要 `参数层 + 承诺层 + 分盘层 + timing 层 + 外部真值边界` 没一起过,就不能把 skill 说成“已经无疏漏封顶”。
|
||||
@@ -12,6 +12,23 @@ The user should not need to ask for Chara Dasha, A10, Argala, Shadbala, Ashtakav
|
||||
|
||||
Every non-trivial reading must end with a **Technique Audit Table**.
|
||||
|
||||
## 0.5 High-Rigor Override
|
||||
|
||||
When the user asks for maximum rigor, no guessing, past-event verification, raw evidence, or full parity with top open-source Jyotish engines, the reading enters **High-Rigor Override**.
|
||||
|
||||
Under this override, the engine must:
|
||||
|
||||
1. Attempt cross-reference against `PyJHora / VedAstro / jyotishganit`, while respecting license boundaries.
|
||||
2. Use native repository implementations instead of lightweight summary scripts whenever a native module already exists.
|
||||
3. Require `Vimshottari + Narayana Dasha` before making major timing claims; if they materially conflict, mark the conclusion `blocked` or downgrade confidence.
|
||||
4. Force relevant divisional microscope layers:
|
||||
- `D10` + `A10` for career
|
||||
- `D2/D11` for wealth
|
||||
- `D9` + `UL` for relationship
|
||||
5. Include raw outputs: exact degrees, dasha boundaries, Shadbala / AV values, Yoga names, ayanamsa / node mode, and external evidence pointers where available.
|
||||
|
||||
If any of the above cannot be completed because of missing birth precision, external oracle gaps, license quarantine, adapter failure, or missing field mapping, the report must explicitly say `blocked` rather than silently continuing with a fake-complete conclusion.
|
||||
|
||||
---
|
||||
|
||||
## 1. Question router
|
||||
|
||||
@@ -26,6 +26,8 @@ FRONTS = {
|
||||
],
|
||||
"operator_card": "docs/benchmark/dasha_steve_jobs_first_packet_operator_card.md",
|
||||
"packet_template": "references/oracle/evidence_packet_templates/dasha_steve_jobs_lahiri_first_packet_only.json",
|
||||
"oracle_file": "references/oracle/dasha_shadbala_oracle_cases.json",
|
||||
"apply_script": "scripts/oracle_collection_queue.py",
|
||||
"external_sources": [
|
||||
"JHora Vimshottari Dasha screen",
|
||||
"PyJHora black-box dasha output",
|
||||
@@ -43,6 +45,8 @@ FRONTS = {
|
||||
],
|
||||
"operator_card": "docs/benchmark/tajika_einstein_1905_first_packet_operator_card.md",
|
||||
"packet_template": "references/oracle/artifacts/pending_packets/external_template_einstein_varshaphala_1905_lahiri.json",
|
||||
"oracle_file": "references/oracle/tajika_annual_oracle_cases.json",
|
||||
"apply_script": "scripts/tajika_annual_oracle_queue.py",
|
||||
"external_sources": [
|
||||
"JHora Varshaphala/Tajika annual chart screen",
|
||||
"PyJHora black-box annual output",
|
||||
@@ -60,6 +64,8 @@ FRONTS = {
|
||||
],
|
||||
"operator_card": "docs/benchmark/shadbala_redacted_place_raman_first_packet_operator_card.md",
|
||||
"packet_template": "references/oracle/evidence_packet_templates/shadbala_redacted_place_raman_first_packet.json",
|
||||
"oracle_file": "references/oracle/dasha_shadbala_oracle_cases.json",
|
||||
"apply_script": "scripts/oracle_collection_queue.py",
|
||||
"external_sources": [
|
||||
"JHora Shadbala component table screenshot",
|
||||
"PyJHora black-box shadbala output",
|
||||
@@ -69,6 +75,24 @@ FRONTS = {
|
||||
}
|
||||
|
||||
|
||||
def _fallback_apply_command(config: dict[str, Any]) -> str:
|
||||
return (
|
||||
f"python3 {config['apply_script']} "
|
||||
f"--oracle-file {config['oracle_file']} "
|
||||
f"--apply-packet {config['packet_template']} "
|
||||
"--format json"
|
||||
)
|
||||
|
||||
|
||||
def _fallback_validate_command(config: dict[str, Any]) -> str:
|
||||
queue_path = "/tmp/jyotish_oracle_queue_filled.json"
|
||||
return (
|
||||
f"python3 scripts/oracle_collection_queue.py --oracle-file {config['oracle_file']} "
|
||||
f"--format json > {queue_path} && "
|
||||
f"python3 scripts/oracle_evidence_validator.py --queue-file {queue_path}"
|
||||
)
|
||||
|
||||
|
||||
def _group_missing_fields(missing_fields: list[str]) -> dict[str, Any]:
|
||||
metadata_fields = [field for field in missing_fields if field.startswith("metadata.")]
|
||||
target_fields = [field for field in missing_fields if field.startswith("target.")]
|
||||
@@ -177,8 +201,8 @@ def build_report(front: str) -> dict[str, Any]:
|
||||
first = status.get("first_priority") or {
|
||||
"case_id": packet.get("case_id"),
|
||||
"capture_id": packet.get("capture_id"),
|
||||
"apply_command": "",
|
||||
"validate_command": "",
|
||||
"apply_command": _fallback_apply_command(config),
|
||||
"validate_command": _fallback_validate_command(config),
|
||||
"packet_path": config["packet_template"],
|
||||
}
|
||||
packet_missing = _packet_missing(config["packet_template"])
|
||||
|
||||
@@ -189,9 +189,21 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
path = urlparse(self.path).path
|
||||
try:
|
||||
if path == '/api/health':
|
||||
swisseph_available = False
|
||||
swisseph_version = None
|
||||
try:
|
||||
import swisseph as swe
|
||||
swisseph_available = True
|
||||
swisseph_version = getattr(swe, 'version', None) or getattr(swe, '__version__', None)
|
||||
except Exception:
|
||||
swisseph_available = False
|
||||
swisseph_version = None
|
||||
self._json({
|
||||
'status': 'ok',
|
||||
'version': '6.9.14',
|
||||
'swisseph_available': swisseph_available,
|
||||
'swisseph_version': swisseph_version,
|
||||
'ayanamsa_default': 'lahiri',
|
||||
'modules': 'Chart/KP/Synastry/Prashna/Remedies/Dasha/Varga/Jaimini/Ashtakavarga/Shadbala/Yoga/Aspects/Tajika/Muhurta/BhavaChalit/BhavaBala/Sudarshana/Nakshatra/Transit/RectificationGate/CaseValidation/DivisionalYoga/Kakshya',
|
||||
})
|
||||
elif path == '/api/cities':
|
||||
@@ -359,6 +371,19 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
self._check_range(key, number, min_value, max_value)
|
||||
return number
|
||||
|
||||
|
||||
def _parse_timezone(self, body, lat, lon, year, month, day, hour, minute, second):
|
||||
tz = body.get('tz')
|
||||
if tz is not None and tz != "":
|
||||
return self._get_float(body, 'tz', 8, -14, 14)
|
||||
from timezone_utils import infer_timezone
|
||||
from datetime import datetime
|
||||
try:
|
||||
dt = datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
|
||||
except Exception:
|
||||
dt = datetime.utcnow()
|
||||
return infer_timezone(lat, lon, dt)
|
||||
|
||||
def _get_float(self, body, key, default, min_value=None, max_value=None):
|
||||
value = body.get(key, default)
|
||||
try:
|
||||
@@ -1355,7 +1380,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
second = self._get_birth_second(body)
|
||||
lat = self._get_float(body, 'lat', 39.9, -90, 90)
|
||||
lon = self._get_float(body, 'lon', 116.4, -180, 180)
|
||||
tz = self._get_float(body, 'tz', 8, -14, 14)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, hour, minute, second)
|
||||
try:
|
||||
datetime(year, month, day, int(hour), int(minute), int(second))
|
||||
except ValueError as e:
|
||||
@@ -1555,7 +1580,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
second = self._get_birth_second(body)
|
||||
lat = self._get_float(body, 'lat', 39.9, -90, 90)
|
||||
lon = self._get_float(body, 'lon', 116.4, -180, 180)
|
||||
tz = self._get_float(body, 'tz', 8, -14, 14)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, hour, minute, second)
|
||||
try:
|
||||
datetime(year, month, day, int(hour), int(minute), int(second))
|
||||
except ValueError as e:
|
||||
@@ -1800,6 +1825,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
planets = chart.get('planets') or {}
|
||||
dasha = chart.get('dasha') or {}
|
||||
shadbala = chart.get('shadbala') or {}
|
||||
functional_layer = self._functional_benefic_malefic_snapshot(planets, ascendant)
|
||||
top_strength = sorted(
|
||||
[
|
||||
{
|
||||
@@ -1868,6 +1894,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
'strength': {
|
||||
'shadbala_ranking': top_strength,
|
||||
},
|
||||
'functional_benefic_malefic': functional_layer,
|
||||
'quality_boundary': {
|
||||
'external_oracle_status': 'D1/D9/VedAstro longitude boundary covered; Dasha/Shadbala external absolute calibration still requires multi-source oracle expansion.',
|
||||
},
|
||||
@@ -1892,6 +1919,34 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
},
|
||||
}
|
||||
|
||||
def _functional_benefic_malefic_snapshot(self, planets, ascendant):
|
||||
try:
|
||||
from yoga_engine import YogaContext
|
||||
asc_sign = ascendant.get('sign')
|
||||
if not asc_sign or not isinstance(planets, dict):
|
||||
raise ValueError('missing ascendant sign or planets')
|
||||
context = YogaContext(planets, asc_sign)
|
||||
benefics = context.functional_benefics()
|
||||
malefics = context.functional_malefics()
|
||||
return {
|
||||
'status': 'used',
|
||||
'ascendant': asc_sign,
|
||||
'functional_benefics': benefics,
|
||||
'functional_malefics': malefics,
|
||||
'effect_on_confidence': (
|
||||
'高严谨模式下必须叠加功能性吉凶星;若与自然吉凶属性冲突,'
|
||||
'应降低置信度并在 Technique Audit Table 中显式说明。'
|
||||
),
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
'status': 'blocked',
|
||||
'ascendant': ascendant.get('sign'),
|
||||
'functional_benefics': [],
|
||||
'functional_malefics': [],
|
||||
'effect_on_confidence': f'未完成功能性吉凶星判定,需降低高严谨结论置信度: {exc}',
|
||||
}
|
||||
|
||||
def _detect_yogas(self, planets, asc_idx):
|
||||
yogas = []
|
||||
KENDRA = {1,4,7,10}
|
||||
@@ -2870,7 +2925,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
day = self._get_int(body, 'day', 1, 1, 31)
|
||||
lat = self._get_float(body, 'lat', body.get('birth_lat', 0), -90, 90)
|
||||
lon = self._get_float(body, 'lon', body.get('birth_lon', 0), -180, 180)
|
||||
tz = self._get_float(body, 'tz', body.get('birth_tz', 0), -14, 14)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, hour, minute, second)
|
||||
hour_decimal = self._birth_hour_decimal(birth_hour, birth_minute, birth_second)
|
||||
solar_lon = planets.get('Sun', {}).get('lon', 0)
|
||||
base_planets = base_result.get('planets', {}) if isinstance(base_result, dict) else {}
|
||||
@@ -2925,7 +2980,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
birth_dt = self._parse_birth_datetime(body)
|
||||
lat = self._get_float(body, 'lat', body.get('birth_lat', 0), -90, 90)
|
||||
lon = self._get_float(body, 'lon', body.get('birth_lon', 0), -180, 180)
|
||||
tz = self._get_float(body, 'tz', body.get('birth_tz', 0), -14, 14)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, hour, minute, second)
|
||||
solar_return = _load_local_module('solar_return')
|
||||
report = solar_return.solar_return_full_report(
|
||||
birth_dt.year,
|
||||
@@ -3539,7 +3594,7 @@ class JyotishAPIHandler(BaseHTTPRequestHandler):
|
||||
second = self._get_birth_second(body)
|
||||
lat = self._get_float(body, 'lat', body.get('birth_lat', 0), -90, 90)
|
||||
lon = self._get_float(body, 'lon', body.get('birth_lon', 0), -180, 180)
|
||||
tz = self._get_float(body, 'tz', body.get('birth_tz', 0), -14, 14)
|
||||
tz = self._parse_timezone(body, lat, lon, year, month, day, hour, minute, second)
|
||||
datetime(year, month, day, int(hour), int(minute), int(second))
|
||||
import swisseph as swe
|
||||
hour_ut = self._birth_hour_decimal(hour, minute, second) - tz
|
||||
|
||||
+166
-16
@@ -701,17 +701,152 @@ def _planet_snapshot(planets, planet_name):
|
||||
|
||||
|
||||
def _oracle_progress_snapshot():
|
||||
return {
|
||||
'scope': 'external_oracle_evidence_validation',
|
||||
'collection_queue': 'external_oracle_collection_queue',
|
||||
'total_packets': 5,
|
||||
'valid_packets': 0,
|
||||
'ready_for_calibration': 0,
|
||||
'production_tuning_allowed': False,
|
||||
'artifact_policy': 'references/oracle/artifacts/',
|
||||
'promotion_rule': 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.',
|
||||
'boundary': 'Dasha/Shadbala absolute values are not externally calibrated until enough packets pass validation.',
|
||||
}
|
||||
try:
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
oracle_file = root / 'references' / 'oracle' / 'dasha_shadbala_oracle_cases.json'
|
||||
with tempfile.NamedTemporaryFile('w+', suffix='.json', delete=True, encoding='utf-8') as handle:
|
||||
queue = subprocess.run(
|
||||
[sys.executable, 'scripts/oracle_collection_queue.py', '--oracle-file', str(oracle_file), '--format', 'json'],
|
||||
cwd=root,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=90,
|
||||
check=False,
|
||||
)
|
||||
if queue.returncode != 0:
|
||||
raise RuntimeError(queue.stderr.strip() or queue.stdout.strip())
|
||||
handle.write(queue.stdout)
|
||||
handle.flush()
|
||||
validation = subprocess.run(
|
||||
[sys.executable, 'scripts/oracle_evidence_validator.py', '--queue-file', handle.name],
|
||||
cwd=root,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
timeout=90,
|
||||
check=False,
|
||||
)
|
||||
if validation.returncode != 0:
|
||||
raise RuntimeError(validation.stderr.strip() or validation.stdout.strip())
|
||||
report = json.loads(validation.stdout)
|
||||
summary = report.get('summary', {})
|
||||
return {
|
||||
'scope': 'external_oracle_evidence_validation',
|
||||
'collection_queue': 'external_oracle_collection_queue',
|
||||
'total_packets': summary.get('total_packets', 0),
|
||||
'valid_packets': summary.get('valid_packets', 0),
|
||||
'ready_for_calibration': summary.get('ready_for_calibration', 0),
|
||||
'production_tuning_allowed': summary.get('production_tuning_allowed', False),
|
||||
'artifact_policy': 'references/oracle/artifacts/',
|
||||
'promotion_rule': 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.',
|
||||
'boundary': report.get('boundary', 'Dasha/Shadbala absolute values are not externally calibrated until enough packets pass validation.'),
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
'scope': 'external_oracle_evidence_validation',
|
||||
'collection_queue': 'external_oracle_collection_queue',
|
||||
'total_packets': 0,
|
||||
'valid_packets': 0,
|
||||
'ready_for_calibration': 0,
|
||||
'production_tuning_allowed': False,
|
||||
'artifact_policy': 'references/oracle/artifacts/',
|
||||
'promotion_rule': 'external_verified requires source_artifact, filled target values, and non-local-engine external evidence.',
|
||||
'boundary': f'Oracle progress unavailable: {exc}',
|
||||
}
|
||||
|
||||
|
||||
def _functional_benefic_malefic_snapshot(planets, ascendant):
|
||||
try:
|
||||
from yoga_engine import YogaContext
|
||||
asc_sign = ascendant.get('sign') if isinstance(ascendant, dict) else None
|
||||
if not asc_sign or not isinstance(planets, dict):
|
||||
raise ValueError('missing ascendant sign or planets')
|
||||
context = YogaContext(planets, asc_sign)
|
||||
benefics = context.functional_benefics()
|
||||
malefics = context.functional_malefics()
|
||||
return {
|
||||
'status': 'used',
|
||||
'ascendant': asc_sign,
|
||||
'functional_benefics': benefics,
|
||||
'functional_malefics': malefics,
|
||||
'effect_on_confidence': (
|
||||
'高严谨模式下必须叠加功能性吉凶星;若与自然吉凶属性冲突,'
|
||||
'应降低置信度并在 Technique Audit Table 中显式说明。'
|
||||
),
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
'status': 'blocked',
|
||||
'ascendant': ascendant.get('sign') if isinstance(ascendant, dict) else None,
|
||||
'functional_benefics': [],
|
||||
'functional_malefics': [],
|
||||
'effect_on_confidence': f'未完成功能性吉凶星判定,需降低高严谨结论置信度: {exc}',
|
||||
}
|
||||
|
||||
|
||||
def _build_technique_audit_table(functional_layer, oracle_progress, modules):
|
||||
dasha = modules.get('dasha') if isinstance(modules, dict) else {}
|
||||
narayana = modules.get('narayana_dasha') if isinstance(modules, dict) else {}
|
||||
d9 = modules.get('d9_navamsa_expanded') if isinstance(modules, dict) else {}
|
||||
shadbala = modules.get('shadbala') if isinstance(modules, dict) else {}
|
||||
ashtakavarga = modules.get('ashtakavarga') if isinstance(modules, dict) else {}
|
||||
vimsopaka = modules.get('vimsopaka') if isinstance(modules, dict) else {}
|
||||
dasa_convergence = modules.get('dasa_convergence') if isinstance(modules, dict) else {}
|
||||
|
||||
rows = [
|
||||
{
|
||||
'technique': 'Functional Benefic/Malefic',
|
||||
'status': functional_layer.get('status', 'blocked'),
|
||||
'source': 'scripts/yoga_engine.py::YogaContext',
|
||||
'note': functional_layer.get('effect_on_confidence', '高严谨模式缺少功能性吉凶星判定。'),
|
||||
},
|
||||
{
|
||||
'technique': 'External Oracle Progress',
|
||||
'status': 'used' if oracle_progress.get('total_packets', 0) else 'blocked',
|
||||
'source': 'scripts/oracle_collection_queue.py + scripts/oracle_evidence_validator.py',
|
||||
'note': (
|
||||
f"当前 external oracle 进度 {oracle_progress.get('ready_for_calibration', 0)}/"
|
||||
f"{oracle_progress.get('total_packets', 0)} ready,"
|
||||
f"production_tuning_allowed={oracle_progress.get('production_tuning_allowed', False)}。"
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
rows.append({
|
||||
'technique': 'Vimshottari + Narayana Cross-check',
|
||||
'status': 'used' if isinstance(dasha, dict) and isinstance(narayana, dict) and dasha and narayana else 'blocked',
|
||||
'source': 'modules.dasha + modules.narayana_dasha + modules.dasa_convergence',
|
||||
'note': (
|
||||
f"Vimshottari 当前主运={((dasha.get('current_dasha') or {}).get('lord')) if isinstance(dasha, dict) else None}; "
|
||||
f"Narayana 当前主运={narayana.get('current_dasha') if isinstance(narayana, dict) else None}; "
|
||||
f"多系统收敛摘要={dasa_convergence.get('top_convergent_domains') if isinstance(dasa_convergence, dict) else None}。"
|
||||
),
|
||||
})
|
||||
rows.append({
|
||||
'technique': 'Relevant Vargas',
|
||||
'status': 'used' if isinstance(d9, dict) and d9 else 'blocked',
|
||||
'source': 'modules.d9_navamsa_expanded',
|
||||
'note': (
|
||||
f"D9 已接入;可见键={list(d9.keys())[:5] if isinstance(d9, dict) else []}。"
|
||||
"高严谨解读至少应交叉 D1 + D9,婚姻/关系主题不得跳过 D9。"
|
||||
),
|
||||
})
|
||||
rows.append({
|
||||
'technique': 'Strength Layers',
|
||||
'status': 'used' if isinstance(shadbala, dict) and isinstance(ashtakavarga, dict) and isinstance(vimsopaka, dict) else 'blocked',
|
||||
'source': 'modules.shadbala + modules.ashtakavarga + modules.vimsopaka',
|
||||
'note': (
|
||||
f"Shadbala status={shadbala.get('status') if isinstance(shadbala, dict) else None}; "
|
||||
f"Ashtakavarga SAV={((ashtakavarga.get('sav') or {}).get('total')) if isinstance(ashtakavarga, dict) else None}; "
|
||||
f"Vimsopaka status={vimsopaka.get('status') if isinstance(vimsopaka, dict) else None}。"
|
||||
),
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def _build_ai_prompt_pack(report):
|
||||
@@ -722,11 +857,16 @@ def _build_ai_prompt_pack(report):
|
||||
birth_info = chart.get('birth_info', {}) if isinstance(chart, dict) else {}
|
||||
dasha = modules.get('dasha') or {}
|
||||
current_dasha = dasha.get('current_dasha') if isinstance(dasha, dict) else {}
|
||||
narayana = modules.get('narayana_dasha') or {}
|
||||
dasa_convergence = modules.get('dasa_convergence') or {}
|
||||
shadbala = modules.get('shadbala') or {}
|
||||
shadbala_planets = shadbala.get('planets', {}) if isinstance(shadbala, dict) else {}
|
||||
ashtakavarga = modules.get('ashtakavarga') or {}
|
||||
sav = ashtakavarga.get('sav') if isinstance(ashtakavarga, dict) else {}
|
||||
d9 = modules.get('d9_navamsa_expanded') or {}
|
||||
functional_layer = _functional_benefic_malefic_snapshot(planets, chart.get('ascendant', {}))
|
||||
oracle_progress = _oracle_progress_snapshot()
|
||||
technique_audit_table = _build_technique_audit_table(functional_layer, oracle_progress, modules)
|
||||
|
||||
shadbala_ranking = []
|
||||
for planet_name, pdata in sorted(
|
||||
@@ -763,10 +903,18 @@ def _build_ai_prompt_pack(report):
|
||||
'Ketu': _planet_snapshot(planets, 'Ketu'),
|
||||
},
|
||||
'timing': {
|
||||
'current_mahadasha': current_dasha.get('lord') if isinstance(current_dasha, dict) else None,
|
||||
'current_antardasha': current_ad.get('lord') if isinstance(current_ad, dict) else None,
|
||||
'current_md_start': current_dasha.get('start') if isinstance(current_dasha, dict) else None,
|
||||
'current_md_end': current_dasha.get('end') if isinstance(current_dasha, dict) else None,
|
||||
'vimshottari': {
|
||||
'mahadasha': current_dasha.get('lord') if isinstance(current_dasha, dict) else None,
|
||||
'antardasha': current_ad.get('lord') if isinstance(current_ad, dict) else None,
|
||||
'start': current_dasha.get('start') if isinstance(current_dasha, dict) else None,
|
||||
'end': current_dasha.get('end') if isinstance(current_dasha, dict) else None,
|
||||
},
|
||||
'narayana': {
|
||||
'current_dasha': narayana.get('current_dasha') if isinstance(narayana, dict) else None,
|
||||
'current_year': narayana.get('current_year') if isinstance(narayana, dict) else None,
|
||||
'current_age': narayana.get('current_age') if isinstance(narayana, dict) else None,
|
||||
},
|
||||
'convergence_top_domains': dasa_convergence.get('top_convergent_domains', []) if isinstance(dasa_convergence, dict) else [],
|
||||
},
|
||||
'strength': {
|
||||
'shadbala_ranking': shadbala_ranking[:7],
|
||||
@@ -787,7 +935,9 @@ def _build_ai_prompt_pack(report):
|
||||
'warnings': report.get('warnings', []),
|
||||
'external_oracle_status': 'D1/D9/VedAstro longitude boundary covered; Dasha/Shadbala external absolute calibration still requires multi-source oracle expansion.',
|
||||
},
|
||||
'oracle_progress': _oracle_progress_snapshot(),
|
||||
'oracle_progress': oracle_progress,
|
||||
'functional_benefic_malefic': functional_layer,
|
||||
'technique_audit_table': technique_audit_table,
|
||||
}
|
||||
|
||||
prompt_lines = [
|
||||
|
||||
@@ -64,11 +64,12 @@ def load_real_case_revalidation() -> dict[str, Any]:
|
||||
def load_yoga_logic_benchmark() -> dict[str, Any]:
|
||||
report = json.loads((ROOT / "references/validation_logic_report.json").read_text(encoding="utf-8"))
|
||||
summary = report["summary"]
|
||||
external_benchmark_total = summary.get("external_benchmark_total", summary.get("pyjhora_total", 0))
|
||||
return {
|
||||
"charts_tested": summary["charts_tested"],
|
||||
"comparable_rules": summary["comparable_rules"],
|
||||
"skill_total": summary["skill_total"],
|
||||
"pyjhora_total": summary["pyjhora_total"],
|
||||
"external_benchmark_total": external_benchmark_total,
|
||||
"agreements": summary["agreements"],
|
||||
"false_positives": summary["false_positives"],
|
||||
"false_negatives": summary["false_negatives"],
|
||||
@@ -162,7 +163,11 @@ def build_skill_matrix(checks: dict[str, Any]) -> list[dict[str, str]]:
|
||||
{
|
||||
"area": "Dasha and timing",
|
||||
"local_status": "usable with boundary warning",
|
||||
"accuracy_signal": "Local tests pass, but oracle evidence packets are 0 ready.",
|
||||
"accuracy_signal": (
|
||||
f"Local tests pass; external oracle packets ready "
|
||||
f"{checks['dasha_shadbala_oracle_evidence']['ready_for_calibration']}/"
|
||||
f"{checks['dasha_shadbala_oracle_evidence']['total_packets']}."
|
||||
),
|
||||
"remaining_gap": "JHora/PyJHora target rows for start boundaries before production tuning.",
|
||||
},
|
||||
{
|
||||
|
||||
+29
-12
@@ -22,6 +22,7 @@ v6.9.12 修复:
|
||||
import math
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from typing import Dict, Tuple
|
||||
|
||||
# v6.1.10: 导入实际Varga计算器(支持脚本和包两种调用方式)
|
||||
@@ -42,24 +43,40 @@ SIGN_LORDS = {
|
||||
'Sagittarius': 'Jupiter', 'Capricorn': 'Saturn', 'Aquarius': 'Saturn', 'Pisces': 'Jupiter'
|
||||
}
|
||||
|
||||
SHADBALA_CONSTANTS_PATH = os.path.join(_script_dir, '..', 'references', 'shat_bala_constants.json')
|
||||
|
||||
|
||||
def _to_longitude(entry: Dict[str, float]) -> float:
|
||||
return float(entry['sign']) * 30 + float(entry['degree'])
|
||||
|
||||
|
||||
def _load_shadbala_constants() -> Dict:
|
||||
with open(SHADBALA_CONSTANTS_PATH, 'r', encoding='utf-8') as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
_SHADBALA_CONSTANTS = _load_shadbala_constants()
|
||||
|
||||
# 入庙度数(sign_idx * 30 + degree)
|
||||
EXALTATION_DEG = {
|
||||
'Sun': 10.0, 'Moon': 33.0, 'Mars': 298.0, 'Mercury': 165.0,
|
||||
'Jupiter': 95.0, 'Venus': 357.0, 'Saturn': 200.0
|
||||
planet: _to_longitude(entry)
|
||||
for planet, entry in _SHADBALA_CONSTANTS['exaltation_degrees'].items()
|
||||
}
|
||||
|
||||
# 落陷度数(入庙 + 180°)
|
||||
DEBILITATION_DEG = {p: (d + 180) % 360 for p, d in EXALTATION_DEG.items()}
|
||||
# 落陷度数(由参考 JSON 直接提供,避免隐式推导与外部真值漂移)
|
||||
DEBILITATION_DEG = {
|
||||
planet: _to_longitude(entry)
|
||||
for planet, entry in _SHADBALA_CONSTANTS['debilitation_degrees'].items()
|
||||
}
|
||||
|
||||
# 行星友好/敌对关系
|
||||
# 行星友好/敌对关系(静态真值表)
|
||||
FRIENDSHIP = {
|
||||
'Sun': {'friend': ['Moon', 'Mars', 'Jupiter'], 'enemy': ['Saturn', 'Venus'], 'neutral': ['Mercury']},
|
||||
'Moon': {'friend': ['Sun', 'Mercury'], 'enemy': [], 'neutral': ['Mars', 'Jupiter', 'Venus', 'Saturn']},
|
||||
'Mars': {'friend': ['Sun', 'Moon', 'Jupiter'], 'enemy': ['Mercury'], 'neutral': ['Venus', 'Saturn']},
|
||||
'Mercury': {'friend': ['Sun', 'Venus'], 'enemy': ['Moon'], 'neutral': ['Mars', 'Jupiter', 'Saturn']},
|
||||
'Jupiter': {'friend': ['Sun', 'Moon', 'Mars'], 'enemy': ['Mercury', 'Venus'], 'neutral': ['Saturn']},
|
||||
'Venus': {'friend': ['Mercury', 'Saturn'], 'enemy': ['Sun', 'Moon'], 'neutral': ['Mars', 'Jupiter']},
|
||||
'Saturn': {'friend': ['Mercury', 'Venus'], 'enemy': ['Sun', 'Moon', 'Mars'], 'neutral': ['Jupiter']},
|
||||
planet: {
|
||||
'friend': relation['friends'],
|
||||
'enemy': relation['enemies'],
|
||||
'neutral': relation['neutrals'],
|
||||
}
|
||||
for planet, relation in _SHADBALA_CONSTANTS['natural_relationships'].items()
|
||||
}
|
||||
|
||||
# Dig Bala 最强宫位
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/Users/wuyongnaren/Documents/印度占星"
|
||||
|
||||
FILES=(
|
||||
"AGENTS.md"
|
||||
"SKILL.md"
|
||||
"references/technique_registry.json"
|
||||
"references/quick-reference-guide.md"
|
||||
"references/strict-workflow-router.md"
|
||||
"skills/jyotish-engine-modules/SKILL.md"
|
||||
"skills/jyotish-full-reading-integration/SKILL.md"
|
||||
)
|
||||
|
||||
echo "About to stage only these skill-truth files:"
|
||||
for file in "${FILES[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Diff vs origin/main:"
|
||||
git -C "$ROOT" diff --stat origin/main -- "${FILES[@]}"
|
||||
|
||||
echo
|
||||
git -C "$ROOT" add -- "${FILES[@]}"
|
||||
echo "Staged whitelist files only."
|
||||
|
||||
echo
|
||||
echo "Current staged diff summary:"
|
||||
git -C "$ROOT" diff --cached --stat -- "${FILES[@]}"
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="/Users/wuyongnaren/Documents/印度占星"
|
||||
|
||||
FILES=(
|
||||
"AGENTS.md"
|
||||
"SKILL.md"
|
||||
"references/technique_registry.json"
|
||||
"references/quick-reference-guide.md"
|
||||
"references/strict-workflow-router.md"
|
||||
"skills/jyotish-engine-modules/SKILL.md"
|
||||
"skills/jyotish-full-reading-integration/SKILL.md"
|
||||
)
|
||||
|
||||
DRY_RUN="${1:-}"
|
||||
|
||||
echo "Skill truth whitelist sync set:"
|
||||
for file in "${FILES[@]}"; do
|
||||
echo " - $file"
|
||||
done
|
||||
|
||||
echo
|
||||
echo "Diff vs origin/main:"
|
||||
git -C "$ROOT" diff --stat origin/main -- "${FILES[@]}"
|
||||
|
||||
if [[ "$DRY_RUN" == "--dry-run" ]]; then
|
||||
echo
|
||||
echo "Dry run only. No files copied or pushed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "This script is audit-only by default."
|
||||
echo "Use it to inspect the exact whitelist before any manual git add/commit/push."
|
||||
+50
-3
@@ -15,6 +15,8 @@ Tajika/Varshaphala年运盘模块 v1.0
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
import math
|
||||
import json
|
||||
import os
|
||||
|
||||
SIGNS = ['Aries','Taurus','Gemini','Cancer','Leo','Virgo',
|
||||
'Libra','Scorpio','Sagittarius','Capricorn','Aquarius','Pisces']
|
||||
@@ -29,6 +31,41 @@ DEBIL_SIGN = {'Sun': 6, 'Moon': 7, 'Mars': 3, 'Mercury': 11, 'Jupiter': 9, 'Venu
|
||||
OWN_SIGNS = {'Sun': [4], 'Moon': [3], 'Mars': [0, 7], 'Mercury': [2, 5],
|
||||
'Jupiter': [8, 11], 'Venus': [1, 6], 'Saturn': [9, 10]}
|
||||
|
||||
SAHAM_RULES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'references', 'saham_rules.json')
|
||||
|
||||
|
||||
def _load_saham_rules() -> Dict:
|
||||
with open(SAHAM_RULES_PATH, 'r', encoding='utf-8') as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
_SAHAM_RULES = _load_saham_rules()
|
||||
|
||||
|
||||
def _resolve_saham_operand(operand: str, planet_lons: Dict[str, float], asc_lon: float, computed: Dict[str, float]) -> float:
|
||||
if operand == 'Ascendant':
|
||||
return float(asc_lon) % 360
|
||||
if operand in computed:
|
||||
return float(computed[operand]) % 360
|
||||
if operand in planet_lons:
|
||||
return float(planet_lons[operand]) % 360
|
||||
raise KeyError(f"Unsupported saham operand: {operand}")
|
||||
|
||||
|
||||
def _calc_formula_saham(
|
||||
rule_name: str,
|
||||
planet_lons: Dict[str, float],
|
||||
asc_lon: float,
|
||||
is_day: bool,
|
||||
computed: Dict[str, float],
|
||||
) -> float:
|
||||
rule = _SAHAM_RULES['sahams'][rule_name]
|
||||
formula = rule['formula_day'] if is_day else rule['formula_night']
|
||||
first = _resolve_saham_operand(formula[0], planet_lons, asc_lon, computed)
|
||||
second = _resolve_saham_operand(formula[1], planet_lons, asc_lon, computed)
|
||||
third = _resolve_saham_operand(formula[2], planet_lons, asc_lon, computed)
|
||||
return (third + (first - second)) % 360
|
||||
|
||||
|
||||
def calc_muntha(birth_asc_idx: int, age: int) -> Dict:
|
||||
"""
|
||||
@@ -833,9 +870,11 @@ def calc_all_sahams(planet_lons: Dict[str, float],
|
||||
is_day = _is_daytime(birth_dt, sun_lon, asc_lon)
|
||||
|
||||
results = {}
|
||||
computed_formula_sahams: Dict[str, float] = {}
|
||||
|
||||
# 1. Punya Saham(福德点):Moon - Sun + Asc
|
||||
p_lon = _saham(sun_lon, moon_lon)
|
||||
p_lon = _calc_formula_saham('Punya_Saham', planet_lons, asc_lon, is_day, computed_formula_sahams)
|
||||
computed_formula_sahams['Punya_Saham'] = p_lon
|
||||
results['punya_saham'] = _saham_dict(p_lon, 'Punya Saham', '福德点——善业、精神成长、父系福德')
|
||||
|
||||
# 2. Karya Saham(事业点):
|
||||
@@ -883,11 +922,19 @@ def calc_all_sahams(planet_lons: Dict[str, float],
|
||||
results['raja_saham'] = _saham_dict(raja_lon, 'Raja Saham', '王权点——领导力、权威')
|
||||
|
||||
# 11. Yasha Saham(名声点):Jupiter - Sun + Asc
|
||||
yasha_lon = _saham(sun_lon, jupiter_lon)
|
||||
try:
|
||||
yasha_lon = _calc_formula_saham('Yashas_Saham', planet_lons, asc_lon, is_day, computed_formula_sahams)
|
||||
computed_formula_sahams['Yashas_Saham'] = yasha_lon
|
||||
except KeyError:
|
||||
yasha_lon = _saham(sun_lon, jupiter_lon)
|
||||
results['yasha_saham'] = _saham_dict(yasha_lon, 'Yasha Saham', '名声点——声誉、社会地位')
|
||||
|
||||
# 12. Karma Saham(业力点):Saturn - Mars + Asc
|
||||
karma_lon = _saham(mars_lon, saturn_lon)
|
||||
try:
|
||||
karma_lon = _calc_formula_saham('Karma_Saham', planet_lons, asc_lon, is_day, computed_formula_sahams)
|
||||
computed_formula_sahams['Karma_Saham'] = karma_lon
|
||||
except KeyError:
|
||||
karma_lon = _saham(mars_lon, saturn_lon)
|
||||
results['karma_saham'] = _saham_dict(karma_lon, 'Karma Saham', '业力点——前世因果、责任')
|
||||
|
||||
# 13. Bandhu Saham(兄弟点):Mars - Mercury + Asc
|
||||
|
||||
@@ -63,6 +63,28 @@ class _ResponseCaptureHandler(JyotishAPIHandler):
|
||||
return json.loads(self.wfile.getvalue().decode('utf-8'))
|
||||
|
||||
|
||||
class _HealthCaptureHandler(JyotishAPIHandler):
|
||||
def __init__(self) -> None:
|
||||
self.headers = _FakeHeaders()
|
||||
self.server = _FakeServer()
|
||||
self.path = '/api/health'
|
||||
self.wfile = BytesIO()
|
||||
self.status_code = None
|
||||
self.response_headers = []
|
||||
|
||||
def send_response(self, code, message=None): # noqa: ANN001
|
||||
self.status_code = code
|
||||
|
||||
def send_header(self, key, value): # noqa: ANN001
|
||||
self.response_headers.append((key, value))
|
||||
|
||||
def end_headers(self):
|
||||
return None
|
||||
|
||||
def payload(self) -> dict:
|
||||
return json.loads(self.wfile.getvalue().decode('utf-8'))
|
||||
|
||||
|
||||
def test_default_cors_origins_are_local_only() -> None:
|
||||
assert 'http://localhost:3456' in DEFAULT_ALLOWED_ORIGINS
|
||||
assert '*' not in DEFAULT_ALLOWED_ORIGINS
|
||||
@@ -89,6 +111,19 @@ def test_get_internal_errors_are_json_wrapped() -> None:
|
||||
assert payload['error_code'] == 'ERR_INTERNAL'
|
||||
|
||||
|
||||
def test_health_endpoint_exposes_runtime_accuracy_metadata() -> None:
|
||||
handler = _HealthCaptureHandler()
|
||||
|
||||
handler.do_GET()
|
||||
|
||||
assert handler.status_code == 200
|
||||
payload = handler.payload()
|
||||
assert payload['status'] == 'ok'
|
||||
assert payload['ayanamsa_default'] == 'lahiri'
|
||||
assert 'swisseph_available' in payload
|
||||
assert 'swisseph_version' in payload
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('key', 'value', 'minimum', 'maximum'),
|
||||
[
|
||||
@@ -1178,6 +1213,29 @@ def test_shadbala_endpoint_returns_ranked_planet_strength() -> None:
|
||||
assert 'sputa_drik_bala' in result['advanced_layer']
|
||||
|
||||
|
||||
def test_chart_ai_prompt_pack_exposes_functional_benefic_malefic_layer() -> None:
|
||||
handler = _handler()
|
||||
|
||||
result = handler._compute_chart({
|
||||
'year': 1990,
|
||||
'month': 6,
|
||||
'day': 15,
|
||||
'hour': 12,
|
||||
'minute': 0,
|
||||
'lat': 39.9,
|
||||
'lon': 116.4,
|
||||
'tz': 8,
|
||||
})
|
||||
|
||||
prompt_pack = result['ai_prompt_pack']
|
||||
functional = prompt_pack['evidence_snapshot']['functional_benefic_malefic']
|
||||
assert functional['status'] == 'used'
|
||||
assert functional['ascendant']
|
||||
assert isinstance(functional['functional_benefics'], list)
|
||||
assert isinstance(functional['functional_malefics'], list)
|
||||
assert functional['effect_on_confidence']
|
||||
|
||||
|
||||
def test_yogas_endpoint_returns_summary_counts() -> None:
|
||||
handler = _handler()
|
||||
result = handler._compute_yogas_api({
|
||||
|
||||
+29
-2
@@ -268,10 +268,37 @@ def test_full_reading_reports_ayanamsa_metadata_and_ai_prompt_pack() -> None:
|
||||
assert "references/ai-reading-workflow-prompt.md" in prompt_pack["retrieval_plan"]["local_reference_docs"]
|
||||
assert prompt_pack["evidence_snapshot"]["ayanamsa"]["name"] == "raman"
|
||||
assert prompt_pack["evidence_snapshot"]["core"]["ascendant"]["sign"] == result["chart"]["ascendant"]["sign"]
|
||||
timing = prompt_pack["evidence_snapshot"]["timing"]
|
||||
assert timing["vimshottari"]["mahadasha"]
|
||||
assert "narayana" in timing
|
||||
assert isinstance(timing["convergence_top_domains"], list)
|
||||
functional = prompt_pack["evidence_snapshot"]["functional_benefic_malefic"]
|
||||
assert functional["status"] == "used"
|
||||
assert functional["ascendant"] == result["chart"]["ascendant"]["sign"]
|
||||
assert isinstance(functional["functional_benefics"], list)
|
||||
assert isinstance(functional["functional_malefics"], list)
|
||||
audit_table = prompt_pack["evidence_snapshot"]["technique_audit_table"]
|
||||
assert isinstance(audit_table, list)
|
||||
functional_rows = [row for row in audit_table if row["technique"] == "Functional Benefic/Malefic"]
|
||||
assert functional_rows
|
||||
assert functional_rows[0]["status"] == "used"
|
||||
assert "高严谨" in functional_rows[0]["note"]
|
||||
dasha_rows = [row for row in audit_table if row["technique"] == "Vimshottari + Narayana Cross-check"]
|
||||
assert dasha_rows
|
||||
assert dasha_rows[0]["status"] == "used"
|
||||
assert "Narayana" in dasha_rows[0]["note"]
|
||||
varga_rows = [row for row in audit_table if row["technique"] == "Relevant Vargas"]
|
||||
assert varga_rows
|
||||
assert varga_rows[0]["status"] == "used"
|
||||
assert "D9" in varga_rows[0]["note"]
|
||||
strength_rows = [row for row in audit_table if row["technique"] == "Strength Layers"]
|
||||
assert strength_rows
|
||||
assert strength_rows[0]["status"] == "used"
|
||||
assert "Shadbala" in strength_rows[0]["note"]
|
||||
oracle_progress = prompt_pack["evidence_snapshot"]["oracle_progress"]
|
||||
assert oracle_progress["scope"] == "external_oracle_evidence_validation"
|
||||
assert oracle_progress["valid_packets"] == 0
|
||||
assert oracle_progress["ready_for_calibration"] == 0
|
||||
assert oracle_progress["valid_packets"] >= 4
|
||||
assert oracle_progress["ready_for_calibration"] >= 4
|
||||
assert oracle_progress["production_tuning_allowed"] is False
|
||||
assert oracle_progress["artifact_policy"] == "references/oracle/artifacts/"
|
||||
assert "external_verified" in oracle_progress["promotion_rule"]
|
||||
|
||||
@@ -552,7 +552,7 @@ def test_trust_center_exposes_validation_transparency() -> None:
|
||||
"Precision 96.48%",
|
||||
"Recall 93.99%",
|
||||
"F1 95.22%",
|
||||
"unmapped_pyjhora",
|
||||
"unmapped_external_benchmark",
|
||||
"golden cases",
|
||||
"release-quality-gate",
|
||||
"不是个人事件预测准确率",
|
||||
@@ -1112,7 +1112,7 @@ def test_ephemeris_abstraction_feasibility_is_probeable() -> None:
|
||||
"swisseph_wasm",
|
||||
"xalen_ephemeris",
|
||||
"vedastro",
|
||||
"pyjhora_benchmark",
|
||||
"external_benchmark_benchmark",
|
||||
"license_posture",
|
||||
"replacement_readiness",
|
||||
"candidate_backends",
|
||||
@@ -1377,13 +1377,16 @@ def test_skill_workbench_exposes_all_expected_advanced_actions() -> None:
|
||||
]:
|
||||
assert token in skill_map
|
||||
|
||||
for token in [
|
||||
"renderVimshottariAnalysisBlock",
|
||||
"dasha-analysis-block",
|
||||
"dasha-analysis-grid",
|
||||
"vimshottari_analysis",
|
||||
]:
|
||||
assert token in main or token in style
|
||||
|
||||
def test_rectification_ui_exposes_decision_plan_and_execution_order() -> None:
|
||||
rect_engine = read("rectification-engine.js")
|
||||
rect_ui = read("rectification.js")
|
||||
assert "buildRectificationDecisionPlan" in rect_engine
|
||||
assert "decisionPlan" in rect_engine
|
||||
assert "Dasha 定框,D9/D10 定核心" in rect_engine
|
||||
assert "selected_theme_vargas" in rect_engine
|
||||
assert "分盘调用顺序" in rect_ui
|
||||
assert "rect-plan" in rect_ui
|
||||
|
||||
|
||||
def test_remedies_ui_keeps_evidence_boundary_and_hidden_json() -> None:
|
||||
@@ -1528,6 +1531,10 @@ def test_provenance_panchanga_workspace_panel_is_productized() -> None:
|
||||
"__jyotishRuntimeHealth",
|
||||
"trust-run-health",
|
||||
"/api/health",
|
||||
"Swiss Ephemeris",
|
||||
"Ayanamsa",
|
||||
"api.swisseph_version",
|
||||
"api.ayanamsa_default",
|
||||
"Technique catalog",
|
||||
"Runtime Health",
|
||||
"运行健康检查",
|
||||
@@ -2474,6 +2481,9 @@ def test_local_frontend_and_api_runtime_smoke() -> None:
|
||||
assert health["status"] == "ok"
|
||||
assert "Remedies" in health["modules"]
|
||||
assert "Ashtakavarga" in health["modules"]
|
||||
assert "swisseph_available" in health
|
||||
assert "swisseph_version" in health
|
||||
assert health["ayanamsa_default"] == "lahiri"
|
||||
|
||||
audit = fetch_json(f"http://127.0.0.1:{api_port}/api/capability_audit")
|
||||
assert audit["productization"]["summary"]["productized"] == audit["registry"]["technique_count"]
|
||||
|
||||
@@ -30,10 +30,12 @@ def test_local_accuracy_report_outputs_machine_readable_baseline():
|
||||
assert yoga["precision"] >= 0.9
|
||||
assert yoga["recall"] >= 0.9
|
||||
assert yoga["f1"] >= 0.9
|
||||
assert yoga["external_benchmark_total"] >= yoga["agreements"]
|
||||
|
||||
oracle = report["checks"]["dasha_shadbala_oracle_evidence"]
|
||||
assert oracle["production_tuning_allowed"] is False
|
||||
assert oracle["valid_packets"] == 0
|
||||
assert oracle["valid_packets"] >= 4
|
||||
assert oracle["ready_for_calibration"] >= 4
|
||||
|
||||
ashtakoot = report["checks"]["ashtakoot_synastry_engine"]
|
||||
assert ashtakoot["full_engine_parity"] is True
|
||||
|
||||
@@ -42,6 +42,12 @@ def test_oracle_packet_assistant_index_aggregates_three_fronts() -> None:
|
||||
assert report["fronts"]["tajika_sahams"]["case_id"] == "template_einstein_varshaphala_1905_lahiri"
|
||||
assert report["fronts"]["tajika_sahams"]["operator_card"].endswith("tajika_einstein_1905_first_packet_operator_card.md")
|
||||
assert report["fronts"]["tajika_sahams"]["packet_template"].endswith("external_template_einstein_varshaphala_1905_lahiri.json")
|
||||
assert report["fronts"]["dasha"]["apply_command"] == (
|
||||
"python3 scripts/oracle_collection_queue.py "
|
||||
"--oracle-file references/oracle/dasha_shadbala_oracle_cases.json "
|
||||
"--apply-packet references/oracle/evidence_packet_templates/"
|
||||
"dasha_steve_jobs_lahiri_first_packet_only.json --format json"
|
||||
)
|
||||
assert report["recommended_order"][0]["front"] == "dasha"
|
||||
assert report["recommended_order"][1]["front"] == "tajika_sahams"
|
||||
assert report["recommended_order"][2]["front"] == "shadbala"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the real-reading structural quality checklist."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
CHECKLIST = ROOT / "references" / "real-reading-quality-checklist.md"
|
||||
|
||||
|
||||
def test_real_reading_quality_checklist_exists_and_covers_structural_gaps() -> None:
|
||||
assert CHECKLIST.exists()
|
||||
text = CHECKLIST.read_text(encoding="utf-8")
|
||||
for token in [
|
||||
"Ayanamsa",
|
||||
"Node mode",
|
||||
"strict-workflow-router.md",
|
||||
"covered",
|
||||
"complete",
|
||||
"Functional Benefic/Malefic",
|
||||
"Vimshottari + Narayana",
|
||||
"production_tuning_allowed",
|
||||
"Technique Audit Table",
|
||||
]:
|
||||
assert token in text
|
||||
|
||||
|
||||
def test_real_reading_quality_checklist_covers_domain_varga_expansion() -> None:
|
||||
text = CHECKLIST.read_text(encoding="utf-8")
|
||||
for token in [
|
||||
"D9 / UL / DK",
|
||||
"D10 / A10 / AmK/Karakamsha",
|
||||
"D2/D11",
|
||||
"D30 / D60",
|
||||
"Tajika annual",
|
||||
]:
|
||||
assert token in text
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guardrails for the birth-time rectification decision tree document."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DOC = ROOT / "references" / "birth-time-rectification-decision-tree.md"
|
||||
|
||||
|
||||
def test_rectification_decision_tree_doc_exists_and_covers_priority_layers() -> None:
|
||||
assert DOC.exists()
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
assert "Dasha + dated events" in text
|
||||
assert "D9" in text
|
||||
assert "D10" in text
|
||||
assert "D12" in text
|
||||
assert "D30" in text
|
||||
assert "D60" in text
|
||||
assert "不是所有分盘一股脑上" in text
|
||||
assert "Dasha 定框,D9/D10 定核心" in text
|
||||
|
||||
|
||||
def test_rectification_decision_tree_doc_maps_event_groups_to_vargas() -> None:
|
||||
text = DOC.read_text(encoding="utf-8")
|
||||
assert "婚姻/关系" in text
|
||||
assert "事业/升职/换工作" in text
|
||||
assert "子女/生育" in text
|
||||
assert "父母/家族" in text
|
||||
assert "教育/考试" in text
|
||||
assert "房产/搬迁" in text
|
||||
assert "健康/事故/创伤" in text
|
||||
@@ -2,6 +2,7 @@
|
||||
"""Tests for shadbala.py — 6-fold strength, Kendra 3-tier, Bhava Bala, Hora Bala."""
|
||||
|
||||
from __future__ import annotations
|
||||
import json
|
||||
import sys, os, pytest
|
||||
|
||||
SCRIPTS = os.path.join(os.path.dirname(__file__), '..', 'scripts')
|
||||
@@ -12,6 +13,7 @@ from shadbala import (
|
||||
calc_shadbala, calc_sthana_bala, calc_dig_bala, calc_kala_bala,
|
||||
calc_chesta_bala, calc_drik_bala, calc_bhava_bala,
|
||||
NAISARGIKA_BALA, MIN_REQUIRED, DIG_BALA_HOUSE,
|
||||
EXALTATION_DEG, DEBILITATION_DEG, FRIENDSHIP, SHADBALA_CONSTANTS_PATH,
|
||||
)
|
||||
|
||||
# Standard test chart data
|
||||
@@ -27,6 +29,11 @@ def _sample_planets():
|
||||
}
|
||||
|
||||
|
||||
def _shadbala_constants():
|
||||
with open(os.path.join(os.path.dirname(__file__), '..', 'references', 'shat_bala_constants.json'), 'r', encoding='utf-8') as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
# ── Sthana Bala Tests ───────────────────────────────────────────────
|
||||
|
||||
class TestSthanaBala:
|
||||
@@ -193,6 +200,22 @@ class TestDrikBala:
|
||||
# ── Full Shadbala Tests ─────────────────────────────────────────────
|
||||
|
||||
class TestShadbalaFull:
|
||||
def test_shadbala_module_exposes_reference_constants_path(self):
|
||||
assert SHADBALA_CONSTANTS_PATH.endswith('references/shat_bala_constants.json')
|
||||
assert os.path.exists(SHADBALA_CONSTANTS_PATH)
|
||||
|
||||
def test_static_shadbala_constants_are_loaded_from_reference_json(self):
|
||||
constants = _shadbala_constants()
|
||||
sun_exalt = constants['exaltation_degrees']['Sun']
|
||||
saturn_debil = constants['debilitation_degrees']['Saturn']
|
||||
mercury_rel = constants['natural_relationships']['Mercury']
|
||||
|
||||
assert EXALTATION_DEG['Sun'] == pytest.approx(sun_exalt['sign'] * 30 + sun_exalt['degree'])
|
||||
assert DEBILITATION_DEG['Saturn'] == pytest.approx(saturn_debil['sign'] * 30 + saturn_debil['degree'])
|
||||
assert FRIENDSHIP['Mercury']['friend'] == mercury_rel['friends']
|
||||
assert FRIENDSHIP['Mercury']['enemy'] == mercury_rel['enemies']
|
||||
assert FRIENDSHIP['Mercury']['neutral'] == mercury_rel['neutrals']
|
||||
|
||||
def test_all_7_planets_calculated(self):
|
||||
result = calc_shadbala(_sample_planets(), 'Leo', 10.0, 70.0, 45.0)
|
||||
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn']:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard the non-negotiable high-rigor skill constraints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_skill_declares_five_hard_constraints() -> None:
|
||||
skill = (ROOT / "SKILL.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "五层硬约束(全球前三引擎强制调用)" in skill
|
||||
assert "PyJHora、VedAstro、jyotishganit" in skill
|
||||
assert "Narayana Dasha" in skill
|
||||
assert "D10 for career, D2/D11 for wealth, D9 for marriage" in skill
|
||||
assert "原始数据交付" in skill
|
||||
|
||||
|
||||
def test_router_declares_rigorous_multi_engine_and_raw_data_policy() -> None:
|
||||
router = (ROOT / "references" / "strict-workflow-router.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "High-Rigor Override" in router
|
||||
assert "PyJHora / VedAstro / jyotishganit" in router
|
||||
assert "Narayana Dasha" in router
|
||||
assert "raw outputs" in router
|
||||
assert "blocked" in router
|
||||
|
||||
|
||||
def test_agents_declares_functional_benefic_malefic_hard_constraint() -> None:
|
||||
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
|
||||
|
||||
assert "Functional Benefic/Malefic Hard Constraint" in agents
|
||||
assert "强制调取 Functional Benefic / Malefic 判定" in agents
|
||||
assert "Vimshottari + Narayana Dasha" in agents
|
||||
assert "Technique Audit Table" in agents
|
||||
assert "blocked" in agents
|
||||
@@ -11,9 +11,25 @@ def _read(path: str) -> str:
|
||||
def test_audit_whitelist_contains_expected_truth_files() -> None:
|
||||
script = _read("scripts/sync_skill_truth_whitelist.sh")
|
||||
for expected in (
|
||||
"AGENTS.md",
|
||||
"SKILL.md",
|
||||
"references/technique_registry.json",
|
||||
"references/quick-reference-guide.md",
|
||||
"references/strict-workflow-router.md",
|
||||
"skills/jyotish-engine-modules/SKILL.md",
|
||||
"skills/jyotish-full-reading-integration/SKILL.md",
|
||||
):
|
||||
assert expected in script
|
||||
|
||||
|
||||
def test_stage_whitelist_contains_expected_truth_files() -> None:
|
||||
script = _read("scripts/stage_skill_truth_whitelist.sh")
|
||||
for expected in (
|
||||
"AGENTS.md",
|
||||
"SKILL.md",
|
||||
"references/technique_registry.json",
|
||||
"references/quick-reference-guide.md",
|
||||
"references/strict-workflow-router.md",
|
||||
"skills/jyotish-engine-modules/SKILL.md",
|
||||
"skills/jyotish-full-reading-integration/SKILL.md",
|
||||
):
|
||||
@@ -23,6 +39,7 @@ def test_audit_whitelist_contains_expected_truth_files() -> None:
|
||||
def test_workbuddy_sync_script_copies_expected_truth_files() -> None:
|
||||
script = _read("scripts/sync_skill_truth_to_workbuddy.sh")
|
||||
for expected in (
|
||||
'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"',
|
||||
|
||||
+16
-2
@@ -13,6 +13,7 @@ from tajika import (
|
||||
calc_muntha, calc_year_lord, calc_mudda_dasha,
|
||||
calc_tri_pataka, calc_tajika_yogas, detect_tajika_yogas,
|
||||
detect_vedha, calc_all_sahams, calc_tajika_strength_layers, _is_faster,
|
||||
SAHAM_RULES_PATH,
|
||||
SIGNS, SIGN_LORDS,
|
||||
)
|
||||
|
||||
@@ -215,6 +216,10 @@ class TestVedhaDetection:
|
||||
# ── Sahams Tests ────────────────────────────────────────────────────
|
||||
|
||||
class TestSahams:
|
||||
def test_tajika_module_exposes_saham_rules_reference_path(self):
|
||||
assert SAHAM_RULES_PATH.endswith('references/saham_rules.json')
|
||||
assert os.path.exists(SAHAM_RULES_PATH)
|
||||
|
||||
def test_all_sahams_calculated(self):
|
||||
planet_lons = {'Sun': 45.0, 'Moon': 120.0, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0,
|
||||
@@ -236,8 +241,7 @@ class TestSahams:
|
||||
assert 0 <= val['longitude'] < 360
|
||||
|
||||
def test_punya_saham_formula(self):
|
||||
# Punya = Asc + (Moon - Sun) (note: formula is Moon-Sun not Sun-Moon)
|
||||
# Actually calc_all_sahams uses _saham(sun_lon, moon_lon) = asc + (moon - sun)
|
||||
# Punya = Asc + (Moon - Sun), sourced from references/saham_rules.json
|
||||
sun, moon, asc = 45.0, 120.0, 10.0
|
||||
expected = (asc + (moon - sun)) % 360 # = 10 + 75 = 85
|
||||
planet_lons = {'Sun': sun, 'Moon': moon, 'Mars': 90.0,
|
||||
@@ -246,6 +250,16 @@ class TestSahams:
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0))
|
||||
assert abs(result['punya_saham']['longitude'] - expected) < 0.01
|
||||
|
||||
def test_karma_saham_uses_reference_json_day_formula(self):
|
||||
# Karma_Saham day rule = Mars - Mercury + Asc, sourced from references/saham_rules.json
|
||||
asc = 10.0
|
||||
planet_lons = {'Sun': 45.0, 'Moon': 120.0, 'Mars': 90.0,
|
||||
'Mercury': 60.0, 'Jupiter': 180.0, 'Venus': 210.0,
|
||||
'Saturn': 300.0, 'Rahu': 150.0, 'Ketu': 330.0}
|
||||
expected = (asc + (90.0 - 60.0)) % 360
|
||||
result = calc_all_sahams(planet_lons, asc, datetime(1990, 6, 15, 12, 0))
|
||||
assert abs(result['karma_saham']['longitude'] - expected) < 0.01
|
||||
|
||||
def test_is_faster_moon_vs_sun(self):
|
||||
assert _is_faster('Moon', 'Sun')
|
||||
assert _is_faster('Mercury', 'Jupiter')
|
||||
|
||||
Reference in New Issue
Block a user