From d11cebacf8262a097f0446734e03e7ff6a7ab229 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Fri, 26 Jun 2026 00:44:09 +0800 Subject: [PATCH] fix: harden accuracy and api guardrails --- README.md | 3 +- findings.md | 10 + progress.md | 15 + references/validation_logic_report.json | 1070 +++++++++++------------ scripts/jyotish_api_server.py | 46 +- scripts/oracle_evidence_validator.py | 27 + scripts/run_quality_gate.py | 31 +- scripts/validate_logic_v2.py | 10 +- tests/test_api_server_security.py | 49 ++ tests/test_frontend_productization.py | 20 +- tests/test_oracle_evidence_validator.py | 112 ++- 11 files changed, 825 insertions(+), 568 deletions(-) diff --git a/README.md b/README.md index 88274a6d..a80cfab7 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Static demo / PWA 发布要求:必须保留 `static_demo_boundary_visible` 说 - quick:快速开发守门,适合普通代码/文案修改后先跑:`python3 scripts/run_quality_gate.py --profile quick` - browser:完整浏览器守门,覆盖 runtime smoke 与真实浏览器用户路径:`python3 scripts/run_quality_gate.py --profile browser` - release:发布前守门,包含关键产品文件未跟踪检查、慢速 golden cases、真实案例复验与 Yoga 逻辑报告:`python3 scripts/run_quality_gate.py --profile release` +- accuracy:本地准确率守门,跳过浏览器点击重活,但强制运行真实案例复验、Dasha/Oracle 审计、Yoga 逻辑对照和本地准确率总报告:`python3 scripts/run_quality_gate.py --profile accuracy` ### 真实案例复验与准确率边界 @@ -283,7 +284,7 @@ The AI does NOT require the user to name techniques (e.g., "Chara Dasha"). It au ## Technique Coverage -Current count: **65 techniques** (55 covered, 10 complete, 0 partial, 0 missing) +Current count: **68 techniques** (58 covered, 10 complete, 0 partial, 0 missing) | Technique | Status | Notes | |-----------|--------|-------| diff --git a/findings.md b/findings.md index d2b1b58b..37b8a145 100644 --- a/findings.md +++ b/findings.md @@ -1,5 +1,15 @@ # 印度占星产品化发现记录 +## 2026-06-25 Round 25 地毯式碎片扫描前置结论 + +- 已按用户要求在继续实现前进行整机/多窗口碎片扫描,并生成 `docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md`。 +- 当前必须作为实现前置读取的规划文件仍是 `task_plan.md`、`findings.md`、`progress.md`。 +- 高价值 Jyotish 资料源不只当前主仓:还包括 `.workbuddy/skills/jyotish-vedic-astrology` 旧 skill 副本、`Documents/星轨talk/engines-repo/jyotish`、`Documents/Codex/2026-06-20/.../engines-repo/jyotish`、Obsidian Jyotish 研究笔记、Downloads 中的 `印度占星.pdf/印度占星1.pdf/Kimi_Agent_高维印度占星师.zip/jyotish_training.agent.final.docx`、以及当前 repo 内 `references/open_source_sources` 和 `benchmarks/jyotish`。 +- 隐私边界:Downloads/Obsidian/私人 PDF/完整解盘报告仅作为需求和差距发现来源,默认不提交原文、不复制私人出生资料、不上传完整报告。 +- 远端状态:HTTPS `git ls-remote` 可达,`origin/codex/release-hygiene-ci` 远端仍停在 `6338cf5`;本地 `bac3748` docs commit 因 SSH 22 超时尚未确认推送成功。后续需使用 SSH-443 或其他可达方式同步。 +- Round 25 副手任务已发布,扫描时已看到部分 `antigravity_round25_*` 报告开始生成,但未满 18+ 前不能视为完成。 +- Ashtakoot 结论边界:Round 24 “全 0/瞎编”属于需纠正的过强说法;当前 `scripts/ashtakoot.py` 有非零本地规则,但外部 oracle 仍 0/5,不能声称与 JHora/AstroSage/VedAstro 完全一致。 + ## 开源与本地基线 - `references/open-source-jyotish-scan-2026.md` 已记录可直接复用/对标项目:dashaflow、jyotishganit、panchanga_api、jaimini-tropical、VedicAstro、KPAstroDashboard、vedic_astro_npm、PyJHora、VedAstro、xalen-ephemeris。 diff --git a/progress.md b/progress.md index 9e608e6b..18773b8b 100644 --- a/progress.md +++ b/progress.md @@ -378,3 +378,18 @@ - 完成 Antigravity Round 21 重型副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round21_2026_06_25.md`,把副手工作量扩到 18 个报告包、至少 20 个联网开源/产品复核对象、Top 40 ROI 任务,并明确 Ashtakoot oracle、VedAstro/RaviKarrii/pyhora2 许可证深挖、Trust Center UX、Git 入库、Shadbala 二期校验和 Round 22 执行计划。 - 完成 Ashtakoot 外部 oracle 第一层队列与 validator 强化:新增 `references/oracle/ashtakoot_oracle_cases.json` 5 条 draft cases,`oracle_collection_queue.py` 可输出 `target_modules=["ashtakoot"]`,README 记录 `Ashtakoot 外部合婚 oracle` 命令;`oracle_evidence_validator.py` 增加 36 分制范围、8 Kuta 分项范围和总分求和一致性拦截,拒绝 99 分、字符串/非法范围与 sum mismatch 的假外部证据包。 - 验证完成:`python3 -m pytest -q tests/test_oracle_evidence_validator.py tests/test_oracle_collection_queue.py` 14 项通过;`python3 -m pytest -q tests/test_ashtakoot.py tests/test_frontend_productization.py::test_dasha_reference_audit_is_documented_and_gated` 53 项通过;`npm run build --prefix jyotish-app` 通过;Ashtakoot queue 输出 5 条 template-only 样本且 `production_tuning_allowed=false`。 +# 2026-06-25 Round 25 地毯式扫描与副手派工 + +- 用户强调:所有工作前必须地毯式探索印度占星全部信息,尤其检查不同窗口/多个文件夹中的碎片。 +- 已读取 `task_plan.md`、`findings.md`、`progress.md`,确认计划中已有“实现前扫描本地碎片、open_source_sources、现有测试与产品差距矩阵”的硬规则。 +- 已发布 `docs/research/antigravity_sidecar_work_order_round25_2026_06_25.md`,要求副手纠正 Round 24 Ashtakoot 误判、复核 VedAstro MIT 可复用范围、验收 accuracy profile、继续拆 UI/API/CLI/Prompt Pack/Oracle 缺口。 +- 已创建 `docs/research/whole_machine_fragment_sweep_round25_2026_06_25.md`,记录整机扫描范围、高价值本地资料源、Git 远端状态、隐私边界和后续实现前置命令。 +- 已发现高价值碎片源:当前主仓、`.workbuddy/skills/jyotish-vedic-astrology`、`Documents/星轨talk/engines-repo/jyotish`、`Documents/Codex/2026-06-20/.../engines-repo/jyotish`、Obsidian Jyotish 笔记、Downloads 私人 PDF/zip/docx、`references/open_source_sources`、`benchmarks/jyotish`。 +- Git 状态:本地 `bac3748 docs(research): archive antigravity round 23 and 24 audits` 已创建;push 通过 SSH 22 超时失败,远端 HTTPS refs 显示 `codex/release-hygiene-ci` 仍在 `6338cf5`。后续需换 SSH-443 或其他可达方式再推。 +- TDD 状态:已为 `run_quality_gate.py --profile accuracy` 写红灯测试,当前失败点是 `accuracy` profile 未实现;实现前已先响应用户要求完成碎片扫描。 +- 已发布 `docs/research/antigravity_sidecar_work_order_round26_2026_06_25.md`,要求副手纠正 Round 25 Panchang 过强结论、黑盒验收 accuracy profile、设计 Git 远端替代同步和继续拆解 Ashtakoot/Shadbala/Kuja/Prompt Pack 票据。 +- 已实现并验证 `run_quality_gate.py --profile accuracy`:跳过浏览器重活,强制运行真实案例复验、Dasha/Oracle 审计、Yoga 逻辑对照和 `local_accuracy_report.py`;真实运行输出 `Quality gate passed`。 +- 修复 `scripts/validate_logic_v2.py` 的 set 顺序抖动,让 `references/validation_logic_report.json` 的 false positive/negative 列表稳定排序,避免 accuracy profile 每次制造无意义 diff。 +- 完成 Antigravity Round 27 重型副手任务单:新增 `docs/research/antigravity_sidecar_work_order_round27_2026_06_25.md`,把副手工作量提高到 24 份报告,强制复核 Round25/26 归档、accuracy profile 稳定性、Yoga diff 稳定性、SSH-443/HTTPS 同步方案、Shadbala Phase 2、Kuja enum、Prompt Pack 安全护栏、Panchanga 商业级 UI、Ashtakoot oracle/许可证、API/前端隐藏技能 ROI、全机碎片后续读取顺序和 Round28 Top60。 +- 完成 Shadbala Evidence Validator Phase 2 第一批实现:在现有 `sthana/dig/kala/chesta/naisargika/drik` schema 上增加 `total_rupa` 必填、单项 Rupa 上限 20.0、七曜六分量求和与 `total_rupa` 0.05 容差校验,避免人工 evidence packet 只填分量但总分不自洽仍被误判有效。TDD 红灯确认 3 个缺口后转绿,聚焦 validator/API 13 项测试通过。 +- 完成“有效文件复用索引”:新增 `docs/research/local_reuse_candidate_index_round28_2026_06_26.md`,把当前 repo、旧 WorkBuddy skill、历史 benchmark、本地开源镜像按 `direct current repo reuse`、`older WorkBuddy skill reuse`、`local open-source mirrors`、`benchmark-only sources` 分类,明确未来补 Kuja/Panchanga/Tajika/Chara/Prompt Pack 时必须先查已有文件和许可证边界,再决定是否新写。 diff --git a/references/validation_logic_report.json b/references/validation_logic_report.json index d20a4001..abb13aa5 100644 --- a/references/validation_logic_report.json +++ b/references/validation_logic_report.json @@ -107,15 +107,120 @@ }, "false_positives": [ { - "chart": "Albert Einstein", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga" + "chart": "Abraham Lincoln", + "rule_id": "bvr_swaveeryaddhana_precise", + "rule_name": "Swaveeryaddhana Yoga" }, { "chart": "Albert Einstein", "rule_id": "bvr_bhaga_chumbana_yoga", "rule_name": "Bhaga Chumbana Yoga" }, + { + "chart": "Albert Einstein", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga" + }, + { + "chart": "Alexander Fleming", + "rule_id": "bvr_bhaga_chumbana_yoga", + "rule_name": "Bhaga Chumbana Yoga" + }, + { + "chart": "Alexander Fleming", + "rule_id": "bvr_yukthi_samanwithavagmi_yoga", + "rule_name": "Yukthi Samanwithavagmi Yoga" + }, + { + "chart": "Amitabh Bachchan", + "rule_id": "bvr_bandhubhisthyaktha_precise", + "rule_name": "Bandhubhisthyaktha Yoga" + }, + { + "chart": "Amitabh Bachchan", + "rule_id": "bvr_karma_malika_precise", + "rule_name": "Karma Malika Yoga" + }, + { + "chart": "Barack Obama", + "rule_id": "bvr_bandhubhisthyaktha_precise", + "rule_name": "Bandhubhisthyaktha Yoga" + }, + { + "chart": "Barack Obama", + "rule_id": "bvr_yukthi_samanwithavagmi_yoga", + "rule_name": "Yukthi Samanwithavagmi Yoga" + }, + { + "chart": "Bill Gates", + "rule_id": "adhi_yoga", + "rule_name": "Adhi Yoga" + }, + { + "chart": "Charles Darwin", + "rule_id": "bvr_matru_sneha_yoga", + "rule_name": "Matru Sneha Yoga" + }, + { + "chart": "Cristiano Ronaldo", + "rule_id": "bvr_dwadasa_sahodara_yoga", + "rule_name": "Dwadasa Sahodara Yoga" + }, + { + "chart": "Indira Gandhi", + "rule_id": "bvr_swaveeryaddhana_precise", + "rule_name": "Swaveeryaddhana Yoga" + }, + { + "chart": "J. Krishnamurti", + "rule_id": "bvr_dehasthoulya_yoga", + "rule_name": "Dehasthoulya Yoga" + }, + { + "chart": "Lata Mangeshkar", + "rule_id": "bvr_dwadasa_sahodara_yoga", + "rule_name": "Dwadasa Sahodara Yoga" + }, + { + "chart": "Lionel Messi", + "rule_id": "brahma_yoga", + "rule_name": "Brahma Yoga" + }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_017_vosi_precise", + "rule_name": "Vosi Yoga" + }, + { + "chart": "MS Dhoni", + "rule_id": "bvr_karma_malika_precise", + "rule_name": "Karma Malika Yoga" + }, + { + "chart": "MS Dhoni", + "rule_id": "bvr_mathibhramana_yoga", + "rule_name": "Mathibhramana Yoga" + }, + { + "chart": "Mahatma Gandhi", + "rule_id": "bvr_utthama_graha_yoga", + "rule_name": "Utthama Griha Yoga" + }, + { + "chart": "Max Planck", + "rule_id": "sankha_yoga", + "rule_name": "Sankha Yoga" + }, + { + "chart": "Narendra Modi", + "rule_id": "bvr_swaveeryaddhana_precise", + "rule_name": "Swaveeryaddhana Yoga" + }, + { + "chart": "Nelson Mandela", + "rule_id": "bvr_mathibhramana_yoga", + "rule_name": "Mathibhramana Yoga" + }, { "chart": "Nikola Tesla", "rule_id": "bvr_bhratruvriddhi_precise", @@ -126,135 +231,20 @@ "rule_id": "bvr_krisanga_yoga", "rule_name": "Krisanga Yoga" }, - { - "chart": "Charles Darwin", - "rule_id": "bvr_matru_sneha_yoga", - "rule_name": "Matru Sneha Yoga" - }, - { - "chart": "Alexander Fleming", - "rule_id": "bvr_bhaga_chumbana_yoga", - "rule_name": "Bhaga Chumbana Yoga" - }, - { - "chart": "Alexander Fleming", - "rule_id": "bvr_yukthi_samanwithavagmi_yoga", - "rule_name": "Yukthi Samanwithavagmi Yoga" - }, - { - "chart": "Max Planck", - "rule_id": "sankha_yoga", - "rule_name": "Sankha Yoga" - }, - { - "chart": "Richard Feynman", - "rule_id": "bvr_nishkapata_precise", - "rule_name": "Nishkapata Yoga" - }, - { - "chart": "Richard Feynman", - "rule_id": "bvr_018_ubhayachara_precise", - "rule_name": "Ubhayachara Yoga" - }, - { - "chart": "Mahatma Gandhi", - "rule_id": "bvr_utthama_graha_yoga", - "rule_name": "Utthama Griha Yoga" - }, - { - "chart": "Narendra Modi", - "rule_id": "bvr_swaveeryaddhana_precise", - "rule_name": "Swaveeryaddhana Yoga" - }, - { - "chart": "Indira Gandhi", - "rule_id": "bvr_swaveeryaddhana_precise", - "rule_name": "Swaveeryaddhana Yoga" - }, - { - "chart": "Nelson Mandela", - "rule_id": "bvr_mathibhramana_yoga", - "rule_name": "Mathibhramana Yoga" - }, - { - "chart": "Abraham Lincoln", - "rule_id": "bvr_swaveeryaddhana_precise", - "rule_name": "Swaveeryaddhana Yoga" - }, - { - "chart": "Barack Obama", - "rule_id": "bvr_bandhubhisthyaktha_precise", - "rule_name": "Bandhubhisthyaktha Yoga" - }, - { - "chart": "Barack Obama", - "rule_id": "bvr_yukthi_samanwithavagmi_yoga", - "rule_name": "Yukthi Samanwithavagmi Yoga" - }, { "chart": "Pablo Picasso", "rule_id": "bvr_018_ubhayachara_precise", "rule_name": "Ubhayachara Yoga" }, - { - "chart": "Lata Mangeshkar", - "rule_id": "bvr_dwadasa_sahodara_yoga", - "rule_name": "Dwadasa Sahodara Yoga" - }, - { - "chart": "Steve Jobs", - "rule_id": "bvr_utthama_graha_yoga", - "rule_name": "Utthama Griha Yoga" - }, - { - "chart": "Bill Gates", - "rule_id": "adhi_yoga", - "rule_name": "Adhi Yoga" - }, - { - "chart": "MS Dhoni", - "rule_id": "bvr_mathibhramana_yoga", - "rule_name": "Mathibhramana Yoga" - }, - { - "chart": "MS Dhoni", - "rule_id": "bvr_karma_malika_precise", - "rule_name": "Karma Malika Yoga" - }, - { - "chart": "Lionel Messi", - "rule_id": "bvr_017_vosi_precise", - "rule_name": "Vosi Yoga" - }, - { - "chart": "Lionel Messi", - "rule_id": "brahma_yoga", - "rule_name": "Brahma Yoga" - }, - { - "chart": "Cristiano Ronaldo", - "rule_id": "bvr_dwadasa_sahodara_yoga", - "rule_name": "Dwadasa Sahodara Yoga" - }, { "chart": "Pele", "rule_id": "bvr_dharidhra_11_precise", "rule_name": "Dharidhra Yoga" }, { - "chart": "Swami Vivekananda", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga" - }, - { - "chart": "Amitabh Bachchan", - "rule_id": "bvr_bandhubhisthyaktha_precise", - "rule_name": "Bandhubhisthyaktha Yoga" - }, - { - "chart": "Amitabh Bachchan", - "rule_id": "bvr_karma_malika_precise", - "rule_name": "Karma Malika Yoga" + "chart": "Princess Diana", + "rule_id": "bvr_ayatna_griha_prapta_yoga", + "rule_name": "Ayatna Griha Prapta Yoga" }, { "chart": "Princess Diana", @@ -266,16 +256,6 @@ "rule_id": "bvr_kaahala_yoga", "rule_name": "Kaahala Yoga" }, - { - "chart": "Princess Diana", - "rule_id": "bvr_ayatna_griha_prapta_yoga", - "rule_name": "Ayatna Griha Prapta Yoga" - }, - { - "chart": "J. Krishnamurti", - "rule_id": "bvr_dehasthoulya_yoga", - "rule_name": "Dehasthoulya Yoga" - }, { "chart": "Ramana Maharshi", "rule_id": "bvr_matru_sneha_yoga", @@ -285,9 +265,51 @@ "chart": "Ramana Maharshi", "rule_id": "bvr_utthama_graha_yoga", "rule_name": "Utthama Griha Yoga" + }, + { + "chart": "Richard Feynman", + "rule_id": "bvr_018_ubhayachara_precise", + "rule_name": "Ubhayachara Yoga" + }, + { + "chart": "Richard Feynman", + "rule_id": "bvr_nishkapata_precise", + "rule_name": "Nishkapata Yoga" + }, + { + "chart": "Steve Jobs", + "rule_id": "bvr_utthama_graha_yoga", + "rule_name": "Utthama Griha Yoga" + }, + { + "chart": "Swami Vivekananda", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga" } ], "false_negatives": [ + { + "chart": "Abraham Lincoln", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga", + "pyjhora_names": [ + "dharidhra_yoga_144", + "dharidhra_yoga_147", + "dharidhra_yoga_148", + "dharidhra_yoga_149", + "dharidhra_yoga_150", + "dharidhra_yoga_151", + "dharidhra_yoga_152" + ] + }, + { + "chart": "Abraham Lincoln", + "rule_id": "bvr_thrikaala_gnana_yoga", + "rule_name": "Thrikaala Gnana Yoga", + "pyjhora_names": [ + "thrikaala_gnana_yoga" + ] + }, { "chart": "Albert Einstein", "rule_id": "bvr_bahu_puthra_precise", @@ -298,306 +320,12 @@ ] }, { - "chart": "Marie Curie", - "rule_id": "bvr_grihanasa_yoga", - "rule_name": "Grihanasa Yoga", + "chart": "Azim Premji", + "rule_id": "bvr_ayatna_griha_prapta_yoga", + "rule_name": "Ayatna Griha Prapta Yoga", "pyjhora_names": [ - "grihanasa_yoga_191", - "grihanasa_yoga_192" - ] - }, - { - "chart": "Marie Curie", - "rule_id": "parvata_yoga", - "rule_name": "Parvata Yoga", - "pyjhora_names": [ - "parvata_yoga" - ] - }, - { - "chart": "Marie Curie", - "rule_id": "bvr_thrikaala_gnana_yoga", - "rule_name": "Thrikaala Gnana Yoga", - "pyjhora_names": [ - "thrikaala_gnana_yoga" - ] - }, - { - "chart": "Nikola Tesla", - "rule_id": "bvr_thrikaala_gnana_yoga", - "rule_name": "Thrikaala Gnana Yoga", - "pyjhora_names": [ - "thrikaala_gnana_yoga" - ] - }, - { - "chart": "Thomas Edison", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga", - "pyjhora_names": [ - "dharidhra_yoga_144", - "dharidhra_yoga_147", - "dharidhra_yoga_148", - "dharidhra_yoga_149", - "dharidhra_yoga_150", - "dharidhra_yoga_151", - "dharidhra_yoga_152" - ] - }, - { - "chart": "Max Planck", - "rule_id": "bvr_thrikaala_gnana_yoga", - "rule_name": "Thrikaala Gnana Yoga", - "pyjhora_names": [ - "thrikaala_gnana_yoga" - ] - }, - { - "chart": "Richard Feynman", - "rule_id": "bvr_parannabhojana_yoga", - "rule_name": "Parannabhojana Yoga", - "pyjhora_names": [ - "parannabhojana_yoga" - ] - }, - { - "chart": "Mahatma Gandhi", - "rule_id": "bvr_matrudeerghayur_yoga", - "rule_name": "Matrudeerghayur Yoga", - "pyjhora_names": [ - "matrudeerghayur_yoga_196", - "matrudeerghayur_yoga_197" - ] - }, - { - "chart": "Mahatma Gandhi", - "rule_id": "bvr_pisacha_grastha_yoga", - "rule_name": "Pisacha Grastha Yoga", - "pyjhora_names": [ - "pisacha_grastha_yoga" - ] - }, - { - "chart": "Mahatma Gandhi", - "rule_id": "brahma_yoga", - "rule_name": "Brahma Yoga", - "pyjhora_names": [ - "brahma_yoga" - ] - }, - { - "chart": "Narendra Modi", - "rule_id": "bvr_nishkapata_precise", - "rule_name": "Nishkapata Yoga", - "pyjhora_names": [ - "nishkapata_yoga_205", - "nishkapata_yoga_206" - ] - }, - { - "chart": "Indira Gandhi", - "rule_id": "bvr_sumukha_precise", - "rule_name": "Sumukha Yoga", - "pyjhora_names": [ - "sumukha_yoga" - ] - }, - { - "chart": "Indira Gandhi", - "rule_id": "bvr_utthama_graha_yoga", - "rule_name": "Utthama Griha Yoga", - "pyjhora_names": [ - "utthama_graha_yoga" - ] - }, - { - "chart": "Nelson Mandela", - "rule_id": "bvr_apakeerthi_yoga", - "rule_name": "Apakeerthi Yoga", - "pyjhora_names": [ - "apakeerthi_yoga" - ] - }, - { - "chart": "Nelson Mandela", - "rule_id": "bvr_parannabhojana_yoga", - "rule_name": "Parannabhojana Yoga", - "pyjhora_names": [ - "parannabhojana_yoga" - ] - }, - { - "chart": "Abraham Lincoln", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga", - "pyjhora_names": [ - "dharidhra_yoga_144", - "dharidhra_yoga_147", - "dharidhra_yoga_148", - "dharidhra_yoga_149", - "dharidhra_yoga_150", - "dharidhra_yoga_151", - "dharidhra_yoga_152" - ] - }, - { - "chart": "Abraham Lincoln", - "rule_id": "bvr_thrikaala_gnana_yoga", - "rule_name": "Thrikaala Gnana Yoga", - "pyjhora_names": [ - "thrikaala_gnana_yoga" - ] - }, - { - "chart": "Margaret Thatcher", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga", - "pyjhora_names": [ - "dharidhra_yoga_144", - "dharidhra_yoga_147", - "dharidhra_yoga_148", - "dharidhra_yoga_149", - "dharidhra_yoga_150", - "dharidhra_yoga_151", - "dharidhra_yoga_152" - ] - }, - { - "chart": "Margaret Thatcher", - "rule_id": "bvr_matru_satrutwa_yoga", - "rule_name": "Matru Satrutwa Yoga", - "pyjhora_names": [ - "matru_satrutwa_yoga" - ] - }, - { - "chart": "Rabindranath Tagore", - "rule_id": "bvr_kapata_yoga", - "rule_name": "Kapata Yoga", - "pyjhora_names": [ - "kapata_yoga_202", - "kapata_yoga_203", - "kapata_yoga_204" - ] - }, - { - "chart": "Rabindranath Tagore", - "rule_id": "brahma_yoga", - "rule_name": "Brahma Yoga", - "pyjhora_names": [ - "brahma_yoga" - ] - }, - { - "chart": "Ludwig van Beethoven", - "rule_id": "bvr_thrikaala_gnana_yoga", - "rule_name": "Thrikaala Gnana Yoga", - "pyjhora_names": [ - "thrikaala_gnana_yoga" - ] - }, - { - "chart": "Wolfgang Mozart", - "rule_id": "bvr_raja_bhanga_yoga", - "rule_name": "Raja Bhanga Yoga", - "pyjhora_names": [ - "raja_bhanga_yoga_298", - "raja_bhanga_yoga_299" - ] - }, - { - "chart": "Wolfgang Mozart", - "rule_id": "bvr_kapata_yoga", - "rule_name": "Kapata Yoga", - "pyjhora_names": [ - "kapata_yoga_202", - "kapata_yoga_203", - "kapata_yoga_204" - ] - }, - { - "chart": "Wolfgang Mozart", - "rule_id": "bvr_pittharoga_yoga", - "rule_name": "Pittharoga Yoga", - "pyjhora_names": [ - "pittharoga_yoga" - ] - }, - { - "chart": "Wolfgang Mozart", - "rule_id": "bvr_theevrabuddhi_yoga", - "rule_name": "Theevrabuddhi Yoga", - "pyjhora_names": [ - "theevrabuddhi_yoga" - ] - }, - { - "chart": "Pablo Picasso", - "rule_id": "bvr_raja_bhanga_yoga", - "rule_name": "Raja Bhanga Yoga", - "pyjhora_names": [ - "raja_bhanga_yoga_298", - "raja_bhanga_yoga_299" - ] - }, - { - "chart": "Vincent van Gogh", - "rule_id": "bvr_yuddha_marana_yoga", - "rule_name": "Yuddha Marana Yoga", - "pyjhora_names": [ - "yuddha_marana_yoga" - ] - }, - { - "chart": "Charlie Chaplin", - "rule_id": "bvr_matrunasa_precise", - "rule_name": "Matrunasa Yoga", - "pyjhora_names": [ - "matrunasa_yoga_198", - "matrunasa_yoga_199" - ] - }, - { - "chart": "Michael Jackson", - "rule_id": "bvr_kaalanirdesat_puthranaasa_yoga", - "rule_name": "Kaalanirdesat Puthranaasa Yoga", - "pyjhora_names": [ - "kaalanirdesat_puthranaasa_yoga_230", - "kaalanirdesat_puthranaasa_yoga_229" - ] - }, - { - "chart": "Bill Gates", - "rule_id": "bvr_bhratruvriddhi_precise", - "rule_name": "Bhratruvriddhi Yoga", - "pyjhora_names": [ - "bhratruvriddhi_yoga" - ] - }, - { - "chart": "Warren Buffett", - "rule_id": "bvr_utthama_graha_yoga", - "rule_name": "Utthama Griha Yoga", - "pyjhora_names": [ - "utthama_graha_yoga" - ] - }, - { - "chart": "Mukesh Ambani", - "rule_id": "bvr_kapata_yoga", - "rule_name": "Kapata Yoga", - "pyjhora_names": [ - "kapata_yoga_202", - "kapata_yoga_203", - "kapata_yoga_204" - ] - }, - { - "chart": "Narayana Murthy", - "rule_id": "adhi_yoga", - "rule_name": "Adhi Yoga", - "pyjhora_names": [ - "adhi_yoga" + "ayatna_griha_prapta_yoga_189", + "ayatna_griha_prapta_yoga_190" ] }, { @@ -632,20 +360,216 @@ ] }, { - "chart": "Azim Premji", - "rule_id": "bvr_ayatna_griha_prapta_yoga", - "rule_name": "Ayatna Griha Prapta Yoga", + "chart": "Bill Gates", + "rule_id": "bvr_bhratruvriddhi_precise", + "rule_name": "Bhratruvriddhi Yoga", "pyjhora_names": [ - "ayatna_griha_prapta_yoga_189", - "ayatna_griha_prapta_yoga_190" + "bhratruvriddhi_yoga" ] }, { - "chart": "Muhammad Ali", - "rule_id": "pushkala_yoga", - "rule_name": "Pushkala Yoga", + "chart": "Charlie Chaplin", + "rule_id": "bvr_matrunasa_precise", + "rule_name": "Matrunasa Yoga", "pyjhora_names": [ - "pushkala_yoga" + "matrunasa_yoga_198", + "matrunasa_yoga_199" + ] + }, + { + "chart": "Cristiano Ronaldo", + "rule_id": "bvr_matru_sneha_yoga", + "rule_name": "Matru Sneha Yoga", + "pyjhora_names": [ + "matru_sneha_yoga" + ] + }, + { + "chart": "Dalai Lama", + "rule_id": "brahma_yoga", + "rule_name": "Brahma Yoga", + "pyjhora_names": [ + "brahma_yoga" + ] + }, + { + "chart": "Dalai Lama", + "rule_id": "bvr_utthama_graha_yoga", + "rule_name": "Utthama Griha Yoga", + "pyjhora_names": [ + "utthama_graha_yoga" + ] + }, + { + "chart": "Indira Gandhi", + "rule_id": "bvr_sumukha_precise", + "rule_name": "Sumukha Yoga", + "pyjhora_names": [ + "sumukha_yoga" + ] + }, + { + "chart": "Indira Gandhi", + "rule_id": "bvr_utthama_graha_yoga", + "rule_name": "Utthama Griha Yoga", + "pyjhora_names": [ + "utthama_graha_yoga" + ] + }, + { + "chart": "J. Krishnamurti", + "rule_id": "bvr_grihanasa_yoga", + "rule_name": "Grihanasa Yoga", + "pyjhora_names": [ + "grihanasa_yoga_191", + "grihanasa_yoga_192" + ] + }, + { + "chart": "J. Krishnamurti", + "rule_id": "bvr_vanchana_chora_bheethi_yoga", + "rule_name": "Vanchana Chora Bheethi Yoga", + "pyjhora_names": [ + "vanchana_chora_bheethi_yoga" + ] + }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga", + "pyjhora_names": [ + "dharidhra_yoga_144", + "dharidhra_yoga_147", + "dharidhra_yoga_148", + "dharidhra_yoga_149", + "dharidhra_yoga_150", + "dharidhra_yoga_151", + "dharidhra_yoga_152" + ] + }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_kapata_yoga", + "rule_name": "Kapata Yoga", + "pyjhora_names": [ + "kapata_yoga_202", + "kapata_yoga_203", + "kapata_yoga_204" + ] + }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_matrudeerghayur_yoga", + "rule_name": "Matrudeerghayur Yoga", + "pyjhora_names": [ + "matrudeerghayur_yoga_196", + "matrudeerghayur_yoga_197" + ] + }, + { + "chart": "Lionel Messi", + "rule_id": "bvr_matrunasa_precise", + "rule_name": "Matrunasa Yoga", + "pyjhora_names": [ + "matrunasa_yoga_198", + "matrunasa_yoga_199" + ] + }, + { + "chart": "Ludwig van Beethoven", + "rule_id": "bvr_thrikaala_gnana_yoga", + "rule_name": "Thrikaala Gnana Yoga", + "pyjhora_names": [ + "thrikaala_gnana_yoga" + ] + }, + { + "chart": "Mahatma Gandhi", + "rule_id": "brahma_yoga", + "rule_name": "Brahma Yoga", + "pyjhora_names": [ + "brahma_yoga" + ] + }, + { + "chart": "Mahatma Gandhi", + "rule_id": "bvr_matrudeerghayur_yoga", + "rule_name": "Matrudeerghayur Yoga", + "pyjhora_names": [ + "matrudeerghayur_yoga_196", + "matrudeerghayur_yoga_197" + ] + }, + { + "chart": "Mahatma Gandhi", + "rule_id": "bvr_pisacha_grastha_yoga", + "rule_name": "Pisacha Grastha Yoga", + "pyjhora_names": [ + "pisacha_grastha_yoga" + ] + }, + { + "chart": "Margaret Thatcher", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga", + "pyjhora_names": [ + "dharidhra_yoga_144", + "dharidhra_yoga_147", + "dharidhra_yoga_148", + "dharidhra_yoga_149", + "dharidhra_yoga_150", + "dharidhra_yoga_151", + "dharidhra_yoga_152" + ] + }, + { + "chart": "Margaret Thatcher", + "rule_id": "bvr_matru_satrutwa_yoga", + "rule_name": "Matru Satrutwa Yoga", + "pyjhora_names": [ + "matru_satrutwa_yoga" + ] + }, + { + "chart": "Marie Curie", + "rule_id": "bvr_grihanasa_yoga", + "rule_name": "Grihanasa Yoga", + "pyjhora_names": [ + "grihanasa_yoga_191", + "grihanasa_yoga_192" + ] + }, + { + "chart": "Marie Curie", + "rule_id": "bvr_thrikaala_gnana_yoga", + "rule_name": "Thrikaala Gnana Yoga", + "pyjhora_names": [ + "thrikaala_gnana_yoga" + ] + }, + { + "chart": "Marie Curie", + "rule_id": "parvata_yoga", + "rule_name": "Parvata Yoga", + "pyjhora_names": [ + "parvata_yoga" + ] + }, + { + "chart": "Max Planck", + "rule_id": "bvr_thrikaala_gnana_yoga", + "rule_name": "Thrikaala Gnana Yoga", + "pyjhora_names": [ + "thrikaala_gnana_yoga" + ] + }, + { + "chart": "Michael Jackson", + "rule_id": "bvr_kaalanirdesat_puthranaasa_yoga", + "rule_name": "Kaalanirdesat Puthranaasa Yoga", + "pyjhora_names": [ + "kaalanirdesat_puthranaasa_yoga_229", + "kaalanirdesat_puthranaasa_yoga_230" ] }, { @@ -666,16 +590,15 @@ ] }, { - "chart": "Lionel Messi", - "rule_id": "bvr_matrunasa_precise", - "rule_name": "Matrunasa Yoga", + "chart": "Muhammad Ali", + "rule_id": "pushkala_yoga", + "rule_name": "Pushkala Yoga", "pyjhora_names": [ - "matrunasa_yoga_198", - "matrunasa_yoga_199" + "pushkala_yoga" ] }, { - "chart": "Lionel Messi", + "chart": "Mukesh Ambani", "rule_id": "bvr_kapata_yoga", "rule_name": "Kapata Yoga", "pyjhora_names": [ @@ -685,21 +608,108 @@ ] }, { - "chart": "Lionel Messi", - "rule_id": "bvr_dharidhra_11_precise", - "rule_name": "Dharidhra Yoga", + "chart": "Narayana Murthy", + "rule_id": "adhi_yoga", + "rule_name": "Adhi Yoga", "pyjhora_names": [ - "dharidhra_yoga_144", - "dharidhra_yoga_147", - "dharidhra_yoga_148", - "dharidhra_yoga_149", - "dharidhra_yoga_150", - "dharidhra_yoga_151", - "dharidhra_yoga_152" + "adhi_yoga" ] }, { - "chart": "Lionel Messi", + "chart": "Narendra Modi", + "rule_id": "bvr_nishkapata_precise", + "rule_name": "Nishkapata Yoga", + "pyjhora_names": [ + "nishkapata_yoga_205", + "nishkapata_yoga_206" + ] + }, + { + "chart": "Nelson Mandela", + "rule_id": "bvr_apakeerthi_yoga", + "rule_name": "Apakeerthi Yoga", + "pyjhora_names": [ + "apakeerthi_yoga" + ] + }, + { + "chart": "Nelson Mandela", + "rule_id": "bvr_parannabhojana_yoga", + "rule_name": "Parannabhojana Yoga", + "pyjhora_names": [ + "parannabhojana_yoga" + ] + }, + { + "chart": "Nikola Tesla", + "rule_id": "bvr_thrikaala_gnana_yoga", + "rule_name": "Thrikaala Gnana Yoga", + "pyjhora_names": [ + "thrikaala_gnana_yoga" + ] + }, + { + "chart": "Pablo Picasso", + "rule_id": "bvr_raja_bhanga_yoga", + "rule_name": "Raja Bhanga Yoga", + "pyjhora_names": [ + "raja_bhanga_yoga_298", + "raja_bhanga_yoga_299" + ] + }, + { + "chart": "Paramahansa Yogananda", + "rule_id": "bvr_bandhubhisthyaktha_precise", + "rule_name": "Bandhubhisthyaktha Yoga", + "pyjhora_names": [ + "bandhubhisthyaktha_yoga" + ] + }, + { + "chart": "Paramahansa Yogananda", + "rule_id": "bvr_dattha_puthra_yoga", + "rule_name": "Dattha Puthra Yoga", + "pyjhora_names": [ + "dattha_puthra_yoga_222", + "dattha_puthra_yoga_223" + ] + }, + { + "chart": "Paramahansa Yogananda", + "rule_id": "bvr_matrunasa_precise", + "rule_name": "Matrunasa Yoga", + "pyjhora_names": [ + "matrunasa_yoga_198", + "matrunasa_yoga_199" + ] + }, + { + "chart": "Pele", + "rule_id": "bvr_parannabhojana_yoga", + "rule_name": "Parannabhojana Yoga", + "pyjhora_names": [ + "parannabhojana_yoga" + ] + }, + { + "chart": "Pope Francis", + "rule_id": "bvr_bhratruvriddhi_precise", + "rule_name": "Bhratruvriddhi Yoga", + "pyjhora_names": [ + "bhratruvriddhi_yoga" + ] + }, + { + "chart": "Pope Francis", + "rule_id": "bvr_raja_bhanga_yoga", + "rule_name": "Raja Bhanga Yoga", + "pyjhora_names": [ + "raja_bhanga_yoga_298", + "raja_bhanga_yoga_299" + ] + }, + { + "chart": "Princess Diana", "rule_id": "bvr_matrudeerghayur_yoga", "rule_name": "Matrudeerghayur Yoga", "pyjhora_names": [ @@ -708,15 +718,49 @@ ] }, { - "chart": "Cristiano Ronaldo", - "rule_id": "bvr_matru_sneha_yoga", - "rule_name": "Matru Sneha Yoga", + "chart": "Princess Diana", + "rule_id": "kahala_yoga", + "rule_name": "Kahala Yoga", "pyjhora_names": [ - "matru_sneha_yoga" + "kahala_yoga" ] }, { - "chart": "Pele", + "chart": "Queen Elizabeth II", + "rule_id": "bvr_dwadasa_sahodara_yoga", + "rule_name": "Dwadasa Sahodara Yoga", + "pyjhora_names": [ + "dwadasa_sahodara_yoga" + ] + }, + { + "chart": "Rabindranath Tagore", + "rule_id": "brahma_yoga", + "rule_name": "Brahma Yoga", + "pyjhora_names": [ + "brahma_yoga" + ] + }, + { + "chart": "Rabindranath Tagore", + "rule_id": "bvr_kapata_yoga", + "rule_name": "Kapata Yoga", + "pyjhora_names": [ + "kapata_yoga_202", + "kapata_yoga_203", + "kapata_yoga_204" + ] + }, + { + "chart": "Ramana Maharshi", + "rule_id": "bvr_pittharoga_yoga", + "rule_name": "Pittharoga Yoga", + "pyjhora_names": [ + "pittharoga_yoga" + ] + }, + { + "chart": "Richard Feynman", "rule_id": "bvr_parannabhojana_yoga", "rule_name": "Parannabhojana Yoga", "pyjhora_names": [ @@ -740,32 +784,29 @@ ] }, { - "chart": "Queen Elizabeth II", - "rule_id": "bvr_dwadasa_sahodara_yoga", - "rule_name": "Dwadasa Sahodara Yoga", + "chart": "Thomas Edison", + "rule_id": "bvr_dharidhra_11_precise", + "rule_name": "Dharidhra Yoga", "pyjhora_names": [ - "dwadasa_sahodara_yoga" + "dharidhra_yoga_144", + "dharidhra_yoga_147", + "dharidhra_yoga_148", + "dharidhra_yoga_149", + "dharidhra_yoga_150", + "dharidhra_yoga_151", + "dharidhra_yoga_152" ] }, { - "chart": "Princess Diana", - "rule_id": "kahala_yoga", - "rule_name": "Kahala Yoga", + "chart": "Vincent van Gogh", + "rule_id": "bvr_yuddha_marana_yoga", + "rule_name": "Yuddha Marana Yoga", "pyjhora_names": [ - "kahala_yoga" + "yuddha_marana_yoga" ] }, { - "chart": "Princess Diana", - "rule_id": "bvr_matrudeerghayur_yoga", - "rule_name": "Matrudeerghayur Yoga", - "pyjhora_names": [ - "matrudeerghayur_yoga_196", - "matrudeerghayur_yoga_197" - ] - }, - { - "chart": "Dalai Lama", + "chart": "Warren Buffett", "rule_id": "bvr_utthama_graha_yoga", "rule_name": "Utthama Griha Yoga", "pyjhora_names": [ @@ -773,23 +814,25 @@ ] }, { - "chart": "Dalai Lama", - "rule_id": "brahma_yoga", - "rule_name": "Brahma Yoga", + "chart": "Wolfgang Mozart", + "rule_id": "bvr_kapata_yoga", + "rule_name": "Kapata Yoga", "pyjhora_names": [ - "brahma_yoga" + "kapata_yoga_202", + "kapata_yoga_203", + "kapata_yoga_204" ] }, { - "chart": "Pope Francis", - "rule_id": "bvr_bhratruvriddhi_precise", - "rule_name": "Bhratruvriddhi Yoga", + "chart": "Wolfgang Mozart", + "rule_id": "bvr_pittharoga_yoga", + "rule_name": "Pittharoga Yoga", "pyjhora_names": [ - "bhratruvriddhi_yoga" + "pittharoga_yoga" ] }, { - "chart": "Pope Francis", + "chart": "Wolfgang Mozart", "rule_id": "bvr_raja_bhanga_yoga", "rule_name": "Raja Bhanga Yoga", "pyjhora_names": [ @@ -798,54 +841,11 @@ ] }, { - "chart": "J. Krishnamurti", - "rule_id": "bvr_grihanasa_yoga", - "rule_name": "Grihanasa Yoga", + "chart": "Wolfgang Mozart", + "rule_id": "bvr_theevrabuddhi_yoga", + "rule_name": "Theevrabuddhi Yoga", "pyjhora_names": [ - "grihanasa_yoga_191", - "grihanasa_yoga_192" - ] - }, - { - "chart": "J. Krishnamurti", - "rule_id": "bvr_vanchana_chora_bheethi_yoga", - "rule_name": "Vanchana Chora Bheethi Yoga", - "pyjhora_names": [ - "vanchana_chora_bheethi_yoga" - ] - }, - { - "chart": "Ramana Maharshi", - "rule_id": "bvr_pittharoga_yoga", - "rule_name": "Pittharoga Yoga", - "pyjhora_names": [ - "pittharoga_yoga" - ] - }, - { - "chart": "Paramahansa Yogananda", - "rule_id": "bvr_bandhubhisthyaktha_precise", - "rule_name": "Bandhubhisthyaktha Yoga", - "pyjhora_names": [ - "bandhubhisthyaktha_yoga" - ] - }, - { - "chart": "Paramahansa Yogananda", - "rule_id": "bvr_matrunasa_precise", - "rule_name": "Matrunasa Yoga", - "pyjhora_names": [ - "matrunasa_yoga_198", - "matrunasa_yoga_199" - ] - }, - { - "chart": "Paramahansa Yogananda", - "rule_id": "bvr_dattha_puthra_yoga", - "rule_name": "Dattha Puthra Yoga", - "pyjhora_names": [ - "dattha_puthra_yoga_222", - "dattha_puthra_yoga_223" + "theevrabuddhi_yoga" ] } ] diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index c96ac870..3f6fbce0 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -171,6 +171,9 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): self.end_headers() self.wfile.write(json.dumps(data, ensure_ascii=False, default=str).encode()) + def _error_json(self, message, status=500, error_code='ERR_INTERNAL'): + self._json({'success': False, 'error': message, 'error_code': error_code}, status) + def _send_cors_headers(self): origin = self.headers.get('Origin') allowed = getattr(self.server, 'allowed_origins', DEFAULT_ALLOWED_ORIGINS) @@ -182,22 +185,27 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): def do_GET(self): path = urlparse(self.path).path - if path == '/api/health': - self._json({ - 'status': 'ok', - 'version': '6.9.14', - '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': - self._json(list(CITY_DB.keys())) - elif path == '/api/capability_audit': - self._json(self._capability_audit()) - elif path == '/api/technique_catalog': - self._json(self._technique_catalog()) - elif path == '/api/real_case_revalidation': - self._json(self._real_case_revalidation()) - else: - self._json({'error': 'Not found'}, 404) + try: + if path == '/api/health': + self._json({ + 'status': 'ok', + 'version': '6.9.14', + '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': + self._json(list(CITY_DB.keys())) + elif path == '/api/capability_audit': + self._json(self._capability_audit()) + elif path == '/api/technique_catalog': + self._json(self._technique_catalog()) + elif path == '/api/real_case_revalidation': + self._json(self._real_case_revalidation()) + else: + self._error_json('Not found', 404, 'ERR_NOT_FOUND') + except Exception: + import logging + logging.exception("[api_server] GET request failed for %s", path) + self._error_json('Internal server error', 500, 'ERR_INTERNAL') def do_POST(self): path = urlparse(self.path).path @@ -306,13 +314,13 @@ class JyotishAPIHandler(BaseHTTPRequestHandler): result = self._compute_technique_example(body) self._json(result) else: - self._json({'error': f'Unknown endpoint: {path}'}, 404) + self._error_json(f'Unknown endpoint: {path}', 404, 'ERR_NOT_FOUND') except BadRequest as e: - self._json({'error': str(e)}, 400) + self._error_json(str(e), 400, 'ERR_BAD_REQUEST') except Exception: import logging logging.exception("[api_server] request failed for %s", path) - self._json({'error': 'Internal server error'}, 500) + self._error_json('Internal server error', 500, 'ERR_INTERNAL') def _read_json_body(self): raw_length = self.headers.get('Content-Length', '0') diff --git a/scripts/oracle_evidence_validator.py b/scripts/oracle_evidence_validator.py index 78d2abd8..6aa37f09 100644 --- a/scripts/oracle_evidence_validator.py +++ b/scripts/oracle_evidence_validator.py @@ -26,6 +26,8 @@ LOCAL_ENGINE_MARKERS = [ ] SHADBALA_REQUIRED_PLANETS = ["Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"] SHADBALA_REQUIRED_COMPONENTS = ["sthana", "dig", "kala", "chesta", "naisargika", "drik"] +SHADBALA_COMPONENT_MAX_RUPA = 20.0 +SHADBALA_TOTAL_TOLERANCE_RUPA = 0.05 ASHTAKOOT_SCORE_RANGES = { "target.total_score": (0.0, 36.0), "target.varna": (0.0, 1.0), @@ -144,6 +146,31 @@ def _validate_shadbala_components(value: Any) -> list[str]: continue if component_value < 0: problems.append(f"invalid_shadbala_component_negative:{planet}.{component}") + continue + if component_value > SHADBALA_COMPONENT_MAX_RUPA: + problems.append(f"invalid_shadbala_component_range:{planet}.{component}") + total_rupa = row.get("total_rupa") + if _is_blank(total_rupa): + problems.append(f"missing_shadbala_total_rupa:{planet}") + continue + if not isinstance(total_rupa, (int, float)) or isinstance(total_rupa, bool): + problems.append(f"invalid_shadbala_total_rupa_type:{planet}") + continue + if total_rupa < 0: + problems.append(f"invalid_shadbala_total_rupa_negative:{planet}") + continue + if total_rupa > SHADBALA_COMPONENT_MAX_RUPA * len(SHADBALA_REQUIRED_COMPONENTS): + problems.append(f"invalid_shadbala_total_rupa_range:{planet}") + continue + numeric_components = [ + float(row[component]) + for component in SHADBALA_REQUIRED_COMPONENTS + if isinstance(row.get(component), (int, float)) and not isinstance(row.get(component), bool) + ] + if len(numeric_components) == len(SHADBALA_REQUIRED_COMPONENTS): + component_sum = sum(numeric_components) + if abs(component_sum - float(total_rupa)) > SHADBALA_TOTAL_TOLERANCE_RUPA: + problems.append(f"shadbala_total_rupa_sum_mismatch:{planet}") return problems diff --git a/scripts/run_quality_gate.py b/scripts/run_quality_gate.py index b63e69d4..d39053de 100644 --- a/scripts/run_quality_gate.py +++ b/scripts/run_quality_gate.py @@ -97,6 +97,7 @@ QUALITY_GATE_PROFILES = { "skip_real_cases": True, "skip_dasha_audit": True, "skip_oracle_audit": True, + "skip_local_accuracy_report": True, }, "browser": { "skip_slow": True, @@ -108,6 +109,7 @@ QUALITY_GATE_PROFILES = { "skip_real_cases": True, "skip_dasha_audit": True, "skip_oracle_audit": True, + "skip_local_accuracy_report": True, }, "release": { "skip_slow": False, @@ -119,6 +121,19 @@ QUALITY_GATE_PROFILES = { "skip_real_cases": False, "skip_dasha_audit": False, "skip_oracle_audit": False, + "skip_local_accuracy_report": False, + }, + "accuracy": { + "skip_slow": True, + "skip_yoga_logic": False, + "skip_frontend_runtime": True, + "skip_frontend_click": True, + "frontend_click_mode": "core", + "check_release_hygiene": False, + "skip_real_cases": False, + "skip_dasha_audit": False, + "skip_oracle_audit": False, + "skip_local_accuracy_report": False, }, } @@ -362,7 +377,16 @@ def validate_json_files() -> None: def run_profile(args: argparse.Namespace) -> dict: profile = dict(QUALITY_GATE_PROFILES[args.profile]) - for key in ["skip_slow", "skip_yoga_logic", "skip_frontend_runtime", "skip_frontend_click", "skip_real_cases", "skip_dasha_audit", "skip_oracle_audit"]: + for key in [ + "skip_slow", + "skip_yoga_logic", + "skip_frontend_runtime", + "skip_frontend_click", + "skip_real_cases", + "skip_dasha_audit", + "skip_oracle_audit", + "skip_local_accuracy_report", + ]: if getattr(args, key): profile[key] = True if args.frontend_click_mode: @@ -372,7 +396,7 @@ def run_profile(args: argparse.Namespace) -> dict: def main() -> int: parser = argparse.ArgumentParser(description="Run Jyotish skill quality gate") - parser.add_argument("--profile", choices=["quick", "browser", "release"], default="browser", help="Quality gate profile: quick, browser, or release") + parser.add_argument("--profile", choices=["quick", "browser", "release", "accuracy"], default="browser", help="Quality gate profile: quick, browser, release, or accuracy") parser.add_argument("--skip-slow", action="store_true", help="Skip slow golden-case regressions") parser.add_argument("--skip-yoga-logic", action="store_true", help="Skip Yoga logic comparison report refresh") parser.add_argument("--skip-frontend-runtime", action="store_true", help="Skip frontend build and runtime smoke") @@ -380,6 +404,7 @@ def main() -> int: parser.add_argument("--skip-real-cases", action="store_true", help="Skip public real-person chart revalidation") parser.add_argument("--skip-dasha-audit", action="store_true", help="Skip Dasha reference-drift audit") parser.add_argument("--skip-oracle-audit", action="store_true", help="Skip combined Dasha/Shadbala external oracle boundary audit") + parser.add_argument("--skip-local-accuracy-report", action="store_true", help="Skip consolidated local accuracy report") parser.add_argument("--frontend-click-mode", choices=["core", "mobile", "offline", "pdf", "workspace", "mobile-trust", "import-files", "all"], default=None, help="Browser click smoke mode for browser/release profiles") parser.add_argument("--frontend-click-timeout", type=int, default=240, help="Timeout seconds for browser click smoke") parser.add_argument("--all-tests", action="store_true", help="Run every pytest file, including optional-dependency suites") @@ -422,6 +447,8 @@ def main() -> int: run_oracle_collection_queue_and_validator() if not profile["skip_yoga_logic"]: run([PYTHON, "scripts/validate_logic_v2.py"], optional=True) + if not profile["skip_local_accuracy_report"]: + run([PYTHON, "scripts/local_accuracy_report.py", "--format", "json"]) print("\nQuality gate passed.") return 0 diff --git a/scripts/validate_logic_v2.py b/scripts/validate_logic_v2.py index 9c1264ee..bf18e6d8 100644 --- a/scripts/validate_logic_v2.py +++ b/scripts/validate_logic_v2.py @@ -340,15 +340,15 @@ def main(): total_false_negatives += len(false_negatives) if false_positives: - for rid in false_positives: + for rid in sorted(false_positives): fp_details.append({ 'chart': name, 'rule_id': rid, 'rule_name': rule_id_to_name.get(rid, '?'), }) if false_negatives: - for rid in false_negatives: - orig_names = [pn for pn, sid in variant_to_rule_id.items() if sid == rid] + for rid in sorted(false_negatives): + orig_names = sorted(pn for pn, sid in variant_to_rule_id.items() if sid == rid) fn_details.append({ 'chart': name, 'rule_id': rid, @@ -414,8 +414,8 @@ def main(): "recall": round(recall, 4), "f1": round(f1, 4), }, - "false_positives": fp_details, - "false_negatives": fn_details, + "false_positives": sorted(fp_details, key=lambda row: (row["chart"], row["rule_id"])), + "false_negatives": sorted(fn_details, key=lambda row: (row["chart"], row["rule_id"])), } with open(report_path, 'w') as f: json.dump(report, f, indent=2, ensure_ascii=False) diff --git a/tests/test_api_server_security.py b/tests/test_api_server_security.py index 964b4def..9a687b0a 100644 --- a/tests/test_api_server_security.py +++ b/tests/test_api_server_security.py @@ -4,8 +4,10 @@ from __future__ import annotations import base64 +import json import os import sys +from io import BytesIO import pytest @@ -27,6 +29,40 @@ def _handler() -> JyotishAPIHandler: return JyotishAPIHandler.__new__(JyotishAPIHandler) +class _FakeHeaders(dict): + def get(self, key, default=None): + return super().get(key, default) + + +class _FakeServer: + allowed_origins = DEFAULT_ALLOWED_ORIGINS + + +class _ResponseCaptureHandler(JyotishAPIHandler): + def __init__(self) -> None: + self.headers = _FakeHeaders() + self.server = _FakeServer() + self.path = '/api/capability_audit' + 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 _capability_audit(self): + raise RuntimeError('simulated internal failure') + + 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 @@ -40,6 +76,19 @@ def test_env_cors_parser_ignores_empty_entries() -> None: } +def test_get_internal_errors_are_json_wrapped() -> None: + handler = _ResponseCaptureHandler() + + handler.do_GET() + + assert handler.status_code == 500 + assert ('Content-Type', 'application/json; charset=utf-8') in handler.response_headers + payload = handler.payload() + assert payload['success'] is False + assert payload['error'] == 'Internal server error' + assert payload['error_code'] == 'ERR_INTERNAL' + + @pytest.mark.parametrize( ('key', 'value', 'minimum', 'maximum'), [ diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index 304a4aa5..afa4a678 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -891,11 +891,14 @@ def test_quality_gate_declares_fast_browser_release_profiles() -> None: readme = (ROOT / "README.md").read_text(encoding="utf-8") for token in [ "--profile", - "choices=[\"quick\", \"browser\", \"release\"]", + "choices=[\"quick\", \"browser\", \"release\", \"accuracy\"]", "QUALITY_GATE_PROFILES", "quick", "browser", "release", + "accuracy", + "skip_local_accuracy_report", + "scripts/local_accuracy_report.py", "skip_slow", "skip_yoga_logic", "skip_frontend_click", @@ -913,9 +916,11 @@ def test_quality_gate_declares_fast_browser_release_profiles() -> None: "quick:快速开发守门", "browser:完整浏览器守门", "release:发布前守门", + "accuracy:本地准确率守门", "python3 scripts/run_quality_gate.py --profile quick", "python3 scripts/run_quality_gate.py --profile browser", "python3 scripts/run_quality_gate.py --profile release", + "python3 scripts/run_quality_gate.py --profile accuracy", ]: assert token in readme @@ -938,6 +943,19 @@ def test_release_quality_gate_tracks_untracked_product_files() -> None: assert path in quality_gate.RELEASE_CRITICAL_UNTRACKED_PATHS +def test_accuracy_quality_gate_runs_local_accuracy_report_without_frontend_click() -> None: + quality_gate = load_quality_gate_module() + profile = quality_gate.QUALITY_GATE_PROFILES["accuracy"] + + assert profile["skip_frontend_click"] is True + assert profile["skip_frontend_runtime"] is True + assert profile["skip_real_cases"] is False + assert profile["skip_dasha_audit"] is False + assert profile["skip_oracle_audit"] is False + assert profile["skip_yoga_logic"] is False + assert profile["skip_local_accuracy_report"] is False + + def test_github_release_quality_gate_runs_browser_release_profile() -> None: workflow = (ROOT / ".github" / "workflows" / "release-quality-gate.yml").read_text(encoding="utf-8") for token in [ diff --git a/tests/test_oracle_evidence_validator.py b/tests/test_oracle_evidence_validator.py index 5ce9a3c1..6f3897fe 100644 --- a/tests/test_oracle_evidence_validator.py +++ b/tests/test_oracle_evidence_validator.py @@ -15,13 +15,15 @@ SHADBALA_COMPONENTS = ["sthana", "dig", "kala", "chesta", "naisargika", "drik"] def complete_shadbala_components() -> dict[str, dict[str, float]]: - return { - planet: { - component: float(40 + planet_index * 3 + component_index) + result = {} + for planet_index, planet in enumerate(SHADBALA_PLANETS): + row = { + component: round(0.1 + planet_index * 0.01 + component_index * 0.02, 4) for component_index, component in enumerate(SHADBALA_COMPONENTS) } - for planet_index, planet in enumerate(SHADBALA_PLANETS) - } + row["total_rupa"] = round(sum(row[component] for component in SHADBALA_COMPONENTS), 4) + result[planet] = row + return result def build_queue() -> dict: @@ -209,6 +211,106 @@ def test_validator_rejects_non_numeric_or_negative_shadbala_components(tmp_path: assert "invalid_shadbala_component_negative:Moon.dig" in first["problems"] +def test_validator_rejects_missing_shadbala_total_rupa(tmp_path: Path) -> None: + queue = build_queue() + packet = queue["tasks"][0]["evidence_packet"] + shadbala = complete_shadbala_components() + del shadbala["Sun"]["total_rupa"] + packet["metadata"] = { + "tool_name": "JHora", + "tool_version_or_url": "manual-screenshot-v1", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png", + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": "UTC+08:00", + "operator_note": "Manual external screenshot; values typed from JHora screen.", + } + packet["target_placeholders"] = { + "target.moon_sidereal_longitude_deg": 311.7897, + "target.vimshottari_start_date": "1986-05-18", + "target.shadbala_components": shadbala, + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "missing_shadbala_total_rupa:Sun" in first["problems"] + + +def test_validator_rejects_shadbala_component_values_above_20_rupas(tmp_path: Path) -> None: + queue = build_queue() + packet = queue["tasks"][0]["evidence_packet"] + shadbala = complete_shadbala_components() + shadbala["Sun"]["sthana"] = 20.01 + shadbala["Sun"]["total_rupa"] = round(sum(shadbala["Sun"][component] for component in SHADBALA_COMPONENTS), 4) + packet["metadata"] = { + "tool_name": "JHora", + "tool_version_or_url": "manual-screenshot-v1", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png", + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": "UTC+08:00", + "operator_note": "Manual external screenshot; values typed from JHora screen.", + } + packet["target_placeholders"] = { + "target.moon_sidereal_longitude_deg": 311.7897, + "target.vimshottari_start_date": "1986-05-18", + "target.shadbala_components": shadbala, + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "invalid_shadbala_component_range:Sun.sthana" in first["problems"] + + +def test_validator_rejects_shadbala_total_rupa_sum_mismatch(tmp_path: Path) -> None: + queue = build_queue() + packet = queue["tasks"][0]["evidence_packet"] + shadbala = complete_shadbala_components() + shadbala["Sun"]["total_rupa"] = round(shadbala["Sun"]["total_rupa"] + 0.06, 4) + packet["metadata"] = { + "tool_name": "JHora", + "tool_version_or_url": "manual-screenshot-v1", + "capture_date": "2026-06-25", + "source_artifact": "references/oracle/artifacts/manual_jhora_user_REDACTED_YEAR.png", + "ayanamsa": "lahiri", + "node_mode": "mean", + "timezone": "UTC+08:00", + "operator_note": "Manual external screenshot; values typed from JHora screen.", + } + packet["target_placeholders"] = { + "target.moon_sidereal_longitude_deg": 311.7897, + "target.vimshottari_start_date": "1986-05-18", + "target.shadbala_components": shadbala, + } + packet["status"] = "external_verified" + queue_path = tmp_path / "queue.json" + queue_path.write_text(json.dumps(queue, ensure_ascii=False), encoding="utf-8") + + completed = run_validator(queue_path) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + first = report["packets"][0] + assert first["valid"] is False + assert "shadbala_total_rupa_sum_mismatch:Sun" in first["problems"] + + def test_validator_rejects_invalid_ashtakoot_score_ranges(tmp_path: Path) -> None: queue = build_queue_from_file(ROOT / "references/oracle/ashtakoot_oracle_cases.json") packet = queue["tasks"][0]["evidence_packet"]