Commit Graph

49 Commits

Author SHA1 Message Date
gitea f24616d342 feat(render): raport PDF przez LaTeX jako osobna usluga (PRE-24)
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 34s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Ostatnia z osmiu wskazowek partnerow. Nowa usluga services/render sklada raport:
generuje plik posredni .tex i kompiluje go XeLaTeX-em do PDF.

OSOBNY komponent, nie czesc prezentacji — ta sama zasada co przy izolacji
swissepha (LOG-27). TeX Live wazy setki megabajtow; w obrazie produktu
spowalnialby kazdy build, a tak aktualizuje sie niezaleznie i jego awaria nie
kladzie aplikacji, tylko przycisk „Pobierz PDF".

SZYFROWANIE, o ktore prosiles: lacze prezentacja↔render idzie tak samo jak
pozostale, ale z WLASNYM, TRZECIM kluczem (LINK_KEY_PRESENTATION_RENDER). Osobny,
bo tym laczem plynie CALY raport — dane urodzeniowe i opisy z baz — wiec przejecie
go nie moze otwierac lacza do logiki ani do danych. Fail-closed: bez klucza pod
nie wstaje. Test kopii link_crypto obejmuje teraz cztery uslugi.

Uklad PDF wg prosby: imie i nazwisko -> wprowadzone dane -> RYSUNEK kosmogramu
-> interpretacja natalna -> predykcje okresowe. Test pilnuje kolejnosci.

Dwie rzeczy, ktore wyszly dopiero przy skladaniu tego kawalka:

1. KOSMOGRAM NIE NADAWAL SIE DO PDF. Uzywa zmiennych CSS (var(--line)) i klasy
   .glyph, ktorej font podaje styles.css — a samodzielny konwerter SVG→PDF nie zna
   naszego arkusza. Wyszlyby czarne kreski BEZ SYMBOLI. Stad wariant „print":
   konkretne kolory na bialym tle i font glifow wpisany wprost w rysunek. Przy
   okazji zalatwia to etap 4 planu kola (wariant do druku).

2. UCIECZKA ZNAKOW LATEXA byla zepsuta — i zlapal to moj wlasny test. Zamiana
   „\” na \textbackslash{} szla w tej samej petli co nawiasy, wiec kolejne
   podmiany ucieklyby nawiasy dopiero co wstawione: wychodzilo
   \textbackslash\{\}. Poprawka: ukosnik chowany pod znacznik zastepczy i
   rozwijany na koncu. To nie kosmetyka — niezauwazony „%” komentuje RESZTE LINII,
   wiec zdanie od modelu urywaloby sie w polowie, a PDF powstawalby normalnie,
   tylko krotszy. Test na wrogim tekscie sprawdza tez, ze \end{document} ani
   \input{} nie wyrwa sie z dokumentu.

Usluga nie zapisuje nic poza katalogiem tymczasowym, ktory sprzata po sobie;
w manifescie readOnlyRootFilesystem + emptyDir na /tmp. /health raportuje
obecnosc xelatex i rsvg-convert, zeby zepsuty obraz bylo widac od razu.

Zweryfikowane na zywo (TestClient uslugi render): zadanie bez szyfrowania
z POPRAWNYM tokenem -> 400; obcy klucz -> 400 i tajny opis nie wraca; wlasciwy
klucz -> zadanie dochodzi do aplikacji, tresci baz NIE MA na kablu, odpowiedz
zaszyfrowana. Manifesty przechodza kubectl apply --dry-run=server na zywym
klastrze.

CZEGO NIE SPRAWDZILEM: samej kompilacji PDF. W tym srodowisku nie ma ani TeX
Live, ani dzialajacego runtime'u kontenerow (docker CLI jest, daemon nie) —
probowalem zbudowac obraz i sie nie dalo. Sprawdzone jest wszystko dookola:
generowanie .tex, ucieczka znakow, szyfrowanie, kontrakt API, samowystarczalnosc
SVG. Pierwsze uruchomienie na klastrze trzeba obejrzec — dlatego PRE-24 zostaje
jako „W trakcie", nie „Zrobione".

Instrukcja wdrozenia: docs/wdrozenie-render-pdf.md (klucz, obraz, merge,
weryfikacja, znane ograniczenia).

Testy: 15 nowych (render) + 14 (lacze i wariant druku w prezentacji).
Calosc: prezentacja 131, logika 265/1 skip, render 15.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 19:21:49 +02:00
gitea 6c9029c496 feat(prezentacja): zakladka „Skompiluj" — zbiorczy raport (PRE-23)
build / build (push) Successful in 52s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m16s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 32s
Testy / Kontrola składni wszystkich warstw (push) Failing after 8s
Czwarta wskazowka partnerow. Nowa zakladka sklada w jedno trzy kawalki liczone
w roznych miejscach: policzony horoskop, interpretacje natalna z AI i wszystkie
zapamietane predykcje okresowe.

Uklad sekcji dokladnie wg prosby: imie i nazwisko -> wprowadzone dane ->
RYSUNEK kosmogramu -> interpretacja natalna -> predykcje okresowe. Test pilnuje
tej kolejnosci, bo to jedyna rzecz, ktora latwo przestawic przy refaktorze,
a partnerzy podali ja wprost.

Skad material:
- HOROSKOP liczymy TU NA NOWO z danych formularza, zamiast go zapamietywac.
  To czysta funkcja wejscia — tanio powtorzyc, a odpada trzymanie w przegladarce
  duzego wyniku, ktory moglby sie rozjechac z aktualnym formularzem.
- INTERPRETACJA NATALNA — nowy natal.js, odpowiednik predictions.js. Natalna jest
  JEDNA (dotyczy momentu urodzenia, nie okresu), wiec ponowne wygenerowanie
  podmienia slot zamiast dokladac wpis.
- PREDYKCJE — z magazynu z PRE-22.

compile.js czyta magazyny przez ICH API (window.astrololoNatal /
astrololoPredictions), a nie siegajac wprost do localStorage — format danych ma
jednego wlasciciela: modul, ktory je zapisuje. Test tego pilnuje.

Zakladka MOWI, CZEGO BRAKUJE: panel gotowosci z trzema pozycjami i podpowiedzia,
na ktorej zakladce uzupelnic. Bez tego uzytkownik zlozylby niekompletny raport
i dowiedzialby sie o tym dopiero po otwarciu PDF-a.

Tresc od modelu jest ESKEJPOWANA przed wstawieniem do DOM — to tekst z zewnatrz,
wiec bez tego mielibysmy wektor wstrzykniecia.

Weryfikacja na zywej aplikacji: dwie predykcje zapisane w Kalendarzu, natalna
w Interpretacjach, obie odczytane na Skompiluj (panel: brak horoskopu na zolto,
dwa pozostale na zielono). Po zlozeniu wszystkie trzy zielone, a raport zaczyna
sie od „Jan Kowalski" i danych wejsciowych. Kolejnosc sekcji sprawdzona na
wyrenderowanym HTML: naglowek 2931 < dane 3033 < kosmogram 3158 < natalna 26544
< predykcje 26575.

Testy: 16 nowych. Poprawione tez trzy wlasne testy, ktore lapaly nazwy plikow
w KOMENTARZACH zamiast w tagach skryptow. Calosc: prezentacja 117 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 18:09:56 +02:00
gitea 8cc329ab17 feat(prezentacja): zapamiętane predykcje okresowe (PRE-22) + wymaganie o cache-bustingu
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 12m9s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Failing after 1m6s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 20s
Piata wskazowka partnerow: Kalendarz ma pozwalac liczyc predykcje dla KILKU
okresow i je zachowywac, zeby wszystkie trafily potem do raportu („Skompiluj",
PRE-23/24).

Nowy predictions.js — magazyn w localStorage, tak jak wspolne dane formularza
(PRE-21): serwer zostaje bezstanowy, zadne dane urodzeniowe ani tresci z baz nie
laduja po stronie uslugi. Zakladka „Skompiluj" odczyta to samo miejsce przez
window.astrololoPredictions (jedno zrodlo prawdy).

Decyzje projektowe:

- KLUCZEM TOZSAMOSCI JEST OKRES. Ponowne policzenie tego samego zakresu podmienia
  wpis zamiast dokladac duplikat — inaczej lista puchlaby przy kazdej probie
  z innym modelem albo budzetem. Dzieki temu zapis jest idempotentny, wiec dziala
  tez wariant bez strumienia (zapis przy wczytaniu strony z gotowym wynikiem)
  i odswiezenie niczego nie mnozy.
- progress.js OGLASZA gotowy horoskop zdarzeniem `astrololo:horoscope` z profilem,
  zamiast sam zapisywac. To okno postepu, a nie magazyn — zapisywanie zostaje
  odpowiedzialnoscia predictions.js. Filtr po profilu pilnuje, zeby interpretacja
  natalna nie trafila na liste predykcji okresowych.
- Przepelniony magazyn (horoskopy bywaja dlugie) jest ZGLASZANY uzytkownikowi,
  a nie polykany — inaczej wynik znikalby po cichu.

UI: lista zapamietanych predykcji na Kalendarzu — okres, data zapisu, objetosc
i przycisk usuwania. Skrypt podpiety w timeline.html (nie w base.html), zeby nie
kolidowac z rownolegle otwartym #29.

PRE-26 — dopisane wymaganie o wersjonowaniu plikow statycznych. Przy PRE-25
przegladarka podala STARY styles.css i powiekszanie kosmogramu „nie dzialalo",
mimo ze klasa byla nakladana. Objaw jest zdradliwy: szablony sa nowe, wiec strona
wyglada na zaktualizowana, a funkcja po prostu milczy. Po wdrozeniu moze to
spotkac uzytkownikow.

Weryfikacja na zywej aplikacji: dwa okresy zapisane; powtorzenie tego samego
zakresu podmienilo wpis (dalej 2, tekst zaktualizowany); lista posortowana po
dacie; predykcje przetrwaly przejscie na inna zakladke i powrot; interpretacja
natalna NIE wpadla na liste; usuwanie zmniejszylo licznik 2 -> 1 i przerysowalo
liste. Testy: 14 nowych. Calosc: prezentacja 101 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:57:24 +02:00
gitea 76fbdefeaa feat(prezentacja): kliknięcie powiększa kosmogram na pełne okno (PRE-25)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m52s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 46s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 19s
build / build (push) Successful in 1m10s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m59s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m50s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 42s
Testy / Kontrola składni wszystkich warstw (push) Successful in 19s
Ósma wskazówka partnerów, jako małe QoL przed PRE-22. Koło rysujemy w kolumnie
tekstu, więc na mniejszym ekranie glify i stopnie robią się nieczytelne.
Kliknięcie rozciąga wykres na całe okno, ponowne wraca do strony. SVG skaluje
się bez utraty jakości, więc nie potrzeba drugiej wersji rysunku ani biblioteki.

