|
|
@@ -69,6 +69,13 @@ def _parse_date(date_str: str) -> float:
|
|
|
return julday(dt.year, dt.month, dt.day, 12.0)
|
|
|
|
|
|
|
|
|
+def _sign_name(lon: float) -> str:
|
|
|
+ signs = ["Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius","Capricorn","Aquarius","Pisces"]
|
|
|
+ return signs[int(lon / 30) % 12]
|
|
|
+
|
|
|
+def _sign_abbr(lon: float) -> str:
|
|
|
+ return _sign_name(lon)[:3]
|
|
|
+
|
|
|
def _utc_date_from_datetime(dt_str: str | None) -> str:
|
|
|
"""Convert an ISO datetime string to a UTC date string."""
|
|
|
if dt_str is None:
|
|
|
@@ -373,6 +380,7 @@ def get_sky_state(
|
|
|
lon: float | None = None,
|
|
|
elevation: float = 0.0,
|
|
|
geocentric: bool = True,
|
|
|
+ house_system: str | None = None,
|
|
|
) -> dict:
|
|
|
"""
|
|
|
Return a consolidated raw sky-state snapshot for downstream chart engines.
|
|
|
@@ -380,6 +388,23 @@ def get_sky_state(
|
|
|
This stays purely descriptive: it combines the core astronomical outputs
|
|
|
already exposed elsewhere in the server without adding signs, houses, or
|
|
|
interpretation.
|
|
|
+
|
|
|
+ Args:
|
|
|
+ datetime: ISO 8601 datetime (UTC). Defaults to now.
|
|
|
+ lat: Observer latitude in decimal degrees.
|
|
|
+ lon: Observer longitude in decimal degrees.
|
|
|
+ elevation: Observer elevation in meters.
|
|
|
+ geocentric: If True, return geocentric positions instead of topocentric.
|
|
|
+ house_system: Optional house system code. When provided, includes house
|
|
|
+ cusps and angles in the response. Supported codes:
|
|
|
+ 'P' = Placidus, 'K' = Koch, 'E' = Equal, 'W' = Whole Sign,
|
|
|
+ 'A' = Alcabitius, 'C' = Campanus, 'M' = Morinus, 'R' = Porphyry,
|
|
|
+ 'T' = Polich/Page, 'U' = Krusinski-Pisa, 'V' = Vehlow Equal,
|
|
|
+ 'X' = Meridian, 'Y' = Horizontal, 'H' = Azimuthal, 'B' = Alcabitius,
|
|
|
+ 'O' = Equal/MC, 'F' = Carter poli-eq, 'D' = Equal from 15° Aries,
|
|
|
+ 'G' = Gauquelin sectors, 'I' = Sunshine, 'J' = Sunshine alt,
|
|
|
+ 'L' = Pullen SD, 'N' = Equal/1, 'Q' = Pullen SR, 'S' = Sripati,
|
|
|
+ 'Z' = APC houses.
|
|
|
"""
|
|
|
lat, lon = _default_location(lat, lon)
|
|
|
date = _utc_date_from_datetime(datetime)
|
|
|
@@ -387,13 +412,14 @@ def get_sky_state(
|
|
|
|
|
|
ck = cache_key(
|
|
|
"get_sky_state",
|
|
|
- cache_version=1,
|
|
|
+ cache_version=2,
|
|
|
datetime=datetime or "now",
|
|
|
date=date,
|
|
|
elevation=elevation,
|
|
|
geocentric=geocentric,
|
|
|
lat=lat,
|
|
|
lon=lon,
|
|
|
+ house_system=house_system,
|
|
|
)
|
|
|
cached = cache.get(ck)
|
|
|
if cached:
|
|
|
@@ -405,6 +431,7 @@ def get_sky_state(
|
|
|
"lon": lon,
|
|
|
"elevation": elevation,
|
|
|
"geocentric": geocentric,
|
|
|
+ "house_system": house_system,
|
|
|
},
|
|
|
**cached,
|
|
|
}
|
|
|
@@ -428,6 +455,7 @@ def get_sky_state(
|
|
|
"lon": lon,
|
|
|
"elevation": elevation,
|
|
|
"geocentric": geocentric,
|
|
|
+ "house_system": house_system,
|
|
|
},
|
|
|
"timestamp_utc": datetime or __import__("datetime").datetime.now(timezone.utc).isoformat(),
|
|
|
"julian_day": round(jd, 6),
|
|
|
@@ -436,6 +464,38 @@ def get_sky_state(
|
|
|
"lunar_state": lunar_state,
|
|
|
"sidereal_time": sidereal_time,
|
|
|
}
|
|
|
+
|
|
|
+ # Compute house cusps when requested
|
|
|
+ if house_system is not None:
|
|
|
+ hs_code = house_system.upper().encode()
|
|
|
+ try:
|
|
|
+ cusps, ascmc = swe.houses(jd, lat, lon, hs_code)
|
|
|
+ houses = []
|
|
|
+ for i in range(12):
|
|
|
+ houses.append({
|
|
|
+ "house": i + 1,
|
|
|
+ "absolute_lon": cusps[i],
|
|
|
+ "sign": _sign_name(cusps[i]),
|
|
|
+ "abbreviation": _sign_abbr(cusps[i]),
|
|
|
+ "degree": cusps[i] % 30.0,
|
|
|
+ })
|
|
|
+ result["houses"] = {
|
|
|
+ "system": house_system.upper(),
|
|
|
+ "cusps": houses,
|
|
|
+ "ascendant": ascmc[0],
|
|
|
+ "midheaven": ascmc[1],
|
|
|
+ "descendant": ascmc[0] + 180.0 if ascmc[0] < 180.0 else ascmc[0] - 180.0,
|
|
|
+ "imum_coeli": ascmc[1] + 180.0 if ascmc[1] < 180.0 else ascmc[1] - 180.0,
|
|
|
+ "armc": ascmc[2] if len(ascmc) > 2 else None,
|
|
|
+ "equatorial_ascendant": ascmc[3] if len(ascmc) > 3 else None,
|
|
|
+ "vertex": ascmc[4] if len(ascmc) > 4 else None,
|
|
|
+ "co_ascendant": ascmc[5] if len(ascmc) > 5 else None,
|
|
|
+ "polar_ascendant": ascmc[6] if len(ascmc) > 6 else None,
|
|
|
+ }
|
|
|
+ except Exception as exc:
|
|
|
+ logger.warning(f"house calculation failed for system {house_system!r}: {exc}")
|
|
|
+ result["houses"] = {"system": house_system.upper(), "error": str(exc)}
|
|
|
+
|
|
|
cache.set(
|
|
|
ck,
|
|
|
{
|
|
|
@@ -445,6 +505,7 @@ def get_sky_state(
|
|
|
"solar_events": solar_events,
|
|
|
"lunar_state": lunar_state,
|
|
|
"sidereal_time": sidereal_time,
|
|
|
+ "houses": result.get("houses"),
|
|
|
},
|
|
|
config.CACHE_SKY,
|
|
|
)
|