年运附表补上整宫、Tripataki 和特殊点公式轨迹。黄经沿用已算好的星盘,不改原有 Saham 数值。
Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Grok (agent loop)
Cursor
parent
6b906f85c4
commit
9b2e6aa0b4
@@ -0,0 +1,73 @@
|
||||
"""Whole-sign houses for an annual chart that is already computed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
SIGNS = (
|
||||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||||
)
|
||||
SIGN_LORDS = {
|
||||
"Aries": "Mars", "Taurus": "Venus", "Gemini": "Mercury", "Cancer": "Moon",
|
||||
"Leo": "Sun", "Virgo": "Mercury", "Libra": "Venus", "Scorpio": "Mars",
|
||||
"Sagittarius": "Jupiter", "Capricorn": "Saturn", "Aquarius": "Saturn", "Pisces": "Jupiter",
|
||||
}
|
||||
|
||||
|
||||
def _longitude(value: Any) -> float | None:
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
return None
|
||||
return float(value) % 360.0
|
||||
|
||||
|
||||
def build_annual_whole_sign_houses(chart: dict | None) -> dict[str, Any]:
|
||||
"""Twelve whole-sign houses from the annual ascendant already on the chart."""
|
||||
chart = chart if isinstance(chart, dict) else {}
|
||||
ascendant = chart.get("ascendant") if isinstance(chart.get("ascendant"), dict) else {}
|
||||
planets = chart.get("planets") if isinstance(chart.get("planets"), dict) else {}
|
||||
asc_lon = _longitude(ascendant.get("degree_raw"))
|
||||
if asc_lon is None:
|
||||
asc_lon = _longitude(ascendant.get("lon") if ascendant.get("lon") is not None else ascendant.get("degree"))
|
||||
if asc_lon is None:
|
||||
return {
|
||||
"schema": "jyotish.annual_whole_sign_houses.v1",
|
||||
"status": "blocked",
|
||||
"reason": "annual_ascendant_longitude_missing",
|
||||
"houses": [],
|
||||
"source_longitudes": {},
|
||||
}
|
||||
asc_index = int(asc_lon // 30.0) % 12
|
||||
placed: dict[int, list[str]] = {index: [] for index in range(12)}
|
||||
source_longitudes = {}
|
||||
for name in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn", "Rahu", "Ketu"):
|
||||
data = planets.get(name)
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
longitude = _longitude(data.get("degree_raw"))
|
||||
if longitude is None:
|
||||
longitude = _longitude(data.get("degree"))
|
||||
if longitude is None:
|
||||
continue
|
||||
if name in ("Sun", "Moon") and data.get("degree_raw") is not None and not isinstance(data.get("degree_raw"), bool):
|
||||
source_longitudes[name] = float(data["degree_raw"]) % 360.0
|
||||
placed[int(longitude // 30.0) % 12].append(name)
|
||||
houses = []
|
||||
for house in range(1, 13):
|
||||
sign_index = (asc_index + house - 1) % 12
|
||||
sign = SIGNS[sign_index]
|
||||
houses.append({
|
||||
"house": house,
|
||||
"sign": sign,
|
||||
"lord": SIGN_LORDS[sign],
|
||||
"occupants": placed[sign_index],
|
||||
"cusp_longitude": sign_index * 30.0,
|
||||
})
|
||||
return {
|
||||
"schema": "jyotish.annual_whole_sign_houses.v1",
|
||||
"status": "partial_verified",
|
||||
"house_system": "whole_sign",
|
||||
"houses": houses,
|
||||
"source_longitudes": source_longitudes,
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Formula traces for Sahams that were already calculated.
|
||||
|
||||
The trace copies the stored longitude. It does not replace it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
# (result key, day (p1, p2), night (p1, p2) or None when day and night match)
|
||||
# _saham(p1, p2) = Asc + p2 - p1.
|
||||
_LOCAL = (
|
||||
("karya_saham", ("Moon", "Mars"), ("Mars", "Moon")),
|
||||
("vivah_saham", ("Mars", "Venus"), None),
|
||||
("rajya_saham", ("Jupiter", "Sun"), None),
|
||||
("shatru_saham", ("Saturn", "Mars"), None),
|
||||
("labha_saham", ("Moon", "Jupiter"), None),
|
||||
("parakrama_saham", ("Sun", "Mars"), None),
|
||||
("putra_saham", ("Moon", "Jupiter"), None),
|
||||
("jnana_saham", ("Mercury", "Jupiter"), None),
|
||||
("raja_saham", ("Moon", "Sun"), None),
|
||||
("yasha_saham", ("Sun", "Jupiter"), None),
|
||||
("karma_saham", ("Mars", "Saturn"), None),
|
||||
("bandhu_saham", ("Mercury", "Mars"), None),
|
||||
("matri_saham", ("Venus", "Moon"), None),
|
||||
("pitri_saham", ("Saturn", "Sun"), None),
|
||||
("janma_saham", ("Sun", "Asc"), None),
|
||||
("mrityu_saham", ("Mars", "Saturn"), None),
|
||||
("rogha_saham", ("Saturn", "Mercury"), None),
|
||||
("aarogya_saham", ("Saturn", "Jupiter"), None),
|
||||
("bhraatri_saham", ("Moon", "Mars"), None),
|
||||
("ghataka_saham", ("Mars", "Rahu"), None),
|
||||
("paradesa_saham", ("Moon", "Saturn"), None),
|
||||
("parabharya_saham", ("Mars", "Venus"), None),
|
||||
("dhan_saham", ("Venus", "Jupiter"), None),
|
||||
("maya_saham", ("Ketu", "Rahu"), None),
|
||||
("moksha_saham", ("Jupiter", "Ketu"), None),
|
||||
("buddha_saham", ("Jupiter", "Mercury"), None),
|
||||
("shastra_saham", ("Ketu", "Mars"), None),
|
||||
("kala_saham", ("Sun", "Saturn"), None),
|
||||
("shakti_saham", ("Moon", "Venus"), None),
|
||||
("bhrigu_saham", ("Jupiter", "Venus"), None),
|
||||
("sundara_saham", ("Mercury", "Venus"), None),
|
||||
("jnati_saham", ("Saturn", "Mars"), None),
|
||||
("artha_saham", ("Venus", "Sun"), None),
|
||||
("dharma_saham", ("Saturn", "Jupiter"), None),
|
||||
("sangrama_saham", ("Rahu", "Mars"), None),
|
||||
)
|
||||
|
||||
|
||||
def _rule_text(meta: dict) -> str:
|
||||
operands = meta.get("formula_operands") or ()
|
||||
text = " + ".join(str(part).replace("_", " ") for part in operands) if operands else str(meta.get("rule_name") or "rule")
|
||||
if meta.get("zodiacal_order_exception_applied"):
|
||||
text += " (+30 deg)"
|
||||
return text
|
||||
|
||||
|
||||
def attach_formula_traces(
|
||||
results: dict[str, Any],
|
||||
*,
|
||||
is_day: bool,
|
||||
formula_meta: dict[str, dict] | None = None,
|
||||
) -> list[dict]:
|
||||
"""Add ``formula_traces`` without changing any existing Saham longitude."""
|
||||
formula_meta = formula_meta or {}
|
||||
traces = []
|
||||
punya = formula_meta.get("punya_saham")
|
||||
if punya and isinstance(results.get("punya_saham"), dict):
|
||||
traces.append({
|
||||
"name": results["punya_saham"].get("name") or "Punya Saham",
|
||||
"key": "punya_saham",
|
||||
"rule": _rule_text(punya),
|
||||
"phase": "day" if is_day else "night",
|
||||
"longitude": results["punya_saham"].get("longitude"),
|
||||
})
|
||||
for key, day_pair, night_pair in _LOCAL:
|
||||
item = results.get(key)
|
||||
if not isinstance(item, dict) or not isinstance(item.get("longitude"), (int, float)):
|
||||
continue
|
||||
meta = formula_meta.get(key)
|
||||
if meta:
|
||||
rule = _rule_text(meta)
|
||||
else:
|
||||
pair = day_pair if is_day or night_pair is None else night_pair
|
||||
rule = f"Asc + {pair[1]} - {pair[0]}"
|
||||
traces.append({
|
||||
"name": item.get("name") or key,
|
||||
"key": key,
|
||||
"rule": rule,
|
||||
"phase": "day" if is_day else "night",
|
||||
"longitude": item.get("longitude"),
|
||||
})
|
||||
results["formula_traces"] = traces
|
||||
return traces
|
||||
@@ -0,0 +1,104 @@
|
||||
"""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": {},
|
||||
}
|
||||
Reference in New Issue
Block a user