105 lines
3.7 KiB
Python
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": {},
|
|
}
|