从报告日期所在月起列出 36 个月的九星表。报告口径和 KP 岁差口径各一张,另有五级大运月采样。黄经仍走本系统恒星计算。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-23 10:17:29 +08:00
co-authored by Grok (agent loop)
parent 4c405d2a51
commit 355946cd1a
5 changed files with 352 additions and 7 deletions
+181
View File
@@ -0,0 +1,181 @@
"""36-month ephemeris from the report month, on this repository's sidereal frame.
Longitudes come from ``_calc_sidereal_planets_for_jd`` (true precession, not
apparent position). The KP table uses the same function with the KP ayanamsa.
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
_PLANETS = ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu")
SCHEMA = "jyotish.report_month_ephemeris.v1"
def attach_report_month_ephemeris(args: Any, annual: dict | None = None) -> dict:
today = getattr(args, "today", None)
if isinstance(today, str) and len(today) >= 7 and today[4] == "-":
start_year = int(today[0:4])
start_month = int(today[5:7])
start_source = "report_date"
else:
profile = annual.get("profile") if isinstance(annual, dict) and isinstance(annual.get("profile"), dict) else {}
target = profile.get("target_year", getattr(args, "target_year", None))
start_year = int(target if target not in (None, "") else args.year)
start_month = 1
start_source = "target_january_report_date_missing"
settings = {}
if isinstance(annual, dict) and isinstance(annual.get("profile"), dict):
settings = annual["profile"]
ayanamsa = str(getattr(args, "ayanamsa", None) or settings.get("ayanamsa") or "lahiri")
node_mode = str(getattr(args, "node_mode", None) or settings.get("node_mode") or "mean")
return build_report_ephemeris(
birth_year=int(args.year),
birth_month=int(args.month),
birth_day=int(args.day),
birth_hour=int(args.hour),
birth_minute=int(getattr(args, "minute", 0) or 0),
birth_second=int(getattr(args, "second", 0) or 0),
latitude=float(args.lat),
longitude=float(args.lon),
tz_hours=float(args.tz),
ayanamsa=ayanamsa,
node_mode=node_mode,
start_year=start_year,
start_month=start_month,
start_source=start_source,
)
def build_report_ephemeris(
*,
birth_year: int,
birth_month: int,
birth_day: int,
birth_hour: int,
birth_minute: int,
birth_second: int,
latitude: float,
longitude: float,
tz_hours: float,
ayanamsa: str,
node_mode: str,
start_year: int,
start_month: int,
start_source: str,
month_count: int = 36,
) -> dict:
if month_count < 1:
raise ValueError("month_count must be at least 1")
if not 1 <= int(start_month) <= 12:
raise ValueError(f"start_month out of range: {start_month}")
natal_jd = _jd(birth_year, birth_month, birth_day, birth_hour + birth_minute / 60 + birth_second / 3600, tz_hours)
natal = _bodies(natal_jd, node_mode, ayanamsa)
moon_body = natal.get("Moon") if isinstance(natal.get("Moon"), dict) else {}
moon = moon_body.get("degree_raw")
if not isinstance(moon, (int, float)) or isinstance(moon, bool):
raise RuntimeError("natal Moon longitude missing for the monthly Vimshottari sample")
report_rows = []
kp_rows = []
level_rows = []
level_error = None
for offset in range(month_count):
year, month = _add_months(start_year, start_month, offset)
jd = _jd(year, month, 1, 12.0, tz_hours)
month_text = f"{year:04d}-{month:02d}"
report_rows.extend(_planet_rows(month_text, _bodies(jd, node_mode, ayanamsa)))
kp_rows.extend(_planet_rows(month_text, _bodies(jd, node_mode, "kp")))
if level_error is None:
try:
level_rows.append(_levels(birth_year, birth_month, birth_day, birth_hour, birth_minute, birth_second, moon, year, month))
except Exception as exc:
level_error = f"{exc.__class__.__name__}: {exc}"
return {
"schema": SCHEMA,
"status": "partial_verified",
"start_source": start_source,
"start_month": f"{int(start_year):04d}-{int(start_month):02d}",
"month_count": month_count,
"ayanamsa": ayanamsa,
"kp_ayanamsa": "kp",
"node_mode": node_mode,
"anchor": "local_month_start_noon",
"report_rows": report_rows,
"kp_rows": kp_rows,
"vimshottari_rows": level_rows,
"vimshottari_reason": level_error,
"sample": {
"month": report_rows[0]["month"] if report_rows else None,
"jd_ut": _jd(start_year, start_month, 1, 12.0, tz_hours),
"moon_degree_raw": next(row["degree_raw"] for row in report_rows if row["planet"] == "Moon"),
},
}
def _planet_rows(month_text: str, bodies: dict) -> list[dict]:
rows = []
for name in _PLANETS:
body = bodies.get(name)
if not isinstance(body, dict) or not isinstance(body.get("degree_raw"), (int, float)):
raise RuntimeError(f"{name} longitude missing for {month_text}")
rows.append({
"month": month_text,
"planet": name,
"sign": body.get("sign"),
"longitude": round(float(body["degree_raw"]), 6),
"degree_raw": float(body["degree_raw"]),
})
return rows
def _bodies(jd: float, node_mode: str, ayanamsa: str) -> dict:
try:
from jyotish_engine import _calc_sidereal_planets_for_jd
except ImportError:
from scripts.jyotish_engine import _calc_sidereal_planets_for_jd
data, _ayan = _calc_sidereal_planets_for_jd(jd, node_mode=node_mode, ayanamsa_name=ayanamsa)
if not isinstance(data, dict) or "Moon" not in data:
raise RuntimeError(f"sidereal planets missing at jd {jd}")
return data
def _levels(year, month, day, hour, minute, second, moon_lon: float, anchor_year: int, anchor_month: int) -> dict:
try:
from kp_monthly_vimshottari import build_monthly_vimshottari_snapshot
except ImportError:
from scripts.kp_monthly_vimshottari import build_monthly_vimshottari_snapshot
snapshot = build_monthly_vimshottari_snapshot(
birth_dt=datetime(year, month, day, hour, minute, second),
moon_lon=float(moon_lon),
anchor_dt=datetime(anchor_year, anchor_month, 1, 12, 0, 0),
)
levels = snapshot.get("levels") if isinstance(snapshot.get("levels"), dict) else {}
return {
"month": f"{anchor_year:04d}-{anchor_month:02d}",
"mahadasha": _lord(levels.get("mahadasha")),
"antardasha": _lord(levels.get("antardasha")),
"pratyantardasha": _lord(levels.get("pratyantardasha")),
"sookshma": _lord(levels.get("sookshma")),
"prana": _lord(levels.get("prana")),
}
def _lord(value: Any) -> str | None:
if isinstance(value, dict):
lord = value.get("lord") or value.get("planet")
return str(lord) if lord else None
if isinstance(value, str) and value.strip():
return value.strip()
return None
def _jd(year: int, month: int, day: int, local_hours: float, tz_hours: float) -> float:
import swisseph as swe
return float(swe.julday(int(year), int(month), int(day), float(local_hours) - float(tz_hours)))
def _add_months(year: int, month: int, offset: int) -> tuple[int, int]:
index = int(year) * 12 + (int(month) - 1) + int(offset)
return index // 12, index % 12 + 1