"""engine-swisseph — IZOLOWANA usługa silnika B (AGPL). UWAGA LICENCYJNA: ta usługa linkuje pyswisseph / Swiss Ephemeris, więc jest objęta **AGPL-3.0** i jest licencjonowana osobno (patrz ./LICENSE). Jest celowo wydzielona jako osobny proces i wołana przez HTTP — dzięki temu permisywny produkt (prezentacja + logika z silnikiem własnym + dane) NIE jest linkowany z kodem AGPL i nie podlega jego obowiązkom (LOG-27). Rola: wyrocznia walidacyjna / tryb porównawczy (LOG-25/26). Nie wchodzi do dystrybucji zamkniętej — uruchamiana tylko w profilu porównawczym/dev/CI. Udostępnia ten sam kontrakt co RemoteEngine po stronie warstwy logicznej. """ from __future__ import annotations from datetime import datetime from fastapi import FastAPI from pydantic import BaseModel import swisseph as swe app = FastAPI(title="astrololo · engine-swisseph (AGPL, izolowany)") # Tryb Moshiera: bez plików efemeryd, w pełni samowystarczalny (~0,1\" dokładności). _FLAGS = swe.FLG_MOSEPH | swe.FLG_SPEED _PLANETS = { "Sun": swe.SUN, "Moon": swe.MOON, "Mercury": swe.MERCURY, "Venus": swe.VENUS, "Mars": swe.MARS, "Jupiter": swe.JUPITER, "Saturn": swe.SATURN, "Uranus": swe.URANUS, "Neptune": swe.NEPTUNE, "Pluto": swe.PLUTO, } DEFAULT_OBJECTS = list(_PLANETS) class PositionsRequest(BaseModel): when_utc: datetime lat: float = 0.0 lon: float = 0.0 objects: list[str] | None = None @app.post("/positions") def positions(req: PositionsRequest) -> dict: d = req.when_utc ut_hours = d.hour + d.minute / 60.0 + d.second / 3600.0 jd = swe.julday(d.year, d.month, d.day, ut_hours) # czas uniwersalny rows = [] for name in (req.objects or DEFAULT_OBJECTS): xx, _retflag = swe.calc_ut(jd, _PLANETS[name], _FLAGS) lon, lat, _dist, lon_speed = xx[0], xx[1], xx[2], xx[3] rows.append({ "name": name, "longitude": lon % 360.0, "latitude": lat, "speed": lon_speed, "retrograde": lon_speed < 0, }) return {"engine": "swisseph", "positions": rows} @app.get("/health") def health() -> dict: return {"engine": "swisseph", "status": "ok", "mode": "moshier", "license": "AGPL-3.0"}