mirror of
https://github.com/migatu/astrololo.git
synced 2026-07-14 21:38:37 +00:00
a8c3072e62
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>
67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
"""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 0–360° (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,
|
||
}
|