Wyjście z powiększenia na trzy sposoby: klik w wykres, klik w tło, Esc.
Dostępne też z klawiatury (Enter/Spacja, focus-visible), bo inaczej obejrzenie
szczegółów wymagałoby myszy.

Dwie decyzje warte odnotowania:

1. WARSTWA. z-index 1100 CELOWO pomiędzy: ponad kontrolkami Leafleta (1000),
   ale PONIŻEJ okna postępu (1200). Gdy trwa pisanie horoskopu, log operacji ma
   zostać na wierzchu. Test pilnuje tej nierówności, bo to jedyna liczba tutaj,
   którą łatwo zmienić bez zastanowienia i zepsuć coś niewidocznego na oko.

2. ROZMIAR. Koło jest kwadratowe, więc ograniczamy je KRÓTSZYM bokiem okna
   (min(96vw, 92vh)) — inaczej na szerokim ekranie wystawałoby w pionie.

Skrypt podpięty globalnie w base.html i osłonięty sprawdzeniem, czy koło w ogóle
jest na stronie — zadziała też na przyszłej zakładce „Skompiluj" bez zmian.
Przy powiększeniu blokujemy przewijanie strony pod spodem.

Weryfikacja na żywej aplikacji, pomiarami w DOM: po kliknięciu .wheel-fig ma
position:fixed, display:flex, z-index:1100; SVG rośnie z 460×460 do 662×653
przy oknie 1280×720, visibility visible; body dostaje overflow:hidden. Ponowne
kliknięcie wraca do 460×460 i zdejmuje klasę. Zrzutu ekranu stanu powiększonego
NIE mam — panel przeglądarki zaciął się w trakcie (puste klatki, viewport
raportowany jako 0×0), więc opieram się na pomiarach i testach.

Testy: 10 nowych. Całość: prezentacja 87 passed, logika 265 / 1 skip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 17:22:48 +02:00
gitea f34016a4a5 feat(prezentacja): wspolne dane formularza miedzy zakladkami + imie i nazwisko (PRE-21)
build / build (push) Successful in 58s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m4s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 38s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Trzecia wskazowka partnerow. Kazda zakladke (Horoskop / Interpretacje /
Kalendarz) wypelnialo sie od nowa — te same imie, data, godzina, strefa
i miejsce. Teraz raz wpisane dane wedruja za uzytkownikiem, a zmiana w jednej
zakladce przenosi sie na pozostale.

Nowy formsync.js trzyma stan w localStorage. Dlaczego nie sesja na serwerze:
serwer zostaje BEZSTANOWY — zadnego magazynu sesji, zadnych danych urodzeniowych
trzymanych po stronie uslugi (spojne z postawa z LOG-32/PRE-16). Przy okazji
dane synchronizuja sie tez miedzy osobnymi kartami przegladarki, bo zdarzenie
`storage` daje to za darmo.

Dolozone pole „imie i nazwisko" (wszystkie trzy zakladki) — potrzebne do
naglowka raportu PDF (PRE-24). Handlery przyjmuja je i oddaja, wiec nie znika
po przeliczeniu.

Dwie rzeczy, ktore trzeba bylo domknac, zeby to dzialalo naprawde:

1. KOLEJNOSC SKRYPTOW. formsync.js ladowany w <head> z `defer` — skrypty defer
   wykonuja sie w kolejnosci dokumentu, wiec ten zdazy odtworzyc wspolrzedne,
   ZANIM geo.js zbuduje mape. Mapa startuje od razu we wlasciwym miejscu,
   zamiast przeskakiwac po chwili.

2. geo.js ustawia pola z KODU (`.value = ...`), co samo z siebie NIE wywoluje
   zdarzen — bez tego synchronizacja przegapilaby kazdy wybor z mapy, z
   wyszukiwarki i z „Tu i teraz". Dolozony setVal(), ktory jawnie zglasza
   `change`.

Zakladka bez danego pola (np. Sygnifikatory) nie kasuje wartosci zapamietanej
gdzie indziej; uszkodzony wpis w localStorage nie blokuje formularza.

Q-14 rozstrzygniete: lancuch LaTeX->PDF stanie jako OSOBNA USLUGA render —
spojne z izolacja swisseph (LOG-27), obraz produktu zostaje maly.

Weryfikacja na zywym stacku (data+logika+prezentacja) w przegladarce: dane
wpisane w Horoskopie pojawily sie w Interpretacjach; zmiana godziny w
Interpretacjach dotarla do Kalendarza; pola nieobecne na zakladce (zodiak,
system domow) przetrwaly; po POST imie zostalo, a horoskop policzyl sie
normalnie. Testy: 22 nowe strukturalne. Calosc: prezentacja 67 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:12:49 +00:00
gitea 5c82e8bd9f fix(prezentacja): offset wzgledem GMT + nazwa lokalizacji po „Tu i teraz”
Dwie pierwsze wskazowki od partnerow biznesowych; pozostale piec zapisane jako
wymagania (PRE-21..PRE-24) do zrobienia w kolejnych krokach.

PRE-19 — offset wzgledem GMT. Etykieta mowila „Strefa (offset h)”, czyli nie
bylo jasne, wzgledem czego liczymy przesuniecie. Teraz „Offset wzgledem GMT (h)”
z podpowiedzia. Krok juz byl 15-minutowy (0,25 h) — dolozony zakres −12…+14,
zeby nie dalo sie wpisac strefy, ktora nie istnieje. Zmiana w trzech zakladkach,
ktore maja to pole (Horoskop, Interpretacje, Kalendarz).

PRE-20 — po „Tu i teraz” wspolrzedne i pineska skakaly na biezace polozenie, ale
w polu tekstowym zostawala STARA, wczesniej wpisana nazwa. Formularz pokazywal
jedno miejsce, a liczyl dla innego — cicha pomylka, nic sie nie wywalalo.
Handler zdarzenia astrololo:coords odswieza teraz nazwe przez reverseName.

Przy okazji druga strona tego samego bledu: reverseName czysci pole ZANIM
wysle zapytanie. Gdyby /reverse nie odpowiedzialo (brak sieci), zostalaby stara
nazwa — lepiej puste pole i poprawne wspolrzedne niz nazwa, ktora klamie.

Wymagania: PRE-19/20 (zrobione), PRE-21 wspolne dane miedzy zakladkami wraz
z polem imie i nazwisko, PRE-22 wiele predykcji okresowych w pamieci sesji,
PRE-23 zakladka „Skompiluj”, PRE-24 raport PDF przez LaTeX. Dolozone pytanie
otwarte Q-14 o lancuch LaTeX→PDF (gdzie postawic TeX Live, silnik unicode owy
pod glify, konwersja SVG) — decyzja wplywa na deploy i rozmiar obrazow.

Testy: 12 nowych, strukturalnych na zrodle (JS-a nie uruchomimy, a obie regresje
sa ciche). Sprawdzone sabotazem — po cofnieciu kazdej poprawki czerwienieja.
Calosc: prezentacja 45 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:12:49 +00:00
gitea 40c9bf7988 feat(prezentacja): obiekty na kosmogramie — glify, stopnie, retrogradacja (PRE-12)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 38s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 27s
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m46s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m54s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Etap 2 rysowania koła (po szkielecie): obiekty na swoich pozycjach.

Każdy obiekt dostaje: kreske na wewnetrznej krawedzi pasa w PRAWDZIWEJ pozycji,
glif, stopien w znaku i znacznik retrogradacji ℞ (dodatkowo kolorem, zeby dalo
sie ja wylapac nie czytajac znak po znaku).

ROZSUWANIE CIASNYCH SKUPISK — sedno tego etapu. W horoskopie referencyjnym
Merkury i Wenus dzieli 0,41°, Wenus i Ksiezyc 3,68°: bez rozsuwania glify
rysuja sie jeden na drugim. Rozsuwamy tylko GLIFY; kreska zostaje w prawdziwej
pozycji, a gdy glif jest odsuniety, laczymy je cienka linia odniesienia — wykres
nie moze klamac o tym, gdzie planeta faktycznie stoi.

