diff --git a/services/logic/app/engine/aspects.py b/services/logic/app/engine/aspects.py index 9950f6e..af9f7dc 100644 --- a/services/logic/app/engine/aspects.py +++ b/services/logic/app/engine/aspects.py @@ -27,6 +27,18 @@ LUMINARIES = {"Sun", "Moon"} DEFAULT_ORB = 8.0 LUMINARY_BONUS = 2.0 +# Pary sztywno powiązane definicyjnie — kąt między nimi wynika z samej definicji +# punktu, nie z układu nieba (SN = NN + 180°). Aspekt taki zawsze wychodzi +# dokładny (orb 0,00°) i nie niesie żadnej informacji astrologicznej, więc +# wycinamy go z wyników: zaśmieca listę w UI i zjada budżet promptu do LLM. +RIGID_PAIRS = frozenset({ + frozenset({"North Node", "South Node"}), +}) + + +def _is_rigid(name_a: str, name_b: str) -> bool: + return frozenset({name_a, name_b}) in RIGID_PAIRS + def separation(a: float, b: float) -> float: """Najmniejsza separacja kątowa [0,180].""" @@ -56,12 +68,16 @@ def find_aspects( Zwraca listę aspektów głównych; gdy znane są prędkości, każdy aspekt ma applying (bool) i skrót 'as': 'A'/'S' (aplikacyjny/separacyjny). + + Pary z RIGID_PAIRS (np. NN/SN) są pomijane — ich kąt jest definicyjny. """ out: list[dict] = [] n = len(positions) for i in range(n): for j in range(i + 1, n): a, b = positions[i], positions[j] + if _is_rigid(a["name"], b["name"]): + continue la, lb = a.get("decimal"), b.get("decimal") if la is None or lb is None: continue diff --git a/services/logic/tests/test_aspects.py b/services/logic/tests/test_aspects.py index 6890ca6..5db74c4 100644 --- a/services/logic/tests/test_aspects.py +++ b/services/logic/tests/test_aspects.py @@ -1,5 +1,5 @@ """Testy aspektów (LOG-06) — czysta matematyka.""" -from app.engine.aspects import find_aspects, separation +from app.engine.aspects import RIGID_PAIRS, find_aspects, separation def test_separation_wraparound(): @@ -55,6 +55,28 @@ def test_separating_when_moving_apart(): assert a["applying"] is False and a["as"] == "S" +def test_rigid_pair_nodes_not_reported(): + # SN = NN + 180° z definicji — trywialna opozycja, nie aspekt + pos = [ + {"name": "North Node", "decimal": 42.0, "speed": -0.053}, + {"name": "South Node", "decimal": 222.0, "speed": -0.053}, + {"name": "Sun", "decimal": 42.5, "speed": 0.96}, + ] + pairs = {frozenset((a["obj1"], a["obj2"])) for a in find_aspects(pos)} + assert frozenset({"North Node", "South Node"}) not in pairs + # aspekty węzłów do innych obiektów zostają nietknięte + assert frozenset({"Sun", "North Node"}) in pairs + assert frozenset({"Sun", "South Node"}) in pairs + + +def test_no_rigid_pairs_in_real_chart(own_engine, reference_moment): + from app.engine.chart import build_chart + + chart = build_chart(own_engine, reference_moment) + found = [a for a in chart["aspects"] if frozenset((a["obj1"], a["obj2"])) in RIGID_PAIRS] + assert not found, f"trywialne aspekty par sztywnych w horoskopie: {found}" + + def test_no_as_flag_without_speeds(): pos = [{"name": "Sun", "decimal": 0.0}, {"name": "Moon", "decimal": 2.0}] assert "as" not in find_aspects(pos)[0]