"""Tabele pomocnicze horoskopu (LOG-23). Wartości referencyjne dla horoskopu 30.04.1984 09:20 UTC, Kraków (50.0647N, 19.9450E) sprawdzone wobec faktów NIEZALEŻNYCH od naszego kodu: * 30.04.1984 to poniedziałek → władca dnia Księżyc; 5. godzina poniedziałku w porządku chaldejskim to Słońce (Mo, Sa, Ju, Ma, Su), * wschód/zachód dla Krakowa końcem kwietnia ≈ 5:18 / 19:57 czasu lokalnego (CEST = UTC+2), czyli 03:18 / 17:57 UTC, * pełnia poprzedzająca urodzenie: 15.04.1984 ok. 19:11 UTC. """ from datetime import datetime, timezone import pytest from app.engine.chart import build_chart from app.engine.models import ChartMoment from app.engine.tables import ( build_tables, critical_degrees, dwadasamsa, element_of, moon_phase, navamsa, planetary_hours, prenatal_syzygy, quality_of, tally, ) @pytest.fixture(scope="module") def krakow(): return ChartMoment(when_utc=datetime(1984, 4, 30, 9, 20, tzinfo=timezone.utc), lat=50.0647, lon=19.9450) @pytest.fixture(scope="module") def tables(own_engine, krakow): return build_tables(own_engine, krakow, build_chart(own_engine, krakow)) # ------------------------------------------------------ żywioły i jakości def test_element_and_quality_mapping(): assert element_of("Aries") == "Fire" and element_of("Cancer") == "Water" assert quality_of("Aries") == "Cardinal" and quality_of("Taurus") == "Fixed" assert quality_of("Gemini") == "Mutable" def test_tally_matches_hand_count(tables): """Ręcznie przeliczone dla horoskopu referencyjnego (10 planet + Asc).""" base = tables["tally"]["with_modern_10_plus_asc"] assert base["elements"] == {"Fire": 4, "Earth": 4, "Air": 0, "Water": 3} assert base["qualities"] == {"Cardinal": 4, "Fixed": 6, "Mutable": 1} assert base["total"] == 11 def test_missing_element_detected(tables): """Klasyczne „no air" — podstawa pod scoring siły (LOG-21).""" assert tables["tally"]["missing_elements"] == ["Air"] assert tables["tally"]["missing_qualities"] == [] def test_tally_variants_differ_by_object_count(tables): t = tables["tally"] assert t["classical_7"]["total"] == 7 assert t["with_modern_10"]["total"] == 10 assert t["classical_7_plus_asc"]["total"] == 8 assert t["with_modern_10_plus_asc"]["total"] == 11 def test_tally_sums_equal_counted_objects(tables): for variant in ("classical_7", "with_modern_10", "with_modern_10_plus_asc"): v = tables["tally"][variant] assert sum(v["elements"].values()) == v["total"] assert sum(v["qualities"].values()) == v["total"] # ---------------------------------------------------------- faza Księżyca def test_moon_phase_reference_is_balsamic_new(tables): """Nów wypadł 1.05.1984, więc 30.04 Księżyc jest tuż przed nowiem.""" mp = tables["moon_phase"] assert mp["phase"] == "New Moon" assert 340.0 < mp["angle"] < 360.0 assert mp["illumination"] < 0.05 assert mp["waxing"] is False # elongacja > 180 = ubywa @pytest.mark.parametrize("angle,expected", [ (0.0, "New Moon"), (90.0, "First Quarter"), (180.0, "Full Moon"), (270.0, "Last Quarter"), (46.0, "Waxing Crescent"), (300.0, "Waning Crescent"), ]) def test_moon_phase_buckets(angle, expected): assert moon_phase(0.0, angle)["phase"] == expected def test_moon_phase_illumination_extremes(): assert moon_phase(0.0, 0.0)["illumination"] == 0.0 assert moon_phase(0.0, 180.0)["illumination"] == 1.0 assert moon_phase(0.0, 90.0)["illumination"] == pytest.approx(0.5) # ------------------------------------------------------- stopnie krytyczne def test_critical_degrees_reference(tables): found = {c["name"]: c["flags"] for c in tables["critical_degrees"]} assert "Jupiter" in found # Cap 12°57' -> 13° kardynalny assert any("13" in f for f in found["Jupiter"]) assert "Pluto" in found # Sco 0°28' -> wejście w znak def test_anaretic_degree_flagged(): flags = critical_degrees([{"name": "X", "sign": "Leo", "decimal": 149.5, "in_sign": "Leo 29°30'"}]) assert flags and any("anaretyczny" in f for f in flags[0]["flags"]) def test_no_flags_for_ordinary_degree(): assert critical_degrees([{"name": "X", "sign": "Leo", "decimal": 135.0, "in_sign": "Leo 15°"}]) == [] # --------------------------------------------------------------- podziały def test_dwadasamsa_starts_from_own_sign(): """12. część liczy się OD znaku, w którym stoi punkt.""" assert dwadasamsa(0.0) == pytest.approx(0.0) # Ari 0 -> Ari assert dwadasamsa(2.5) == pytest.approx(30.0) # Ari 2°30' -> Tau 0 assert dwadasamsa(30.0) == pytest.approx(30.0) # Tau 0 -> Tau def test_navamsa_classic_starts(): """Znaki kardynalne zaczynają od siebie, stałe od 9. znaku.""" assert navamsa(0.0) == pytest.approx(0.0) # Ari -> Ari assert navamsa(30.0) == pytest.approx(270.0) # Tau -> Cap (9. od Byka) assert navamsa(60.0) == pytest.approx(180.0) # Gem -> Lib def test_divisional_covers_all_positions(tables, own_engine, krakow): chart = build_chart(own_engine, krakow) assert len(tables["divisional"]) == len(chart["positions"]) # ------------------------------------------------- dzień i godziny planetarne def test_planetary_day_ruler_is_moon_on_monday(tables): """30.04.1984 to poniedziałek → władcą dnia jest Księżyc.""" assert tables["planetary_hours"]["day_ruler"] == "Moon" def test_planetary_hour_matches_chaldean_sequence(tables): """Poniedziałek: 1=Mo, 2=Sa, 3=Ju, 4=Ma, 5=Su — urodzenie w 5. godzinie dnia.""" ph = tables["planetary_hours"] assert ph["hour_number"] == 5 assert ph["hour_ruler"] == "Sun" assert ph["daytime"] is True def test_sunrise_sunset_match_krakow_late_april(tables): """Wschód ≈ 03:18 UTC, zachód ≈ 17:57 UTC (5:18 i 19:57 czasu lokalnego).""" ph = tables["planetary_hours"] assert ph["period_start"].startswith("1984-04-30T03:1") assert ph["period_end"].startswith("1984-04-30T17:5") def test_planetary_hours_are_unequal_and_complete(tables): """Godziny są nierówne: wiosną dzienna trwa dłużej niż 60 minut.""" ph = tables["planetary_hours"] assert ph["hour_length_minutes"] > 60.0 assert len(ph["hours"]) == 12 assert sum(1 for h in ph["hours"] if h["current"]) == 1 def test_polar_night_returns_none(own_engine): """Za kołem podbiegunowym w grudniu Słońce nie wschodzi — brak godzin.""" polar = ChartMoment(when_utc=datetime(2024, 12, 21, 12, 0, tzinfo=timezone.utc), lat=78.0, lon=15.0) assert planetary_hours(own_engine, polar) is None # ------------------------------------------------------- syzygia prenatalna def test_prenatal_syzygy_is_april_1984_full_moon(tables): """Rzeczywista pełnia: 15.04.1984 ok. 19:11 UTC.""" s = tables["prenatal_syzygy"] assert s["type"] == "full_moon" assert s["when_utc"].startswith("1984-04-15T19:1") def test_prenatal_syzygy_precedes_birth_within_a_cycle(tables): days = tables["prenatal_syzygy"]["days_before_birth"] assert 0 < days < 29.6, "syzygia musi być w ostatnim cyklu przed urodzeniem" def test_prenatal_syzygy_elongation_is_at_target(own_engine, krakow): """W znalezionym momencie elongacja MUSI wynosić 0° albo 180°.""" from app.engine.formats import norm360 s = prenatal_syzygy(own_engine, krakow) when = datetime.fromisoformat(s["when_utc"]) pts = {p.name: p.longitude for p in own_engine.positions(ChartMoment(when_utc=when, lat=krakow.lat, lon=krakow.lon), ["Sun", "Moon"])} elong = norm360(pts["Moon"] - pts["Sun"]) target = 0.0 if s["type"] == "new_moon" else 180.0 assert abs(((elong - target + 180.0) % 360.0) - 180.0) < 0.02 # ------------------------------------------------------------------ całość def test_build_tables_light_skips_numeric_search(own_engine, krakow): """heavy=False pomija to, co wymaga szukania numerycznego.""" light = build_tables(own_engine, krakow, build_chart(own_engine, krakow), heavy=False) assert "tally" in light and "moon_phase" in light assert "planetary_hours" not in light and "prenatal_syzygy" not in light