mirror of
https://github.com/migatu/astrololo.git
synced 2026-07-14 21:38:37 +00:00
Silnik efemeryd: EngineProvider + SkyfieldEngine + harness porównawczy
Pierwszy increment implementacji warstwy logicznej (ścieżka A). LOG-24: interfejs EphemerisEngine z dwoma backendami — SkyfieldEngine (własny, permisywny: Skyfield MIT + dane JPL public domain) oraz RemoteEngine (klient izolowanej usługi swisseph). Fabryka + leniwa inicjalizacja; endpointy /chart/positions i /chart/compare. LOG-01: pozycje obiektów (długość/szerokość ekliptyczna, prędkość, kierunek, formaty: w znaku / absolutny / dziesiętny). LOG-25/28: harness porównawczy (compare.py) z progami tolerancji oraz wspólny kontrakt parzystości; pełen zestaw testów. LOG-27: services/engine-swisseph — osobna, opcjonalna usługa AGPL (pyswisseph, tryb Moshiera), licencjonowana osobno, w compose pod profilem "comparison"; nie wchodzi do zamkniętego produktu. Walidacja: SkyfieldEngine zgadza się ze Swiss Ephemeris co do ~1" dla wszystkich 10 obiektów na horoskopie referencyjnym (30.04.1984, Warszawa); 12 testów przechodzi (silnik B pomijany gdy nieskonfigurowany). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""Formatowanie długości ekliptycznej (LOG-01: kilka zapisów).
|
||||
|
||||
Astrolog myśli w stopniach/minutach/sekundach w znaku, Excel woli dziesiętne,
|
||||
a część technik używa pozycji absolutnej 0–360°. Tu są czyste, bezstanowe
|
||||
funkcje konwersji — bez zależności od jakiegokolwiek silnika.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
SIGNS = [
|
||||
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
|
||||
]
|
||||
SIGN_ABBR = ["Ari", "Tau", "Gem", "Can", "Leo", "Vir",
|
||||
"Lib", "Sco", "Sag", "Cap", "Aqu", "Pis"]
|
||||
|
||||
|
||||
def norm360(lon: float) -> float:
|
||||
return lon % 360.0
|
||||
|
||||
|
||||
def sign_index(lon: float) -> int:
|
||||
"""0 = Aries … 11 = Pisces."""
|
||||
return int(norm360(lon) // 30)
|
||||
|
||||
|
||||
def _dms(deg: float) -> tuple[int, int, int]:
|
||||
"""Rozkład stopni (>=0) na (°, ', ") z poprawnym przeniesieniem zaokrąglenia."""
|
||||
total = round(deg * 3600)
|
||||
d, rem = divmod(total, 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
return d, m, s
|
||||
|
||||
|
||||
def in_sign(lon: float) -> str:
|
||||
"""Np. 'Tau 28°12'57\"' — pozycja w znaku."""
|
||||
lon = norm360(lon)
|
||||
idx = sign_index(lon)
|
||||
d, m, s = _dms(lon - idx * 30)
|
||||
if d >= 30: # zaokrąglenie przekroczyło granicę znaku
|
||||
idx = (idx + 1) % 12
|
||||
d -= 30
|
||||
return f"{SIGN_ABBR[idx]} {d}°{m:02d}'{s:02d}\""
|
||||
|
||||
|
||||
def absolute(lon: float) -> str:
|
||||
"""Np. '58°12'57\"' — pozycja absolutna 0–360°."""
|
||||
d, m, s = _dms(norm360(lon))
|
||||
return f"{d}°{m:02d}'{s:02d}\""
|
||||
|
||||
|
||||
def decimal(lon: float, places: int = 6) -> float:
|
||||
return round(norm360(lon), places)
|
||||
Reference in New Issue
Block a user