merge: upstream sync 10 into the data audit branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DbBXoyeZrYn6ryNcSe5Y2Y

# Conflicts:
#	docs/BUG_HISTORY.md
This commit is contained in:
2026-11-04 12:41:54 +08:00
28 changed files with 601 additions and 76 deletions
+10
View File
@@ -1,5 +1,15 @@
# 印度占星 Skill 更新日志
## 2026-11-04 — 上游同步第十轮:研究快照移到上游最新版,三处计算按上游修正
- 上游研究版 Skill 的只读快照更新到上游 main 最新提交 `5fb8379c`;新快照不再带私人样例的说法。
- 解读流程、PDF 读盘说明和两个命令的用法里残留的私人出生样例,换成上游用的公开名人样例。
- 五份参考说明按上游的原文更正:Argala 次级看 5 宫、由 9 宫阻挡;Saham 用表 74 全部 36 项;十六分盘权重 D27 为 0.5、D30 为 1;Yogi 点说明标明当前公式;解读流程加上「开场也要有条件、没查到不等于不成立」的全文证据边界。
- 分盘联动里 D12 一列现在有宫位(以前一直是空的);星座名异常时报错,不再当成白羊(BUG-1541)。
- 指定日期的完整解读里,Narayana 当前段、星宿大运年龄、年运备用年龄都按那一天算;年运备用年龄到公历生日才加一岁(BUG-1542)。
- KP 子主星和宫头用原始度数和含秒的时刻,不再用显示用的四舍五入值;只影响贴着分界的行星(BUG-1543)。
- 网页对话的提示词没有变化。Skill 版本不变;没有数据库变更。
## 2026-11-03 — 输入框:单行起步,字多了变两层(产品定的方案 2)
- 空着或只有一行字时,输入框是一条圆胶囊:电脑上 64px 高,手机上 56px 高,左边「常用问题」,中间输入,右边一颗 40px 的圆形发送键(可点区域 44px)。占位字仍是「想聊什么都可以」。
+1 -1
View File
@@ -261,7 +261,7 @@ adapter available 解释为已完成 VedAstro、PyJHora/JHora 或 jyotishganit r
**凡是用户询问事业、婚恋、财务、事件应期、历史回测或技法可靠性,必须先读取 `references/strict-workflow-router.md`。**
核心要求:
1. 先判断问题类型,再自动选择 `career-timing-strict` / `relationship-timing-strict` / `wealth-timing-strict` / `event-timing-strict` / `event-verification-strict`。
1. 先判断问题类型,再自动选择 `career-timing-strict` / `relationship-timing-strict` / `finance-timing-strict`(兼容 `wealth-timing-strict`)/ `event-timing-strict` / `event-verification-strict`。
2. 用户不需要知道 Chara Dasha、A10、Argala、Shadbala、Ashtakavarga 等技法名称;AI 必须按问题类型自动调用。
3. 必须产出 Technique Audit Table,说明每项高级技法是否调用、结果是什么、缺失会如何降低置信度。网页由界面折叠展示,口语正文不要再贴该表。
4. 不得把未实现或未调用的技法静默省略;A10/Karma Pada、Pushkara、Vargottama、Dasha Sandhi 已进入 full-reading 输出;Bhava Chalit 与 Sudarshana Chakra 已进入 complete,可正常纳入 Technique Audit Table。
+48
View File
@@ -21209,6 +21209,54 @@
- 复发自:BUG-1165 / BUG-1182(示例句被照抄)。BUG-1182 的防复发只禁了示例括号里「宫位 → 人物行为」的读法,没有禁写死的请求句和范围句,也没有给规则总量设上限,所以未拦住。
- 修复版本:分支 `codex/consult-pattern-voice-20261104`。
## BUG-1541 | 完整解读的分盘联动里 D12 一列永远没有宫位;坏星座名被当成白羊
- 状态:resolved(代码 + 回归测试;待部署)
- 首次发现 / 最近更新:2026-11-04(上游同步第十轮核对上游 `cf6c316d`、`f60568a0`)/ 2026-11-04
- 影响面:`scripts/jyotish_engine.py` `calc_inter_chart_linkage`、`calc_all_inter_chart_linkages` 与 `_cmd_full_reading` 的调用;完整解读 `modules.inter_chart_linkage`;咨询数据卡在分盘本身没有宫位时回落读这里(`scripts/consult_card_domain_facts.py`)。
- 现象:D12 一列每颗星的 `house` 都是 `None`;D1 / D9 / D10 某颗星的星座名不在十二星座表里时,按白羊算宫位,不报错。
- 触发条件:任何有 D12 的完整解读;星座名异常时。
- 根因:D12 加进联动表时上升位置写死成 `None`,完整解读已经算好的 `d12_asc_idx` 没有传进去;星座查表失败时默认取下标 0。上游同一缺陷,10-16 修复,我方第九轮只搬了甲批,没有核对这两个提交。
- 修复:两个函数加 `d12_asc_idx` 参数,完整解读把已算好的 D12 上升传进去;星座名不认识、上升下标不是 0–11 的整数时抛 `ValueError`,由完整解读的 `try` 记进 `errors`(咨询原生层由 `_guard` 记成 `blocked`),不再静默当白羊。
- 验证:新测试 `tests/test_upstream_sync10_d12_linkage.py`(移植上游 `tests/test_inter_chart_d12_house.py` 并补坏上升下标):十二个 D12 上升各算一次宫位;缺 D12 上升仍为 `None`;坏星座、坏上升报错;完整解读调用的最后一个参数是 `d12_asc_idx`。改前 18 条失败、改后 19 条通过;`test_consultation_native_layers.py`、`test_flying_star_audit.py`、`test_full_reading_module_parity.py` 20 条通过。
- 影响输出:完整解读联动表的 D12 宫位从空变成有值;咨询卡 D12 宫位只在分盘本身没有宫位、且出生时间精度够时才会读到(精度不够仍是 withheld)。计算口径没有改,不涉及冻结身份文件。
- 防复发:上述测试。
- 关联记录:BUG-1483~BUG-1490(第九轮甲批)、issue #141。
- 复发自:无
- 修复版本:分支 `codex/upstream-skill-sync-20261104`,待合并。
## BUG-1542 | 指定日期的完整解读里,Narayana 当前段、星宿大运年龄和年运备用年龄仍按今天算
- 状态:resolved(代码 + 回归测试;待部署)
- 首次发现 / 最近更新:2026-11-04(上游同步第十轮核对上游 `933f254e`)/ 2026-11-04
- 影响面:`scripts/jyotish_engine.py` `cmd_full_reading` 的 Step 4.13 Narayana(`today_dt`)、星宿大运年龄(`nak_age`)、Step 9 Tajika 备用年龄(`age`)。
- 现象:传了 `transit_date`(回放、年运、指定日期的报告)时,Vimshottari 与行运按该日期算,Narayana 当前段和星宿大运按运行当天算,同一份报告里混着两个日期。没给年龄时,年运备用年龄用 `天数 // 365`,每四年早一天进下一岁,生日前几天就按新一岁起年盘。
- 触发条件:完整解读带 `transit_date`,或只带 `today`;年运没给年龄。
- 根因:这三处各自写了「今天」:Narayana 只读 `args.today`,星宿大运和年运备用年龄直接用 `datetime.now()`;而 `today_str` 与 `transit_reference_date` 已经按 `transit_date → today → now` 取。上游同一缺陷,10-16 修复,第九轮没核对这个提交。
- 修复:三处都按 `transit_date → today → now` 取观察日期;年运备用年龄按公历生日算(到了生日才加一岁)。提示文字不变。没有照搬上游的 `target_year` 分支和 `total_seconds` 年龄(我方这里没有 `target_year`,Narayana 年龄沿用 `days / 365.25`)。
- 验证:新测试 `tests/test_upstream_sync10_observation_date.py`(移植上游 `test_full_reading_observation_date.py`、`test_tajika_report_age.py`):四个日期变量对四种入参取同一日期;年运备用年龄在生日前后、`transit_date` 优先、给了年龄不覆盖;2000-01-01 到 2039-12-25 是 39 岁不是 40;星宿大运年龄按观察日期。改前 10 条失败、改后 11 条通过。
- 影响输出:只在请求日期不是今天时变化(回放、年运、指定日期);网页咨询按当天算的结果不变。年运备用年龄在生日前约 N/4 天内(N 为年龄)会少一岁,对应的年盘年份随之更正。不涉及冻结身份文件。
- 防复发:上述测试;同一份报告里新增日期变量要沿用同一取法。
- 关联记录:BUG-1541、BUG-1220(年盘保留秒)。
- 复发自:无
- 修复版本:分支 `codex/upstream-skill-sync-20261104`,待合并。
## BUG-1543 | KP 子主星与宫头用的是显示用的四舍五入值
- 状态:resolved(代码 + 回归测试;待部署)
- 首次发现 / 最近更新:2026-11-04(上游同步第十轮核对上游 `26d5a52d`)/ 2026-11-04
- 影响面:`scripts/jyotish_engine.py` `cmd_kp`(命令 `kp`、`/api/kp` 一类入口)。KP 在商业覆盖层仍是 `reference_only`。
- 现象:行星落在星主 / 子主 / 子子主分界附近、或宫头附近时,KP 表可能差一格。
- 触发条件:行星度数或宫头离 KP 分界小于约 0.0001 度;出生时间带秒。
- 根因:`cmd_kp` 把 `calculate_bhava_boundaries()` 返回的宫头(保留 4 位小数)、行星的 `degree_in_sign`(保留 4 位小数)和星盘结果里的 `julian_day`(保留 6 位小数,约 0.09 秒)交给 KP 分析,KP 分界是按原始经度切的。上游同一缺陷,10-16 修复,第九轮没核对。
- 修复:按出生时刻重算儒略日(与 `compute_chart_data` 同一公式,含秒);宫头改用 `calculate_cusps()` 原值;行星度数用 `degree_raw % 30`,没有原值时才退回显示值。
- 验证:新测试 `tests/test_upstream_sync10_kp_unrounded_inputs.py`(移植上游 `tests/test_kp_unrounded_inputs.py`):儒略日、宫头、行星度数都是原值;改前失败、改后通过。`test_kp_system.py`、`test_kp_cusp_house_assignment.py`、`test_kp_monthly_report_packet.py` 5 条通过、2 条跳过;公开名人盘实跑 `cmd_kp` 正常返回。
- 影响输出:只有贴着分界的行星或宫头会换一格,其余逐字节相同。不涉及冻结身份文件。
- 未搬:同一上游提交里的 Vivaha Saham 昼夜公式与 Yoga 燃烧状态透传,见本轮进度记录。
- 防复发:上述测试;显示用的取整值不进计算。
- 关联记录:BUG-1383(KP 边界,第八轮)、BUG-1541、BUG-1542。
- 复发自:无
- 修复版本:分支 `codex/upstream-skill-sync-20261104`,待合并。
## BUG-1545 | Narayana 大运(旧算法)过了第一轮就把第一轮重放,六七十岁以上的人拿到错的当前运
- 状态:resolved(引擎 + 回归测试;未部署)
@@ -25,3 +25,8 @@
- **事件映射(甲8)**:在 77 例上,±10 / ±30 / ±60 分钟三档的回放头名分别下降 13~17、10~16、8~21 个百分点,主要来自事业一行扩到 10、6、9、11 宫。
- **月亮行运(甲9)**:按 0.25 权重加入月亮起点,结果不变;按 1.0 权重,±60 分钟档降 1.3 个百分点。
- 详细记录:`affliction_weight_trial_2026_10_03.md`、`rectification_mapping_trial_2026_10_03.md`。
## 2026-11-04 复核(上游 main `5fb8379c`)
1. Narayana:上游 `narayana_dasha_full_report` 已把 seed / sequence / duration / dual-lord profile 传给 `calc_narayana_mahadasha`,另加 `cycle_count` 与 AD `strength_profile`。用虚构行星位置在三个上升上实跑,`kn_rao` 与 `legacy_local` 的大运序列不同,第 1 条已修。命令行默认仍叫 `kn_rao`,这个名字指什么仍未说明。我方默认 legacy,标为旧算法,另给 Rath 视图(BUG-1215、BUG-1218)。我方没有第二周期,也没有 AD 强度口径(`6859b263`,第九轮决定不做)。
2. 8 星制 Karaka:上游 `jaimini.KARAKA_8` 已改为 BPHS 次序,罗睺按 30 减星座内度数排;我方同样已修,两边一致。
@@ -0,0 +1,55 @@
# PROGRESS · 上游同步第十轮(2026-11-04)
- 分支:`codex/upstream-skill-sync-20261104`;基线 `origin/staging` `db2fc3fc`
- 上游:`732642856/yinduzhanxing` main `5fb8379c4f3e649cb4804065c9a3059e4bf633fe`(2026-10-21,到 11-04 仍是 HEAD)
- 上次镜像:`b9a0ef8f`(已被上游改写,提交时间对应 `dc3b8fef`);第九轮甲 / 乙批已按语义移植到 `5fb8379c`,但没有动镜像
- 范围:本轮查 skill 文档层(SKILL.md、references/)和第九轮没有核对过的引擎修正;不推送,不碰 staging / main
- 预检:`scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` 全部通过(外部引擎适配、远端可见性、碎片扫描、聚焦测试)
## 做了什么
| 提交 | 内容 | 风险 |
| --- | --- | --- |
| `3bae79fb` | 用导入器 `--apply` 从干净克隆镜像上游 `SKILL.md` 到 `references/upstream/yinduzhanxing/`;`source-manifest.json`、`import-plan.json`、导入记录 `commit-5fb8379c….json` 改钉 `5fb8379c`;b9a0ef8f 的导入记录和测试留作历史 | 仅文档 |
| `297e27e0` | 把残留的私人样例(4 月 17 日 14:45、约北纬 36.5 东经 114.2)换成上游用的公开样例(1935-06-21 Cajarc):`ai-reading-workflow-prompt.md`、`pdf-chart-reading-guide.md`、`cmd_narayana_dasha.py` / `cmd_solar_return.py` 的用法说明 | 仅文档 |
| `2539d57f` | 五份参考文档按上游的原文更正:Argala 次级为 5 / 9 宫、Saham 表 74 全 36 项、十六分盘 D27 0.5 / D30 1、Yogi 公式说明(路径改成我方 `scripts/annual_native/yogi_sphuta.py`)、解读流程加 10-02 全文证据边界;`finance-timing-strict` 别名对齐 | 仅文档 |
| `4b6aa087` | BUG-1541:D12 分盘联动按 D12 上升算宫位;坏星座名报错,不再当白羊(上游 `cf6c316d`、`f60568a0`) | 改对外输出 |
| `8137c692` | BUG-1542:完整解读 Narayana 当前段、星宿大运年龄、年运备用年龄统一按 `transit_date → today → now` 取日期;备用年龄按公历生日(上游 `933f254e`) | 改计算 |
| `9d69cd56` | BUG-1543:KP 用原始宫头、原始行星度数和含秒的儒略日(上游 `26d5a52d` 的 KP 部分) | 改计算(仅分界附近) |
三处引擎文件都不在校正冻结清单(`sealed_holdout_rerun.PRODUCTION_FILES`、dataset `frozen_scoring.files`)里,不需要按 ERR-110 重冻。
## 没有搬的
| 上游 | 原因 |
| --- | --- |
| `SKILL.md` 正文(真源索引 09-15、分类 skill 联用、技法查漏编译链、PL9 / VedAstro 长段、P0/P1 回填长段、Shadbala / Dasha 升格门槛措辞) | 我方 `SKILL.md` 是商业入口,导入策略把它列为保护路径;这些段落指向我方没有的文件(`skills/<family>/`、`technique_ontology.json`、`current_capability_truth_index_2026_09_15.md` 等),或已有更短的商业版。研究原文已在快照里 |
| `references/strict-workflow-router.md`:MEVG 改成「可选」、外部 oracle 缺口不再 `blocked`、新增 `interpretation_status_contract.md` | 与本仓 AGENTS.md §3、§4(MEVG 必做、缺口必须 blocked 或降级)冲突;路由章节会进对话提示词 |
| `bphs-ch48-narayana-dasha.md` 新版 | 描述的第二周期(`cycle_count`)与 AD 强度口径我方没有;我方默认是标为旧算法的 legacy 加 Rath 视图(BUG-1215、BUG-1218) |
| `quick-reference-guide.md`、`technique-capability-matrix.md`、`functional-role-profile.md` 的差异 | 我方较新(`marriage-match` 七项、#163 盘面说明) |
| 上游独有的 113 个顶层 references(rangacharya / raman 研究包、web intake、skill family router、MEVG 审计等)和 1 600 多个 oracle / 书籍 / 案例文件 | 研究资产或依赖上游分类 skill;不进商业包 |
| `26d5a52d` 的 Vivaha Saham 昼夜公式 | 需要在完整解读 Step 16 之前算昼夜,还要改 `consultation_native_layers.build_vivah_saham`(没有出生地时刻);夜生盘的婚姻点会变,需要产品与数据核对 |
| `26d5a52d` 的 Yoga 燃烧状态透传 | 我方 `cmd_yoga` 已分叉(第 5 字段 `R` 逆行),要另核 |
| `0acf7833`、`01f53764`(报告表格与英文报告) | 上游 PL9 渲染器,我方报告链不同 |
| `b4dada9a`、`7e5498f6`(年运计算缓存范围) | 依赖上游 `annual_calculation_cache.py` 等研究回放 |
| `6859b263`(Narayana 小运强度口径)、`2f52cfca`(karaka_calculator)、33 个未接入大运模块 | 第九轮已决定不做或另评估,原因不变 |
## 跑过的检查
见本文件末尾「验证」一节。
## 两处 10-03 上游问题的现状
1. Narayana 的 K.N. Rao profile:上游 `narayana_dasha_full_report` 现在会把 seed / sequence / duration / dual-lord 传给 `calc_narayana_mahadasha`(另加 `cycle_count`、`strength_profile`)。拿虚构行星位置在三个上升上实跑,`kn_rao` 与 `legacy_local` 的大运序列不同,机制上已修。上游命令行默认仍是 `variant_profile='kn_rao'`,「K.N. Rao 版 Narayana」这个名字指什么仍未说明。我方:默认 legacy 并标为旧算法,另给 Rath 视图,已删 `--variant-profile`;非 legacy profile 同样会传进去。我方没有第二周期和 AD 强度口径。
2. 8 星制 Chara Karaka:上游 `jaimini.KARAKA_8` 已改为 BPHS 次序(第 5 位 PiK、第 8 位 DK),并有罗睺倒数度数(`974403a6`);我方同样已修。两边一致。
## 验证
| 检查 | 命令 | 结果 |
| --- | --- | --- |
| 预检 | `scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45` | 全部通过 |
| Python 快速门 | `python scripts/run_quality_gate.py --profile quick --skip-frontend-runtime`(在第 6 个提交 `9d69cd56` 上) | 通过(py_compile 448 个文件,pytest 395 秒) |
| 新测试 | `tests/test_upstream_sync10_*.py` 三个文件 | 31 条通过;改前 D12 18 条失败、日期 10 条失败、KP 1 条失败 |
| 快照与导入 | `test_upstream_import_plan.py`、`test_upstream_git_import_{b9a0ef8f,5fb8379c}.py`、`test_report_orchestrator_reader_contract.py`、`test_repo_privacy_markers.py`、`test_cross_project_contract.py`、`test_import_yinduzhanxing.py` | 通过 |
| 联动相关 | `test_consultation_native_layers.py`、`test_flying_star_audit.py`、`test_full_reading_module_parity.py` | 20 条通过(台账里提到的不稳定用例这次没有失败) |
| 前端 skill | `npx tsx --test tests/skill-*.test.ts tests/consultation-methodology.test.ts` | 48 条:47 通过、1 跳过(本机没有 docker) |
+1
View File
@@ -552,3 +552,4 @@
| — | — | **#163 星盘页术语与盘上标注(占星师答复 4–8)**:六力达标线、大运 / 小运 / 细运、运主落宫边框与「从运主起算」、Chara 运主、敌友默认上升主星与三种关系、瑜伽不连线、八分法 / Arudha 映射(Claude 直接执行) | 待验收 | `codex/163-terms-overlays` |
| `TASK-birth-time-source-intake-20261102.md` | `PROGRESS-birth-time-source-intake-20261102.md` | **#144 第一单(jesse 选 A)**:建档六个出生时间来源选项;不再把来源换成固定误差,改填最早 / 最晚或「不清楚误差」;加列 `id_document`、记录方式、误差是否已知、时辰(只加不删);第二单(分项算稳定性)另开排队 | 待验收(Claude 11-02 完成 T1–T7;T5 原句仓内 0 处;迁移在本机 PostgreSQL 16 跑通,门禁 PG17 待复核;顺带 BUG-1513、1514) | `codex/birth-time-intake-144` |
| —(issue #165,Claude 直接执行) | `PROGRESS-birth-time-stability-20261103.md` | **#144 第二单**:星盘页按用户给的出生时间范围逐分钟算九项(上升星座 / 度数 / 宫头、D9、D10 上升、月亮星座 / 星宿 / pada / 宫位),各说一致 / 有变化;先核实原始资料的提示(日期有疑问、时区未确认、上升或月亮星宿变化);大运周期边界原文;上升预检与两个扫描脚本显式传岁差与交点(BUG-1515);只记得时辰第三问,不改数据库 | 本地提交,待验收 | `codex/birth-time-stability-165` |
| —(Claude 直接执行) | `PROGRESS-upstream-sync10-20261104.md` | **上游同步第十轮**:研究快照镜像移到上游 main `5fb8379c`;私人样例换成公开样例;五份参考文档按上游原文更正;引擎三处修正(BUG-1541 D12 联动宫位、BUG-1542 同一观察日期、BUG-1543 KP 原始输入)。Vivaha Saham 昼夜公式与 Yoga 燃烧透传未搬,待产品决定 | 待验收 | `codex/upstream-skill-sync-20261104` |
+11 -1
View File
@@ -15,6 +15,16 @@
## ⚠️ 核心原则
### 全文证据边界(2026-10-02)
标题、开场第一句、摘要、卡片、正文、结尾建议均受相同证据约束。
先回答问题不等于先作确定断言;条件性迹象在开场也必须保持条件性。
计算完成、规则条件满足、传统解释、外部复核和现实事件是不同状态。
未检测、检测失败或尚未验证不能写成“不成立”;可保留带规则与限制的候选格局。
事业分别说明行业候选、工作方式、公众可见度与发展节奏,逐项绑定10宫星、10宫主、
D10、A10、功能角色和强度等实际证据。禁止无依据的“幕后、专业交付、晚成”模板。
不得用名人姓名或已知职业补全盲读;职业象征须保留相近候选与现实核验问题。
1. **不跳步**:每个阶段必须完成后才能进入下一阶段
2. **不猜测**:数据缺失时标注"缺失",不编造数据
3. **不锚定**:先输出星盘推导,再等用户反馈,不先知道用户生活再"找依据"
@@ -307,7 +317,7 @@ Step 0.5: Karaka 系统识别与声明
**示例输入**:
- "1990年1月1日 12:00(示例) 示例城市"
- "我出生于 1990-08-15 早上6:30 北京"
- "April 17, REDACTED_YEAR, 2:45 PM, San Francisco, China (36.6N, 114.5E)"
- "June 21, 1935, 11:00 AM, Cajarc, France (44.486N, 1.842E)"
**执行指令**(一条命令搞定所有计算):
+6 -5
View File
@@ -1,5 +1,7 @@
# Argala 行星干预体系完整指南
> 2026-10-14 原文裁决:当前计算以 PVR《Integrated Approach》§§10.5–10.7(正文来源页117–118)为依据。第5宫是次级干预,第9宫是对应阻挡;计都所在参照星座逆向计数;双方数量相等不能直接判受阻,须比较强弱。本指南其余历史扩展(第8宫、性别条件、占位数量直接换算Rajayoga等级)没有在上述原文中得到支持,不得用作当前计算依据。下方旧矩阵只作为历史记录,不代表已核验规则。
> **适用场景**:分析行星对宫位的"锁门/开门"效应——哪些行星在帮助/阻碍某个宫位的结果实现
> **版本**:v1.0.0 | **创建日期**:2026-04-24
> **优先级**:⭐⭐⭐⭐⭐(K.N. Rao PAC-DARES方法论核心组件)
@@ -57,12 +59,12 @@
### 2.2 次Argala(Secondary Argala)
**规则**:从目标宫位起算,第5宫和第8宫有次级Argala效应:
**当前规则**:从目标宫位起算,第5宫有次级Argala,第9宫阻挡它:
| 干预宫位 | 名称 | 含义 | 条件 |
|----------|------|------|------|
| **第5宫** | 次级创造Argala | 智慧/创造力的干预 | 仅当该宫内有**男性行星**(Sun/Mars/Jupiter)时生效 |
| **第8宫** | 次级隐藏Argala | 隐藏资源/危机转化的干预 | 仅当该宫内有**女性行星**(Moon/Venus)或**暗行星**(Rahu/Ketu/Saturn)时生效 |
| **第5宫** | 次级Argala | 干预 | 与第9宫配对比较,不附加行星性别条件 |
| **第9宫** | Virodhargala | 阻挡第5宫干预 | 数量相等时须比较强弱 |
### 2.3 Virodha Argala(反干预/解锁)
@@ -74,7 +76,6 @@
| 第4宫 | **第10宫** | 外部职责抵消基础支持 |
| 第11宫 | **第3宫** | 个人行动抵消网络收益 |
| 第5宫(次级) | **第9宫** | 信仰/远见抵消创造力干预 |
| 第8宫(次级) | **第2宫** | 资源消耗抵消隐藏力量 |
**判断规则**:
- 如果主Argala宫有行星 → 有Argala
@@ -272,4 +273,4 @@ E (Exchange) → S (Strength)
- K.N. Rao PAC-DARES体系
- B.V. Raman《Three Hundred Important Combinations》
- Sanjay Rath《Jaimini Sutras》Argala章节
- Parasara《Brihat Parasara Hora Sastra》Argala Adhyaya
- Parasara《Brihat Parasara Hora Sastra》Argala Adhyaya
@@ -0,0 +1,79 @@
{
"generated_at": "2026-10-21T15:49:21Z",
"mirror_files": [
{
"license": "MIT",
"source": "SKILL.md",
"source_sha256": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"status": "applied",
"target": "references/upstream/yinduzhanxing/SKILL.md",
"target_sha256_after": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"target_sha256_before": "6bed459b8b971b189895e62aabfb90e41dbdf8f5b9e80a4b5330d40922fe21eb"
}
],
"operator_review_required": true,
"policy_version": 2,
"privacy_scan": {
"rejections": [],
"scanned_files": 1,
"status": "pass"
},
"protected_rejections": [],
"schema_version": 1,
"semantic_merge_files": [
{
"diff_summary": "manual semantic merge required: +93/-94 lines",
"path": "SKILL.md",
"source_sha256": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"status": "review_required",
"target_sha256": "552080ec6901198de896012fa38badb4f055857183f7f263038f7dbf232eef4a"
},
{
"diff_summary": "manual semantic merge required: +234/-177 lines",
"path": "AGENTS.md",
"source_sha256": "34165d7d95ba0b85f18e75ba526fd630ba9fed9abbf522cc2008f99a7d6e90f7",
"status": "review_required",
"target_sha256": "521292fa13037bc06f08c7cc002f506c3f0651eb4c9f2da55290969a8c615594"
},
{
"diff_summary": "manual semantic merge required: +17/-6 lines",
"path": "references/strict-workflow-router.md",
"source_sha256": "a0bb91ba04bfb1218fa494ec07011c271e073fa9cac4e6f16fe05157554e9a96",
"status": "review_required",
"target_sha256": "b2dc3b8a86cf74d875841ee4714fd1be64bb594503088311e6622556175b90bc"
},
{
"diff_summary": "manual semantic merge required: +700/-385 lines",
"path": "scripts/unified_consultation_orchestrator.py",
"source_sha256": "2c836861bc0132dee035050ecb4ae14ab9215140d2559086f0d3728c80e546fe",
"status": "review_required",
"target_sha256": "d249528a9f281917c6b895d198053cc81fb0c7d06666302658b11ef79495e7c3"
},
{
"diff_summary": "manual semantic merge required: +83/-141 lines",
"path": "scripts/report_orchestrator.py",
"source_sha256": "27956a632793735ec660da312b66be967a35c0345edab90b8efe4a65663e4262",
"status": "review_required",
"target_sha256": "0fb80002561a6718088eceed0b20df2a8faa0adbc222756fadf8cd158a6957a6"
},
{
"diff_summary": "manual semantic merge required: +10060/-4011 lines",
"path": "scripts/jyotish_api_server.py",
"source_sha256": "689468af6d4cfefb4e378ae0de2a6550d5b7b2f04f357a4b16adbb9b245139e5",
"status": "review_required",
"target_sha256": "b3aead367ef53003c72fc2cbdea3d827463d629fc90a173a8095eb81812b0d74"
}
],
"source_commit": "5fb8379c4f3e649cb4804065c9a3059e4bf633fe",
"source_mode": "git",
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_skill_sha256": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"source_tree_hash": "ee8bd9865cf11a6eb7f90705a262db8b2d145ae0cee6ee6eda32b311548f5aa2",
"target_base_commit": "db2fc3fcfe155c5f5ed64000c46e2a669de54a1f",
"target_repository": "root/Jyotisha",
"tests_run": [
"tests/test_cross_project_contract.py",
"tests/test_import_yinduzhanxing.py"
]
}
@@ -208,6 +208,36 @@
],
"hash_contract_result": "pass: pinned mirror remains b9a0ef8f; batch A is a semantic port of upstream main 5fb8379c; b9a0ef8f was rewritten and its timestamp matches dc3b8fef; SKILL.md was not re-mirrored; importer --apply was not run",
"rollback": "git revert 8aed605a43ad8e235cd341ebcaeb7675503325e6 bd09485243000ea7012b865dbee04fc00c535005 c1bd59e97a76e914b884b650cae1adfb13b1e858 7bede9c99a056446648aa1e8fa36b3a4734a36eb 96312893b7ad7660e72081c81bf4bf031f0b38a0 b35c424945e578abe95fca69de5eff1a8e14c0e1 16bae006158aad8b493362ce1a51391626a95dca 9d3f59a87206d4b7f6c10f94c56565bbd98089a6 d226b0e5388cc3606272956bc5b9c2072685210d on branch codex/upstream-sync9-20261028"
},
{
"source_repository": "732642856/yinduzhanxing",
"source_commit": "5fb8379c4f3e649cb4804065c9a3059e4bf633fe",
"target_repository": "jesse-ux/Jyotisha",
"target_commit": "9d69cd5625456c452949da2e47c7ad68332b2b46",
"change_class": "calculation_contract",
"copied_files": [
"references/upstream/yinduzhanxing/SKILL.md",
"references/upstream/yinduzhanxing/source-manifest.json",
"references/upstream/yinduzhanxing/import-plan.json",
"references/cross_project_contract/imports/commit-5fb8379c4f3e649cb4804065c9a3059e4bf633fe.json",
"references/ai-reading-workflow-prompt.md",
"references/pdf-chart-reading-guide.md",
"references/argala-complete-guide.md",
"references/sahams-execution-guide.md",
"references/shodasavarga-complete-guide.md",
"references/yogi-avayogi-system.md",
"references/event_judgment_wealth.md",
"scripts/cmd_narayana_dasha.py",
"scripts/cmd_solar_return.py",
"scripts/jyotish_engine.py"
],
"dependency_delta": "none",
"privacy_review": "pass: mirrored SKILL.md drops the private sample wording; the leftover private sample date, time and place in two references and two usage strings are replaced with upstream's public example; no production configuration, secret, or user data",
"focused_tests": [
"python -m pytest tests/test_upstream_import_plan.py tests/test_upstream_git_import_b9a0ef8f.py tests/test_upstream_git_import_5fb8379c.py tests/test_report_orchestrator_reader_contract.py tests/test_repo_privacy_markers.py tests/test_upstream_sync10_d12_linkage.py tests/test_upstream_sync10_observation_date.py tests/test_upstream_sync10_kp_unrounded_inputs.py"
],
"hash_contract_result": "pass: git-mode mirror moved to 5fb8379c with scripts/import_yinduzhanxing.py --apply from a clean clone; semantic-merge paths stay review_required; three engine fixes are semantic ports of cf6c316d/f60568a0, 933f254e and the KP part of 26d5a52d",
"rollback": "git revert 9d69cd5625456c452949da2e47c7ad68332b2b46 8137c692b1bbe862bef83b13910437cb2549eb05 4b6aa08726cb5ec4c179c7ad89ec3e25ecfbe7d6 2539d57fee9da72f97045577279aaa3bdb3796fc 297e27e0bda9232d7de1c5747e1a14be3845db62 3bae79fbcae80a8db644f7f0252fadb1c9a5c4bc on branch codex/upstream-skill-sync-20261104"
}
]
}
+1 -1
View File
@@ -158,7 +158,7 @@
- `lakshmi_dhana_activation_chain`
- `yogi_asc_tight_orb_wealth`
- `strict-workflow-router.md` 的 `wealth-timing-strict`
- `strict-workflow-router.md` 的 `finance-timing-strict`(兼容 `wealth-timing-strict`)
- `divisional-chart-deep-reading.md` 中的财富链 `D2 -> D4 -> D10 -> D11`
若调用不到,必须在 Audit Table 里标注其对置信度的削弱。
+6 -6
View File
@@ -53,9 +53,9 @@
| 出生日期 | 1990-01-01 | 年月日 |
| 出生时间 | 12:00:19 | 时分秒,越精确越好 |
| 出生地点 | 示例城市 | 城市名 |
| 经度 | 115.5°E | 东经/西经 |
| 纬度 | 36.6°N | 北纬/南纬 |
| 时区 | UTC+8 | 相对于UTC |
| 经度 | 122.4°W | 东经/西经 |
| 纬度 | 37.8°N | 北纬/南纬 |
| 时区 | UTC-8 | 相对于UTC |
| 性别 | Male/Female | 用于关系分析征象星选择 |
| Ayanamsa | Lahiri 23°51'11" | 恒星黄道校正值 |
@@ -244,9 +244,9 @@
"date": "1990-01-01",
"time": "12:00:19",
"location": "San Francisco",
"latitude": 36.6,
"longitude": 115.5,
"timezone": "UTC+8",
"latitude": 37.7749,
"longitude": -122.4194,
"timezone": "UTC-8",
"gender": "Male",
"ayanamsa": "Lahiri 23°51'11\""
},
+4 -1
View File
@@ -47,7 +47,10 @@
## 四、当前真实边界
当前 skill 已能输出常见 36 个 Saham 与婚姻点等专项点。
2026-10-14计算链更新:通用计算器已接入PVR表74全部36项及其昼夜例外。
来源为《Integrated Approach》印刷页394–396;并保留独立婚姻点入口。
额外旧候选项仍单独标为 `legacy_candidate_unverified`,不属于这36项的认证范围。
这项源码更新不等于客户冻结版本已经更新或报告已经重新发布。
但必须保留的边界:
+2 -2
View File
@@ -56,8 +56,8 @@ BPHS提供的行星功能性吉凶量化系统(与Shadbala天文强度互补
| D16 | — | — | 1 | 2 |
| D20 | — | — | — | 0.5 |
| D24 | — | — | — | 0.5 |
| D27 | — | — | — | 1 |
| D30 | 1 | 1 | 1.5 | 1.5 |
| D27 | — | — | — | 0.5 |
| D30 | 1 | 1 | 1.5 | 1 |
| D40 | — | — | — | 0.5 |
| D45 | — | — | — | 0.5 |
| D60 | — | — | **5** | **4** |
+18 -14
View File
@@ -7,11 +7,14 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
# 印度占星专业解盘与推运系统
> **版本**:v6.9.15 | **详细变更**:`CHANGELOG.md`
> **对标状态**:中文用户端与技法覆盖领先;D1/D9/AV/Chara 等有守门,Dasha/Shadbala 外部 oracle 扩充仍在进行。
> **对标状态**:中文用户端与技法覆盖领先;D1/D9/AV/Chara 等有守门,Dasha/Shadbala/Tajika 已进入分层证据治理阶段:部分 target-set / packet / source-bounded 链路已闭合,但 formal case admission、same-chart parity、production tuning 与 global oracle closure 仍不得过度宣称。
>
> **真相边界**:当前问题已不是“完全缺技法名称”,而是少数高价值传统深度仍未闭环;请优先修复精度与裁决链,而不是继续表面堆功能名。读取能力状态时必须优先使用 `references/oracle/effective_skill_capability_view_2026_07_19.json` 与 `references/oracle/skill_truth_overlay_2026_07_19.json`,不得直接把 `references/technique_registry.json` 的旧 `covered` 当作完整闭环。
> **Ashtottari(2026-09-01)**:规则族冲突锁已部分解开 → `selected_rule_family_parameter_sensitive`;主源验证文档 `docs/research/ashtottari_primary_text_verification_2026_09_01.md`,适用族 `rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight`({4,5,7,9,10});未闭环项:第二引擎 oracle(`attach_external_same_profile_comparison`)。yn-1993 本盘不适用。
> **当前真源索引(2026-09-15)**:读取能力状态时,优先使用 `docs/research/current_capability_truth_index_2026_09_15.md`、`python3 scripts/current_report_authority_snapshot.py --format json`、`python3 scripts/workspace_governance_audit.py`、`docs/research/pl9_full_parity_dashboard_2026_09_11.md` 与当前质量门输出。`references/oracle/effective_skill_capability_view_2026_07_19.json`、`references/oracle/skill_truth_overlay_2026_07_19.json`、`skill_gap_truth_audit_latest.md` 等旧快照只能作历史边界,不得覆盖 9 月以来的 closure / authority / report-export 事实。
> **真相边界**:当前问题已不是“完全缺技法名称”,而是高价值传统深度的升格门槛:packet complete、source-bounded wired、external verified、formal admitted、production tuning allowed、global closure 必须分开写。请优先修复精度、证据仲裁、报告交付和状态真源,而不是继续表面堆功能名。不得直接把 `references/technique_registry.json` 的旧 `covered` 当作完整闭环。
> **Ashtottari(2026-09-01)**:规则族冲突锁已部分解开 → `selected_rule_family_parameter_sensitive`;主源验证文档 `docs/research/ashtottari_primary_text_verification_2026_09_01.md`,适用族 `rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight`({4,5,7,9,10});未闭环项:第二引擎 oracle(`attach_external_same_profile_comparison`)。适用性必须按当前输入独立计算。
> **执行总控**:`references/quick-reference-guide.md`
> **分类 skill 联用**:按 `references/skill_family_router.json` 与本轮 `selected_skill_families` 逐个读取 `skills/<family>/SKILL.md`,遵循 `references/skill_family_handoff.md` 交接。综合模式各传统并列解释;路由记录不代表真实执行。
> **技法查漏编译链**:`references/technique_ontology.json` 统一身份与别名。先运行 `python3 scripts/technique_source_adapters.py --write` 刷新 JH/PyJHora 命名技法和本地书籍主题候选,再运行 `python3 scripts/technique_gap_scanner.py --write --check-web` 对照注册表、书籍规则链和源码,生成各 skill 的缺口队列与用法包。文本命中或身份归并不等于可执行。
> **严格路由**:`references/strict-workflow-router.md`(涉及事业/婚恋/财务/应期/技法验证时必须优先读取)
> **机器注册表**:`references/oracle/effective_skill_capability_view_2026_07_19.json` + `references/oracle/skill_truth_overlay_2026_07_19.json` + `references/technique_registry.json` + `scripts/audit_capabilities.py`
> **文章级细节模板**:`references/interpretation_template_registry.json` + `scripts/validate_interpretation_templates.py`
@@ -25,7 +28,7 @@ description: 印度占星(Jyotish)专业解盘与推运系统。核心能力
| 分盘 | D1-D144 + D2/D3变体 + 复合D-m×n + 自定义D-N(2-300) |
| Bhava Chalit | Sripati/Porphyry/Equal/Whole Sign/Placidus/Koch 不等宫位调整 |
| Sudarshana | Asc/Moon/Sun 三参考点盘 + 宫位收敛分析 |
| Shadbala | absolute Rupa 分量求和;内部不变量通过,外部绝对值 oracle 扩充中 |
| Shadbala | absolute Rupa 分量求和;入口 profile parity 与 packet fields 已推进,same-chart absolute parity / residual attribution / second oracle 仍需守门 |
| Ashtakavarga | BAV+SAV+PAV(展开式)+Sodhita(净化式) |
| KP系统 | reference-only / partial;不得按完整主链能力宣称,须以 `skill_truth_overlay` 为准 |
| 合盘 | 16因子36分制(Ashtakoot+Kuta) |
@@ -122,7 +125,7 @@ adapter available 解释为已完成 VedAstro、PyJHora/JHora 或 jyotishganit r
VedAstro 官方 MCP 端点(`https://mcp.vedastro.org/api/mcp/public`)initialize/tools-list 正常,
但 tools/call 层已知 resolver 故障(`Invalid or Outdated Call`)。**默认计算通路改为 REST 桥直连**:
- 脚本:`scripts/vedastro_rest_bridge.py`(horoscope / dasa 子命令,默认 yn-1993 参数)
- 脚本:`scripts/vedastro_rest_bridge.py`(horoscope / dasa 子命令,默认公开回放基准参数)
- 端点:`https://api.vedastro.org/api/Calculate`(已实测 `Status: Pass`,绕过 MCP resolver 故障)
- 决策记录:`docs/research/vedastro_rest_bridge_default_decision_2026_09_01.md`
- REST 桥返回 `Status: Pass` 视为官方 raw evidence 可用(对应 `official_verified`);失败时降级 `local_fallback`
@@ -421,15 +424,16 @@ python3 scripts/validate_interpretation_templates.py --format markdown
- Bhrigu Pada / RTN / Tithi Lord / Pancha Pakshi 必须作为辅助确认层;若没有主承诺和推运激活,输出置信度不得超过 C。
- 所有文章/课程/网上说法默认归入 B/C 级线索,必须经过注册表中的 `required_cross_checks` 与 `forbidden_claims` 过滤。
## 当前最硬的未闭环点
## 当前最硬的升格门槛
> 这部分比“再加几个技法名”更重要,决定 skill 距离传统软件级深度还有多远。
> 这部分不是说基础能力不存在,而是说明哪些层级尚未从 `packet/source-bounded/verified slice` 升格到 `formal admitted/global closure/production tuning allowed`。
1. **Dasha 外部绝对边界闭环**
- 当前 `ready_for_calibration: 4`
- Dasha-only 目标集已由 PyJHora 黑盒证据推进到 3/3,但全局 Dasha/Shadbala 队列仍有非 Dasha 靶点缺字段,`production_tuning_allowed: false`
2. **Shadbala 外部绝对值闭环**
- 当前 absolute Rupa 结构自洽,但还不是外部绝对值完全校准
1. **Dasha promotion gates**
- 多数 Dasha 链路已 source-bounded wired;下一步是 p92 selector、p93-p103 Rashi selector/duration、p109-p120 row-date / child-row profile、p132/p133/p135 minute-boundary gates。
- 不得把同案 producer 输出直接升格为 PL9 universal selector 或 formal case admission。
2. **Shadbala same-chart absolute parity**
- 入口 profile parity 与 packet fields 已推进;当前重点是固定同盘/ayanamsa/node mode 后,拿到安全第二来源原始表并解释 Mercury Chesta / Sthana residual。
- `production_tuning_allowed: false` 前,禁止调生产常数贴合单一截图或 PDF。
3. **Chara Dasha 共主仲裁尾差**
- KN Rao benchmark 已过,但 Aquarius/Scorpio 的 Rahu/Ketu 共主强弱仲裁仍有尾差
4. **KP ruling planets / 事件裁决细节**
@@ -437,7 +441,7 @@ python3 scripts/validate_interpretation_templates.py --format markdown
5. **Prashna 分支工作流**
- 问事类型分支、裁决链、时机判断仍需更稳定的传统链路
6. **Varshaphala / Tajika / Sahams 年运裁决深度**
- 年盘骨架已在,第一条 Steve Jobs 1984 外部年运样本已闭环;但整体仍只有 `1/5`,事件裁决、权重层、Einstein 1905 等后续样本仍需继续成熟
- 第一目标包已推进到 target-set verified;下一步是第二波公开样本、多年/边界样本、judgment templates 与 Mudda Dasha 事件链,不是继续证明年盘骨架存在。
7. **Kalachakra / Narayana 等替代 Dasha 的成熟度边界**
- 已有覆盖,但部分子层、边界口径、外部黑盒对照仍需继续收紧
8. **高阶解释层整合**
@@ -558,7 +562,7 @@ $PYTHON $SCRIPT <子命令> [参数]
| `aspects` | 度数精确相位系统 |
| `jaimini` | Jaimini Karaka/Karakamsha、A1-A12/UL、Graha Pada、Special Lagnas;Chara Dasha timing 为 KN Rao Method(covered;仍需保留共主仲裁与外部对标边界) |
| `nakshatra-adv` | 高级Nakshatra(Tara Bala+Chandra Bala+Sub-Lord) |
| `nakshatra-dasha` | 星宿大运推演(Ashtottari + Nakshatra-level Vimshottari);**Ashtottari 状态 2026-09-01 解冻**:`blocked_rule_source_conflict` → `selected_rule_family_parameter_sensitive`(可执行但 unverified;适用判定需走 `rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight` 族,详见 `docs/research/ashtottari_primary_text_verification_2026_09_01.md`,yn-1993 本盘因 Rahu 距太阳 8 宫不适用) |
| `nakshatra-dasha` | 星宿大运推演(Ashtottari + Nakshatra-level Vimshottari);**Ashtottari 状态 2026-09-01 解冻**:`blocked_rule_source_conflict` → `selected_rule_family_parameter_sensitive`(可执行但 unverified;适用判定需走 `rahu_from_lagna_lord_kendra_trikona_plus_paksha_daynight` 族,详见 `docs/research/ashtottari_primary_text_verification_2026_09_01.md`,结果随当前盘独立判定) |
| `nakshatra-full` | 星宿综合报告(本命 + 大运 + 过境星宿) |
| `argala` | Argala门闩系统:主 Argala + Virodhargala + Rajayoga 分类 |
| `tajika` | Tajika年运盘(Muntha+YearLord+Mudda Dasha) |
@@ -2,9 +2,9 @@
"schema_version": 1,
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_commit": "b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09",
"source_tree_sha256": "9ae737b02a48b38dca7d78094413b961a75e5abad17f2fe206f7dc3b1401d51a",
"boundary": "Task 1 git-mode snapshot b9a0ef8f from origin/codex/add-birth-time-rectification-skill (not on main). The importer still mirrors only the pinned upstream root SKILL.md under references/upstream; none of these research scripts is copied into the commercial runtime. Semantic-merge destinations remain review-only except for files this commercial round copies by hand.",
"source_commit": "5fb8379c4f3e649cb4804065c9a3059e4bf633fe",
"source_tree_sha256": "ee8bd9865cf11a6eb7f90705a262db8b2d145ae0cee6ee6eda32b311548f5aa2",
"boundary": "Tenth sync: git-mode snapshot 5fb8379c, upstream main HEAD on 2026-11-04. The nine plan sources below still exist at that commit. The importer still mirrors only the pinned upstream root SKILL.md under references/upstream; none of these research scripts is copied into the commercial runtime. Semantic-merge destinations remain review-only except for files this commercial round copies by hand.",
"entries": [
{
"source": "scripts/evidence_maturity.py",
@@ -1,16 +1,16 @@
{
"boundary": "Git source b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09 was origin/codex/add-birth-time-rectification-skill (branch=codex/add-birth-time-rectification-skill, not on main). Only root SKILL.md is mirrored into this directory; commercial and rectification Skills remain protected. skill_sha256 is retained as a legacy alias of source_skill_sha256. Semantic-merge paths were reviewed by the importer and not overwritten. Ninth sync on 2026-10-28 (issue #141) follows upstream main 5fb8379c4f3e649cb4804065c9a3059e4bf633fe for batch A only: Yogini d268d519, Tithi 33537b31, Saham cb8a1ca7, Ashtottari 0712f98f, Kalachakra f353a096, Arudha 8b55630a, Darakaraka 45b3b66b, dasha boundary test 07fc5be3, aspects e628118d. b9a0ef8f was rewritten and is absent; its commit time matches dc3b8fef7f769758dd0e63bdab037cde8be194bc. source_commit, source_git_tree, and the skill hashes stay on the mirrored SKILL.md and were not re-imported.",
"boundary": "Git source 5fb8379c4f3e649cb4804065c9a3059e4bf633fe is upstream main HEAD on 2026-11-04 (no upstream commits after it). Tenth sync on 2026-11-04 re-mirrors only root SKILL.md into this directory with scripts/import_yinduzhanxing.py --apply from a clean clone; commercial and rectification Skills remain protected. skill_sha256 is retained as a legacy alias of source_skill_sha256. Semantic-merge paths were reviewed by the importer and not overwritten. The previous mirror b9a0ef8f (rewritten upstream; commit time matches dc3b8fef) keeps its import record under references/cross_project_contract/imports as history. Engine content between dc3b8fef and 5fb8379c was ported semantically in syncs 8, 9 and 9b and is not implied by this mirror.",
"import_policy_version": 2,
"imported_at": "2026-09-09T07:40:00Z",
"imported_at": "2026-11-04T04:00:00Z",
"license": "MIT",
"schema_version": 1,
"skill_sha256": "6bed459b8b971b189895e62aabfb90e41dbdf8f5b9e80a4b5330d40922fe21eb",
"source_commit": "b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09",
"source_committed_at": "2026-09-08T17:36:09Z",
"source_git_tree": "864cab471cadb31b71a7256c995c12b7552d7cb5",
"skill_sha256": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"source_commit": "5fb8379c4f3e649cb4804065c9a3059e4bf633fe",
"source_committed_at": "2026-10-21T15:49:21Z",
"source_git_tree": "6cd6160804ea7419d609ccd639cc537a8a81c3c9",
"source_mode": "git",
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_skill_sha256": "6bed459b8b971b189895e62aabfb90e41dbdf8f5b9e80a4b5330d40922fe21eb",
"source_tree_sha256": "9ae737b02a48b38dca7d78094413b961a75e5abad17f2fe206f7dc3b1401d51a"
"source_skill_sha256": "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d",
"source_tree_sha256": "ee8bd9865cf11a6eb7f90705a262db8b2d145ae0cee6ee6eda32b311548f5aa2"
}
+7
View File
@@ -1,5 +1,12 @@
# Yogi / Ava Yogi / Duplicate Yogi 行星系统
> 2026-09-26 校正(上游 5fb8379c 同步,2026-11-04 移入):下文是保留的历史混合笔记,不是当前可执行公式。
> 其中“Lagna+93°20′”、93°20′=6宿、结果为双子6°40′等说法相互矛盾,不能用于计算。
> 当前原生 profile 为 Sun+Moon+93°20′,Avayogi 再加186°40′;93°20′是巨蟹3°20′,
> 精确宿界归入 Pushya(Saturn)。计算以 `scripts/annual_native/yogi_sphuta.py`
> (`YOGI_OFFSET`、`AVA_OFFSET`)为准。
> 未找到明确来源的 Lagna 变体保持待考,不与当前日月公式合并。
> 来源:公众号文章(1-4印度占星),整合 Tithi Lord、财富业力、灵性距离、Dasha/Transit 激活等跨文章知识点。
> 版本:v1.0 | 2026-04-25
+2 -2
View File
@@ -13,8 +13,8 @@ def cmd_narayana_dasha(args, chart_data):
narayana-dasha 子命令:计算并报告 Narayana Dasha(Rishi Dasha)。
用法:
python jyotish_engine.py narayana-dasha --year REDACTED_YEAR --month 4 --day 17 \\
--hour 14 --minute 45 --lat 36.47 --lon 114.2 --tz 8 --age 33
python jyotish_engine.py narayana-dasha --year 1935 --month 6 --day 21 \\
--hour 11 --minute 0 --lat 44.486 --lon 1.842 --tz 1 --age 33
"""
if chart_data is None:
return "\n".join([
+2 -2
View File
@@ -5,8 +5,8 @@ cmd_solar_return: Solar Return (Varshaphala) CLI 子命令
Jyotish Vedic Astrology Skill — v6.0.18
用法:
python3 jyotish_engine.py solar-return --year REDACTED_YEAR --month 4 --day 17 \
--hour 14 --minute 45 --lat 36.4667 --lon 114.2 --tz 8 --target-year 2026
python3 jyotish_engine.py solar-return --year 1935 --month 6 --day 21 \
--hour 11 --minute 0 --lat 44.486 --lon 1.842 --tz 1 --target-year 2026
"""
import sys
+39 -13
View File
@@ -587,7 +587,7 @@ def calc_dispositor_chain(planet_name, planets_data, max_depth=12):
return chain
def calc_inter_chart_linkage(planet_name, d1_data, d9_data, d10_data, d12_data=None, d1_asc_idx=None, d9_asc_idx=None, d10_asc_idx=None):
def calc_inter_chart_linkage(planet_name, d1_data, d9_data, d10_data, d12_data=None, d1_asc_idx=None, d9_asc_idx=None, d10_asc_idx=None, d12_asc_idx=None):
"""
计算行星在 D1/D9/D10 分盘之间的飞星落宫。
返回 {chart_type: {sign, house_in_chart, lord_of_sign_in_chart}}
@@ -599,7 +599,8 @@ def calc_inter_chart_linkage(planet_name, d1_data, d9_data, d10_data, d12_data=N
('D10', d10_data, d10_asc_idx),
]
if d12_data:
charts.append(('D12', d12_data, None))
# D12 houses count from the D12 ascendant (upstream cf6c316d / f60568a0).
charts.append(('D12', d12_data, d12_asc_idx))
for chart_name, chart_data, asc_idx in charts:
if not isinstance(chart_data, dict):
@@ -608,9 +609,14 @@ def calc_inter_chart_linkage(planet_name, d1_data, d9_data, d10_data, d12_data=N
if not isinstance(pdata, dict) or 'sign' not in pdata:
continue
sign = pdata['sign']
sign_idx = SIGNS.index(sign) if sign in SIGNS else 0
if sign not in SIGNS:
# An unknown sign used to become Aries silently.
raise ValueError(f'Unknown sign in {chart_name}: {sign!r}')
sign_idx = SIGNS.index(sign)
house = None
if asc_idx is not None:
if isinstance(asc_idx, bool) or not isinstance(asc_idx, int) or not 0 <= asc_idx < 12:
raise ValueError(f'Invalid ascendant sign index in {chart_name}: {asc_idx!r}')
house = ((sign_idx - asc_idx) % 12) + 1
lord = SIGN_LORDS.get(sign, '')
result[chart_name] = {
@@ -655,7 +661,7 @@ def calc_all_dispositor_chains(planets_data):
return result
def calc_all_inter_chart_linkages(d1_data, d9_data, d10_data, d12_data=None, d1_asc_idx=None, d9_asc_idx=None, d10_asc_idx=None):
def calc_all_inter_chart_linkages(d1_data, d9_data, d10_data, d12_data=None, d1_asc_idx=None, d9_asc_idx=None, d10_asc_idx=None, d12_asc_idx=None):
"""
为所有行星计算 D1/D9/D10 分盘间飞星。
需要各分盘的 asc_idx 来正确计算落宫。
@@ -664,7 +670,7 @@ def calc_all_inter_chart_linkages(d1_data, d9_data, d10_data, d12_data=None, d1_
for pname in ['Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter', 'Venus', 'Saturn', 'Rahu', 'Ketu']:
result[pname] = calc_inter_chart_linkage(
pname, d1_data, d9_data, d10_data, d12_data,
d1_asc_idx, d9_asc_idx, d10_asc_idx
d1_asc_idx, d9_asc_idx, d10_asc_idx, d12_asc_idx
)
return result
@@ -13765,6 +13771,12 @@ def cmd_kp(args):
_apply_ayanamsa(requested_report_ayanamsa)
return {"error": "swisseph未安装"}
# The chart's julian_day is rounded for display. KP cusp and sub-lord
# boundaries use the civil instant itself, seconds included (upstream
# 26d5a52d, BUG-1543). Same formula as compute_chart_data.
jd = swe.julday(args.year, args.month, args.day,
_birth_hour_decimal(args.hour, args.minute, _arg_second(args)) - args.tz)
asc_sign = chart.get("ascendant", {}).get("sign", "Aries")
planets = chart.get("planets", {})
planet_lons = {
@@ -13778,7 +13790,8 @@ def cmd_kp(args):
from bhava_chalit import BhavaChalitCalculator
kp_house_calculator = BhavaChalitCalculator()
asc_lon = chart.get('ascendant', {}).get('degree_raw', chart.get('ascendant', {}).get('degree', 0.0))
boundaries = kp_house_calculator.calculate_bhava_boundaries(
# Unrounded cusps; calculate_bhava_boundaries rounds them to 4 decimals.
raw_cusps = kp_house_calculator.calculate_cusps(
float(asc_lon), 0.0, 'placidus', jd, args.lat, args.lon,
)
bhava_chart = kp_house_calculator.get_bhava_chalit_chart(
@@ -13792,16 +13805,19 @@ def cmd_kp(args):
for pname, pdata in planets.items():
if isinstance(pdata, dict) and 'sign' in pdata:
bhava_row = bhava_planets.get(pname) if isinstance(bhava_planets.get(pname), dict) else {}
raw_lon = planet_lons.get(pname)
planet_positions[pname] = {
'sign': pdata['sign'],
'degree': pdata.get('degree_in_sign', pdata.get('degree', 0) % 30),
# degree_in_sign is rounded to 4 decimals for display.
'degree': (float(raw_lon) % 30 if raw_lon is not None
else pdata.get('degree_in_sign', pdata.get('degree', 0) % 30)),
'house': bhava_row.get('bhava_house', pdata.get('house', 1)),
}
result = calc_kp_analysis(
planet_positions,
asc_sign,
house_cusps=boundaries.get('cusps'),
house_cusps=raw_cusps,
house_occupancy_basis='cusp_to_cusp',
)
moon_lon = planets.get('Moon', {}).get('degree_raw', planets.get('Moon', {}).get('degree', 0.0)) if isinstance(planets.get('Moon'), dict) else 0.0
@@ -15856,7 +15872,7 @@ def cmd_full_reading(args):
ic_result = calc_all_inter_chart_linkages(
d1_for_dc, d9_for_ic, d10_for_ic, d12_for_ic,
asc_idx, d9_asc_idx, d10_asc_idx
asc_idx, d9_asc_idx, d10_asc_idx, d12_asc_idx
)
report['modules']['inter_chart_linkage'] = ic_result
@@ -16124,7 +16140,10 @@ def cmd_full_reading(args):
from narayana_dasha import attach_narayana_method_comparison, narayana_dasha_full_report
# 计算当前年龄(从出生到 today)
birth_dt = _birth_datetime_from_args(args)
today_dt = datetime.strptime(args.today, '%Y-%m-%d') if hasattr(args, 'today') and args.today else datetime.now()
# One observation date for the whole report (upstream 933f254e, BUG-1542).
today_dt = datetime.strptime(
getattr(args, 'transit_date', None) or getattr(args, 'today', None)
or datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d')
current_age = (today_dt - birth_dt).days / 365.25
narayana_result = narayana_dasha_full_report(
lagna_sign_idx=asc_idx,
@@ -16451,7 +16470,9 @@ def cmd_full_reading(args):
if nak_age is None:
try:
birth_date = datetime(args.year, args.month, args.day)
nak_age = (datetime.now() - birth_date).days / 365.25
nak_reference = getattr(args, 'transit_date', None) or getattr(args, 'today', None)
observation = datetime.strptime(nak_reference, '%Y-%m-%d') if nak_reference else datetime.now()
nak_age = (observation - birth_date).days / 365.25
except:
nak_age = None
@@ -16490,8 +16511,13 @@ def cmd_full_reading(args):
if age is None:
# 自动计算年龄
try:
birth_date = datetime(args.year, args.month, args.day)
age = (datetime.now() - birth_date).days // 365
# Calendar-birthday age on the report's observation date; days // 365
# drifted about one day early every four years (upstream 933f254e).
ref_today = datetime.strptime(
getattr(args, 'transit_date', None) or getattr(args, 'today', None)
or datetime.now().strftime('%Y-%m-%d'), '%Y-%m-%d')
age = ref_today.year - int(args.year) - (
(ref_today.month, ref_today.day) < (int(args.month), int(args.day)))
report['warnings'].append(f"未提供年龄,自动计算为 {age} 岁")
except:
age = None
+1
View File
@@ -131,6 +131,7 @@ CORE_PYTEST_TARGETS = [
# Summary lines by default; failure keeps the last 200 lines and a full log (BUG-1230).
"tests/test_run_quality_gate_output.py",
"tests/test_upstream_git_import_b9a0ef8f.py",
"tests/test_upstream_git_import_5fb8379c.py",
"tests/test_interpretation_template_registry.py",
"tests/test_vedastro_external_technique_evidence.py",
# Self-hosted heading font slices: 6500-character coverage, no overlap, 120 KB cap.
@@ -0,0 +1,37 @@
"""Git-mode import record for upstream main 5fb8379c (tenth sync, 2026-11-04). Does not replace the 5db72537 archive pin test."""
from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
ROOT = Path(__file__).resolve().parents[1]
RECORD = ROOT / "references/cross_project_contract/imports/commit-5fb8379c4f3e649cb4804065c9a3059e4bf633fe.json"
SOURCE_MANIFEST = ROOT / "references/upstream/yinduzhanxing/source-manifest.json"
SKILL_SNAPSHOT = ROOT / "references/upstream/yinduzhanxing/SKILL.md"
SCHEMA = ROOT / "references/cross_project_contract/import_manifest.schema.json"
COMMIT = "5fb8379c4f3e649cb4804065c9a3059e4bf633fe"
def test_git_import_record_matches_mirrored_skill_and_manifest() -> None:
record = json.loads(RECORD.read_text(encoding="utf-8"))
manifest = json.loads(SOURCE_MANIFEST.read_text(encoding="utf-8"))
schema = json.loads(SCHEMA.read_text(encoding="utf-8"))
Draft202012Validator(schema, format_checker=FormatChecker()).validate(record)
assert record["source_mode"] == "git"
assert record["source_commit"] == COMMIT
assert re.fullmatch(r"[0-9a-f]{40}", record["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", record["source_tree_hash"])
assert record["source_tree_hash"] == manifest["source_tree_sha256"]
assert record["source_skill_sha256"] == manifest["source_skill_sha256"]
snapshot_hash = hashlib.sha256(SKILL_SNAPSHOT.read_bytes()).hexdigest()
assert snapshot_hash == record["source_skill_sha256"] == record["mirror_files"][0]["target_sha256_after"]
assert record["mirror_files"][0]["status"] == "applied"
assert manifest["source_mode"] == "git"
assert manifest["source_commit"] == COMMIT
assert all(row["status"] == "review_required" for row in record["semantic_merge_files"])
+11 -11
View File
@@ -1,8 +1,12 @@
"""Git-mode import record for upstream b9a0ef8f. Does not replace the 5db72537 archive pin test."""
"""Git-mode import record for upstream b9a0ef8f, kept as history.
原值: 本文件要求 source-manifest.json 与 SKILL.md 快照仍停在 b9a0ef8f
新值: 只核对这份历史记录自身完整,且已不是当前镜像
原因: 2026-11-04 第十轮同步把镜像移到 5fb8379c(tests/test_upstream_git_import_5fb8379c.py)
"""
from __future__ import annotations
import hashlib
import json
import re
from pathlib import Path
@@ -12,12 +16,12 @@ from jsonschema import Draft202012Validator, FormatChecker
ROOT = Path(__file__).resolve().parents[1]
RECORD = ROOT / "references/cross_project_contract/imports/commit-b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09.json"
SOURCE_MANIFEST = ROOT / "references/upstream/yinduzhanxing/source-manifest.json"
SKILL_SNAPSHOT = ROOT / "references/upstream/yinduzhanxing/SKILL.md"
SCHEMA = ROOT / "references/cross_project_contract/import_manifest.schema.json"
COMMIT = "b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09"
HISTORICAL_SKILL_SHA256 = "6bed459b8b971b189895e62aabfb90e41dbdf8f5b9e80a4b5330d40922fe21eb"
def test_git_import_record_matches_mirrored_skill_and_manifest() -> None:
def test_historical_git_import_record_stays_valid_and_is_not_the_current_mirror() -> None:
record = json.loads(RECORD.read_text(encoding="utf-8"))
manifest = json.loads(SOURCE_MANIFEST.read_text(encoding="utf-8"))
schema = json.loads(SCHEMA.read_text(encoding="utf-8"))
@@ -25,13 +29,9 @@ def test_git_import_record_matches_mirrored_skill_and_manifest() -> None:
assert record["source_mode"] == "git"
assert record["source_commit"] == COMMIT
assert re.fullmatch(r"[0-9a-f]{40}", record["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", record["source_tree_hash"])
assert record["source_tree_hash"] == manifest["source_tree_sha256"]
assert record["source_skill_sha256"] == manifest["source_skill_sha256"]
snapshot_hash = hashlib.sha256(SKILL_SNAPSHOT.read_bytes()).hexdigest()
assert snapshot_hash == record["source_skill_sha256"] == record["mirror_files"][0]["target_sha256_after"]
assert record["source_skill_sha256"] == HISTORICAL_SKILL_SHA256
assert record["mirror_files"][0]["target_sha256_after"] == HISTORICAL_SKILL_SHA256
assert record["mirror_files"][0]["status"] == "applied"
assert manifest["source_mode"] == "git"
assert manifest["source_commit"] == COMMIT
assert all(row["status"] == "review_required" for row in record["semantic_merge_files"])
assert manifest["source_commit"] != COMMIT
+10 -6
View File
@@ -41,16 +41,20 @@ EXPECTED_REJECTIONS = {
"weaken_stability_gate",
"remove_commercial_routes",
}
# 原值: b9a0ef8f(树 9ae737b0…、git 树 864cab47…、SKILL 6bed459b…、提交时间 2026-09-08T17:36:09Z)
# 新值: 5fb8379c(上游 main 2026-11-04 的 HEAD)
# 原因: 第十轮同步用导入器重新镜像 SKILL.md;旧记录留作历史,见 test_upstream_git_import_b9a0ef8f.py
EXPECTED_UPSTREAM_IDENTITY = {
"source_repository": "732642856/yinduzhanxing",
"source_repository_url": "https://github.com/732642856/yinduzhanxing",
"source_commit": "b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09",
"source_tree_sha256": "9ae737b02a48b38dca7d78094413b961a75e5abad17f2fe206f7dc3b1401d51a",
"source_commit": "5fb8379c4f3e649cb4804065c9a3059e4bf633fe",
"source_tree_sha256": "ee8bd9865cf11a6eb7f90705a262db8b2d145ae0cee6ee6eda32b311548f5aa2",
}
EXPECTED_SOURCE_GIT_TREE = "864cab471cadb31b71a7256c995c12b7552d7cb5"
EXPECTED_SOURCE_SKILL_SHA256 = "6bed459b8b971b189895e62aabfb90e41dbdf8f5b9e80a4b5330d40922fe21eb"
EXPECTED_SOURCE_GIT_TREE = "6cd6160804ea7419d609ccd639cc537a8a81c3c9"
EXPECTED_SOURCE_SKILL_SHA256 = "5c1c0738fbe6f586fad36cfa13f602934dcea8f8d54c536655f6cb2091773d8d"
EXPECTED_SOURCE_COMMITTED_AT = "2026-10-21T15:49:21Z"
LEGACY_ARCHIVE_COMMIT = "5db72537741fcedaa7b5498502d4a31b0f9fc147"
CURRENT_IMPORT_RECORD = ROOT / "references/cross_project_contract/imports/commit-b9a0ef8f0f0de8714982a5aa7100f5aea16c9d09.json"
CURRENT_IMPORT_RECORD = ROOT / "references/cross_project_contract/imports/commit-5fb8379c4f3e649cb4804065c9a3059e4bf633fe.json"
def _load(path: Path) -> dict:
@@ -128,7 +132,7 @@ def test_source_manifest_pins_valid_git_commit_tree_and_root_skill_hash() -> Non
assert manifest["source_git_tree"] == EXPECTED_SOURCE_GIT_TREE
assert "archive_sha256" not in manifest
assert "archive_file_count" not in manifest
assert manifest["source_committed_at"] == "2026-09-08T17:36:09Z"
assert manifest["source_committed_at"] == EXPECTED_SOURCE_COMMITTED_AT
assert re.fullmatch(r"[0-9a-f]{40}", manifest["source_commit"])
assert re.fullmatch(r"[0-9a-f]{64}", manifest["source_tree_sha256"])
source_skill_sha256 = manifest.get("source_skill_sha256", manifest["skill_sha256"])
+47
View File
@@ -0,0 +1,47 @@
"""D12 houses in the inter-chart linkage count from the D12 ascendant.
Ported from upstream yinduzhanxing cf6c316d and f60568a0 (sync 10). Before
the fix the D12 row always had house None, and an unknown sign became Aries.
"""
import ast
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
from jyotish_engine import calc_all_inter_chart_linkages # noqa: E402
@pytest.mark.parametrize('ascendant', range(12))
def test_d12_every_house_from_its_own_ascendant(ascendant):
frame = {'Sun': {'sign': 'Taurus'}}
actual = calc_all_inter_chart_linkages(frame, frame, frame, frame, 0, 4, 8, ascendant)['Sun']
assert actual['D12']['house'] == (1 - ascendant) % 12 + 1
assert actual['D1']['house'] == 2
assert actual['D9']['house'] == 10
assert actual['D10']['house'] == 6
def test_missing_d12_ascendant_is_not_aries():
assert calc_all_inter_chart_linkages({}, {}, {}, {'Sun': {'sign': 'Taurus'}})['Sun']['D12']['house'] is None
def test_bad_sign_does_not_become_aries():
with pytest.raises(ValueError):
calc_all_inter_chart_linkages({'Sun': {'sign': 'unknown'}}, {}, {}, d1_asc_idx=0)
@pytest.mark.parametrize('bad', [True, -1, 12, 1.0])
def test_bad_ascendant_index_is_rejected(bad):
with pytest.raises(ValueError):
calc_all_inter_chart_linkages({'Sun': {'sign': 'Taurus'}}, {}, {}, d1_asc_idx=bad)
def test_full_reading_passes_the_precomputed_d12_index():
tree = ast.parse((ROOT / 'scripts/jyotish_engine.py').read_text(encoding='utf-8'))
calls = [n for n in ast.walk(tree) if isinstance(n, ast.Call) and isinstance(n.func, ast.Name)
and n.func.id == 'calc_all_inter_chart_linkages']
assert calls
assert all(isinstance(c.args[-1], ast.Name) and c.args[-1].id == 'd12_asc_idx' for c in calls)
@@ -0,0 +1,48 @@
"""KP must not reuse display-rounded time, cusps or planet degrees (BUG-1543).
Ported from upstream yinduzhanxing 26d5a52d tests/test_kp_unrounded_inputs.py
(sync 10). Synthetic chart only.
"""
import sys
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
import jyotish_engine as engine # noqa: E402
from bhava_chalit import BhavaChalitCalculator # noqa: E402
def test_kp_passes_raw_time_cusps_and_longitudes(monkeypatch):
args = SimpleNamespace(year=2000, month=1, day=2, hour=3, minute=4, second=5,
lat=20., lon=75., tz=5.5, ayanamsa='raman')
chart = {'ascendant': {'sign': 'Aries', 'degree_raw': 12.123456789},
'planets': {'Sun': {'sign': 'Aries', 'degree_raw': 1.234567891, 'degree_in_sign': 1.2346},
'Moon': {'sign': 'Taurus', 'degree_raw': 42.987654321, 'degree_in_sign': 12.9877}}}
raw_cusps = [12.123456789 + 30 * i for i in range(12)]
seen = {}
monkeypatch.setattr(engine, '_compute_chart_from_args', lambda a: (chart, 0, 2451545., 23.))
def cusps(self, asc, mc, hs, jd, lat, lon):
seen['jd'] = jd
return raw_cusps
monkeypatch.setattr(BhavaChalitCalculator, 'calculate_cusps', cusps)
monkeypatch.setattr(BhavaChalitCalculator, 'get_bhava_chalit_chart',
lambda *a, **k: {'planets': {'Sun': {'bhava_house': 12}, 'Moon': {'bhava_house': 2}}})
def analyze(planets, asc, **kwargs):
seen['planets'] = planets
seen.update(kwargs)
return {}
monkeypatch.setattr(engine, 'calc_kp_analysis', analyze)
monkeypatch.setattr(engine, 'attach_calculation_profile', lambda result, args: result)
monkeypatch.setattr(engine, '_build_response_envelope', lambda command, result, **k: result)
engine.cmd_kp(args)
expected = engine.swe.julday(2000, 1, 2, 3 + 4 / 60 + 5 / 3600 - 5.5)
assert seen['jd'] == expected and seen['jd'] != round(expected, 6)
assert seen['house_cusps'] == raw_cusps
assert seen['planets']['Sun']['degree'] == 1.234567891
assert seen['planets']['Moon']['degree'] == 42.987654321 % 30
assert seen['house_occupancy_basis'] == 'cusp_to_cusp'
@@ -0,0 +1,109 @@
"""A dated full reading uses one observation date everywhere (BUG-1542).
Ported from upstream yinduzhanxing 933f254e tests/test_full_reading_observation_date.py
and tests/test_tajika_report_age.py (sync 10). The expressions are taken from
scripts/jyotish_engine.py by name, so the real code is exercised without
generating a report.
"""
import ast
from datetime import datetime
from functools import lru_cache
from pathlib import Path
from types import SimpleNamespace
import pytest
ENGINE = Path(__file__).resolve().parents[1] / 'scripts/jyotish_engine.py'
@lru_cache(maxsize=1)
def _tree():
return ast.parse(ENGINE.read_text(encoding='utf-8'))
@lru_cache(maxsize=1)
def clock_expressions():
found = {}
for node in ast.walk(_tree()):
if not isinstance(node, ast.Assign):
continue
names = [t.id for t in node.targets if isinstance(t, ast.Name)]
for name in ('today_str', 'transit_reference_date', 'today_dt', 'ref_today'):
if name in names:
assert name not in found, f'{name} assigned twice; the test would read only one'
found[name] = node.value
return {k: compile(ast.Expression(v), '<report-clock>', 'eval') for k, v in found.items()}
class _Clock(datetime):
@classmethod
def now(cls, tz=None):
return cls(2030, 6, 7)
@pytest.mark.parametrize('arguments,expected', [
({'transit_date': '2001-02-03', 'today': '2020-04-05'}, '2001-02-03'),
({'transit_date': '2001-02-03'}, '2001-02-03'),
({'today': '2020-04-05'}, '2020-04-05'),
({}, '2030-06-07'),
])
def test_all_report_periods_observe_the_same_requested_date(arguments, expected):
values = {k: eval(code, {'args': SimpleNamespace(**arguments), 'datetime': _Clock})
for k, code in clock_expressions().items()}
values = {k: v.strftime('%Y-%m-%d') if isinstance(v, datetime) else v for k, v in values.items()}
assert values == dict.fromkeys(('today_str', 'transit_reference_date', 'today_dt', 'ref_today'), expected)
def _block_after(predicate, length):
for function in _tree().body:
if not isinstance(function, ast.FunctionDef) or function.name not in ('_cmd_full_reading', 'cmd_full_reading'):
continue
for node in ast.walk(function):
body = getattr(node, 'body', None)
if not isinstance(body, list):
continue
for index, child in enumerate(body):
if predicate(child):
return compile(ast.Module(body=body[index:index + length], type_ignores=[]), '<block>', 'exec')
raise AssertionError('block not found in cmd_full_reading')
def _assigns_args_age(node, target):
return (isinstance(node, ast.Assign) and [getattr(t, 'id', None) for t in node.targets] == [target]
and isinstance(node.value, ast.Attribute) and node.value.attr == 'age'
and isinstance(node.value.value, ast.Name) and node.value.value.id == 'args')
@pytest.mark.parametrize('extra,expected', [
({'today': '2020-12-30'}, 20),
({'today': '2021-01-01'}, 21),
({'today': '2021-01-01', 'transit_date': '2020-12-30'}, 20),
({'today': '2020-12-30', 'age': 18}, 18),
])
def test_fallback_annual_age_changes_at_the_calendar_birthday(extra, expected):
code = _block_after(lambda n: _assigns_args_age(n, 'age'), 2)
args = SimpleNamespace(**{'year': 2000, 'month': 1, 'day': 1, 'age': None, **extra})
state = {'args': args, 'datetime': datetime, 'report': {'warnings': []}}
exec(code, state)
assert state['age'] == expected
def test_fallback_annual_age_is_not_days_over_365():
# 2000-01-01 to 2039-12-25 is 14603 days; 14603 // 365 = 40, but the person is 39.
code = _block_after(lambda n: _assigns_args_age(n, 'age'), 2)
args = SimpleNamespace(year=2000, month=1, day=1, age=None, today='2039-12-25')
state = {'args': args, 'datetime': datetime, 'report': {'warnings': []}}
exec(code, state)
assert state['age'] == 39
@pytest.mark.parametrize('extra,expected', [
({'transit_date': '2010-01-01', 'today': '2020-01-01'}, 3653 / 365.25),
({'today': '2020-01-01'}, 7305 / 365.25),
])
def test_nakshatra_dasha_age_uses_the_observation_date(extra, expected):
code = _block_after(lambda n: _assigns_args_age(n, 'nak_age'), 2)
args = SimpleNamespace(**{'year': 2000, 'month': 1, 'day': 1, 'age': None, **extra})
state = {'args': args, 'datetime': _Clock}
exec(code, state)
assert state['nak_age'] == pytest.approx(expected)