Files
astrololo/services/logic/app/engine/models.py
T
gitea a8c3072e62 Węzły księżycowe, mean Lilith i wykrywanie stacji (LOG-02/03)
Punkty wirtualne (LOG-02):
- engine/points.py: mean Node (Ω) i mean Lilith (apogeum) wzorami Meeusa;
  prędkości numerycznie. SN = NN + 180° (ta sama prędkość), zawsze Rx.
- DEFAULT_OBJECTS + North Node / South Node / Lilith — automatycznie dostają
  domy, aspekty i A/S. Parzystość silnika B: swe.MEAN_NODE / swe.MEAN_APOG
  (uwaga: stała pyswisseph to MEAN_APOG, nie MEAN_APOGEE).
- significators: tokeny [NN / [SN / [Lilith (zgodne z SIGNIFICATORS KEY).

Stacje (LOG-03):
- engine/stations.py: skan prędkości (krok 4 dni, okno ±800 dni — pokrywa
  najdłuższe przerwy Marsa/Wenus) + bisekcja; klasyfikacja SD/SR; poprzednia/
  następna stacja (dni, data, stopień w znaku) + flaga station_soon (<7 dni).
- /chart/positions: opt-in stations:true; UI: checkbox + tabela stacji.

Walidacja:
- mean NN vs astro-seek (Gem 8°09'24"): Δ=0,3'; vs swisseph: Δ=17";
  mean Lilith vs swisseph: Δ=1,5'. NN dom 12 / SN dom 6 zgodnie z astro-seek.
- Stacje Marsa 1984 trafiają w historię: SR 5.04.1984, SD 19.06.1984;
  samospójność |speed|<0,01°/d w znalezionych momentach; flaga "blisko"
  działa (Merkury +5,3d, Jowisz -0,6d).
- E2E na realnej bazie: [SN 134 rekordy, trafienie w 6. domu. 54 testy przechodzą.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:51:29 +02:00

67 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Modele domenowe silnika efemeryd — wspólny kontrakt dla KAŻDEGO silnika.
To jest część LOG-28 (parzystość): każdy silnik (własny Skyfield czy zdalny
swisseph) przyjmuje `ChartMoment` i zwraca listę `ObjectPosition` w identycznym
kształcie i jednostkach. Dzięki temu wyniki są bezpośrednio porównywalne, a
prezentacja/dane nie wiedzą, który silnik liczył.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Any
from app.engine import formats
# kanoniczny zestaw i kolejność obiektów (LOG-02: światła + 7 klasycznych +
# 3 nowożytne + punkty wirtualne: węzły mean i mean Lilith)
DEFAULT_OBJECTS = [
"Sun", "Moon", "Mercury", "Venus", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune", "Pluto",
"North Node", "South Node", "Lilith",
]
@dataclass(frozen=True)
class ChartMoment:
"""Wejście silnika: moment w UTC + lokalizacja geograficzna.
Pozycje obiektów zależą tylko od czasu (geocentrycznie); szerokość/długość
geograficzna będą potrzebne dopiero przy osiach i domach (LOG-05).
"""
when_utc: datetime # musi być świadome strefy (UTC)
lat: float = 0.0 # szerokość geograficzna, + na północ
lon: float = 0.0 # długość geograficzna, + na wschód
@dataclass(frozen=True)
class ObjectPosition:
"""Wynik dla jednego obiektu — surowe wartości w jednostkach SI astrologii."""
name: str
longitude: float # długość ekliptyczna 0360° (tropikalna, of-date)
latitude: float # szerokość ekliptyczna (°)
speed: float # prędkość w długości (°/dobę)
retrograde: bool
@property
def sign(self) -> str:
return formats.SIGNS[formats.sign_index(self.longitude)]
@property
def direction(self) -> str:
return "Rx" if self.retrograde else "D"
def as_dict(self) -> dict[str, Any]:
return {
"name": self.name,
"sign": self.sign,
"in_sign": formats.in_sign(self.longitude),
"absolute": formats.absolute(self.longitude),
"decimal": formats.decimal(self.longitude),
"latitude": round(self.latitude, 6),
"speed": round(self.speed, 6),
"direction": self.direction,
}