f1956a08ff
build / build (push) Successful in 1m1s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m33s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 28s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
Aspekty głowne (LOG-06) mierzą kąt wzdłuż ekliptyki. LOG-07 dokłada dwa rodzaje powiązań, które klasyczna astrologia liczy naprawdę, nie na oko: - PARALELA/KONTRPARALELA DEKLINACJI — dwa ciała na tej samej („parallel") lub przeciwnej („contraparallel") deklinacji działają jak koniunkcja/opozycja, mimo że wzdłuż ekliptyki mogą stać gdziekolwiek. Deklinacja liczona PEŁNYM wzorem (z szerokością ekliptyczną — istotne dla Księżyca i planet schodzących z ekliptyki), przez to_equatorial z LOG-04. Orb konfigurowalny (domyślnie 1°, ciasno — to kontakty punktowe). - ANTYSCJA/KONTRANTYSCJA — odbicie długości względem osi przesileń (0° Raka – 0° Koziorożca) albo równonocy (0° Barana – 0° Wagi). Liczone na współrzędnych TROPIKALNYCH of-date, bo deklinacja jest fizyczna (równikowa, niezależna od zodiaku), a antyscja z definicji tropikalna — jej oś to kardynalne punkty zodiaku tropikalnego. Dlatego PRZED przesunięciem na zodiak syderyczny/draconiczny. Dodatkowo: flaga „out of bounds" (|deklinacja| > nachylenie ekliptyki — ciało poza zakresem Słońca) i kolumna deklinacji przy każdej pozycji. Integracja z bazą: baza interpretacyjna ZNA paralele pod frazą „P. Dec.", więc generujemy fasetkę paraleli (trafia też do promptu LLM). PUŁAPKA: samo „Dec." w bazie to często DEKANAT („3rd Dec. of [Gem"), więc szukamy dokładnie „P. Dec." — inaczej sypnęłoby fałszywymi trafieniami. Kontrparaleli i antyscji baza nie opisuje osobnym znacznikiem, więc zostają obliczeniem display-only. Pary z RIGID_PAIRS (węzły) odsiane: SN = NN+180° na ekliptyce → deklinacja ZAWSZE przeciwna, czyli definicyjna kontrparalela bez informacji. Walidacja wobec faktów NIEZALEŻNYCH od kodu: - deklinacja Słońca 30.04.1984 = +14.88° wobec ~+14.9° z almanachu, - antyscja to arytmetyka odbicia: inwolucja i pary znaków (Rak↔Bliźnięta, Baran↔Panna) sprawdzone na piechotę, - węzły faktycznie mają przeciwną deklinację i są odsiane, - deklinacja niezależna od zodiaku (tropikalny == syderyczny co do 1e-6°), - realna baza: mechanizm paraleli znajduje wpisy „P. Dec." i odsiewa dekanaty (z 33 wpisów „P. Dec." 5 ma niepusty efekt — głównie „[conj or P. Dec."). UI: kolumna deklinacji (+znacznik OOB), tabela paraleli/kontrparaleli i tabela antyscji na ekranie Horoskop. Testy: 15 nowych (out_of_zodiac) + 2 (fasetka paraleli vs pułapka dekanatu). Całość: logika 251 passed / 1 skipped, prezentacja 25 passed. Render szablonu sprawdzony osobno (bez błędu Jinja, wszystkie sekcje obecne). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
130 lines
6.1 KiB
Python
130 lines
6.1 KiB
Python
"""Testy mostu obliczenia → sygnifikatory → wyszukiwanie (bez efemeryd/HTTP)."""
|
|
from app.engine.models import DEFAULT_OBJECTS
|
|
from app.significators import PLANET_ABBR, _ordinal, build_report
|
|
|
|
|
|
class FakeData:
|
|
def __init__(self, rows_by_value: dict) -> None:
|
|
self.rows_by_value = rows_by_value
|
|
|
|
def search(self, key, value, exact, limit, fields=None) -> dict:
|
|
rows = self.rows_by_value.get(value, [])
|
|
return {"provider": "fake", "total": len(rows), "rows": rows}
|
|
|
|
|
|
def test_sign_and_house_facets():
|
|
positions = [{"name": "Sun", "sign": "Taurus", "direction": "D", "house": 11}]
|
|
data = FakeData({"[Su": [
|
|
{"significator": "[Su in [Tau", "actioneffect": "efekt znak"}, # faseta znak
|
|
{"significator": "[Su in 11th H.", "actioneffect": "efekt dom"}, # faseta dom
|
|
{"significator": "[Su in [Vir", "actioneffect": "inny"}, # żadna
|
|
{"significator": "[Su in [Tau", "actioneffect": "nan"}, # szum
|
|
]})
|
|
item = build_report(positions, data)["objects"][0]
|
|
assert item["object"] == "Sun" and item["house"] == 11
|
|
facets = {f["type"]: f for f in item["facets"]}
|
|
assert facets["sign"]["count"] == 1
|
|
assert facets["sign"]["samples"][0]["effect"] == "efekt znak"
|
|
assert facets["sign"]["samples"][0]["expanded"] == "Sun in Taurus" # rozwinięcie skrótu
|
|
assert facets["house"]["count"] == 1
|
|
assert facets["house"]["token"] == "11th H."
|
|
assert facets["house"]["samples"][0]["effect"] == "efekt dom"
|
|
|
|
|
|
def test_no_house_facet_when_house_missing():
|
|
positions = [{"name": "Mars", "sign": "Scorpio", "direction": "Rx"}]
|
|
data = FakeData({"[Ma": [{"significator": "[Ma in [Sco", "actioneffect": "eff"}]})
|
|
types = [f["type"] for f in build_report(positions, data)["objects"][0]["facets"]]
|
|
assert "sign" in types and "house" not in types
|
|
|
|
|
|
def test_dedup_by_significator_and_effect():
|
|
positions = [{"name": "Moon", "sign": "Taurus", "direction": "D", "house": 11}]
|
|
data = FakeData({"[Mo": [
|
|
{"significator": "[Mo in 11th H.", "actioneffect": "efekt"},
|
|
{"significator": "[Mo in 11th H.", "actioneffect": "efekt"}, # duplikat (sig+opis)
|
|
{"significator": "[Mo in 11th H.", "actioneffect": "inny efekt"}, # ten sam sig, inny opis
|
|
]})
|
|
facets = {f["type"]: f for f in build_report(positions, data)["objects"][0]["facets"]}
|
|
assert facets["house"]["count"] == 2 # duplikat odsiany, różny opis zostaje
|
|
|
|
|
|
def test_aspect_facet():
|
|
positions = [
|
|
{"name": "Sun", "sign": "Taurus", "direction": "D", "house": 11},
|
|
{"name": "Moon", "sign": "Taurus", "direction": "D", "house": 11},
|
|
]
|
|
aspects = [{"obj1": "Sun", "obj2": "Moon", "aspect": "conjunction", "orb": 2.0}]
|
|
data = FakeData({"[Su": [{"significator": "[Su [conj [Mo", "actioneffect": "złączeni"}]})
|
|
sun = build_report(positions, data, aspects=aspects)["objects"][0]
|
|
asp = [f for f in sun["facets"] if f["type"] == "aspect"]
|
|
assert asp and asp[0]["count"] == 1 and "Moon" in asp[0]["label"]
|
|
|
|
|
|
def test_parallel_facet_hits_db_and_avoids_decanate_trap():
|
|
"""Paralela deklinacji (LOG-07): baza zna ją jako „P. Dec.". Pułapka: samo
|
|
„Dec." to często DEKANAT („3rd Dec. of [Gem"), który NIE może wpaść do paraleli."""
|
|
positions = [
|
|
{"name": "Mercury", "sign": "Taurus", "direction": "D", "house": 3},
|
|
{"name": "Saturn", "sign": "Scorpio", "direction": "Rx", "house": 9},
|
|
]
|
|
parallels = [{"obj1": "Mercury", "obj2": "Saturn", "type": "parallel",
|
|
"orb": 0.3, "allowed": 1.0}]
|
|
data = FakeData({"[Me": [
|
|
{"significator": "[Me at B. in P. Dec. [Sa and [Ma", "actioneffect": "dyscyplina umysłu"},
|
|
{"significator": "3rd Dec. of [Gem on Asc.", "actioneffect": "TO DEKANAT, nie paralela"},
|
|
{"significator": "[Me [conj [Sa", "actioneffect": "inny aspekt, nie paralela"},
|
|
]})
|
|
par = [f for f in build_report(positions, data, parallels=parallels)["objects"][0]["facets"]
|
|
if f["type"] == "parallel"]
|
|
assert par and par[0]["count"] == 1
|
|
assert "Saturn" in par[0]["label"]
|
|
assert all("Dec. of" not in s["significator"] for s in par[0]["samples"]), "dekanat przeciekł"
|
|
|
|
|
|
def test_contraparallel_gets_no_db_facet():
|
|
"""Baza nie ma osobnego znacznika dla kontrparaleli — nie zmyślamy fasety."""
|
|
positions = [
|
|
{"name": "Mercury", "sign": "Taurus", "direction": "D", "house": 3},
|
|
{"name": "Saturn", "sign": "Scorpio", "direction": "Rx", "house": 9},
|
|
]
|
|
parallels = [{"obj1": "Mercury", "obj2": "Saturn", "type": "contraparallel",
|
|
"orb": 0.3, "allowed": 1.0}]
|
|
data = FakeData({"[Me": [
|
|
{"significator": "[Me at B. in P. Dec. [Sa", "actioneffect": "paralela, nie kontr-"},
|
|
]})
|
|
types = [f["type"] for f in build_report(positions, data, parallels=parallels)["objects"][0]["facets"]]
|
|
assert "parallel" not in types
|
|
|
|
|
|
def test_grouping_by_effect():
|
|
positions = [{"name": "Mars", "sign": "Scorpio", "direction": "Rx", "house": 5}]
|
|
data = FakeData({"[Ma": [
|
|
{"significator": "[Ma in 5th H.", "actioneffect": "miscarriage"},
|
|
{"significator": "[Sa in 5th H.", "actioneffect": "miscarriage"}, # inny sig, ten sam opis
|
|
{"significator": "[Ma in 5th H.", "actioneffect": "fever"},
|
|
]})
|
|
house = [f for f in build_report(positions, data, group=True)["objects"][0]["facets"]
|
|
if f["type"] == "house"][0]
|
|
groups = {g["effect"]: g for g in house["groups"]}
|
|
assert groups["miscarriage"]["count"] == 2
|
|
assert len(groups["miscarriage"]["significators"]) == 2
|
|
assert "score" in house
|
|
|
|
|
|
def test_facets_carry_score():
|
|
positions = [{"name": "Sun", "sign": "Taurus", "direction": "D", "house": 11}]
|
|
data = FakeData({"[Su": [{"significator": "[Su in [Tau", "actioneffect": "e"}]})
|
|
facets = build_report(positions, data)["objects"][0]["facets"]
|
|
assert all("score" in f for f in facets)
|
|
|
|
|
|
def test_ordinal():
|
|
assert _ordinal(1) == "1st" and _ordinal(2) == "2nd" and _ordinal(3) == "3rd"
|
|
assert _ordinal(4) == "4th" and _ordinal(11) == "11th" and _ordinal(12) == "12th"
|
|
|
|
|
|
def test_planet_abbr_covers_all_default_objects():
|
|
for o in DEFAULT_OBJECTS:
|
|
assert o in PLANET_ABBR
|