110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""Compact D1 house table for the public rectification surface.
|
|
|
|
Sign names and occupants only. Longitudes, coordinates, scores and
|
|
fingerprints stay out of this projection.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from scripts.jyotish_engine import SIGNS, SIGNS_CN
|
|
|
|
PLANET_ORDER = (
|
|
"Sun",
|
|
"Moon",
|
|
"Mars",
|
|
"Mercury",
|
|
"Jupiter",
|
|
"Venus",
|
|
"Saturn",
|
|
"Rahu",
|
|
"Ketu",
|
|
)
|
|
|
|
PLANET_ZH = {
|
|
"Sun": "太阳",
|
|
"Moon": "月亮",
|
|
"Mars": "火星",
|
|
"Mercury": "水星",
|
|
"Jupiter": "木星",
|
|
"Venus": "金星",
|
|
"Saturn": "土星",
|
|
"Rahu": "罗睺",
|
|
"Ketu": "计都",
|
|
}
|
|
|
|
|
|
def _sign_cn(sign: str | None) -> str | None:
|
|
if not isinstance(sign, str) or sign not in SIGNS_CN:
|
|
return None
|
|
return SIGNS_CN[sign]
|
|
|
|
|
|
def _house_number(value: Any, sign: str | None, asc_idx: int | None) -> int | None:
|
|
if isinstance(value, int) and 1 <= value <= 12:
|
|
return value
|
|
if not sign or sign not in SIGNS or asc_idx is None:
|
|
return None
|
|
return ((SIGNS.index(sign) - asc_idx) % 12) + 1
|
|
|
|
|
|
def compact_house_table(chart: Any, *, time: str) -> dict[str, Any] | None:
|
|
if not isinstance(chart, dict) or not isinstance(time, str):
|
|
return None
|
|
ascendant = chart.get("ascendant") if isinstance(chart.get("ascendant"), dict) else {}
|
|
lagna = _sign_cn(str(ascendant.get("sign") or "") or None)
|
|
if not lagna:
|
|
return None
|
|
try:
|
|
asc_idx = SIGNS.index(str(ascendant["sign"]))
|
|
except (KeyError, ValueError):
|
|
return None
|
|
|
|
occupants: dict[int, list[str]] = {house: [] for house in range(1, 13)}
|
|
planets = chart.get("planets") if isinstance(chart.get("planets"), dict) else {}
|
|
for name in PLANET_ORDER:
|
|
data = planets.get(name)
|
|
if not isinstance(data, dict):
|
|
continue
|
|
house = _house_number(data.get("house"), str(data.get("sign") or "") or None, asc_idx)
|
|
label = PLANET_ZH.get(name)
|
|
if house is None or not label or label in occupants[house]:
|
|
continue
|
|
occupants[house].append(label)
|
|
|
|
houses = []
|
|
for house in range(1, 13):
|
|
sign = _sign_cn(SIGNS[(asc_idx + house - 1) % 12])
|
|
if not sign:
|
|
return None
|
|
houses.append({
|
|
"house": house,
|
|
"sign": sign,
|
|
"occupants": occupants[house],
|
|
})
|
|
return {
|
|
"time": time[:5],
|
|
"lagna": lagna,
|
|
"houses": houses,
|
|
}
|
|
|
|
|
|
def compact_house_table_from_contexts(contexts: Any, time: str | None) -> dict[str, Any] | None:
|
|
if not isinstance(time, str) or not time or not isinstance(contexts, list):
|
|
return None
|
|
wanted = time[:5]
|
|
for context in contexts:
|
|
if not isinstance(context, dict):
|
|
continue
|
|
feature = context.get("feature") if isinstance(context.get("feature"), dict) else {}
|
|
feature_time = feature.get("time")
|
|
candidate_at = context.get("candidate_at")
|
|
context_time = feature_time if isinstance(feature_time, str) else None
|
|
if context_time is None and hasattr(candidate_at, "strftime"):
|
|
context_time = candidate_at.strftime("%H:%M")
|
|
if context_time != wanted:
|
|
continue
|
|
return compact_house_table(context.get("chart"), time=wanted)
|
|
return None
|