feat(prezentacja): szkielet kosmogramu — koło horoskopowe SVG (PRE-12)
build / build (push) Successful in 1m26s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m10s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
build / build (push) Successful in 1m26s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m10s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Pierwszy krok rysowania wykresu (PRE-12): SZKIELET koła — pierścień znaków, podział na domy i osie. Planety i linie aspektów w kolejnych iteracjach. Rysowane po stronie serwera jako czysty string SVG (bez zależności zewnętrznych — spójne z CSP). Ciemny motyw, spójny z paletą aplikacji (kolory z --line, --accent, --muted; glify znaków barwione wg żywiołu w stonowanych kolorach czytelnych na ciemnym tle). Geometria wg konwencji astrologicznej: Ascendent po LEWEJ, długość ekliptyczna rośnie przeciwnie do ruchu wskazówek zegara. Punkt λ → kąt φ = 180° − (λ − Asc); w SVG y rośnie w dół, co formuła uwzględnia. Zakotwiczenie sprawdzone testami liczbowo: Asc po lewej, Dsc po prawej, oś pozioma; Asc+90° (II dom) na dole. Elementy: dwa okręgi + piasta, podziałki co 5°/10°, granice znaków co 30° z glifem w środku sektora, szprychy domów od pasa do piasty, numery domów w środku każdego domu, osie Asc–Dsc i MC–IC wyróżnione akcentem z etykietami AC/DC/MC/IC. Dane bierzemy WYŁĄCZNIE z wyniku /chart/positions — prezentacja nic nie liczy: - `sign_glyphs` (pierścień 12 znaków) i glify — z LOG-22, - `angles` z długością `decimal` — już były, - `cusps` z `decimal` — DOŁOŻONE w tym PR (jedna linia w build_chart). Bez tego domy dało się narysować tylko dla whole sign; z długością cuspu działa dla KAŻDEGO systemu. Zweryfikowane na porphyry: cuspy poza wielokrotnościami 30°, szprychy odchodzą od granic znaków. Degradacja: silnik bez osi/domów albo starszy wynik bez `decimal` w cuspach → brak koła (pusty string), nie wyjątek. Testy: 8 (poprawność XML, 12 glifów, 4 osie, numery domów, Asc po lewej liczbowo, CCW, degradacja). Całość: prezentacja 33 passed, logika 265 / 1 skipped. Render potwierdzony wizualnie na horoskopie referencyjnym (AC=Leo po lewej, MC=Aries u góry, domy CCW). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #26.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
"""Szkielet kosmogramu — SVG po stronie serwera (PRE-12).
|
||||
|
||||
Sprawdzamy, że SVG jest POPRAWNY (parsuje się jako XML — inaczej wstawiony do
|
||||
strony rozjedzie render) i że niesie to, co ma nieść: pierścień 12 znaków, cztery
|
||||
osie i numery domów. Geometria (Asc po lewej) sprawdzona liczbowo, nie na oko.
|
||||
"""
|
||||
import xml.dom.minidom as minidom
|
||||
|
||||
from app import chartwheel
|
||||
|
||||
|
||||
def _sample_chart(asc=128.9, mc=5.0):
|
||||
"""Minimalny wynik z warstwy logiki: osie + cuspy (whole sign) + pierścień znaków."""
|
||||
signs = ["Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
|
||||
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces"]
|
||||
glyphs = ["♈", "♉", "♊", "♋", "♌", "♍", "♎", "♏", "♐", "♑", "♒", "♓"]
|
||||
asc_sign = int(asc // 30)
|
||||
cusps = [{"house": i + 1, "sign": signs[(asc_sign + i) % 12],
|
||||
"decimal": (asc_sign * 30 + i * 30) % 360, "sign_glyph": glyphs[(asc_sign + i) % 12]}
|
||||
for i in range(12)]
|
||||
return {
|
||||
"angles": {
|
||||
"Asc": {"name": "Asc", "decimal": asc, "sign": signs[asc_sign]},
|
||||
"MC": {"name": "MC", "decimal": mc},
|
||||
"Dsc": {"name": "Dsc", "decimal": (asc + 180) % 360},
|
||||
"IC": {"name": "IC", "decimal": (mc + 180) % 360},
|
||||
},
|
||||
"cusps": cusps,
|
||||
"sign_glyphs": [{"sign": s, "glyph": g} for s, g in zip(signs, glyphs)],
|
||||
}
|
||||
|
||||
|
||||
def _rendered():
|
||||
return chartwheel.render(_sample_chart())
|
||||
|
||||
|
||||
def test_output_is_well_formed_xml():
|
||||
"""Wstawiamy surowy SVG do strony — musi być poprawnym XML, bo inaczej
|
||||
zepsuje cały dokument."""
|
||||
svg = _rendered().replace("var(--line)", "#000").replace(
|
||||
"var(--accent)", "#000").replace("var(--muted)", "#000")
|
||||
doc = minidom.parseString(svg) # rzuci wyjątkiem, gdy błędny
|
||||
assert doc.documentElement.tagName == "svg"
|
||||
|
||||
|
||||
def test_has_twelve_sign_glyphs():
|
||||
assert _rendered().count('class="glyph"') == 12
|
||||
|
||||
|
||||
def test_has_all_four_axis_labels():
|
||||
svg = _rendered()
|
||||
for label in (">AC<", ">DC<", ">MC<", ">IC<"):
|
||||
assert label in svg
|
||||
|
||||
|
||||
def test_has_house_numbers_one_to_twelve():
|
||||
svg = _rendered()
|
||||
for h in range(1, 13):
|
||||
assert f">{h}<" in svg
|
||||
|
||||
|
||||
def test_ascendant_is_on_the_left():
|
||||
"""Konwencja: Asc po lewej. Sprawdzamy oś AC–DC liczbowo — punkt Asc musi mieć
|
||||
x wyraźnie mniejszy od środka (220), a Dsc większy."""
|
||||
chart = _sample_chart(asc=128.9)
|
||||
asc = chart["angles"]["Asc"]["decimal"]
|
||||
ax, ay = chartwheel._pt(asc, asc, chartwheel.R_ZOD)
|
||||
dx, dy = chartwheel._pt(asc + 180, asc, chartwheel.R_ZOD)
|
||||
assert ax < chartwheel._CX - 100, "Asc powinien być po lewej"
|
||||
assert dx > chartwheel._CX + 100, "Dsc powinien być po prawej"
|
||||
assert abs(ay - chartwheel._CY) < 1e-6, "oś Asc–Dsc jest pozioma"
|
||||
|
||||
|
||||
def test_longitude_increases_counterclockwise():
|
||||
"""Asc+90° (II dom) ląduje na DOLE koła (y > środek, bo w SVG y rośnie w dół)."""
|
||||
asc = 100.0
|
||||
_, y = chartwheel._pt(asc + 90, asc, chartwheel.R_ZOD)
|
||||
assert y > chartwheel._CY + 100
|
||||
|
||||
|
||||
def test_empty_when_no_angles():
|
||||
"""Silnik bez osi/domów (brak sidereal) → brak koła, nie wyjątek."""
|
||||
assert chartwheel.render({"positions": []}) == ""
|
||||
assert chartwheel.render({"angles": {}, "cusps": [], "sign_glyphs": []}) == ""
|
||||
|
||||
|
||||
def test_empty_when_cusps_lack_longitude():
|
||||
"""Starsze wyniki bez `decimal` w cuspach — degradujemy do pustego, nie sypiemy."""
|
||||
chart = _sample_chart()
|
||||
for c in chart["cusps"]:
|
||||
del c["decimal"]
|
||||
assert chartwheel.render(chart) == ""
|
||||
Reference in New Issue
Block a user