Bledy zlapane przy weryfikacji (oba wyszly z pomiarow, nie z „wyglada dobrze"):

1. PODPISY STOPNI zlewaly sie w skupiskach. O ciasnocie decyduje nie glif, tylko
   podpis — lezy blizej srodka (r=130), gdzie ten sam kat to mniej pikseli.
   Stad odstep 8° zamiast 7°, podpis bez „°" (jak w programach astrologicznych)
   i mniejszy font. Teraz: glify min 20,9 px, podpisy 18,1 px (prog 16).

2. ROZSUWANIE NIE DZIALALO na prawdziwych danych — Wenus ladowala DOKLADNIE na
   Ksiezycu (0,3 px). Przyczyna: odstep liczony modulo 360. Przesuniecie, ktore
   przerzucalo obiekt ZA sasiada, dawalo luke ~359,9° zamiast ujemnej, wiec
   algorytm uznawal, ze jest luzem, i konczyl. Poprawka: rozwijamy katy do osi
   MONOTONICZNEJ, gdzie ujemna luka zostaje ujemna i zawsze sie ja wylapie.
   Test regresyjny na dokladnie tych danych; sprawdzony sabotazem (po przywroceniu
   modulo czerwienieje).

Etykiety osi (AC/DC/MC/IC) przeniesione POZA kolo — w srodku wchodzily w pierscien
obiektow i zaslanialy glify (Ksiezyc znikal pod linia MC). ViewBox 440→470, kolo
bez zmian, margines mieści etykiety.

Testy: 11 nowych (regresja rozsuwania, zachowanie kolejnosci, obiekty bez kolizji
nieruszone, zawiniecie przez 0°, awaryjny rowny rozklad, stopien w znaku,
retrogradacja, niekompletny obiekt nie wywala rysunku). Calosc: prezentacja 44,
logika 265 / 1 skip. Potwierdzone wizualnie: cale skupisko Slonce/Ksiezyc/Wenus/
Merkury czytelne i rozdzielone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:49:51 +02:00
gitea 1d7f3e2136 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
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>
2026-07-24 12:32:47 +02:00
gitea ace76183e5 feat(logic): glify astrologiczne — konwersja tekst↔symbol (LOG-22)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m55s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 37s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 1m22s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m39s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
Warunek wstępny pod kosmogram (PRE-12): planety i znaki na kole rysujemy
symbolami. Nowy moduł engine/glyphs.py:

- forward: nazwa → glif (planety, punkty, Loty, znaki, aspekty, retro ℞),
- reverse: glif → nazwa (znosi obecność/brak selektora wariantu),
- glyphify(): tokeny [XX z bazy → glify; przyklad z wymagan
  „[Sa [Pis 26°08' [conj [PF" → „♄  26°08' ☌ ⊗" (stopnie nietkniete).

DAN-18 „tylko tekst, nigdy emoji" potraktowane serio, TRZEMA warstwami — bo
sam selektor NIE wystarcza (potwierdzone wizualnie w przegladarce):
1. w danych: selektor wariantu tekstowego U+FE0E na znakach zodiaku oraz ♀/♂
   (maja wariant emoji); ☉ i reszta go nie dostaja (zbedny),
2. CSS font-variant-emoji:text,
3. CSS font-family celujacy w MONOCHROMATYCZNE fonty symboli PRZED emoji.
   Bez pkt. 3 macOS/Chromium i tak renderowal znaki  jako kolorowe kafelki
   Apple Color Emoji mimo VS15 — planety wychodzily tekstem, znaki nie. Stack
   „Apple Symbols / Segoe UI Symbol / Noto Sans Symbols2" naprawia render.
   NIGDZIE nie emitujemy U+FE0F (emoji) — pilnuje tego test.

Wpiete w build_chart: kazda pozycja dostaje glyph (planeta) + sign_glyph (znak,
zalezny od zodiaku, wiec po przesunieciu), osie i Loty tak samo (Fortuna ⊗,
reszta Lotow bez standardowego symbolu → None), aspekty dostaja glif,
result[„sign_glyphs"] to pierscien 12 znakow pod kolo.

UI: kolumna „Sym." w tabeli pozycji (planeta + znak) i symbol przy aspekcie.

Testy: 14 (kompletnosc — kazdy obiekt/znak/aspekt ma glif; dwukierunkowosc;
DAN-18 brak FE0F, znaki maja VS15, ☉ nie; przyklad glyphify z wymagan).
Calosc: logika 265 passed / 1 skipped, prezentacja 25. Render znakow
potwierdzony wizualnie (czarno-biale symbole, nie emoji).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 20:46:38 +02:00
gitea f1956a08ff feat(logic): aspekty pozazodiakalne — antyscja i paralele deklinacji (LOG-07)
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>
2026-07-23 18:34:26 +00:00
gitea 56fdf01d7d feat(bezpieczenstwo): szyfrowanie lacz miedzy warstwami AES-256-GCM (PRE-16)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m31s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 31s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 15s
build / build (push) Successful in 4m15s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m41s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m36s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 33s
Testy / Kontrola składni wszystkich warstw (push) Successful in 15s
Warstwy rozmawialy ze soba jawnym tekstem wewnatrz klastra. Token
miedzywarstwowy (LOG-32) mowil KTO pyta, ale nie ukrywal CZEGO dotyczy
odpowiedz — a plyna nia surowe wiersze oryginalnych baz interpretacyjnych,
czyli rdzen produktu. Kto podsluchal ruch wewnatrz sieci (drugi pod, mirror
portu na switchu, zrzut z wezla), mial je w calosci.

Nowy modul link_crypto (kopia w kazdej z trzech uslug — nie maja wspolnej
biblioteki; test pilnuje, ze kopie sa identyczne):
- AES-256-GCM na ciele kazdego zadania i odpowiedzi. GCM daje poufnosc I
  uwierzytelnienie naraz, wiec nie ma wariantu „zaszyfrowane, ale podatne na
  modyfikacje".
- DWA niezalezne klucze, po jednym na pare rozmowcow (prezentacja-logika,
  logika-dane). Przejecie klucza prezentacji nie otwiera warstwy danych, gdzie
  leza cale bazy. Z kazdego klucza lacza HKDF wyprowadza osobne podklucze na
  kierunek, wiec zadanie i odpowiedz nigdy nie szyfruja sie tym samym kluczem.
- Do materialu uwierzytelnianego (AAD) wchodza kierunek, sciezka, znacznik
  czasu i numer ramki — wiec ramki nie da sie przekleic na inny endpoint,
  odtworzyc po czasie (okno MAX_SKEW) ani przestawic w strumieniu.
- Strona serwerowa to czyste ASGI: podmienia cialo zanim zobaczy je FastAPI
  i przepuszcza odpowiedz strumieniowa kawalek po kawalku (okno postepu dziala
  dalej). Fail-closed: przy ustawionym kluczu jawne zadanie dostaje odmowe.

Strumien postepu (okno pisania horoskopu) tez idzie przez szyfrowane lacze:
link_crypto.stream_lines() pieczetuje zadanie i odszyfrowuje odpowiedz ramka
po ramce (granice ramek != granice linii NDJSON), zachowujac dostarczanie na
zywo. Bez tego przy wlaczonym LINK_ENCRYPTION_REQUIRED serwer odrzucalby
strumien (400) i okno postepu przestaloby dzialac. Fail-closed obejmuje takze
strumien: klient bez klucza nie wysyla nic, zamiast puscic dane urodzenia
jawnym tekstem, zanim serwer zdazy odmowic.

Najgrozniejszy blad wyszedl z PODSLUCHU prawdziwego gniazda, nie z testow:
klient bez klucza wysylal pytanie jawnym tekstem, ZANIM serwer zdazyl odmowic.
Stad LINK_ENCRYPTION_REQUIRED: klient nie wysyla niczego, a usluga nie wstaje,
jesli klucza brak. Ta sama zasada co przy sekrecie logowania.

Klient prezentacji przepuszczony przez jeden punkt _post()/stream_lines: dopoki
kazda metoda skladala zadanie sama, dolozenie nowej znaczylo, ze latwo zapomniec
o tokenie albo kluczu (401 wyszedl juz raz dopiero na produkcji). Test
strukturalny: kazde wyjscie w dol musi miec i token, i klucz lacza (takze
strumien), a surowe httpx wolno tylko na sciezkach wyjetych spod szyfrowania.

Weryfikacja:
- testy link_crypto (round-trip, brak tresci baz w bajtach na sieci, odrzucenie
  obcego klucza / przestawionego bitu / przekleconej sciezki / przestawionej
  ramki / przeterminowanej koperty / urwanego strumienia; round-trip strumienia
  i fail-closed klienta i serwera dla strumienia),
- e2e na prawdziwym uvicornie z proxy zrzucajacym gniazdo: tresci baz brak na
  kablu w obie strony (grep=0), takze dla strumienia horoskopu; klucz jednej
  pary nie otwiera drugiej,
- calosc: logika 234 passed / 1 skipped, prezentacja 25 passed.

docs/wdrozenie-pre16.md: instrukcja krok po kroku z uzasadnieniem kolejnosci.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:06:11 +02:00
gitea 23416cb1f9 fix(prezentacja): limit zadan po adresie klienta, nie proxy (PRE-16)
Po wlaczeniu TLS aplikacja stanie za Ingressem, a wtedy `request.client.host`
to adres POD-a Traefika — jednakowy dla wszystkich. Limiter wrzucalby caly ruch
do jednego wiadra 120/min i pierwsza osoba, ktora go wyklika, odcielaby
pozostalych. Cicha regresja, ktora ujawnilaby sie dopiero na produkcji.

Nowe `client_ip()` czyta adres z naglowka, ale WYLACZNIE przy TRUST_PROXY —
bo inaczej wystarczyloby dopisywac wlasny X-Forwarded-For, zeby przy kazdym
zadaniu wygladac na kogos innego i ominac limit calkowicie. Z tego samego
powodu bierzemy OSTATNI wpis listy: to jedyny, ktory dopisal nasz proxy;
wczesniejsze mogl podstawic klient, wiec nie znacza nic.

Szesc testow, w tym dwa istotne:
- podszycie sie pod X-Forwarded-For NIE resetuje wiadra przy wylaczonym
  TRUST_PROXY (inaczej baze dalo by sie pompowac bez ograniczen),
- za proxy dwa rozne adresy dostaja osobne wiadra i nie odcinaja sie nawzajem.

Oba sprawdzone celowym zepsuciem implementacji (zawsze ufaj naglowkowi +
bierz pierwszy wpis) — testy wtedy czerwienieja. 23 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 16:58:28 +02:00
gitea 473d059a6a feat(logic): tabele pomocnicze horoskopu (LOG-23)
build / build (push) Successful in 1m8s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m2s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 34s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Komplet wyliczen, ktore astrolog czyta „obok" pozycji:

- bilans zywiolow i jakosci w czterech wariantach (7 klasycznych / 10 z nowozytnymi,
  z Ascendentem i bez) + wykrywanie BRAKUJACYCH zywiolow — klasyczne „no air",
  podstawa pod scoring sily (LOG-21),
- faza Ksiezyca: elongacja, nazwa fazy, procent oswietlenia, przybywa/ubywa,
- stopnie krytyczne wg jakosci znaku (kardynalne 0/13/26, stale 8/21, zmienne
  4/17) + 29 stopien anaretyczny i 0 stopni wejscia w znak,
- dzien i godziny planetarne w porzadku chaldejskim,
- syzygia prenatalna (ostatni now albo pelnia przed urodzeniem),
- podzialy: dwunastniki (D12) i nawamsa (D9).

Dwie rzeczy wymagaly prawdziwego liczenia, nie tabelki:
* godziny planetarne sa NIEROWNE — dzien od wschodu do zachodu dzieli sie na 12,
  noc osobno. Bez faktycznego wschodu/zachodu wynik bylby zmyslony, wiec szukamy
  ich numerycznie (przejscie wysokosci Slonca przez -0°50', bisekcja jak przy
  stacjach z LOG-03). Doba planetarna startuje o WSCHODZIE, nie o polnocy.
* syzygia prenatalna — szukanie wstecz przejscia elongacji przez 0/180 stopni.

Walidacja wobec faktow NIEZALEZNYCH od naszego kodu:
- 30.04.1984 to poniedzialek -> wladca dnia Ksiezyc; 5. godzina poniedzialku
  w porzadku chaldejskim to Slonce (Mo, Sa, Ju, Ma, Su) — zgadza sie,
- wschod/zachod dla Krakowa: 03:18 / 17:57 UTC = 5:18 / 19:57 lokalnie — zgodne
  z rzeczywistoscia dla konca kwietnia,
- syzygia: pelnia 15.04.1984 19:10:45 UTC; rzeczywista byla 19:11 — roznica
  ponizej minuty,
- bilans przeliczony recznie: Ogien 4, Ziemia 4, Woda 3, Powietrze 0.

UI: checkbox „tabele dodatkowe" na ekranie Horoskop (opt-in, bo szuka numerycznie)
i sekcja wynikow. Endpoint: /chart/positions?tables=true.

Testy: 28 nowych (w tym noc polarna -> brak godzin planetarnych, oraz sprawdzenie,
ze w znalezionej syzygii elongacja FAKTYCZNIE wynosi 0/180). Calosc: 202 passed /
1 skipped + 17 (prezentacja). Zweryfikowane e2e w UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:34:01 +00:00
gitea 929b691238 fix(ui): okno postepu nad mapa, nie pod nia
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m48s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 10m1s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 42s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 32s
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m54s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 39s
Testy / Kontrola składni wszystkich warstw (push) Successful in 23s
Modal postepu przy pisaniu horoskopu renderowal sie POD kontrolkami mapy —
przyciski zoomu i atrybucja OSM przebijaly przez zaciemnione tlo okna.

Przyczyna: Leaflet trzyma kontrolki (.leaflet-top/.leaflet-bottom) na
z-index:1000, a .leaflet-container NIE tworzy wlasnego kontekstu stackowania
(position:relative bez z-index), wiec te 1000 trafia wprost do korzenia. Zaden
przodek mapy (form, .geo, .geo-map, .wrap) tez kontekstu nie tworzy. Okno
postepu mialo z-index:50 — czyli ladowalo pod mapa.

Poprawka: z-index okna 50 -> 1200 (zapas nad 1000). Jedna wartosc.

Zweryfikowane w przegladarce na PRAWDZIWYCH arkuszach (leaflet.css + styles.css):
- runtime elementFromPoint w punkcie kontrolek zoomu: przed = element mapy na
  wierzchu („MAPA PRZYKRYWA MODAL"), po = overlay na wierzchu („MODAL NA WIERZCHU"),
- wizualnie: kontrolki zoomu i © OSM przed poprawka jasne na wierzchu, po —
  przygaszone pod modalem.

Niezalezne od PRE-16 (nie rusza styles.css) — mozna zmergeowac przed nim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:24:25 +02:00
gitea 114b7eebdf feat(ui): okno postepu z logiem podczas pisania horoskopu
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m21s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m50s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 34s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 1m49s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m20s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m0s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 37s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Generowanie trwa minutami, a zwykly POST nie dawal zadnego sygnalu — aplikacja
wygladala na zawieszona. Teraz w trakcie pracy pojawia sie okno z logiem,
zegarem i spinnerem.

Log pokazuje RZECZYWISTE zdarzenia z serwera, nie udawany pasek postepu:
- app/progress.py — strumien NDJSON; praca leci w watku roboczym, generator
  odpompowuje kolejke, wiec zdarzenia docz w TRAKCIE pracy, nie na koncu;
  heartbeat co 10s, zeby proxy nie uznalo polaczenia za martwe,
- providers.generate(..., on_event) — raportuje kazda ture (start, czas trwania,
  liczba znakow, czy urwana), bo to tura trwa,
- POST /chart/horoscope/stream w logice + proxy /horoscope/stream w prezentacji.

Wynik: ostatnie zdarzenie niesie GOTOWY HTML wyrenderowany z tego samego
szablonu, ktory renderuje przeladowanie strony (_prompt_result.html wydzielony
z _prompt_block.html). Jedno zrodlo prawdy dla wygladu wyniku — okno wstawia go
bez przeladowania.

Degradacja: bez strumieniowania w przegladarce formularz idzie klasycznie
i wszystko dziala jak wczesniej, tylko bez okna. Blad polaczenia konczy sie
komunikatem w logu, nie cisza.

BLAD ZNALEZIONY PRZY TESCIE NA ZYWO: petla kontynuacji odejmowala od budzetu
ZAMOWIONY limit tury zamiast tokenow faktycznie wyprodukowanych — pierwsza tura
zjadala caly budzet, wiec urwana odpowiedz nigdy nie doczekala sie dokonczenia
i wracala do uzytkownika jako calosc. Naprawione i pokryte testem regresyjnym.

Testy: 176 passed / 1 skipped (logika) + 17 (prezentacja). Zweryfikowane na zywo
z wolna atrapa modelu: zdarzenia z poprawnymi czasami, okno z 11 liniami logu,
wynik wstawiony bez przeladowania strony.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:58:56 +02:00
gitea 163ace4283 feat(ui): interaktywny wybor modelu u dostawcy
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m14s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 10m19s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 46s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 27s
build / build (push) Successful in 1m18s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m2s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 36s
Testy / Kontrola składni wszystkich warstw (push) Successful in 23s
Uzytkownik wybiera nie tylko dostawce, ale konkretny model — Fable czy Opus
u Anthropica, gpt-4o-mini czy gpt-5 u OpenAI, cokolwiek ma pobrane lokalnie.

- app/llm/catalog.py: podpowiedzi modeli per dostawca wraz z oknem kontekstu,
  nadpisywalne przez <DOSTAWCA>_MODELS; GET /llm/models wystawia je dla UI.
- Pole modelu w UI jest TEKSTOWE z datalista, nie zamknietym <select> — konto
  moze miec dostep do modeli, o ktorych kod nie wie, a nowe wychodza szybciej,
  niz aktualizuje sie katalog. Puste pole = model domyslny dostawcy.
- static/models.js: zmiana dostawcy przelacza podpowiedzi, podmienia placeholder
  na model domyslny i pokazuje okno kontekstu wybranego modelu.
- build_provider(name, model) — model z zadania wygrywa nad konfiguracja.

WAZNE (znalezione przy tescie e2e): liczenie budzetu „maksymalny kontekst modelu"
szlo przez build_provider(), ktory WYMAGA klucza API — bez klucza budzet cicho
spadal do wartosci zapasowej i byl identyczny dla wszystkich modeli Anthropic.
Budzet zalezy wylacznie od okna kontekstu, wiec doszlo resolve_model(), ktore
rozwiazuje nazwe modelu bez budowania dostawcy. Teraz budzet realnie sie rozni:
Opus/Fable 3,48 mln znakow, Haiku 536 tys., gpt-4o-mini 438 tys., llama3.1 8 tys.

Pewnosc danych w katalogu: modele Anthropic pochodza z oficjalnej dokumentacji
API (okna i limity zgodne z limits.py); modele OpenAI to podpowiedzi, ktorych
nie weryfikowalem; lokalne zaleza od tego, co masz pobrane.

Testy: 174 passed / 1 skipped (logika) + 17 (prezentacja). Nowy test strukturalny
pilnuje, ze KAZDE wywolanie w dol niesie wybrany model i dostawce — dokladnie ta
klasa bledu zlapala brakujacy parametr przy horoskopie okresowym.
Zweryfikowane w przegladarce: przelaczanie dostawcy podmienia liste modeli.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:40:19 +02:00
gitea f33cdc88f4 feat(llm): horoskop powstaje zawsze — kontynuacja, okna kontekstu, budzet max
PRZYCZYNA PUSTYCH ODPOWIEDZI NA ANTHROPICU (potwierdzona w dokumentacji API):
domyslnym modelem byl `claude-sonnet-5`, ktory przy POMINIETYM parametrze
`thinking` wlacza myslenie adaptacyjne, a `thinking.display` domyslnie jest
"omitted". Tokeny myslenia licza sie do max_tokens, wiec przy LLM_MAX_TOKENS=2000
cala tura wychodzila jako bloki `thinking` z pustym tekstem — parser filtrowal
type=="text" i zwracal pusty string. Opus 4.8 bez `thinking` nie mysli, wiec tam
objaw by nie wystapil.

Gwarancja niepustej odpowiedzi (wszyscy trzej dostawcy):
- generate() to teraz PETLA, nie pojedynczy strzal: tura -> jesli urwana na
  limicie, dopisz ture „kontynuuj" w tej samej rozmowie i sklej tekst,
- tura zlozona z samego myslenia traktowana jak urwana (nie jak pustka),
- pusta i NIE urwana -> jedna proba z podpowiedzia, dopiero potem blad,
- kontynuacja konczy sie tura UZYTKOWNIKA — Claude odrzuca prefill asystenta (400),
- `thinking` konfigurowany JAWNIE (adaptive + effort=high; ANTHROPIC_THINKING=off).

Okna kontekstu i rezerwa na odpowiedz (app/llm/limits.py):
- tabela okien/limitow wyjscia per model + nadpisanie z ENV,
- plan() liczy okno odpowiedzi jako okno - prompt - margines i NIGDY nie oddaje
  calego kontekstu promptowi,
- Anthropic liczy tokeny DOKLADNIE (/v1/messages/count_tokens), reszta szacuje,
- >90 tys. tokenow promptu -> ostrzezenie, ale wyslanie NADAL mozliwe i z pelnym
  oknem odpowiedzi.

UI: suwak budzetu rozszerzony o „bardzo obszerny" i „maksymalny kontekst modelu"
(liczony z okna wybranego modelu po odjeciu rezerwy); przy wyniku widac plan
tokenow, liczbe tur i ostrzezenia.

Domyslny model Anthropic: claude-opus-4-8.

Testy: 170 passed / 1 skipped (logika) + 15 (prezentacja). Nowe testy pokrywaja
sklejanie kontynuacji, brak prefillu asystenta, ture z samego myslenia, rezerwe
na odpowiedz i prog ostrzezenia. Zweryfikowane e2e na atrapie Anthropica
odtwarzajacej zgloszony objaw: 3 tury, obie czesci tekstu obecne.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:40:19 +02:00
gitea 877ec91ff0 fix(llm): pusta odpowiedz modelu to blad, nie pusta strona
build / build (push) Successful in 57s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 13m7s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m57s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 40s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Objaw zgloszony przez uzytkownika: w Kalendarzu horoskop wyswietla sie
poprawnie, w Interpretacjach zapytanie wychodzi, wraca — i NIC sie nie
pokazuje. Bez zadnego komunikatu.

Przyczyna: model potrafi oddac pusta tresc (finish_reason=length,
completion_tokens=0), a generate() zwracalo wtedy pusty tekst BEZ bledu.
Widok sprawdza {% if prompt_result.horoscope %} -> falsz -> nie renderuje nic,
a llm_error nie jest ustawiony -> zero wyjasnienia. Cicha awaria.

Asymetria miedzy ekranami wynika z rozmiaru promptu: natalny (13 obiektow x
fasety x opisy z bazy) wypelnia okno kontekstu modelu lokalnego i na odpowiedz
nie zostaje miejsca; okresowy jest mniejszy i sie miesci.

- wspolny straznik _require_text() dla obu dostawcow: pusta lub bialoznakowa
  odpowiedz podnosi LLMError,
- komunikat PROWADZI DO PRZYCZYNY: podaje finish_reason i zuzycie tokenow oraz
  radzi zmniejszyc budzet promptu / zwiekszyc num_ctx / LLM_MAX_TOKENS,
- Anthropic sprowadzony do wspolnego ksztaltu diagnostyki (input/output_tokens).

Dzieki temu uzytkownik widzi powod ORAZ gotowy prompt do recznego uzycia.

Zweryfikowane na zywym stosie z atrapa modelu oddajaca pusta tresc: zamiast
pustej strony pojawia sie pelny komunikat z diagnostyka. Testy: 148 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:05:20 +00:00
gitea 5203ba9e76 fix(llm): konfiguracja per dostawca — przelacznik w UI byl iluzja
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m49s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 23s
build / build (push) Successful in 1m14s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m0s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 10m0s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 50s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
UI pozwala wybrac dostawce przy KAZDYM zadaniu, ale factory czytalo jedna
wspolna trojke LLM_MODEL / LLM_BASE_URL / LLM_API_KEY dla wszystkich. Na
klastrze LLM_BASE_URL trzeba ustawic na lokalny model (localhost:11434 w podzie
nie istnieje) — i wtedy:
  - wybor „OpenAI" wysylal zadanie do Ollamy,
  - LLM_MODEL=llama3.1:8b kazal Anthropic uzyc modelu llama,
  - jeden LLM_API_KEY nie moze byc kluczem OpenAI i Anthropic naraz.
Czyli nie bylo miejsca, w ktore dalo sie sensownie wpisac klucze do chmury.

- konfiguracja per dostawca: <DOSTAWCA>_MODEL / _BASE_URL / _API_KEY
  (LOCAL_*, OPENAI_*, ANTHROPIC_*),
- zgodnosc wstecz: wspolne LLM_* dziala nadal, ale stosuje sie WYLACZNIE do
  dostawcy domyslnego (LLM_PROVIDER) — instalacja jednodostawcowa bez zmian,
- Anthropic dostal brakujaca walidacje klucza (mial ja tylko OpenAI),
- komunikat bledu wskazuje konkretna zmienna do ustawienia.

Testy regresyjne pilnuja, ze ustawienia jednego dostawcy NIE przeciekaja na
pozostalych. Calosc: 143 passed / 1 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 22:32:04 +02:00
gitea 64d1afc76d fix(presentation): brakujacy token przy /chart/prompt i /chart/horoscope
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m57s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 30s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
Blad z mergu: _auth_headers() dodano na galezi hardeningu, ktora odbila sie
od mastera ZANIM powstaly metody prompt() i horoscope() (LOG-29/30, LOG-31).
Git zmergowal obie zmiany czysto — byly w roznych liniach — ale semantycznie
nowe metody wyszly bez tokenu i dostawaly 401 przy wlaczonej ochronie.

Skutek dla uzytkownika: przyciski „Generuj prompt (AI)" i „Napisz horoskop"
nie dzialaly po wdrozeniu INTERNAL_TOKEN, mimo ze reszta aplikacji dzialala.

- naprawione oba wywolania,
- nowy test strukturalny (AST): KAZDE wyjscie HTTP w dol musi niesc headers=.
  Test jednej metody by tego nie zlapal — regula musi byc pilnowana calosciowo.

Straznik zweryfikowany sabotazem: po usunieciu naglowka test pada ze
wskazaniem konkretnej linii; po przywroceniu 15/15 przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 19:02:40 +00:00
gitea 4ef90b30bc feat(logic): dostawcy LLM i pisanie horoskopu (LOG-31)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m38s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m43s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 24s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 24s
build / build (push) Successful in 59s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m53s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 18s
Domyslnie model LOKALNY — prompt niesie oryginalne opisy z baz, wiec
domyslnie NIC nie opuszcza sieci. Chmura wlaczana swiadomie (LOG-32).

- app/llm/: LLMProvider (jak EphemerisEngine z LOG-24) + dwie implementacje.
  Lokalny serwer modelu (Ollama/vLLM/llama.cpp) i OpenAI mowia TYM SAMYM
  protokolem /chat/completions, wiec obsluguje je jedna klasa; Anthropic ma
  wlasny /v1/messages. Napisane na samym httpx — bez SDK openai/anthropic:
  mniej zaleznosci i pelna kontrola nad tym, co wychodzi z sieci.
- Kazdy dostawca deklaruje `leaves_lan` — interfejs MUSI jawnie mowic, czy
  tresc baz opuszcza siec; UI na tej podstawie ostrzega.
- Ponawianie z backoffem (429/5xx), timeouty, czytelne bledy zamiast stacktrace.
- Klucz wylacznie z LLM_API_KEY (sekret), nigdy w repo ani w UI.
- POST /chart/horoscope + GET /llm/health.

WAZNE: prompt jest zwracany ZAWSZE — takze gdy model padnie lub brakuje
klucza. Dzieki temu awaria dostawcy nie blokuje pracy: prompt mozna
skopiowac i uzyc recznie.

Prezentacja: wybor modelu (lokalny/Anthropic/OpenAI), przycisk „Napisz
horoskop (AI)", wynik z informacja kto go napisal, czy dane opuscily siec,
ile wskazan weszlo, oraz zastrzezenie ze to nie porada medyczna (PRE-15).

Testy: 13 nowych (transport podstawiony — zaden prawdziwy model nie wolany),
calosc 131 passed / 1 skipped. Zweryfikowane e2e na atrapie serwera modelu:
horoskop napisany, leaves_lan=false, tokeny zliczone; a przy padnietym
modelu / braku klucza / zlym dostawcy — czytelny blad i zachowany prompt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 20:34:07 +02:00
gitea 73fd41b9d3 fix(logic): pomijaj trywialne aspekty par sztywnych (NN/SN)
build / build (push) Successful in 53s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m44s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m55s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 23s
Węzły księżycowe są z definicji w dokładnej opozycji (SN = NN + 180°),
więc wpis "North Node opposition South Node orb 0.00°" nie niósł żadnej
informacji astrologicznej — zaśmiecał /chart/positions i UI horoskopu,
a w generatorze promptów LLM zjadał budżet znaków i mógł zostać wzięty
przez model za realne świadectwo.

- aspects.py: RIGID_PAIRS + pomijanie takich par w find_aspects;
  struktura zbioru pozwala dopisać kolejne pary definicyjne,
- aspekty węzłów do pozostałych obiektów bez zmian,
- testy: jawne sprawdzenie braku pary NN/SN (jednostkowo i na
  horoskopie referencyjnym).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 18:33:16 +00:00
gitea 752f477a27 feat(security): zamkniecie dostepu do baz interpretacyjnych (LOG-32)
build / build (push) Successful in 1m16s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m9s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m51s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
Bazy sa rdzeniem produktu i wlasnie zostaly kupione — a aplikacja nie miala
ZADNEGO uwierzytelniania. Prezentacja to NodePort, wiec kazdy w LAN wchodzil
bez logowania, a `/search` oddawal surowe wiersze do 50 000 na zapytanie.
Bazy mogly wyjsc przez sama aplikacje, bez udzialu jakiegokolwiek LLM.

- prezentacja: HTTP Basic (APP_USER/APP_PASSWORD) + limit zadan na IP
  (RATE_LIMIT_PER_MIN, domyslnie 120/min). Limit dziala TAKZE przed
  uwierzytelnieniem, zeby zgadywanie hasla i sondowanie API nie bylo darmowe.
- logika i dane: token miedzywarstwowy X-Astrololo-Token (INTERNAL_TOKEN) —
  bez niego dalo sie ominac logowanie, uderzajac wprost w warstwe nizej.
  Warstwa danych oddaje surowe wiersze, wiec to najwrazliwszy punkt.
- /search: gorny limit 50 000 -> 5000 (tyle realnie uzywa build_report).
  Publiczne /api/query zostaje na 200.
- /health celowo publiczny (sondy k8s go nie uwierzytelnia).
- swiadomie nie logujemy tresci zadan ani promptow — logi to kolejny nosnik.

Fail-open przy braku konfiguracji (zgodnosc wstecz i dev), ale z GLOSNYM
ostrzezeniem przy starcie, zeby nikt nie wdrozyl tego w przekonaniu, ze jest
chroniony. Wlaczenie w produkcji wymaga ustawienia sekretow w repo deploy.

Testy: 12 (prezentacja, nowy katalog + job w CI) i 5 (logika). Zweryfikowane
na zywym stosie: bez hasla 401, z haslem 200, logika wprost bez tokenu 401,
z tokenem 200, /health 200, limit 50000 odrzucony (422), a prezentacja nadal
liczy horoskop przez logike.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:50:18 +00:00
gitea 04b26afa6d feat(logic): generator promptow do LLM + budzetowanie (LOG-29, LOG-30)
build / build (push) Successful in 1m4s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 26s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Pierwszy krok ustalonej kolejnosci: prompt z podgladem, uzyteczny od razu
BEZ integracji API (ta przyjdzie w LOG-31).

LOG-29 — app/prompt.py:
- profil natal (ekran Interpretacje) i period (ekran Kalendarz),
- prompt po polsku: zadanie -> dane horoskopu (pozycje, osie, Lots, aspekty,
  sekta, zodiak) -> wskazania z baz wg wagi -> instrukcje -> zastrzezenie,
- twarde reguly: kazda teza musi cytowac konkretny sygnifikator, zakaz
  wychodzenia poza dostarczone dane, jawne wskazanie sprzecznosci,
- deterministyczny: ten sam horoskop + budzet = ten sam prompt.

LOG-30 — redukcja do budzetu (concise/medium/extensive):
dedup -> grupowanie z licznikiem -> sortowanie wg punktacji sily (LOG-21) ->
obciecie ogona (jednostka = CALE wskazanie) -> skracanie dlugich opisow.
Statystyki zwracaja ile weszlo/pominieto i jaki byl prog — takze w tresci
promptu, zeby model wiedzial, ze widzi wybor.

POST /chart/prompt — zwraca sam prompt + statystyki, bez wolania modelu.

Prezentacja: przycisk „Generuj prompt (AI)" na obu ekranach, wybor budzetu,
pole z promptem + kopiowanie (z fallbackiem dla http bez secure context).

WAZNE (znalezione przy tescie e2e): padnieta warstwa danych zabiera tylko
wskazania — horoskop i OS CZASU zostaja, bo sa czysto obliczeniowe. Wczesniej
blad bazy gubil cala osie czasu, czyniac prognoze okresowa bezuzyteczna.
Zabezpieczone testem.

Testy: 19 nowych, calosc 118 passed / 1 skipped. Zweryfikowane e2e w
przegladarce: profil natal (3340 znakow) i period (15 zdarzen, 4728 znakow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:44:17 +00:00
gitea 6f87b2b323 feat(logic): systemy zodiaku — syderyczny, draconic (LOG-04)
build / build (push) Successful in 1m6s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m51s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 25s
Testy / Kontrola składni wszystkich warstw (push) Successful in 20s
Jedyne wymaganie Must bez implementacji. Nowy zodiac.py + wpiecie w horoskop:

- zodiac.py: ayanamsy (Lahiri, Fagan-Bradley, Krishnamurti) modelem
  ayan(jd)=ayan0+B*x+C*x^2 (wspolna precesja, rozna stala) — skalibrowanym
  do Swiss Ephemeris jako WYROCZNI: zgodnosc do ~0,02" w latach 1900-2100.
  Draconic = wzgledem wzla wznoszacego (wzel = 0 Barana). RA: konwersja
  ekliptyka->rownik (to_equatorial) na przyszly widok rownikowy.
- chart.py: build_chart(..., zodiac): offset jednolicie przesuwa etykiety
  znakow/dlugosci obiektow, osi, cusps i Lots; DOMY licza sie po dlugosci
  tropikalnej (geometria niezmiennicza wzgledem obrotu -> numery domow bez
  zmian). Domyslnie tropical -> sciezka i wyniki bez zmian.
- main.py: /chart/positions przyjmuje `zodiac`; bledny -> 422.
- prezentacja: dropdown „Zodiak" + pokazanie ayanamshy w wynikach.

Testy (14): ayanamsy vs wyrocznia swisseph (<0.1"), julian_day, draconic
(wzel=0 Barana), niezmienniczosc domow, RA w punktach charakterystycznych,
odrzucenie bledow. Cala logika: 99 passed, 1 skipped. Zweryfikowane e2e w
przegladarce (horoskop syderyczny Lahiri: Slonce Aries 16°34', ayan 23.6382).

RA jako osobny widok zodiaku (per-obiekt, z szerokoscia) — do osobnego PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 14:32:51 +00:00
gitea a2aabd37a5 feat(presentation): wyszukiwarka lokalizacji + mapa (OSM/Leaflet)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m39s
Testy / Build obrazu silnika B (swisseph) (pull_request) Failing after 28s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 21s
build / build (push) Successful in 57s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m47s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 29s
Testy / Kontrola składni wszystkich warstw (push) Successful in 22s
QoL 2/2: pole „Szukaj miejsca" (nazwa/adres/POI) + interaktywna mapa na
formularzach horoskopu / interpretacji / kalendarza. Bez klucza API.

- geocode.py: proxy OSM/Nominatim PO STRONIE SERWERA (poprawny User-Agent,
  throttling ~1 req/s, cache TTL 1h) — endpointy /geocode i /reverse.
  Wolanie z serwera, nie z przegladarki: latwiej trzymac polityke Nominatim
  i dziala niezaleznie od secure-context (http://<ip>).
- _location_picker.html: wspolny partial (search + wyniki + mapa + atrybucja),
  wpiety includem do 3 formularzy z polami lat/lon.
- geo.js: Leaflet — wyszukiwanie (debounce), klik na wynik ustawia lat/lon i
  centruje mape, klik/drag pineski ustawia wspolrzedne + /reverse pokazuje
  nazwe; sync z „Tu i teraz" (now.js emituje event astrololo:coords).
- Leaflet 1.9.4 vendorowany lokalnie (static/vendor/leaflet, BSD-2-Clause,
  permisywny) — niezaleznosc od CDN; kafelki mapy z OSM. Marker jako divIcon
  (bez plikow PNG).
- styles.css: style pod ciemny motyw.

Zweryfikowane w przegladarce: domyslny widok (pineska na Szpitalu Barlickiego),
wyszukanie „Wawel Krakow" -> lista -> klik ustawia 50.0547/19.9361 i przesuwa
mape, klik w mape ustawia wspolrzedne + reverse wypelnia nazwe. Zero bledow
w konsoli. /geocode i /reverse zwracaja szpital; cache dziala.

Uwaga wdrozeniowa: pod prezentacji potrzebuje egressu do
nominatim.openstreetmap.org; przegladarki — do tile.openstreetmap.org.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 03:32:07 +02:00
gitea de5d58958c feat(presentation): domyslna lokalizacja = Szpital Barlickiego, Lodz
build / build (push) Successful in 1m9s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m43s
Testy / Build obrazu silnika B (swisseph) (push) Failing after 47s
Testy / Kontrola składni wszystkich warstw (push) Successful in 24s
QoL: formularze (horoskop / interpretacje / kalendarz) maja wstepnie
wpisana lokalizacje urodzenia wlasciciela — Szpital Barlickiego w Lodzi
(51.7739N, 19.4829E; potwierdzone reverse-geokodowaniem OSM: Kopcinskiego
22/28). Nie trzeba jej wpisywac za kazdym razem.

- config.py: jedno zrodlo prawdy (DEFAULT_LAT/LON/LABEL, nadpisywalne ENV)
  + helper default_form().
- main.py: GET wstrzykuje default_form() + location_label do 3 formularzy
  z polami lokalizacji (significators pominiete — nie ma tam lat/lon).
- szablony: dyskretna podpowiedz z nazwa lokalizacji, widoczna tylko na
  czystym formularzu (po POST znika, wygrywa wpisana wartosc).
- „Tu i teraz" nadal nadpisuje domyslne wspolrzedne geolokalizacja.

Zweryfikowane TestClientem: 3 strony renderuja 51.7739/19.4829 + etykiete;
POST z innymi wspolrzednymi je zachowuje i chowa podpowiedz.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 01:16:24 +00:00
gitea 79bce8ae90 Lots / punkty arabskie — 7 Lots hermetycznych (LOG-08)
- engine/lots.py: formuła Lot = C + A − B z odwracaniem w horoskopach nocnych
  (zamiana A<->B); 7 Lots hermetycznych (Fortuna, Duch, Eros, Konieczność,
  Odwaga, Zwycięstwo, Nemezis) — Fortuna i Duch liczone pierwsze, bo pozostałe
  się do nich odwołują. Dwa warianty: degree (domyślny) i sign (całe znaki).
- build_chart: liczy sektę (reużyta is_day_birth z Firdarii) i zwraca lots
  ze znakiem, pozycją i domem; parametr lots_method.
- wyszukiwarka: token [PF (tak Fortuna występuje w realnej bazie) + rozwinięcie
  skrótu do "Part of Fortune".
- widok Horoskop: tabela Lots z formułami i sektą.

Walidacja (dwie niezależne wyrocznie z notes3):
- Fortuna = Can 12°35'27" vs astro-seek Can 12°35'24" (3 sekundy różnicy), dom 1;
- Duch potwierdzony przez swoją antyscję (Taurus 28°14' z tabeli antyscji);
- odwracanie nocne: Fortuna nocna == Duch dzienny i odwrotnie;
- Lots pochodne faktycznie używają Fortuny/Ducha; wariant sign trafia w 0° znaku.
85 testów przechodzi (nowy test_lots).

Odblokowuje Zodiacal Releasing (LOG-11), które startuje z Fortuny/Ducha.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:50:54 +00:00
gitea 85e9182f12 fix(swisseph): naprawa builda obrazu silnika B
build / build (push) Successful in 43s
pyswisseph to rozszerzenie C, a na PyPI (2.10.3.2) gotowe wheels koncza sie
na cp311 i obejmuja wylacznie i686/x86_64. Obraz stoi na python:3.12-slim,
wiec pip ZAWSZE kompilowal ze zrodel - a slim nie ma kompilatora. Stad fail.

- Dockerfile wieloetapowy: kompilacja w etapie builder (build-essential),
  do runtime trafia juz tylko gotowy wheel - obraz zostaje czysty i maly.
  Dziala tez na arm64, gdzie wheeli linuksowych nie ma dla zadnej wersji.
- sanity check (import swisseph) na etapie builda, zeby niedzialajacy silnik
  wywracal build, a nie dopiero pierwszy request.
- CI: nowy job swisseph-image - realny docker build + smoke test /health
  i /positions na horoskopie referencyjnym.
- README: udokumentowany powod wieloetapowego builda.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:30:10 +02:00
gitea b28c612bf7 CI: jawne pobranie jądra efemeryd + brak silnika = błąd zamiast pomijania
Pierwszy run CI dał 59 passed / 20 skipped zamiast 78/1: auto-pobranie jądra
przez Skyfield nie powiodło się na runnerze, więc testy referencyjne (walidacja
względem astro.com) cicho znikały.

- workflow: jawne pobranie de421.bsp curlem z ssd.jpl.nasa.gov (naif zwraca 404),
  z retry; EPHEMERIS_DIR wskazany explicite; pytest z -rs (widoczne powody skipów).
- conftest: gdy CI=true, niedostępny silnik kończy się pytest.fail zamiast skip —
  żeby utrata pokrycia nigdy więcej nie przeszła niezauważona. Lokalnie (dev bez
  pobranego jądra) nadal łagodny skip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 14:47:50 +02:00
gitea 9d5b65ddbd Firdaria (LOG-11) — perska technika time-lord
- engine/firdaria.py: sekta (dzień = Słońce nad horyzontem, ta sama półkula
  osi Asc-Dsc co MC); kolejność diurnalna/nokturnalna; klasyczne długości
  okresów (Su10 Ve8 Me13 Mo9 Sa11 Ju12 Ma7 + NN3 + SN2 = 75 lat); okresy główne
  planet dzielone na 7 podokresów (sub-lord od władcy okresu), węzły bez sub.
- endpoint /chart/firdaria.
- oś czasu: firdaria_events — starty (pod)okresów w oknie; wpięte w build_timeline
  (domyślnie) + tokeny [major][sub] do dopięcia interpretacji (1B->2B).

Walidacja:
- sekta = day dla horoskopu referencyjnego (zgodnie z notes3 "Day birth");
  night gdy Słońce po stronie IC; sumy i przyleganie okresów; podokresy 7x
  sumujące się do okresu; wiek 42 w okresie Saturna.
- E2E: okresy Sun 1984-1994 ... Saturn 2024-2035; w osi czasu
  "Firdaria: Saturn / Mars" 2027 -> 223 interpretacje ([Sa+[Ma -> "injury").
- 78 testów przechodzi (nowy test_firdaria).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 22:48:53 +02:00
gitea c4a181b810 Spięcie osi czasu z bazą interpretacji i widokiem (LOG-14, 1B->2B)
Realizuje przepływ 1B->2B z notes2: predykcyjne sygnifikatory (z datami)
dopasowane do interpretacji z bazy.

Logika:
- timeline.py: zdarzenia niosą strukturę (directed/aspect/target dla dyrekcji,
  lord/sign dla profekcji) do budowy tokenów.
- significators.interpret_events + _event_tokens: z każdego zdarzenia buduje
  tokeny bazy (dyrekcja: [planeta][aspekt][cel]; profekcja: [władca][znak]) i
  dopina interpretacje reużywając _facet_samples (AND tokenów, dedup, rozwinięcie).
- /chart/timeline: flaga interpret=true.

Prezentacja:
- nowa strona /timeline "Kalendarz": formularz (urodzenie + zakres dat) -> oś
  czasu z technikami, datami i interpretacjami; nawigacja + wspólne now.js.

Walidacja E2E na realnym main_base.xlsx (2025-2026):
- dyr. Saturn kwadratura MC -> 50 interpret. ("...5th house" -> "abortion/miscarriage")
- Władca Roku Saturn (wiek 42) -> 63; strona renderuje badge dat/technik.
71 testów przechodzi (nowy test_timeline_interpret).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:31:50 +02:00
gitea 2dbd3410e9 Zbiorcza tabela dat z technik (LOG-14)
engine/timeline.py: scala w jedną posortowaną oś czasu (technique | significator |
start | exact | end):
- profekcje roczne (LOG-10) — Władca Roku per rok życia,
- Solar Return (LOG-12) — moment powrotu Słońca,
- dyrekcje solar-arc — daty dokładnych aspektów kierowanych planet do punktów
  natalnych (klucz Naiboda 0°59'08"/rok, konfigurowalny; okno orbowe ±1 rok).
Endpoint /chart/timeline (zakres from_date..to_date, wybór technik).

Walidacja:
- profekcje spójne z tabelą notes3; dyrekcja Sun koniunkcja MC (łuk 312°) słusznie
  poza życiem; oś posortowana po dacie dokładnej; wiek = łuk/klucz spójny z datą.
- E2E: dla 2025-2026 zwraca 19 zdarzeń (m.in. Władca Roku 42 Saturn 30.04.2026,
  Solar Return, dyr. Venus koniunkcja North Node 27.05.2025). 66 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:33:15 +02:00
gitea b013831492 Profekcje roczne (LOG-10) i Solar/Lunar Return (LOG-12)
Pierwsze techniki predykcyjne.

Profekcje (LOG-10):
- engine/profections.py: Whole Sign, wiek mod 12; profektowany Asc + Władca Roku
  (władca domicylowy) + profekcje MC/Su/Mo. Obsługa 29 lutego.
- endpoint /chart/profections (zakres lat: start_age, count).

Returns (LOG-12):
- engine/returns.py: moment powrotu Słońca/Księżyca do długości natalnej;
  skan dobowy + bisekcja do ~sekundy, z pominięciem artefaktu zawinięcia 0/360.
- endpoint /chart/return (kind solar|lunar, around) — zwraca pełny horoskop
  na znaleziony moment (oba warianty użycia po stronie technik wyżej).

Walidacja:
- profekcje zgodne co do joty z tabelą astro-seek z notes3 (wiek 0..42:
  Asc + Władca Roku + MC/Su/Mo).
- Solar Return 2026: Słońce wraca do Tau 10°08'22" = natalny stopień;
  Lunar Return trafia natalny Księżyc <2'; samospójność potwierdzona.
- 62 testy przechodzą (nowe: test_profections, test_returns).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 11:12:49 +02:00
gitea a8c3072e62 Węzły księżycowe, mean Lilith i wykrywanie stacji (LOG-02/03)
Punkty wirtualne (LOG-02):
- engine/points.py: mean Node (Ω) i mean Lilith (apogeum) wzorami Meeusa;
  prędkości numerycznie. SN = NN + 180° (ta sama prędkość), zawsze Rx.
- DEFAULT_OBJECTS + North Node / South Node / Lilith — automatycznie dostają
  domy, aspekty i A/S. Parzystość silnika B: swe.MEAN_NODE / swe.MEAN_APOG
  (uwaga: stała pyswisseph to MEAN_APOG, nie MEAN_APOGEE).
- significators: tokeny [NN / [SN / [Lilith (zgodne z SIGNIFICATORS KEY).

Stacje (LOG-03):
- engine/stations.py: skan prędkości (krok 4 dni, okno ±800 dni — pokrywa
  najdłuższe przerwy Marsa/Wenus) + bisekcja; klasyfikacja SD/SR; poprzednia/
  następna stacja (dni, data, stopień w znaku) + flaga station_soon (<7 dni).
- /chart/positions: opt-in stations:true; UI: checkbox + tabela stacji.

Walidacja:
- mean NN vs astro-seek (Gem 8°09'24"): Δ=0,3'; vs swisseph: Δ=17";
  mean Lilith vs swisseph: Δ=1,5'. NN dom 12 / SN dom 6 zgodnie z astro-seek.
- Stacje Marsa 1984 trafiają w historię: SR 5.04.1984, SD 19.06.1984;
  samospójność |speed|<0,01°/d w znalezionych momentach; flaga "blisko"
  działa (Merkury +5,3d, Jowisz -0,6d).
- E2E na realnej bazie: [SN 134 rekordy, trafienie w 6. domu. 54 testy przechodzą.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:51:29 +02:00
gitea 8d579de34a Aspekty: applying/separating (A/S) + bonus siły dla aplikujących (LOG-06)
- aspects.py: _is_applying — odchyłka od dokładnego kąta teraz vs po małym
  kroku czasu (prędkość·dt, dt=0,01 doby by szybki Księżyc nie przeskoczył
  dokładności); aspekt niesie applying (bool) i "as": A/S. Bez prędkości —
  brak flagi (None).
- scoring: aspekt aplikacyjny silniejszy (APPLYING_BONUS 1.15) — zgodnie z
  notatkami projektu ("impact considered more powerful").
- significators: faseta aspektu niesie applying, etykieta z sufiksem (A)/(S).
- widok Horoskop: kolumna A/S w tabeli aspektów (tooltip z objaśnieniem).

Walidacja: flagi A/S wszystkich 16 aspektów horoskopu referencyjnego
(30.04.1984, Warszawa) zgodne z tabelą astro-seek z notes3 (test regresyjny
REFERENCE_AS). 45 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 19:22:51 +02:00
gitea 4a86d34f5a Geolokalizacja: jawny komunikat gdy brak secure context (http://)
Przyczyna "nie pyta o zgodę": navigator.geolocation działa tylko w secure
context (https:// lub localhost). Na http://<ip> przeglądarka po cichu
odmawia — bez promptu; kod nie miał callbacku błędu, więc nic nie było widać.

- wspólny static/now.js (deduplikacja skryptu z chart.html i interpret.html)
- jawna detekcja window.isSecureContext + czytelny komunikat w #geoNote
  ("wymaga HTTPS lub localhost — wpisz lat/lon ręcznie")
- callback błędu (odmowa/timeout) też widoczny; status "Pobieram lokalizację…"
  i potwierdzenie po sukcesie

Zweryfikowano: /static/now.js serwowany (200), obie strony referencjonują
skrypt i mają #geoNote.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 18:59:03 +02:00
gitea f6323cac10 Ranking siły (LOG-21) + grupowanie identycznych opisów + geolokalizacja
Punktacja siły (LOG-21):
- scoring.py: siła fasety z sygnałów obliczalnych (typ fasety, rodzaj aspektu,
  ciasnota orbu). Konfigurowalne wagi. Hook na przyszłość: kolumny countas*/level*
  z SIGNIFICATORS KEY (obecnie puste).
- aspekty niosą orb+allowed; fasety dostają "score"; ranking faset malejąco.

Grupowanie:
- opcja group: zwija próbki po opisie (ten sam efekt = jedna grupa z listą
  sygnifikatorów i licznikiem). Checkbox "grupuj identyczne opisy" w /interpret.

Geolokalizacja (bajer):
- "Tu i teraz" (widok Horoskop i Interpretacje) uzupełnia lat/lon z przeglądarki
  (navigator.geolocation; wymaga zgody, https/localhost).

Zweryfikowano na realnym main_base.xlsx: ranking sensowny (ciasna opozycja z
Saturn 9.59 > szeroka koniunkcja z Moon 6.25 > znak/dom 5.0); grupowanie zwija
powtórzone opisy. 41 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 01:16:47 +02:00
gitea 166b438f83 Aspekty (LOG-06) + faseta aspektu, dedup i dopieszczenie wyników
Aspekty:
- engine/aspects.py: aspekty główne (conj/sex/sq/tri/opp) z orbami
  (bonus dla luminarzy), separacja z obsługą zawinięcia. Applying/sep na później.
- build_chart zwraca listę aspektów; /chart/positions je udostępnia;
  widok Horoskop pokazuje tabelę aspektów.

Bogatsze sygnifikatory:
- trzecia faseta "w aspekcie": dla każdego aspektu głównego obiektu filtruje
  rekordy po tokenie aspektu + drugiej planety ([conj + [Mo). Cookbook
  komplet: znak + dom + aspekt.

Dopieszczenie wyników:
- ODSIEWANIE DUPLIKATÓW: duplikat = ten sam sygnifikator ORAZ ten sam opis
  (po normalizacji). Dedup wewnątrz fasety, działa też na wynikach z wielu baz.
- _facet_samples przyjmuje wiele tokenów (AND); dedup + istniejące odsiewanie szumu.

Zweryfikowano na realnym main_base.xlsx (30.04.1984): 16 aspektów zgodnych z
astro.com (Sun conj Moon 9.59°, Sun opp Saturn 3.17°); faseta aspektu daje
bogate trafienia (Sun koniunkcja z Moon 84, opozycja z Saturn 43); dedup obniżył
duplikaty (Sun w znaku 46->44). 36 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 08:55:27 +02:00
gitea 82809665ff Odsiewanie szumu + rozwijanie skrótów sygnifikatorów
- abbreviations.py: słownik skrót -> pełna nazwa (z SIGNIFICATORS KEY, built-in)
  + expand(): [Su in [Tau -> "Sun in Taurus", [Sa in 6th H. -> "...6th house",
  affl. -> afflicted itd.
- significators: każda próbka ma pole "expanded" (postać czytelna); hartowanie
  filtra szumu (efekty zastępcze x/?/-, wiersze *MARKER, legendy/nagłówki).
- prezentacja /interpret: pokazuje rozwiniętą postać, surowy skrót w tooltipie.

Zweryfikowano na realnym main_base.xlsx: "[Sa or [Ma in the 5th H." ->
"Saturn or Mars in the 5th house". 9 testów przechodzi (w tym test_abbreviations).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:42:47 +02:00
gitea 0e74566a78 Bogatsze sygnifikatory: faseta "w domu" obok "w znaku" (LOG-15/16)
Most horoskop -> sygnifikatory generuje teraz dla każdego obiektu dwie fasety:
- "w znaku": planeta + token znaku ([Su + [Tau)
- "w domu":  planeta + token domu ([Su + 11th H.) — używa domów z LOG-05

- significators.build_report: przyjmuje pozycje z build_chart (z numerami domów),
  generuje fasety znak/dom, filtruje szum. Ordinal helper (1st..12th).
- /chart/report: używa build_chart (pozycje z domami).
- Prezentacja /interpret: render faset (znak/dom) per obiekt.

Aspekty ([conj/[sq/[opp) na później — wymagają policzenia aspektów (LOG-06).

Zweryfikowano na realnym main_base.xlsx (53969 wierszy), 30.04.1984:
Mars w 5. domu 10 dopasowań ("[Sa or [Ma in the 5th H." -> "abortion/miscarriage"),
Neptune w 7. domu 6, Uranus w 6. domu 4. 7 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:37:20 +02:00
gitea dabbaef9dd Merge branch 'master' into feat/search-integration 2026-07-01 15:03:12 +02:00
gitea eef67d37b5 Wyszukiwarka: wynik obliczeń szukany w bazie interpretacji
Pierwsza wersja mostu horoskop -> sygnifikatory -> baza (zalążek LOG-16/18/19).

- logic/significators.py: z pozycji generuje tokeny w składni bazy (planeta
  [Su, znak [Tau...), pyta warstwę danych o rekordy z tokenem planety i zawęża
  do tych, które wspominają też jej znak ("planeta w swoim znaku"); odsiewa szum.
- logic /chart/report: nowy endpoint (pozycje -> raport dopasowań z interpretacjami).
- logic DataClient.search: parametr fields (lżejszy payload).
- data: naprawa str.contains regex=True -> regex=False (sygnifikatory zawierają
  [ + itd., metaznaki regex); podniesiony górny limit zapytania (le=50000).
- prezentacja: strona /interpret (formularz -> wyszukane interpretacje per obiekt)
  + nawigacja.

Zweryfikowano end-to-end na realnym pliku (Encyclopaedia of Medical Astrology,
53969 wierszy): dla horoskopu 30.04.1984 znaleziono m.in. Sun w Taurus 46,
Mars w Scorpio 57, Saturn w Scorpio 61 dopasowań; przykłady: "[Su in [Tau" ->
"the bump of amativeness prominent". 15 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:57:26 +02:00
gitea 94c3023d3a Silnik: osie (Asc/MC) i systemy domów (LOG-05)
Kontynuacja silnika efemeryd o osie i domy.

- engine/houses.py: czysta matematyka sferyczna — Asc, MC (z RAMC + ε + φ),
  cusps dla Whole Sign / Equal / Porphyry, przypisanie obiektu do domu.
- SkyfieldEngine.sidereal(): RAMC (lokalny apparent ST) + średnie nachylenie
  ekliptyki ze Skyfielda.
- engine/chart.py: build_chart() składa pełny horoskop (pozycje + osie + domy).
- Endpoint /chart/positions rozszerzony o house_system i zwraca angles + cusps
  + numer domu per obiekt.
- Prezentacja: lokalizacja i wybór systemu domów w formularzu, tabela osi,
  kolumna Dom, rozwijane cusps.

Walidacja względem astro.com (30.04.1984, Warszawa): Asc Can 22°10'43",
MC Pis 22°35'29" (~1' od referencji); wszystkie przypisania domów Whole Sign
zgodne (Sun 11, Mercury 10, Mars 5, ...). 20 testów przechodzi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:06:13 +02:00
gitea 413c46b5dd Napraw lokalne środowisko dev: Parquet mixed-types, brak deps, compose
Trzy usterki uniemożliwiające uruchomienie stosu lokalnie:

1. Warstwa danych wykładała się na starcie przy zapisie Parquet dla realnych
   plików (np. Encyclopaedia of Medical Astrology) — kolumny o mieszanych
   typach (int+str+NaN). frame_cache.put() zapisuje teraz ramkę jako string
   (warstwa i tak wyszukuje po tekście). Dodatkowo warmup() jest odporny:
   pojedynczy uszkodzony plik nie blokuje startu usługi.

2. Brakowało kroku instalacji zależności — dev-logic/dev-presentation padały na
   'No module named httpx'. Nowy cel `make install` instaluje zależności
   WSZYSTKICH warstw do aktywnego venv. README zaktualizowane (instalowało
   wcześniej tylko warstwę danych).

3. `make up` zakładał `docker compose`, którego użytkownik nie ma. Makefile
   wykrywa `docker compose` lub `docker-compose`, a przy braku obu podaje
   czytelną instrukcję trybu lokalnego. Dodano `make test` i `make clean-cache`.

Zweryfikowane end-to-end na realnym środowisku (.env, Python 3.14) i danych:
warmup przechodzi (3 pliki), cały stos wstaje, formularz → logika → silnik
Skyfield zwraca poprawne pozycje.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:55:04 +02:00
gitea 340b3058e4 Merge pull request #6 from migatu/feat/presentation-chart
Warstwa prezentacji: widok horoskopu do recznego testowania
2026-06-30 20:33:31 +02:00
gitea 3195d9b003 Warstwa prezentacji: widok horoskopu do ręcznego testowania
Strona główna "/" = formularz podstawowych danych momentu (data, godzina,
strefa, opcjonalnie lokalizacja) → tabela policzonych pozycji w formie
human-readable (znak, pozycja w znaku, absolutna, kierunek, prędkość).
Woła logic /chart/positions; przelicza czas lokalny + offset na UTC.
Przycisk "Tu i teraz" uzupełnia bieżącą datę/godzinę i strefę przeglądarki.
Retrogradacja wyróżniona w tabeli.

Wyszukiwarkę sygnifikatorów przeniesiono pod "/significants" -> /significators,
dodano nawigację (base.html). Czytelny komunikat, gdy logika nie ma jeszcze
endpointu silnika.

Zweryfikowano end-to-end: formularz → przeliczenie UTC → render tabeli
(przez stub kontraktu /chart/positions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:08:43 +02:00
gitea e69c0714b9 Silnik efemeryd: EngineProvider + SkyfieldEngine + harness porównawczy
Pierwszy increment implementacji warstwy logicznej (ścieżka A).

LOG-24: interfejs EphemerisEngine z dwoma backendami — SkyfieldEngine
  (własny, permisywny: Skyfield MIT + dane JPL public domain) oraz
  RemoteEngine (klient izolowanej usługi swisseph). Fabryka + leniwa
  inicjalizacja; endpointy /chart/positions i /chart/compare.
LOG-01: pozycje obiektów (długość/szerokość ekliptyczna, prędkość,
  kierunek, formaty: w znaku / absolutny / dziesiętny).
LOG-25/28: harness porównawczy (compare.py) z progami tolerancji oraz
  wspólny kontrakt parzystości; pełen zestaw testów.
LOG-27: services/engine-swisseph — osobna, opcjonalna usługa AGPL
  (pyswisseph, tryb Moshiera), licencjonowana osobno, w compose pod
  profilem "comparison"; nie wchodzi do zamkniętego produktu.

Walidacja: SkyfieldEngine zgadza się ze Swiss Ephemeris co do ~1" dla
wszystkich 10 obiektów na horoskopie referencyjnym (30.04.1984, Warszawa);
12 testów przechodzi (silnik B pomijany gdy nieskonfigurowany).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 19:48:31 +02:00
gitea 16d35c16dc Szkielet aplikacji trójwarstwowej (prezentacja / logika / dane)
Trzy niezależne usługi FastAPI komunikujące się przez HTTP/JSON, każda
zna tylko adres warstwy bezpośrednio pod nią:

- presentation (:8000) — strona WWW + formularz
- logic (:8001) — reguły biznesowe, pośrednik
- data (:8002) — wyszukiwanie danych za interfejsem DataProvider

Warstwa danych: czytanie setek plików .xlsx z wykrywaniem nagłówka i
mapowaniem układu kolumn na schemat kanoniczny, z 4-poziomowym cache
(schemat L1, Parquet L2, wyniki zapytań L3, odwrócony indeks L4) i
unieważnianiem po odcisku pliku. Gotowa ścieżka migracji do SQL
(ingest/to_sql.py + SqlDataProvider, przełączane przez DATA_PROVIDER).

Zawiera docker-compose, Makefile, generator danych przykładowych.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 17:35:09 +02:00