"""Testy osi i domów — czysta matematyka (LOG-05, bez efemeryd).""" import pytest from app.engine import houses as H # RAMC i ε policzone Skyfieldem dla horoskopu referencyjnego (30.04.1984, Warszawa) RAMC, EPS, LAT = 353.1968, 23.44133, 52.2333 def _near(a, b, tol=0.05): return abs(((a - b + 180) % 360) - 180) < tol def test_asc_mc_match_reference(): asc = H.compute_asc(RAMC, EPS, LAT) mc = H.compute_mc(RAMC, EPS) assert _near(asc, 112.18) # Cancer 22°10' (astro.com) assert _near(mc, 352.59) # Pisces 22°35' def test_whole_sign_starts_on_sign_boundary(): cusps = H.cusps(112.18, 352.59, H.WHOLE_SIGN) assert cusps[0] == 90.0 # dom 1 = 0° Raka assert cusps[1] == 120.0 def test_equal_cusps_are_30_apart_from_asc(): cusps = H.cusps(112.18, 352.59, H.EQUAL) assert abs(cusps[0] - 112.18) < 1e-9 assert abs(cusps[1] - 142.18) < 1e-9 def test_porphyry_angles_on_cusps(): cusps = H.cusps(112.18, 352.59, H.PORPHYRY) assert abs(cusps[0] - 112.18) < 1e-9 # Asc = dom 1 assert abs(cusps[9] - 352.59) < 1e-9 # MC = dom 10 assert abs(cusps[6] - (112.18 + 180) % 360) < 1e-9 # Dsc = dom 7 def test_assign_house_whole_sign(): cusps = H.cusps(112.18, 352.59, H.WHOLE_SIGN) # dom 1 = Rak (90–120°) assert H.assign_house(100.0, cusps) == 1 # w Raku assert H.assign_house(40.0, cusps) == 11 # Byk -> 11. dom # ── regresje wykryte przez porównanie z wyrocznią (tests/oracle) ────────── def test_ascendant_branch_beyond_polar_circle(): """REGRESJA: powyżej koła podbiegunowego `atan2` wybierał NIEWŁAŚCIWY punkt przecięcia ekliptyki z horyzontem — zwracaliśmy Descendent zamiast Ascendenta, czyli błąd o 180°. Planety lądowały w PRZECIWNYCH domach dla całej północnej Skandynawii. Rozstrzyga położenie względem MC: punkt wschodzący leży zawsze w półkolu (0°,180°) na wschód od MC.""" eps = 23.4393 for lat in (67.0, 70.0, 85.0, -70.0): for ramc in (60.0, 100.0, 200.0, 270.0, 300.0): asc = H.compute_asc(ramc, eps, lat) mc = H.compute_mc(ramc, eps) offset = (asc - mc) % 360.0 assert 0.0 <= offset <= 180.0, ( f"Asc poza półkolem wschodzącym: lat={lat} ramc={ramc} offset={offset:.4f}") def test_whole_sign_is_deterministic_at_sign_boundary(): """REGRESJA: Ascendent o włos od granicy znaku (359,999999999976 vs 1e-10 — ta sama wartość, różne strony) przerzucał cały dom I o 30°. Ten sam horoskop na innej maszynie dawał inny wynik.""" below = H.cusps(359.999999999976, 270.0, H.WHOLE_SIGN) above = H.cusps(1e-10, 270.0, H.WHOLE_SIGN) assert below[0] == above[0] == 0.0, (below[0], above[0]) def test_polar_circle_moves_with_obliquity(): """Granica dziedziny Placidusa/Kocha NIE jest stałą 66,56° — zależy od ε, a ε zmienia się z datą. Testy brzegowe muszą ją liczyć per data.""" assert H.polar_circle(23.4393) == pytest.approx(66.5607, abs=1e-4) # dziś assert H.polar_circle(23.747) == pytest.approx(66.253, abs=1e-4) # 370 p.n.e. # ── systemy egzotyczne o zamkniętym wzorze (Etap 1) ────────────────────── # Zgodność z wyrocznią sprawdza tests/oracle; tu pilnujemy niezmienników, które # muszą zachodzić także bez swissepha (czyli w każdym środowisku). EXOTIC = ("vehlow", "morinus", "regiomontanus", "campanus", "alcabitus") @pytest.mark.parametrize("system", EXOTIC) def test_exotic_returns_twelve_cusps_in_range(system): out = H.cusps_for(100.0, 23.4393, 50.0, system) assert len(out) == 12 assert all(0.0 <= c < 360.0 for c in out) @pytest.mark.parametrize("system", EXOTIC) def test_opposite_houses_are_antipodal(system): """Domy przeciwległe leżą na tym samym kole wielkim, więc ich cuspy są dokładnie antypodyczne. Naruszenie tego oznacza błąd w wyborze gałęzi.""" out = H.cusps_for(137.0, 23.4393, 42.0, system) for i in range(6): assert abs(((out[i + 6] - out[i]) % 360.0) - 180.0) < 1e-9, f"domy {i+1}/{i+7}" @pytest.mark.parametrize("system", ("regiomontanus", "campanus", "alcabitus")) def test_quadrant_systems_anchor_on_ascendant(system): """Systemy kwadrantowe zaczynają dom I na Ascendencie.""" ramc, eps, lat = 100.0, 23.4393, 50.0 assert H.cusps_for(ramc, eps, lat, system)[0] == pytest.approx( H.compute_asc(ramc, eps, lat), abs=1e-9) def test_vehlow_puts_ascendant_in_the_middle_of_house_one(): ramc, eps, lat = 100.0, 23.4393, 50.0 asc = H.compute_asc(ramc, eps, lat) assert H.cusps_for(ramc, eps, lat, "vehlow")[0] == pytest.approx( H.norm360(asc - 15.0), abs=1e-9) def test_morinus_ignores_latitude(): """Morinus rzutuje równik wprost na ekliptykę, bez horyzontu — jako jedyny nie zależy od szerokości geograficznej.""" a = H.cusps_for(100.0, 23.4393, 20.0, "morinus") b = H.cusps_for(100.0, 23.4393, 65.0, "morinus") assert a == pytest.approx(b, abs=1e-12) def test_unknown_system_is_rejected(): with pytest.raises(ValueError): H.cusps_for(100.0, 23.4393, 50.0, "nie-ma-takiego")