feat: 完整数据版加入六种星座大运

Sthira、Drig、Shoola、Navamsha、Niryaana Shoola 与 K.N. Rao 的 Chara 从本盘算出。Lagna Kendradi 不上表。未推送。
This commit is contained in:
jesse-ux
2026-10-09 00:43:00 +08:00
parent e1c7383c5e
commit 9d3eb23ee4
12 changed files with 4148 additions and 3 deletions
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Freeze PyJHora 4.8.7 rashi-dasha rows for four public or fictional charts.
Run with the Anaconda interpreter that has PyJHora 4.8.7. Product code does
not import this module. Tests read the JSON only.
"""
from __future__ import annotations
import importlib
import importlib.metadata
import io
import json
import sys
from contextlib import redirect_stdout
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path[:0] = [str(ROOT), str(ROOT / "scripts")]
OUT = ROOT / "tests" / "fixtures" / "rashi_dasha_pyjhora_20261008.json"
CASE_IDS = (
"steve_jobs_1955_aa",
"barack_obama_1961_aa",
"einstein_1879_aa",
"fictional_reader_main",
)
def _births() -> dict:
public = json.loads((ROOT / "references" / "public_oracle_cases.json").read_text(encoding="utf-8"))
births = {case["id"]: case["birth"] for case in public["cases"]}
special = json.loads((ROOT / "tests" / "fixtures" / "special_lagnas_pyjhora_487.json").read_text(encoding="utf-8"))
births["fictional_reader_main"] = special["cases"]["fictional_reader_main"]["birth"]
return births
def _parse_row(row):
if not isinstance(row, (list, tuple)) or len(row) < 3:
return None
if (
len(row) >= 4
and isinstance(row[0], int)
and isinstance(row[1], int)
and not isinstance(row[2], (int, float))
):
signs = [int(row[0]), int(row[1])]
start, years = row[2], row[3]
else:
lord, start, years = row[0], row[1], row[2]
signs = [int(item) for item in lord] if isinstance(lord, (list, tuple)) else [int(lord)]
if isinstance(start, (list, tuple)):
start_iso = f"{int(start[0]):04d}-{int(start[1]):02d}-{int(start[2]):02d}"
else:
start_iso = str(start)[:10]
years_n = float(years)
years_out = int(round(years_n)) if abs(years_n - round(years_n)) < 1e-6 else years_n
return signs, start_iso, years_out
def _call(fn, args):
with redirect_stdout(io.StringIO()):
return fn(*args)
def _pyjhora_case(birth: dict, modules, swe, drik, charts, Place) -> dict:
local_hour = birth["hour"] + birth["minute"] / 60.0 + birth.get("second", 0) / 3600.0
with redirect_stdout(io.StringIO()):
drik.set_ayanamsa_mode("LAHIRI")
jd = swe.julday(birth["year"], birth["month"], birth["day"], local_hour)
place = Place("birth_place", birth["lat"], birth["lon"], birth["tz"])
positions = charts.rasi_chart(jd, place)
dob = (birth["year"], birth["month"], birth["day"])
tob = (birth["hour"], birth["minute"], birth["second"])
calls = {
"sthira": lambda level: modules["sthira"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
"shoola": lambda level: modules["shoola"].shoola_dhasa(positions, dob, tob, dhasa_level_index=level),
"niryaana_shoola": lambda level: modules["nirayana"].nirayana_shoola_dhasa(positions, dob, tob, dhasa_level_index=level),
"drig": lambda level: modules["drig"].drig_dhasa(positions, dob, tob, dhasa_level_index=level),
"navamsha": lambda level: modules["navamsa"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
"chara": lambda level: modules["chara"].get_dhasa_antardhasa(dob, tob, place, dhasa_level_index=level),
"lagna_kendradi": lambda level: modules["lagna_kendraadhi"].lagna_kendradhi_rasi_dhasa(dob, tob, place, dhasa_level_index=level),
}
parsed = {}
for key, call in calls.items():
level_1 = [_parse_row(row) for row in _call(call, (1,))]
level_2 = [_parse_row(row) for row in _call(call, (2,))]
majors = [row for row in level_1 if row and len(row[0]) == 1]
if not majors:
majors = [row for row in level_2 if row and len(row[0]) == 1]
subs = [row for row in level_2 if row and len(row[0]) == 2]
first_sign = majors[0][0][0] if majors else None
first_subs = [row[0][1] for row in subs if row[0][0] == first_sign]
parsed[key] = {
"sign_indexes": [row[0][0] for row in majors],
"years": [row[2] for row in majors],
"second_start": majors[1][1] if len(majors) > 1 else None,
"first_sub_sign_indexes": first_subs,
}
return parsed
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 []
if our_sub != (pyjhora.get("first_sub_sign_indexes") or []):
steps.append("subperiod")
if our_signs == their_signs and our_years == their_years and len(section["periods"]) > 1:
if section["periods"][1]["start"] != pyjhora.get("second_start"):
steps.append("date_basis")
return steps
def main() -> None:
with redirect_stdout(io.StringIO()):
import swisseph as swe
from jhora.horoscope.chart import charts
from jhora.panchanga import drik
from jhora.panchanga.drik import Place
modules = {
name: importlib.import_module("jhora.horoscope.dhasa.raasi." + module_name)
for name, module_name in (
("sthira", "sthira"),
("shoola", "shoola"),
("nirayana", "nirayana"),
("drig", "drig"),
("navamsa", "navamsa"),
("chara", "chara"),
("lagna_kendraadhi", "lagna_kendraadhi"),
)
}
from jyotish_engine import compute_chart_data
from rashi_dashas_native import calculate_rashi_dashas
payload = {
"pyjhora_version": importlib.metadata.version("PyJHora"),
"generator_python_has_pyjhora": True,
"generator": "scripts/research/freeze_rashi_dasha_pyjhora_fixture.py",
"note": "Anaconda PyJHora 4.8.7 produced the external rows. Pytest reads this file and does not import PyJHora.",
"cases": {},
}
births = _births()
for case_id in CASE_IDS:
birth = births[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,
)
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)
external = _pyjhora_case(birth, modules, swe, drik, charts, Place)
native_view = {}
diff_steps = {}
for key, section in native.items():
if key == "lagna_kendradi":
diff_steps[key] = ["start_sign", "direction", "years", "subperiod"]
native_view[key] = {"status": "omitted", "sign_indexes": [], "years": []}
continue
native_view[key] = {
"sign_indexes": [row["sign_index"] for row in section["periods"]],
"years": [row["years"] for row in section["periods"]],
}
diff_steps[key] = _diff_steps(section, external[key])
payload["cases"][case_id] = {
"birth_source": birth,
"pyjhora": external,
"native": native_view,
"diff_steps": diff_steps,
}
print(case_id, {key: value for key, value in diff_steps.items()}, flush=True)
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(OUT)
if __name__ == "__main__":
main()