Files
Jyotisha/scripts/annual_native/monthly_charts.py
T

126 lines
4.7 KiB
Python

"""Twelve monthly charts measured from an annual solar-return instant.
The Sun search uses this repository's sidereal Sun. Each row's planets come
from ``compute_chart_data``. No apparent-position flag is passed.
"""
from __future__ import annotations
from datetime import datetime, timedelta
from typing import Any
try:
from annual_native.sidereal_bodies import sun_moon_longitudes
except ImportError: # pragma: no cover - repo root is on sys.path
from scripts.annual_native.sidereal_bodies import sun_moon_longitudes
SCHEMA = "jyotish.tajika_monthly_chart_pack.v1"
TROPICAL_YEAR_DAYS = 365.24219
_PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
def build_monthly_charts(
*,
annual_jd: float,
birth_sun_longitude: float,
latitude: float,
longitude: float,
tz_hours: float,
ayanamsa: str,
node_mode: str,
target_year: int,
timezone_id: str | None = None,
) -> dict[str, Any]:
rows = []
for month_index in range(1, 13):
target_sun = (float(birth_sun_longitude) + 30.0 * (month_index - 1)) % 360.0
guess = float(annual_jd) + TROPICAL_YEAR_DAYS * (month_index - 1) / 12.0
monthly_jd = _find_sun(guess, target_sun, ayanamsa, node_mode)
rows.append(_chart_row(
month_index, monthly_jd, target_sun, target_year,
latitude, longitude, tz_hours, ayanamsa, node_mode, timezone_id,
))
ready = [row for row in rows if row.get("status") == "partial_verified"]
return {
"schema": SCHEMA,
"status": "partial_verified" if len(ready) == 12 else "blocked",
"reason": None if len(ready) == 12 else "monthly_root_incomplete",
"target_year": target_year,
"rows": rows,
}
def _find_sun(guess: float, target: float, ayanamsa: str, node_mode: str) -> float | None:
jd = guess
for _ in range(24):
longitude = sun_moon_longitudes(jd, node_mode=node_mode, ayanamsa_name=ayanamsa)[0]
difference = ((longitude - target + 180.0) % 360.0) - 180.0
if abs(difference) < 0.0001:
return jd
future = sun_moon_longitudes(jd + 1.0 / 24.0, node_mode=node_mode, ayanamsa_name=ayanamsa)[0]
speed = (((future - longitude + 180.0) % 360.0) - 180.0) * 24.0
jd -= difference / speed if abs(speed) > 0.01 else difference
final = sun_moon_longitudes(jd, node_mode=node_mode, ayanamsa_name=ayanamsa)[0]
if abs(((final - target + 180.0) % 360.0) - 180.0) < 0.001:
return jd
return None
def _chart_row(
month_index: int,
jd: float | None,
target_sun: float,
target_year: int,
latitude: float,
longitude: float,
tz_hours: float,
ayanamsa: str,
node_mode: str,
timezone_id: str | None,
) -> dict[str, Any]:
if jd is None:
return {
"month_index": month_index,
"target_year": target_year,
"status": "blocked",
"reason": "solar_longitude_root_failed",
}
try:
from solar_return import _localize_return_datetime
from jyotish_engine import compute_chart_data
except ImportError:
from scripts.solar_return import _localize_return_datetime
from scripts.jyotish_engine import compute_chart_data
utc = datetime(2000, 1, 1) + timedelta(days=jd - 2451544.5)
local = _localize_return_datetime(utc, tz_hours, timezone_id)
chart, _asc, _chart_jd, _ayan = compute_chart_data(
utc.year, utc.month, utc.day, utc.hour, utc.minute,
float(latitude), float(longitude), 0,
node_mode, second=utc.second, ayanamsa_name=ayanamsa,
)
if not isinstance(chart, dict):
raise RuntimeError(f"compute_chart_data returned no chart for monthly jd {jd}")
planets = chart.get("planets") if isinstance(chart.get("planets"), dict) else {}
sun = planets.get("Sun") if isinstance(planets.get("Sun"), dict) else {}
ascendant = chart.get("ascendant") if isinstance(chart.get("ascendant"), dict) else {}
return {
"month_index": month_index,
"target_year": target_year,
"jd_ut": jd,
"local_datetime": local.strftime("%Y-%m-%d %H:%M:%S"),
"utc_datetime": utc.strftime("%Y-%m-%d %H:%M:%S"),
"target_sun_longitude": target_sun,
"sun_longitude": sun.get("degree_raw"),
"ascendant": {
"sign": ascendant.get("sign"),
"degree_in_sign": ascendant.get("degree_in_sign", ascendant.get("degree")),
"longitude": ascendant.get("degree_raw", ascendant.get("lon")),
},
"planet_longitudes": {
name: planets[name].get("degree_raw")
for name in _PLANETS
if isinstance(planets.get(name), dict) and planets[name].get("degree_raw") is not None
},
"status": "partial_verified",
}