Files
Jyotisha/scripts/annual_native/tripataki_chakra.py
T
2026-10-22 23:11:48 +08:00

105 lines
3.7 KiB
Python

"""Tripataki placement grid and Vedha rows from an annual chart.
Longitudes are the ones already on our chart. This module does not call an
ephemeris and does not import PyJHora.
"""
from __future__ import annotations
from typing import Any
SIGNS = (
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
)
VEDHA_TARGETS = {
"Moon": ("Sun", "Mars", "Mercury", "Venus", "Saturn"),
"Lagna": ("Jupiter", "Ketu"),
"Sun": ("Moon", "Mars", "Mercury", "Venus", "Saturn"),
"Mars": ("Sun", "Moon", "Mercury", "Venus", "Saturn"),
"Mercury": ("Sun", "Moon", "Mars", "Venus", "Saturn"),
"Jupiter": ("Ketu",),
"Venus": ("Sun", "Moon", "Mars", "Mercury", "Saturn"),
"Saturn": ("Sun", "Moon", "Mars", "Mercury", "Venus"),
}
def build_tripataki_chakra(
planet_longitudes: dict[str, Any],
ascendant_longitude: float | int | None,
) -> dict[str, Any]:
if not isinstance(planet_longitudes, dict):
return _blocked("annual_planet_longitudes_missing")
try:
if isinstance(ascendant_longitude, bool):
raise TypeError
ascendant = float(ascendant_longitude) % 360
except (TypeError, ValueError):
return _blocked("annual_ascendant_longitude_missing")
placements = [_placement("Lagna", ascendant, is_lagna=True)]
for planet in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"):
try:
raw = planet_longitudes[planet]
if isinstance(raw, bool):
raise TypeError
placements.append(_placement(planet, float(raw) % 360))
except (KeyError, TypeError, ValueError):
continue
sign_cells = []
for sign_index, sign in enumerate(SIGNS):
members = [row["body"] for row in placements if row["sign_index"] == sign_index]
sign_cells.append({"sign_index": sign_index, "sign": sign, "bodies": members})
available = {row["body"] for row in placements}
vedha_rows = []
for target, sources in VEDHA_TARGETS.items():
ready_sources = [source for source in sources if source in available]
missing_sources = [source for source in sources if source not in available]
vedha_rows.append({
"target": target,
"sources": list(sources),
"available_sources": ready_sources,
"missing_sources": missing_sources,
"status": "partial_verified" if target in available and not missing_sources else "blocked",
})
return {
"schema": "pl9.tripataki_chakra.v1",
"status": "partial_verified" if len(placements) >= 8 else "blocked",
"reason": None if len(placements) >= 8 else "fewer_than_eight_bodies",
"placements": placements,
"sign_cells": sign_cells,
"vedha_rows": vedha_rows,
"source_longitudes": {
name: next(row["longitude"] for row in placements if row["body"] == name)
for name in ("Sun", "Moon")
if any(row["body"] == name for row in placements)
},
}
def _placement(body: str, longitude: float, *, is_lagna: bool = False) -> dict[str, Any]:
sign_index = int(longitude // 30) % 12
return {
"body": body,
"longitude": longitude,
"sign_index": sign_index,
"sign": SIGNS[sign_index],
"degree_in_sign": longitude % 30,
"is_lagna": is_lagna,
}
def _blocked(reason: str) -> dict[str, Any]:
return {
"schema": "pl9.tripataki_chakra.v1",
"status": "blocked",
"reason": reason,
"placements": [],
"sign_cells": [],
"vedha_rows": [],
"source_longitudes": {},
}