fix(reading): give Niryaana Shoola its own PVR table
The report used the same nine-year rule as Shoola. Start from the stronger second or eighth sign and list mahadashas only. Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Grok (agent loop)
Cursor
parent
b827901c1d
commit
4d2506c8c1
@@ -1,5 +1,9 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-10-19 — 完整数据版报告的 Niryaana Shoola 大运表改了
|
||||
|
||||
- 这张表不再和 Shoola 相同。起运从第二宫和第八宫里较强的星座开始;奇数星座顺行,偶数星座逆行;动、固、双星座分别是 7、8、9 年。只列主运,子运暂不列出。两宫分不出强弱时,这张表本轮不列出。合盘仍不用它做应期。
|
||||
|
||||
## 2026-10-18 — 完整解读补上西占对照和五处缺失模块
|
||||
|
||||
- 个人报告里的西占对照层不再因为缺文件而整段空白。它仍只作对照,不拿来改印度占星的结论。
|
||||
|
||||
@@ -36840,3 +36840,18 @@
|
||||
- 相关记录:BUG-423、BUG-1367。
|
||||
- 复发自:无。
|
||||
- 修复版本:分支 `codex/rectification-narrower-delivery-20261016`。
|
||||
|
||||
## BUG-1393 | Niryaana Shoola 和 Shoola 用了同一套算法,报告里两张表逐行相同
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现 / 最近更新:2026-10-18 / 2026-10-19
|
||||
- 来源:issue #84。任务书 `docs/tasks/TASK-niryaana-shoola-pvr-20261018.md`。检索「niryaana_shoola」「goel_nirayana_shoola_v1」,命中 BUG-1283。
|
||||
- 现象:完整数据版报告里的 Niryaana Shoola 与 Shoola 逐行相同。两张表都从上升和第七宫里取较强的星座起运,每宫 9 年,一律顺行。
|
||||
- 触发条件:打开带本命盘的完整数据版报告,或读取 full-reading 的 `niryaana_shoola_dasha`。
|
||||
- 根因:BUG-1283 把报告改走 `goel_nirayana_shoola_v1` 时,所选规则其实是 Shoola 的。当时没有和同文件里的 Shoola 对过输出。本条推翻该 profile。BUG-1283 的另一半不变:新路径仍不得调用旧的 `scripts/niryaana_shoola_dasha.py`。
|
||||
- 修复:在 `scripts/rashi_dashas_native.py` 内改成 `pvr_niryaana_shoola_v1`。起运比较第二宫和第八宫,依次看宫内九曜个数、星座相位数、有没有入旺行星;三层都相同则本轮不上表。奇数星座顺行,偶数星座逆行。动、固、双星座分别 7、8、9 年。只出主运。full-reading 的 `niryaana_shoola_dasha` 改填这张表。合盘校验器对这一行仍固定为不适用。
|
||||
- 验证:`tests/test_rashi_dashas_native.py` 在屏蔽 PyJHora 后 18 通过;`tests/test_full_reading_module_parity.py` 与 `tests/test_niryaana_shoola_dasha.py` 合计 10 通过。同一张样例盘上,Niryaana Shoola 与 Shoola 的首运或年数不再全同。语法树断言仍确认新路径不导入旧文件。
|
||||
- 防复发:上述测试。不得再把 Niryaana Shoola 接回 `goel_nirayana_shoola_v1` 或 `_nine_year`。平手不得默认挑一个星座。不得为这张表编造子运。
|
||||
- 相关记录:BUG-1283。
|
||||
- 复发自:无。这是推翻 BUG-1283 所选 profile,不是旧文件被重新调用。
|
||||
- 修复版本:分支 `codex/niryaana-shoola-pvr-20261018`。
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 进度:Niryaana Shoola 改为 PVR 口径(2026-10-19)
|
||||
|
||||
- 基线:`origin/staging` `b827901c1`。分支 `codex/niryaana-shoola-pvr-20261018`。
|
||||
- 前置:#81 已在 staging(`900581068`),issue 标签为 `agent/待真机`。本单在其后开工。
|
||||
- BUG 编号:开工时最大号 BUG-1392,本单使用 BUG-1393。关联 BUG-1283,并写明推翻其所选 `goel_nirayana_shoola_v1`。旧文件仍不得被新路径调用。
|
||||
|
||||
## 做了什么
|
||||
|
||||
- `scripts/rashi_dashas_native.py`:Niryaana Shoola 改为 `pvr_niryaana_shoola_v1`。起运比较第二宫和第八宫(九曜个数、星座相位数、入旺行星);奇数星座顺行,偶数星座逆行;动 / 固 / 双星座 7 / 8 / 9 年。只出主运。三层平手则本轮不上表。没有搬上游文件,也没有 import 旧的 `niryaana_shoola_dasha.py`。
|
||||
- `scripts/jyotish_engine.py`:full-reading 的 `niryaana_shoola_dasha` 改填上面的结果,替换 #81 的占位。
|
||||
- `scripts/rashi_dasha_report.py`:该节列出主运,并写「子运规则未定,暂不列出。」
|
||||
- 对照文件里 Niryaana Shoola 的本站行和差异步骤按新表重记。PyJHora 行没有重跑。
|
||||
|
||||
## 断言三栏
|
||||
|
||||
| 位置 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `test_rashi_dashas_native.py` 同盘 Niryaana | 首运 Virgo,十二段都是 9 年,有子运 | `pvr_niryaana_shoola_v1`,首运 Aries、顺行,年数从 7/8/9 起,无子运 | 推翻与 Shoola 相同的旧 profile |
|
||||
| `test_full_reading_module_parity.py` | `execution_status=blocked`,原因「口径更正中」 | `parameter_sensitive`,profile `pvr_niryaana_shoola_v1`,periods 为主运 | #84 用真结果替换占位 |
|
||||
| fixture `native.niryaana_shoola` 与 `diff_steps` | 与 Shoola 相同的 9 年顺行表 | PVR 主运星座和年数;差异步骤按新表重记 | 只改本站期望,PyJHora 行不动 |
|
||||
|
||||
## 命令
|
||||
|
||||
工作树没有 `.venv`。下面用已装 Swiss Ephemeris 的 Anaconda 3.11:`C:\Users\74082\anaconda3\python.exe`。
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `tests/test_rashi_dashas_native.py`(屏蔽 PyJHora 导入) | 18 通过,退出码 0 |
|
||||
| `tests/test_full_reading_module_parity.py` + `tests/test_niryaana_shoola_dasha.py` | 10 通过,退出码 0 |
|
||||
| 全量 pytest、`run_quality_gate.py --profile quick` | 未跑。Windows 分工把这两项留给 Claude 验收 |
|
||||
|
||||
## 偏离
|
||||
|
||||
- 没有整份搬上游 `niryaana_shoola_dasha.py`。力量比较、方向和年数按上游 `9f08b57b` / `258e08e3` 写在本仓文件里。
|
||||
- 两位公开名人用仓库里已有的公开盘:一张起射手并顺行,一张起水瓶并顺行,主运序列与该起运下的 7/8/9 年规则一致。
|
||||
- 平手样例(七曜都在巨蟹、白羊上升)结果为「本轮不上表」。节点占宫、节点星座相位、外行星不计数,与上游 `258e08e3` 的样例一致。
|
||||
@@ -505,6 +505,6 @@
|
||||
| `TASK-marriage-match-p1-fix-20261018.md` | #79 | 只存两个 profile 的配对结果;旧历史列表不读新行;并发重复写入改为重读 | 待验收 | `codex/marriage-match-p1-20261017` |
|
||||
| `TASK-marriage-match-p1-fix-20261018-2.md` | #79 | 胶水挪进子包,根目录文件数回到 446;调用清单改指向 marriage_match.py | 待验收 | `codex/marriage-match-p1-20261017` |
|
||||
| `TASK-full-reading-module-parity-20261018.md` | #81 | full-reading 补齐上游 5 个模块(西占层文件没搬致永远 blocked、Navamsha / Niryaana 用我们自己的实现填键、Lagna Vargottamamsa / Pushkara / Punarphoo)(BUG-1375 起) | 待执行(可与 #78 并行) | `codex/full-reading-module-parity-20261018` |
|
||||
| `TASK-niryaana-shoola-pvr-20261018.md` | #84 | Niryaana Shoola 改 PVR 口径(我们的表与 Shoola 逐行相同;推翻 BUG-1283 所选 profile;占星师 7a773d13) | 排队(等 #81) | `codex/niryaana-shoola-pvr-20261018` |
|
||||
| `TASK-niryaana-shoola-pvr-20261018.md` | `PROGRESS-niryaana-shoola-pvr-20261018.md` | #84 Niryaana Shoola 改 PVR 口径(我们的表与 Shoola 逐行相同;推翻 BUG-1283 所选 profile;占星师 7a773d13) | 待验收 | `codex/niryaana-shoola-pvr-20261018` |
|
||||
| `TASK-synastry-full-report-20261018.md` | #82 | 婚恋合盘:选人页 + 完整合盘报告(关系画像九节:结论+折叠的分析逻辑+关系时间线+技术依据摘要;英文按需;计费键 synastry.full) | 排队(等 #78、#79、#81) | `codex/synastry-full-report-20261018`;动表 |
|
||||
| `TASK-synastry-report-chat-20261018.md` | #83 | 从合盘报告某一节进入对话问依据(绑定报告与节、对话里可用术语、只依据已存计算结果并逐条核对、计费照普通对话) | 排队(等 #82) | `codex/synastry-report-chat-20261018`;动表 |
|
||||
|
||||
@@ -16124,18 +16124,20 @@ def cmd_full_reading(args):
|
||||
planet_lons,
|
||||
)
|
||||
report['modules']['navamsha_dasha'] = _public_secondary_dasha_module(secondary_dashas.get('navamsha'))
|
||||
report['modules']['niryaana_shoola_dasha'] = _public_secondary_dasha_module(
|
||||
secondary_dashas.get('niryaana_shoola')
|
||||
)
|
||||
except Exception as e:
|
||||
report['modules']['navamsha_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'navamsha_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
# F2b: do not publish the current Niryaana Shoola table. It matches Shoola.
|
||||
# The synastry validator marks this row not applicable, so the pair task stays open.
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'execution_status': 'blocked',
|
||||
'reason': '口径更正中,见 TASK-niryaana-shoola-pvr-20261018',
|
||||
}
|
||||
report['modules']['niryaana_shoola_dasha'] = {
|
||||
'status': 'blocked',
|
||||
'reason': f'niryaana_shoola_dasha_producer_failed:{e}',
|
||||
'periods': [],
|
||||
}
|
||||
_record_stage_timing(
|
||||
stage_timings,
|
||||
'dasha_and_core_varga_stack',
|
||||
|
||||
@@ -140,6 +140,13 @@ def render_rashi_dasha_chapter(packet: Mapping) -> str:
|
||||
section = sections[key]
|
||||
title = title_en if language == "en" else title_zh
|
||||
lines.extend([f"### {title}", "", _method_line(section, language), ""])
|
||||
if key == "niryaana_shoola" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"Child periods stay off this table: the cited book does not give a separate rule for them."
|
||||
if language == "en"
|
||||
else "子运规则未定,暂不列出。",
|
||||
"",
|
||||
])
|
||||
if key == "chara" and section.get("status") == "listed":
|
||||
lines.extend([
|
||||
"This section is the K.N. Rao Chara table. It is not the Narayana table."
|
||||
|
||||
@@ -38,6 +38,17 @@ except ImportError: # pragma: no cover - package import
|
||||
_MOVABLE = {0, 3, 6, 9}
|
||||
_FIXED = {1, 4, 7, 10}
|
||||
_DUAL = {2, 5, 8, 11}
|
||||
# Nine grahas only. Outer planets and upagrahas are ignored. Nodes are not weighted.
|
||||
_AYUR_RASI_STRENGTH_BODIES = (
|
||||
"Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu",
|
||||
)
|
||||
_EXALTED_SIGNS = {
|
||||
"Sun": {0}, "Moon": {1}, "Mars": {9}, "Mercury": {5},
|
||||
"Jupiter": {3}, "Venus": {11}, "Saturn": {6},
|
||||
}
|
||||
_NIRYAANA_SOURCE_ZH = "PVR《Vedic Astrology: An Integrated Approach》"
|
||||
_NIRYAANA_SOURCE_EN = "PVR, Vedic Astrology: An Integrated Approach"
|
||||
_NIRYAANA_PROFILE = "pvr_niryaana_shoola_v1"
|
||||
|
||||
|
||||
def _as_date(value: date | datetime | str) -> date:
|
||||
@@ -303,6 +314,171 @@ def _drig_children(md_sign: int, start: date, end: date, graha: Mapping, planet_
|
||||
return _equal_children(order, start, end)
|
||||
|
||||
|
||||
def niryaana_shoola_years(sign_idx: int) -> int:
|
||||
"""Movable, fixed, and dual signs last 7, 8, and 9 years."""
|
||||
sign_idx = int(sign_idx) % 12
|
||||
if sign_idx in _FIXED:
|
||||
return 8
|
||||
if sign_idx in _DUAL:
|
||||
return 9
|
||||
return 7
|
||||
|
||||
|
||||
def select_niryaana_shoola_start_sign(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
planet_longitudes: Mapping[str, float],
|
||||
) -> dict:
|
||||
"""Pick the stronger of the second and eighth signs for a longevity dasha.
|
||||
|
||||
Compare occupant count, then every planet's Jaimini sign aspect (the sign
|
||||
lord is not weighted), then whether an occupant is exalted. A three-way
|
||||
tie stays off the table. Count only the nine grahas.
|
||||
"""
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
second, eighth = (lagna + 1) % 12, (lagna + 7) % 12
|
||||
positions = {
|
||||
name: float(longitude)
|
||||
for name, longitude in planet_longitudes.items()
|
||||
if name in _AYUR_RASI_STRENGTH_BODIES and isinstance(longitude, (int, float))
|
||||
}
|
||||
candidates = {}
|
||||
for sign_idx in (second, eighth):
|
||||
occupants = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) == sign_idx
|
||||
)
|
||||
aspecting = sorted(
|
||||
name for name, longitude in positions.items()
|
||||
if sign_index(longitude) != sign_idx and sign_idx in _aspecting_signs(sign_index(longitude))
|
||||
)
|
||||
exalted = sorted(
|
||||
name for name in occupants
|
||||
if sign_idx in _EXALTED_SIGNS.get(name, set())
|
||||
)
|
||||
candidates[sign_idx] = {
|
||||
"sign_index": sign_idx,
|
||||
"sign": SIGNS[sign_idx],
|
||||
"occupants": occupants,
|
||||
"occupant_count": len(occupants),
|
||||
"rasi_aspecting_planets": aspecting,
|
||||
"rasi_aspect_count": len(aspecting),
|
||||
"exalted_occupants": exalted,
|
||||
}
|
||||
second_score = (
|
||||
candidates[second]["occupant_count"],
|
||||
candidates[second]["rasi_aspect_count"],
|
||||
bool(candidates[second]["exalted_occupants"]),
|
||||
)
|
||||
eighth_score = (
|
||||
candidates[eighth]["occupant_count"],
|
||||
candidates[eighth]["rasi_aspect_count"],
|
||||
bool(candidates[eighth]["exalted_occupants"]),
|
||||
)
|
||||
payload = {
|
||||
"profile": "pvr_niryaana_shoola_ayur_rasi_strength_v1",
|
||||
"included_bodies": list(_AYUR_RASI_STRENGTH_BODIES),
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"candidates": candidates,
|
||||
"selection_levels": [
|
||||
"occupant_count",
|
||||
"all_planet_rasi_aspect_count",
|
||||
"exalted_occupant_presence",
|
||||
],
|
||||
}
|
||||
if second_score == eighth_score:
|
||||
return {
|
||||
**payload,
|
||||
"status": "blocked",
|
||||
"reason": "niryaana_shoola_ayur_rasi_strength_tie_requires_source_rule",
|
||||
}
|
||||
selected = second if second_score > eighth_score else eighth
|
||||
return {
|
||||
**payload,
|
||||
"status": "selected",
|
||||
"selected_sign_idx": selected,
|
||||
"selected_sign": SIGNS[selected],
|
||||
}
|
||||
|
||||
|
||||
def niryaana_shoola_applicability(
|
||||
*,
|
||||
ascendant_longitude: float,
|
||||
second_strength: float,
|
||||
eighth_strength: float,
|
||||
) -> dict:
|
||||
"""Start from the stronger given second or eighth strength. Equal strength blocks."""
|
||||
lagna = sign_index(float(ascendant_longitude))
|
||||
second, eighth = (lagna + 1) % 12, (lagna + 7) % 12
|
||||
second_strength, eighth_strength = float(second_strength), float(eighth_strength)
|
||||
if second_strength == eighth_strength:
|
||||
return {
|
||||
"applicable": False,
|
||||
"execution_status": "blocked",
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"second_strength": second_strength,
|
||||
"eighth_strength": eighth_strength,
|
||||
"rule_family": "pvr_niryaana_shoola_2nd_8th_modality",
|
||||
"reason": "The cited Niryaana Shoola rule does not provide a tie-break for equal second/eighth strength.",
|
||||
}
|
||||
start = second if second_strength > eighth_strength else eighth
|
||||
return {
|
||||
"applicable": True,
|
||||
"ascendant_sign_index": lagna,
|
||||
"second_sign_index": second,
|
||||
"eighth_sign_index": eighth,
|
||||
"second_strength": second_strength,
|
||||
"eighth_strength": eighth_strength,
|
||||
"starting_sign_index": start,
|
||||
"starting_sign": SIGNS[start],
|
||||
"direction": "forward" if start % 2 == 0 else "reverse",
|
||||
"rule_family": "pvr_niryaana_shoola_2nd_8th_modality",
|
||||
"reason": "Niryaana Shoola starts from the stronger second/eighth sign; odd seeds run forward and even seeds reverse.",
|
||||
}
|
||||
|
||||
|
||||
def niryaana_shoola_mahadashas(birth: date, start_sign: int) -> list[dict]:
|
||||
"""Twelve mahadashas only. Odd signs go forward; even signs go backward."""
|
||||
start_sign = int(start_sign) % 12
|
||||
order = _forward(start_sign) if start_sign % 2 == 0 else _reverse(start_sign)
|
||||
periods = []
|
||||
cursor = birth
|
||||
for sign_idx in order:
|
||||
years = niryaana_shoola_years(sign_idx)
|
||||
end = add_years(cursor, years)
|
||||
periods.append(_period(sign_idx, years, cursor, end))
|
||||
cursor = end
|
||||
return periods
|
||||
|
||||
|
||||
def _niryaana_shoola(birth: date, ascendant_longitude: float, planet_lons: Mapping[str, float]) -> dict:
|
||||
picked = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=ascendant_longitude,
|
||||
planet_longitudes=planet_lons,
|
||||
)
|
||||
if picked.get("status") != "selected":
|
||||
return _omitted(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
"本轮不上表:二宫与八宫力量相同,原书没有给出进一步的裁决规则。",
|
||||
"This report does not include Niryaana Shoola: the second and eighth signs are equal, and the source gives no further tie-break.",
|
||||
)
|
||||
start_sign = int(picked["selected_sign_idx"])
|
||||
return _listed(
|
||||
_NIRYAANA_PROFILE,
|
||||
_NIRYAANA_SOURCE_ZH,
|
||||
_NIRYAANA_SOURCE_EN,
|
||||
niryaana_shoola_mahadashas(birth, start_sign),
|
||||
direction="forward" if start_sign % 2 == 0 else "reverse",
|
||||
strength_profile=picked["profile"],
|
||||
)
|
||||
|
||||
|
||||
def _nine_year(birth: date, lagna: int, planet_lons: Mapping[str, float], rashi: Mapping, profile: str, source_zh: str, source_en: str) -> dict:
|
||||
seventh = (lagna + 6) % 12
|
||||
picked = stronger_sign(lagna, seventh, rashi, planet_lons)
|
||||
@@ -397,15 +573,7 @@ def calculate_rashi_dashas(birth: date | datetime | str, ascendant_longitude: fl
|
||||
"Crux of Vedic Astrology, printed page 395",
|
||||
),
|
||||
"navamsha": _navamsha(birth_day, float(ascendant_longitude), longitudes),
|
||||
"niryaana_shoola": _nine_year(
|
||||
birth_day,
|
||||
lagna,
|
||||
longitudes,
|
||||
rashi,
|
||||
"goel_nirayana_shoola_v1",
|
||||
"Predicting through Jaimini Astrology,印页 162",
|
||||
"Predicting through Jaimini Astrology, printed page 162",
|
||||
),
|
||||
"niryaana_shoola": _niryaana_shoola(birth_day, float(ascendant_longitude), longitudes),
|
||||
"lagna_kendradi": _omitted(
|
||||
"",
|
||||
"",
|
||||
|
||||
+48
-77
@@ -564,32 +564,32 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
10,
|
||||
11,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
10,
|
||||
9,
|
||||
8,
|
||||
9
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"years": [
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
8,
|
||||
7
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -647,7 +647,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -1224,7 +1223,6 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
@@ -1235,21 +1233,22 @@
|
||||
11,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
2,
|
||||
3
|
||||
],
|
||||
"years": [
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
7
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -1308,7 +1307,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -1909,32 +1907,32 @@
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
1
|
||||
11,
|
||||
10
|
||||
],
|
||||
"years": [
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
8,
|
||||
7,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
8
|
||||
]
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
@@ -1992,7 +1990,6 @@
|
||||
"subperiod"
|
||||
],
|
||||
"niryaana_shoola": [
|
||||
"start_sign",
|
||||
"direction",
|
||||
"years",
|
||||
"subperiod"
|
||||
@@ -2568,34 +2565,8 @@
|
||||
]
|
||||
},
|
||||
"niryaana_shoola": {
|
||||
"sign_indexes": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
0
|
||||
],
|
||||
"years": [
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9,
|
||||
9
|
||||
]
|
||||
"sign_indexes": [],
|
||||
"years": []
|
||||
},
|
||||
"lagna_kendradi": {
|
||||
"status": "omitted",
|
||||
|
||||
@@ -147,10 +147,15 @@ def test_secondary_dashas_use_native_profiles_and_match_goel_method_1(reports) -
|
||||
niryaana = modules["niryaana_shoola_dasha"]
|
||||
assert navamsha["status"] == "parameter_sensitive"
|
||||
assert navamsha["profile_id"] == "goel_method_1_v1"
|
||||
assert niryaana == {
|
||||
"execution_status": "blocked",
|
||||
"reason": "口径更正中,见 TASK-niryaana-shoola-pvr-20261018",
|
||||
}
|
||||
# 原值:niryaana == {execution_status: blocked, reason: 口径更正中,见 TASK-niryaana-shoola-pvr-20261018}
|
||||
# 新值:status parameter_sensitive,profile pvr_niryaana_shoola_v1,periods 是 PVR 主运
|
||||
# 原因:#84 用真结果换掉 #81 的占位。合盘校验器仍把这一行固定判为不适用。
|
||||
assert niryaana["status"] == "parameter_sensitive"
|
||||
assert niryaana["profile_id"] == "pvr_niryaana_shoola_v1"
|
||||
assert niryaana["periods"] == niryaana["native_result"]["periods"]
|
||||
assert niryaana["native_result"]["status"] == "listed"
|
||||
assert "antardasha" not in niryaana["periods"][0]
|
||||
assert sum(row["years"] for row in niryaana["periods"]) == 96
|
||||
assert navamsha["native_result"]["status"] == "listed"
|
||||
first = navamsha["periods"][0]
|
||||
d9 = modules["varga_full"]["D9_Navamsa"]
|
||||
|
||||
@@ -13,6 +13,9 @@ from scripts.rashi_dashas_native import (
|
||||
drig_aspect_sequence,
|
||||
navamsha_sign,
|
||||
navamsha_years,
|
||||
niryaana_shoola_applicability,
|
||||
niryaana_shoola_mahadashas,
|
||||
select_niryaana_shoola_start_sign,
|
||||
)
|
||||
from scripts.rashi_dasha_report import render_rashi_dasha_chapter
|
||||
|
||||
@@ -63,15 +66,23 @@ def test_goel_short_method_matches_the_printed_totals_and_direct_sthira() -> Non
|
||||
assert "pratyantar" not in aquarius
|
||||
|
||||
|
||||
def test_same_goel_chart_niryaana_uses_short_bala_not_the_printed_jaimini_start() -> None:
|
||||
# Printed Jaimini rashi bala on this chart starts Pisces. Short-method totals tie,
|
||||
# and the karaka rank of the lords selects Virgo. The report keeps the short method.
|
||||
def test_same_goel_chart_niryaana_uses_pvr_not_the_shoola_rule() -> None:
|
||||
# 原值:首运 Virgo,十二段都是 9 年,并有从 Virgo 起的子运。
|
||||
# 新值:profile pvr_niryaana_shoola_v1,首运 Aries、顺行,年数 7/8/9,没有子运。
|
||||
# 原因:产品 2026-10-18 推翻 BUG-1283 所选的 goel_nirayana_shoola_v1。那套和 Shoola 是同一张表。
|
||||
packet = calculate_rashi_dashas(date(2001, 1, 1), 5 * 30 + 1.55, GOEL_LONS)
|
||||
niryaana = packet["niryaana_shoola"]
|
||||
assert niryaana["periods"][0]["sign"] == "Virgo"
|
||||
assert [row["years"] for row in niryaana["periods"]] == [9] * 12
|
||||
assert niryaana["periods"][0]["antardasha"][0]["sign"] == "Virgo"
|
||||
assert niryaana["periods"][0]["antardasha"][0]["end"] == "2001-10-01"
|
||||
shoola = packet["shoola"]
|
||||
assert niryaana["profile_id"] == "pvr_niryaana_shoola_v1"
|
||||
assert niryaana["status"] == "listed"
|
||||
assert niryaana["direction"] == "forward"
|
||||
assert niryaana["periods"][0]["sign"] == "Aries"
|
||||
assert [row["years"] for row in niryaana["periods"][:3]] == [7, 8, 9]
|
||||
assert "antardasha" not in niryaana["periods"][0]
|
||||
assert (
|
||||
niryaana["periods"][0]["sign"] != shoola["periods"][0]["sign"]
|
||||
or [row["years"] for row in niryaana["periods"]] != [row["years"] for row in shoola["periods"]]
|
||||
)
|
||||
|
||||
|
||||
def test_drig_aspect_order_for_a_cancer_lagna_and_narayana_style_years() -> None:
|
||||
@@ -152,7 +163,128 @@ def test_report_modules_do_not_call_the_old_shoola_or_niryaana_helpers() -> None
|
||||
assert all(not item.split(".")[0] == "jhora" for item in imported)
|
||||
|
||||
|
||||
def test_niryaana_shoola_starts_from_the_stronger_second_or_eighth() -> None:
|
||||
taurus = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0})
|
||||
scorpio = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 211.0})
|
||||
assert taurus["selected_sign"] == "Taurus"
|
||||
assert scorpio["selected_sign"] == "Scorpio"
|
||||
taurus_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), taurus["selected_sign_idx"])
|
||||
scorpio_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), scorpio["selected_sign_idx"])
|
||||
assert [row["sign"] for row in taurus_rows[:2]] == ["Taurus", "Aries"]
|
||||
assert [row["sign"] for row in scorpio_rows[:2]] == ["Scorpio", "Libra"]
|
||||
|
||||
|
||||
def test_niryaana_shoola_odd_signs_go_forward_and_even_signs_go_backward() -> None:
|
||||
even_seed = niryaana_shoola_applicability(ascendant_longitude=0.0, second_strength=2.0, eighth_strength=1.0)
|
||||
odd_seed = select_niryaana_shoola_start_sign(ascendant_longitude=45.0, planet_longitudes={"Mars": 61.0})
|
||||
assert even_seed["starting_sign"] == "Taurus"
|
||||
assert even_seed["direction"] == "reverse"
|
||||
assert odd_seed["selected_sign"] == "Gemini"
|
||||
odd_rows = niryaana_shoola_mahadashas(date(2000, 1, 1), odd_seed["selected_sign_idx"])
|
||||
assert [row["sign"] for row in odd_rows[:2]] == ["Gemini", "Cancer"]
|
||||
assert [row["years"] for row in odd_rows[:3]] == [9, 7, 8]
|
||||
|
||||
|
||||
def test_niryaana_shoola_given_strengths_match_the_upstream_mahadasha_golden() -> None:
|
||||
# Golden copied from upstream 9f08b57b tests/test_niryaana_shoola_dasha.py
|
||||
# test_niryaana_shoola_uses_modality_years_and_parity_direction.
|
||||
picked = niryaana_shoola_applicability(
|
||||
ascendant_longitude=15.0, second_strength=2.0, eighth_strength=1.0,
|
||||
)
|
||||
rows = niryaana_shoola_mahadashas(date(2000, 1, 1), picked["starting_sign_index"])
|
||||
assert [row["sign"] for row in rows[:3]] == ["Taurus", "Aries", "Pisces"]
|
||||
assert [row["years"] for row in rows[:3]] == [8, 7, 9]
|
||||
assert [row["sign"] for row in rows] == [
|
||||
"Taurus", "Aries", "Pisces", "Aquarius", "Capricorn", "Sagittarius",
|
||||
"Scorpio", "Libra", "Virgo", "Leo", "Cancer", "Gemini",
|
||||
]
|
||||
assert [row["years"] for row in rows] == [8, 7, 9, 8, 7, 9, 8, 7, 9, 8, 7, 9]
|
||||
assert sum(row["years"] for row in rows) == 96
|
||||
assert all("antardasha" not in row for row in rows)
|
||||
|
||||
|
||||
def test_two_public_charts_match_the_upstream_start_signs() -> None:
|
||||
# Claude 10-18, upstream 9f08b57b: one chart starts in Sagittarius, one in Aquarius.
|
||||
# These two public charts are the pair. Sequences are the PVR mahadashas from that start.
|
||||
expected = {
|
||||
"brad_pitt": (
|
||||
"Sagittarius",
|
||||
["Sagittarius", "Capricorn", "Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio"],
|
||||
[9, 7, 8, 9, 7, 8, 9, 7, 8, 9, 7, 8],
|
||||
),
|
||||
"jolie": (
|
||||
"Aquarius",
|
||||
["Aquarius", "Pisces", "Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo", "Libra", "Scorpio", "Sagittarius", "Capricorn"],
|
||||
[8, 9, 7, 8, 9, 7, 8, 9, 7, 8, 9, 7],
|
||||
),
|
||||
}
|
||||
sources = {
|
||||
"brad_pitt": ROOT / "tests/indastro_cases.json",
|
||||
"jolie": ROOT / "tests/celebrity_cases.json",
|
||||
}
|
||||
from scripts.jyotish_engine import compute_chart_data
|
||||
|
||||
for case_id, (start, signs, years) in expected.items():
|
||||
rows = json.loads(sources[case_id].read_text(encoding="utf-8"))
|
||||
birth = next(row for row in rows if row["id"] == case_id)
|
||||
chart, _asc, _jd, _ayanamsa = compute_chart_data(
|
||||
birth["year"], birth["month"], birth["day"], birth["hour"], birth["minute"],
|
||||
birth["lat"], birth["lon"], birth["tz"], second=birth.get("second") or 0,
|
||||
ayanamsa_name="lahiri",
|
||||
)
|
||||
longitudes = {
|
||||
name: float(row["degree_raw"])
|
||||
for name, row in chart["planets"].items()
|
||||
if isinstance(row, dict) and "degree_raw" in row
|
||||
}
|
||||
section = calculate_rashi_dashas(
|
||||
chart["birth_info"]["date"], float(chart["ascendant"]["degree_raw"]), longitudes,
|
||||
)["niryaana_shoola"]
|
||||
assert section["status"] == "listed"
|
||||
assert section["direction"] == "forward"
|
||||
assert [row["sign"] for row in section["periods"]] == signs
|
||||
assert section["periods"][0]["sign"] == start
|
||||
assert [row["years"] for row in section["periods"]] == years
|
||||
assert all("antardasha" not in row for row in section["periods"])
|
||||
|
||||
|
||||
def test_niryaana_shoola_tie_stays_off_the_table() -> None:
|
||||
tied = {name: 3 * 30 + 10.0 for name in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")}
|
||||
section = calculate_rashi_dashas(date(2001, 1, 1), 0.5, tied)["niryaana_shoola"]
|
||||
assert section["status"] == "omitted"
|
||||
assert section["periods"] == []
|
||||
assert "本轮不上表" in section["reason_zh"]
|
||||
assert "二宫与八宫力量相同" in section["reason_zh"]
|
||||
|
||||
|
||||
def test_niryaana_shoola_counts_both_nodes_and_ignores_outer_planets() -> None:
|
||||
# Same placements as upstream 258e08e3.
|
||||
occupancy = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0,
|
||||
planet_longitudes={"Rahu": 31.0, "Ketu": 211.0, "Pluto": 31.0},
|
||||
)
|
||||
aspects = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0,
|
||||
planet_longitudes={"Rahu": 1.0, "Ketu": 181.0},
|
||||
)
|
||||
assert occupancy["included_bodies"][-2:] == ["Rahu", "Ketu"]
|
||||
assert occupancy["candidates"][1]["occupants"] == ["Rahu"]
|
||||
assert occupancy["candidates"][7]["occupants"] == ["Ketu"]
|
||||
assert "Pluto" not in occupancy["candidates"][1]["occupants"]
|
||||
assert aspects["candidates"][1]["rasi_aspecting_planets"] == ["Ketu"]
|
||||
assert aspects["candidates"][7]["rasi_aspecting_planets"] == ["Rahu"]
|
||||
without_outer = select_niryaana_shoola_start_sign(ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0})
|
||||
with_outer = select_niryaana_shoola_start_sign(
|
||||
ascendant_longitude=0.0, planet_longitudes={"Mars": 31.0, "Uranus": 211.0},
|
||||
)
|
||||
assert without_outer["selected_sign"] == with_outer["selected_sign"] == "Taurus"
|
||||
assert "Uranus" not in with_outer["candidates"][7]["occupants"]
|
||||
|
||||
|
||||
def test_pyjhora_fixture_diffs_stay_on_the_recorded_steps() -> None:
|
||||
# 原值:niryaana_shoola 的 native 行与 Shoola 一样,从上升或第七宫起、每段 9 年,并带子运差异。
|
||||
# 新值:同一键改为 PVR 主运的星座和年数;diff_steps 按新表相对 PyJHora 重记。
|
||||
# 原因:产品 2026-10-18 改口径。PyJHora 对照行没有重跑,只改我们自己的期望。
|
||||
payload = json.loads(FIXTURE.read_text(encoding="utf-8"))
|
||||
assert payload["pyjhora_version"] == "4.8.7"
|
||||
assert payload["generator_python_has_pyjhora"] is True
|
||||
@@ -227,6 +359,10 @@ def test_jobs_chapter_names_the_method_and_leaves_kendradi_out() -> None:
|
||||
text = render_rashi_dasha_chapter(packet)
|
||||
for title in ("Sthira", "Niryaana Shoola", "Drig", "Navamsha", "Shoola", "Chara"):
|
||||
assert f"### {title}" in text
|
||||
niryaana = text.split("### Niryaana Shoola", 1)[1].split("### ", 1)[0]
|
||||
assert "pvr_niryaana_shoola_v1" in niryaana
|
||||
assert "子运规则未定,暂不列出。" in niryaana
|
||||
assert "| 序号 | 主运 | 子运 |" not in niryaana
|
||||
assert "本站选用的方法:" in text
|
||||
assert "未与外部软件逐一核对" in text
|
||||
assert "K.N. Rao" in text
|
||||
@@ -236,6 +372,9 @@ def test_jobs_chapter_names_the_method_and_leaves_kendradi_out() -> None:
|
||||
assert "| 序号 | 星座 |" not in kendradi
|
||||
english = render_rashi_dasha_chapter({**packet, "report_language": "en"})
|
||||
assert "Method used on this site:" in english
|
||||
english_niryaana = english.split("### Niryaana Shoola Dasha", 1)[1].split("### ", 1)[0]
|
||||
assert "pvr_niryaana_shoola_v1" in english_niryaana
|
||||
assert "Child periods stay off this table" in english_niryaana
|
||||
assert "This report does not include Lagna Kendradi" in english
|
||||
assert "not checked one-by-one against external software." in english
|
||||
leaked = [char for char in english if "\u4e00" <= char <= "\u9fff"]
|
||||
|
||||
Reference in New Issue
Block a user