Files
Jyotisha/tests/test_rashi_dashas_native.py
T
jesse-ux 9d3eb23ee4 feat: 完整数据版加入六种星座大运
Sthira、Drig、Shoola、Navamsha、Niryaana Shoola 与 K.N. Rao 的 Chara 从本盘算出。Lagna Kendradi 不上表。未推送。
2026-10-09 00:43:00 +08:00

277 lines
12 KiB
Python

"""Book-shaped rashi dasha rows. These tests do not import PyJHora."""
from __future__ import annotations
import ast
import json
from datetime import date
from pathlib import Path
from scripts.goel_short_strength import dignity_units, graha_strengths, rashi_strengths
from scripts.rashi_dashas_native import (
calculate_rashi_dashas,
drig_aspect_sequence,
navamsha_sign,
navamsha_years,
)
from scripts.rashi_dasha_report import render_rashi_dasha_chapter
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT / "tests" / "fixtures" / "rashi_dasha_pyjhora_20261008.json"
GOEL_LONS = {
"Sun": 0 * 30 + 20.18,
"Moon": 7 * 30 + 20.15,
"Mars": 7 * 30 + 23.02,
"Mercury": 1 * 30 + 11.12,
"Jupiter": 5 * 30 + 3.13,
"Venus": 11 * 30 + 17.28,
"Saturn": 0 * 30 + 7.07,
}
CAPRICORN_STRONGER = {
"Sun": 9 * 30 + 1,
"Moon": 9 * 30 + 2,
"Mars": 9 * 30 + 3,
"Mercury": 9 * 30 + 4,
"Jupiter": 9 * 30 + 5,
"Venus": 9 * 30 + 6,
"Saturn": 6 * 30 + 15,
"Rahu": 15.0,
"Ketu": 195.0,
}
def test_goel_short_method_matches_the_printed_totals_and_direct_sthira() -> None:
graha = graha_strengths(GOEL_LONS)
assert [graha[name]["total"] for name in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn")] == [
150, 120, 180, 130, 70, 150, 50,
]
rashi = rashi_strengths(GOEL_LONS, graha)
assert [rashi[index]["total"] for index in range(12)] == [
280, 260, 250, 260, 250, 260, 230, 300, 250, 130, 150, 260,
]
packet = calculate_rashi_dashas(date(2001, 1, 1), 5 * 30 + 1.55, GOEL_LONS)
sthira = packet["sthira"]
assert sthira["profile_id"] == "goel_rao_short_brahma_method_v1"
assert sthira["brahma_planet"] == "Mars"
assert [row["sign_index"] for row in sthira["periods"]] == list(range(7, 12)) + list(range(0, 7))
assert [row["years"] for row in sthira["periods"]] == [8, 9, 7, 8, 9, 7, 8, 9, 7, 8, 9, 7]
aquarius = sthira["periods"][3]
assert aquarius["sign"] == "Aquarius"
assert aquarius["start"] == "2025-01-01"
assert [row["sign"] for row in aquarius["antardasha"][:2]] == ["Aquarius", "Pisces"]
assert aquarius["antardasha"][0]["end"] == "2025-09-01"
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.
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"
def test_drig_aspect_order_for_a_cancer_lagna_and_narayana_style_years() -> None:
assert drig_aspect_sequence(3) == [11, 8, 5, 2, 0, 4, 7, 10, 1, 9, 6, 3]
packet = calculate_rashi_dashas(date(2001, 1, 1), 3 * 30 + 1.0, GOEL_LONS)
drig = packet["drig"]
assert drig["profile_id"] == "drig_rath_aspect_sequence_v1"
assert [row["sign_index"] for row in drig["periods"]] == drig_aspect_sequence(3)
assert all(isinstance(row["years"], int) and 0 <= row["years"] <= 12 for row in drig["periods"])
def test_shoola_from_the_stronger_seventh_puts_aries_at_year_27() -> None:
packet = calculate_rashi_dashas(date(2001, 1, 1), 3 * 30 + 1.0, CAPRICORN_STRONGER)
shoola = packet["shoola"]
assert shoola["profile_id"] == "shoola_forward_md_direct_ad_v1"
assert [row["sign"] for row in shoola["periods"][:4]] == ["Capricorn", "Aquarius", "Pisces", "Aries"]
assert shoola["periods"][3]["start"] == "2028-01-01"
assert shoola["periods"][3]["years"] == 9
assert shoola["periods"][0]["antardasha"][0]["sign"] == "Capricorn"
def test_navamsha_method_1_starts_from_the_seventh_when_d9_lagna_is_even() -> None:
longitude = 3 * 30 + 26.7
assert navamsha_sign(longitude) == 11
assert navamsha_years(0, 0) == 12
assert navamsha_years(0, 1) == 2
assert navamsha_years(1, 0) == 2
packet = calculate_rashi_dashas(date(2001, 1, 1), longitude, GOEL_LONS)
navamsha = packet["navamsha"]
assert navamsha["profile_id"] == "goel_method_1_v1"
assert navamsha["d9_lagna_index"] == 11
assert navamsha["periods"][0]["sign"] == "Virgo"
assert navamsha["periods"][0]["antardasha"][0]["sign"] == "Virgo"
assert "pratyantar" not in navamsha["periods"][0]
def test_chara_keeps_the_existing_kn_rao_order_and_a_zero_year_row() -> None:
longitudes = {name: 10.0 for name in ("Sun", "Moon", "Mars", "Mercury", "Venus", "Saturn", "Rahu")}
longitudes["Jupiter"] = 9 * 30 + 5.0
longitudes["Ketu"] = 190.0
packet = calculate_rashi_dashas(date(2001, 1, 1), 10.0, longitudes)
chara = packet["chara"]
assert chara["profile_id"] == "kn_rao"
assert [row["sign_index"] for row in chara["periods"]] == list(range(12))
sagittarius = chara["periods"][8]
assert sagittarius["sign"] == "Sagittarius"
assert sagittarius["years"] == 0
assert sagittarius["start"] == sagittarius["end"]
assert chara["periods"][0]["antardasha"][0]["sign"] == "Taurus"
assert chara["periods"][0]["antardasha"][-1]["sign"] == "Aries"
def test_lagna_kendradi_is_omitted() -> None:
packet = calculate_rashi_dashas(date(2001, 1, 1), 10.0, GOEL_LONS)
section = packet["lagna_kendradi"]
assert section["status"] == "omitted"
assert section["periods"] == []
def test_mercury_in_virgo_uses_longitude_bands() -> None:
assert dignity_units("Mercury", 5, 10.0) == 70
assert dignity_units("Mercury", 5, 16.0) == 60
assert dignity_units("Mercury", 5, 22.0) == 50
def test_report_modules_do_not_call_the_old_shoola_or_niryaana_helpers() -> None:
for name in ("rashi_dashas_native.py", "rashi_dasha_report.py", "goel_short_strength.py"):
source = (ROOT / "scripts" / name).read_text(encoding="utf-8")
tree = ast.parse(source)
imported = []
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module:
imported.append(node.module)
elif isinstance(node, ast.Import):
imported.extend(alias.name for alias in node.names)
assert "niryaana_shoola_dasha" not in imported
assert "extended_dashas" not in imported
assert all(not item.split(".")[0] == "jhora" for item in imported)
def test_pyjhora_fixture_diffs_stay_on_the_recorded_steps() -> None:
payload = json.loads(FIXTURE.read_text(encoding="utf-8"))
assert payload["pyjhora_version"] == "4.8.7"
assert payload["generator_python_has_pyjhora"] is True
from scripts.jyotish_engine import compute_chart_data
for case_id, case in payload["cases"].items():
birth = case["birth_source"]
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,
)
assert chart is not None
longitudes = {
name: float(row["degree_raw"])
for name, row in chart["planets"].items()
if isinstance(row, dict) and "degree_raw" in row
}
native = calculate_rashi_dashas(chart["birth_info"]["date"], float(chart["ascendant"]["degree_raw"]), longitudes)
for key, expected in case["diff_steps"].items():
section = native[key]
recorded = case["native"][key]
if key == "lagna_kendradi":
assert section["status"] == "omitted"
assert expected == ["start_sign", "direction", "years", "subperiod"]
continue
assert [row["sign_index"] for row in section["periods"]] == recorded["sign_indexes"]
assert [row["years"] for row in section["periods"]] == recorded["years"]
assert _diff_steps(section, case["pyjhora"][key]) == expected
def _succession(signs: list[int]) -> tuple[int, ...]:
return tuple((signs[index] - signs[index - 1]) % 12 for index in range(1, len(signs)))
def _diff_steps(section: dict, pyjhora: dict) -> list[str]:
our_signs = [row["sign_index"] for row in section["periods"]]
their_signs = pyjhora["sign_indexes"]
our_years = [row["years"] for row in section["periods"]]
their_years = pyjhora["years"]
steps = []
if not our_signs or not their_signs or our_signs[0] != their_signs[0]:
steps.append("start_sign")
if _succession(our_signs) != _succession(their_signs):
steps.append("direction")
if sorted(zip(our_signs, our_years)) != sorted(zip(their_signs, their_years)):
steps.append("years")
our_sub = [row["sign_index"] for row in (section["periods"][0].get("antardasha") or [])] if section["periods"] else []
their_sub = pyjhora.get("first_sub_sign_indexes") or []
if our_sub != their_sub:
steps.append("subperiod")
if our_signs == their_signs and our_years == their_years and section["periods"][1:2]:
if section["periods"][1]["start"] != pyjhora.get("second_start"):
steps.append("date_basis")
return steps
def test_jobs_chapter_names_the_method_and_leaves_kendradi_out() -> None:
payload = json.loads(FIXTURE.read_text(encoding="utf-8"))
birth = payload["cases"]["steve_jobs_1955_aa"]["birth_source"]
from scripts.jyotish_engine import compute_chart_data
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,
)
packet = {
"report_language": "zh",
"birth_info": {"date": chart["birth_info"]["date"], "name": "steve_jobs_1955_aa"},
"core_chart": {"ascendant": chart["ascendant"], "planets": chart["planets"]},
}
text = render_rashi_dasha_chapter(packet)
for title in ("Sthira", "Niryaana Shoola", "Drig", "Navamsha", "Shoola", "Chara"):
assert f"### {title}" in text
assert "本站选用的方法:" in text
assert "未与外部软件逐一核对" in text
assert "K.N. Rao" in text
assert "不是 Narayana 表" in text
kendradi = text.split("### Lagna Kendradi", 1)[1]
assert "本轮不上表" in kendradi
assert "| 序号 | 星座 |" not in kendradi
english = render_rashi_dasha_chapter({**packet, "report_language": "en"})
assert "Method used on this site:" in english
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"]
assert leaked == []
def test_full_data_report_appends_the_chapter_without_touching_a_chartless_packet() -> None:
from scripts.pl9_full_data_export import render_full_data_markdown
from tests.test_report_full_data_fix import _packet
chartless = render_full_data_markdown(_packet("zh"))
assert "Sthira Dasha" not in chartless
payload = json.loads(FIXTURE.read_text(encoding="utf-8"))
birth = payload["cases"]["steve_jobs_1955_aa"]["birth_source"]
from scripts.jyotish_engine import compute_chart_data
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,
)
packet = _packet("zh")
packet["birth_info"]["date"] = chart["birth_info"]["date"]
packet["core_chart"] = {"ascendant": chart["ascendant"], "planets": chart["planets"]}
text = render_full_data_markdown(packet)
assert "星座大运" in text
assert "Sthira" in text
assert "本站选用的方法" in text
assert "Kendradi" in text
assert "本轮不上表" in text
kendradi = text.split("Kendradi", 1)[1]
assert "| 序号 | 星座 |" not in kendradi
english_packet = _packet("en")
english_packet["birth_info"]["date"] = chart["birth_info"]["date"]
english_packet["core_chart"] = packet["core_chart"]
english = render_full_data_markdown(english_packet)
assert "Sthira" in english
assert "Method used on this site:" in english
assert "This report does not include Lagna Kendradi" in english
assert not any("\u4e00" <= char <= "\u9fff" for char in english)