Compare commits

..

9 Commits

Author SHA1 Message Date
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 9bf297d461 Merge pull request #4 from migatu/docs/plain-language-column
Wymagania: nietechniczna kolumna wyjaśnień (Po ludzku, bez żargonu)
2026-06-27 18:22:03 +02:00
gitea 561d95c7f4 Wymagania: kolumna "Po ludzku (bez żargonu)" — nietechniczne wyjaśnienia
Dodaje per-requirement, kompletnie nietechniczne tłumaczenie do trzech
arkuszy warstw (dane 24, logika 28, prezentacja 13) jako ostatnią kolumnę.
Dla osób nietechnicznych — m.in. współpracownika dostarczającego bazy —
żeby każdy wiersz był zrozumiały bez żargonu. Autofiltr rozszerzony.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:16:33 +02:00
gitea 4231f640b8 Merge pull request #3 from migatu/requirements/dual-engine
Wymagania: dwa silniki w warstwie logicznej (własny + AGPL) do porównań
2026-06-27 17:01:16 +02:00
gitea f86ab55b29 Merge pull request #2 from migatu/docs/requirements-spreadsheet
Dodaj arkusz wymagań projektu (model trójwarstwowy)
2026-06-27 16:53:29 +02:00
gitea 21442b35b9 Wymagania: dwa silniki w warstwie logicznej (własny + AGPL) do porównań
Decyzja: warstwa logiczna ma dwie wymienne implementacje silnika obliczeń —
własną/permisywną (ścieżka A) i AGPL (Swiss Ephemeris) — dla wbudowanego
frameworku porównań i walidacji. Prezentacja i dane pozostają wspólne.

- docs/architektura-dwoch-silnikow.md: schemat, izolacja licencyjna silnika
  AGPL jako osobnej usługi (engine-swisseph), mechanizm dual-run, mapowanie
  na wymagania, profile wdrożeniowe.
- astrololo_wymagania.xlsx (Warstwa logiczna): rewizja LOG-24 (EngineProvider
  z dwoma backendami) i LOG-25 (framework porównawczy); nowe LOG-26 (dual-run
  + raport różnic), LOG-27 (izolacja licencyjna silnika AGPL), LOG-28
  (kontrakt parzystości silników). Licznik w Przeglądzie: 28.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:50:56 +02:00
gitea 3655b39acf Rozszerz analizę warstwy logicznej: gotowce, licencje, złożoność + LOG-25
- docs/przeglad-bibliotek-i-licencji.md: przegląd istniejących bibliotek/
  programów z komentarzem licencyjnym i oceną ryzyka praw autorskich;
  rekomendacja ścieżki A (Skyfield/Moshier permisywne) + nota o prawach do
  treści baz.
- docs/warstwa-logiczna-analiza.md: rozwinięcie 24 wymagań LOG, przypisanie
  permisywnych gotowców, ocena złożoności implementacji od zera, rola B/C
  jako wyroczni walidacyjnej; kolejność budowy.
- astrololo_wymagania.xlsx: w arkuszu "Warstwa logiczna" 4 nowe kolumny
  (gotowe rozwiązanie / licencja / złożoność od zera / rola B/C) oraz nowy
  wiersz LOG-25 (harness walidacyjny). Licznik w Przeglądzie zaktualizowany.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 12:31:37 +02:00
gitea 7bda39ab46 Dodaj arkusz wymagań projektu (model trójwarstwowy)
docs/astrololo_wymagania.xlsx — spójna lista wymagań zsyntetyzowana z
dokumentów astroparser_notes2, astroparser notes3 i astro19 version2026.

Sześć arkuszy: Przegląd, Warstwa danych (24), Warstwa logiczna (24),
Warstwa prezentacji (13), Pytania otwarte (11), Słownik (25). Każde
wymaganie: ID, kategoria, opis, szczegóły, priorytet (MoSCoW), status,
źródło. Dokument roboczy do iteracyjnego przeglądu przed implementacją.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 18:18:46 +02:00
gitea 269c89152e Merge pull request #1 from migatu/scaffold/three-tier-app
Szkielet aplikacji trójwarstwowej (prezentacja / logika / dane)
2026-06-26 17:36:22 +02:00
12 changed files with 625 additions and 71 deletions
+84
View File
@@ -0,0 +1,84 @@
# Architektura dwóch silników (warstwa logiczna)
Dokument roboczy. Decyzja: warstwa logiczna ma **dwie wymienne implementacje silnika obliczeń** — jedną **własną/permisywną** (ścieżka A) i jedną **opartą o AGPL** (ścieżka C, Swiss Ephemeris) — żeby od pierwszego dnia mieć **wbudowany framework porównań i walidacji**. Warstwy **prezentacji i danych pozostają wspólne i pojedyncze**; rozdwaja się wyłącznie silnik wewnątrz warstwy logicznej.
---
## 1. Schemat
```
(jedna) (jedna, ale 2 silniki) (jedna)
┌──────────────────────┐ ┌─────────────────────────────────────┐ ┌──────────────────────┐
│ PREZENTACJA │ ─────▶ │ LOGIKA │ ─────▶ │ DANE │
│ (bez zmian) │ ◀───── │ EngineProvider (interfejs) │ ◀───── │ (bez zmian) │
└──────────────────────┘ │ ├── EngineA: własny (permisywny)│ └──────────────────────┘
│ │ Skyfield/Moshier — in-proc │
│ └── EngineB: swisseph (AGPL) │
│ ⇢ OSOBNY proces/usługa │
│ Komparator (dual-run + raport) │
└─────────────────────────────────────┘
⇢ (granica sieciowa/IPC)
┌─────────────────────────────────┐
│ engine-swisseph (AGPL, OSOBNO) │
│ pyswisseph / Swiss Ephemeris │
└─────────────────────────────────┘
```
Prezentacja i dane „widzą" tylko interfejs `EngineProvider`**nie wiedzą, który silnik liczy** (engineagnostic). To ta sama zasada modułowości co `DataProvider` w warstwie danych.
---
## 2. Po co dwa silniki
1. **Wyrocznia walidacyjna od dnia zero.** Każdy wynik EngineA możemy natychmiast porównać z EngineB (Swiss Ephemeris = defacto standard). Bezcenne przy budowie własnego silnika od zera (primary directions, domy, węzły/Lilith).
2. **Framework porównań.** Tryb „policz oboma i pokaż różnice" — do testów regresyjnych (CI) i do podglądu sidebyside w UI.
3. **Bezpieczeństwo decyzji.** Gdyby własny silnik gdzieś odstawał, widać to od razu, a nie po miesiącach.
---
## 3. Izolacja licencyjna (warunek konieczny) ⚠️
EngineB jest **AGPL**, więc nie może być wlinkowany w permisywny produkt. Rozwiązanie zgodne z naszą architekturą (osobne usługi jak warstwa danych):
- **EngineB = osobny proces/usługa `engine-swisseph`**, wołany przez **granicę sieciową/IPC** (HTTP). Brak linkowania = brak „zarażenia" copyleftem reszty.
- **Produkt zamknięty (dystrybucja/hosting):** prezentacja + logika(**EngineA**) + dane. **Bez** EngineB. Zero AGPL.
- **Profil porównawczy/dev/CI:** dodatkowo uruchamiamy usługę `engine-swisseph` (AGPL, sama w sobie zgodna z AGPL — to w zasadzie cienkie API na pyswisseph). Używana wewnętrznie do porównań, nie wystawiana użytkownikom produktu.
- **Konfiguracja** decyduje, czy EngineB jest w ogóle dostępny (`ENGINE_B_URL` ustawione lub nie).
Efekt: pełny framework porównań **bez** narażania kodu i danych produktu na obowiązki AGPL (patrz `przeglad-bibliotek-i-licencji.md` oraz analiza zakresu AGPL względem baz danych).
---
## 4. Jak działa porównanie (dualrun)
1. To samo wejście (dane horoskopu + ustawienia) trafia do EngineA i EngineB.
2. Komparator zestawia wyniki: pozycje obiektów, cusps domów, aspekty, daty technik.
3. Różnice liczone z **progami tolerancji per wielkość** (np. długość ekliptyczna ≤ 1″, cusp ≤ 1″, data zdarzenia ≤ 1 min); przekroczenia oflagowane.
4. Raport: (a) jako asercje w **CI** (regresja), (b) jako **podgląd sidebyside** w prezentacji na żądanie.
---
## 5. Wpływ na warstwy i mapowanie na wymagania
| Warstwa | Zmiana |
|---|---|
| Prezentacja | brak (opcjonalnie: widok porównawczy sidebyside) |
| Dane | brak |
| **Logika** | `EngineProvider` + 2 backendy (EngineA inproc, EngineB jako klient usługi AGPL) + komparator |
| **Nowa usługa** | `engine-swisseph` (AGPL, osobno, opcjonalna) |
Wymagania (arkusz „Warstwa logiczna"):
- **LOG24** — interfejs `EngineProvider` z **dwoma** backendami (zrewidowane).
- **LOG25** — framework walidacyjnoporównawczy (zrewidowane).
- **LOG26** — tryb dualrun + raport różnic (nowe).
- **LOG27** — izolacja licencyjna silnika AGPL jako osobnej usługi (nowe).
- **LOG28** — kontrakt parzystości silników: identyczny interfejs, te same testy (nowe).
---
## 6. Implikacje wdrożeniowe
- **Dwa profile `docker-compose`:** `proprietary` (prezentacja+logika+dane, EngineA) i `comparison` (dodaje `engine-swisseph`).
- **Testy kontraktowe** uruchamiane na obu silnikach (ten sam zestaw → gwarancja parzystości interfejsu).
- **Decyzja A vs B na produkcji** pozostaje po stronie A; B nigdy nie trafia do dystrybucji zamkniętej — służy jako wzorzec i narzędzie QA.
Binary file not shown.
+110
View File
@@ -0,0 +1,110 @@
# Przegląd istniejących rozwiązań + analiza licencji
Dokument roboczy. Przegląd bibliotek i programów, które mogą być przydatne dla projektu **astrololo**, ze szczególnym naciskiem na **licencje i ryzyko praw autorskich** (zgodnie z prośbą). Stan: czerwiec 2026.
> ⚠️ **Najważniejszy wniosek (TL;DR).** Branżowy standard obliczeń — **Swiss Ephemeris** — oraz **wszystkie** popularne biblioteki astrologiczne, które na nim bazują (pyswisseph, kerykeion, immanuel, flatlib, swisseph-wasm), są objęte licencją **AGPL3.0 albo płatną licencją komercyjną**. AGPL wymusza udostępnienie **całego kodu źródłowego aplikacji** — i to także wtedy, gdy aplikacja jest tylko udostępniana przez sieć (SaaS / API / hosting online, o którym mowa w notatkach). Dla produktu zamkniętego z unikalnymi bazami kolaboratora oznacza to albo (a) zakup licencji komercyjnej Swiss Ephemeris, albo (b) zbudowanie warstwy obliczeń na permisywnym fundamencie (Skyfield MIT + dane NASA JPL = public domain) i samodzielną implementację części astrologicznej. Szczegóły i rekomendacja: sekcja 6.
Legenda ryzyka: 🟢 permisywne (MIT/BSD/PD) · 🟡 wymaga uwagi/atrybucji · 🔴 copyleft/AGPL — ryzykowne dla produktu zamkniętego.
---
## 1. Silniki efemeryd / obliczenia astronomiczne (fundament warstwy logicznej)
| Rozwiązanie | Co robi | Licencja | Komentarz / ryzyko |
|---|---|---|---|
| **Swiss Ephemeris** (C, Astrodienst — `aloistr/swisseph`) | Defacto standard: pozycje obiektów, domy, aspekty, gwiazdy stałe, lots — pełna astrologia, najwyższa dokładność | **Dual: AGPL3.0 LUB komercyjna** (CHF 750 za pierwszą licencję + CHF 400 za każdą kolejną; ważna 99 lat) | 🔴 AGPL: użycie w usłudze sieciowej = obowiązek otwarcia całego kodu. Licencja komercyjna zdejmuje copyleft z **silnika**, ale wrappery językowe (poniżej) mają **własne** licencje — trzeba je sprawdzić osobno. Decyzję trzeba podjąć **przed** dystrybucją/uruchomieniem usługi. |
| **pyswisseph** (`astrorigin/pyswisseph`) | Wrapper Pythona do Swiss Ephemeris | **AGPL3.0** | 🔴 To prawdopodobnie „kalkulator z GitHuba", o którym wiesz. Sam wrapper jest AGPL niezależnie od licencji silnika. |
| **swissephwasm** (`prolaxu/swisseph-wasm`) | Swiss Ephemeris skompilowany do WebAssembly (JS) | dziedziczy po Swiss Ephemeris (**AGPL/komercyjna**) | 🔴 Ta sama pułapka AGPL, tyle że w przeglądarce. |
| **Skyfield** (`skyfielders/python-skyfield`) | Czysto astronomiczne pozycje planet (researchgrade), oparte o dane NASA JPL | **MIT** | 🟢 Permisywne, idealne dla produktu zamkniętego. **ALE**: tylko astronomia — **nie liczy domów, aspektów, lots ani technik**. Warstwę astrologiczną trzeba dopisać samemu. |
| **Dane NASA JPL DE440 / DE441** | Współrzędne efemeryd (Chebyshev), źródło dla Skyfielda | Dane rządu USA — **faktycznie public domain**, swobodnie dystrybuowane | 🟢 Permisywny fundament danych. DE440: lata 15502650; DE441: 13200 do +17191. |
| **Moshier ephemeris** (Steve Moshier, `moshier.net`) | Semianalityczna teoria, ~0,1″ dokładności, bez plików danych | **Public domain** | 🟢 Wolny od jakichkolwiek zobowiązań. Mniej dokładny niż JPL, ale dla astrologii w zupełności wystarcza. Istnieje też reimplementacja JS (`0xStarcat/Moshier-Ephemeris-JS`, MIT). |
| **Astropy** | Astronomia ogólna (układy współrzędnych, czas) | **BSD3** | 🟢 Permisywne; pomocnicze (czas, transformacje), nie astrologia. |
---
## 2. Biblioteki astrologiczne wyższego poziomu (domy, aspekty, lots, techniki)
| Rozwiązanie | Co robi | Licencja | Komentarz / ryzyko |
|---|---|---|---|
| **kerykeion** (`g-battaglia/kerykeion`) | Nowoczesna, utrzymywana: pozycje, domy, aspekty, wykresy SVG, synastria/tranzyty/composite | **AGPL3.0** | 🔴 Wymaga otwarcia projektu. Autor oferuje hostowane **Astrologer API** (REST) — korzystanie z niego **nie** narzuca copyleft na Twój kod (ale to usługa zewnętrzna, płatna, wysyłasz dane na zewnątrz). |
| **immanuel** (`theriftlab/immanuel-python`) | Dane czytelne dla człowieka + JSON, wzorowane na astro.com / Astro Gold; na pyswisseph | **GPL3.0+** | 🔴 GPL (sieciowo łagodniejsze niż AGPL, ale wciąż copyleft); ciągnie AGPLowy pyswisseph. |
| **flatlib** (`flatangle/flatlib`) | Astrologia tradycyjna (domy, aspekty, godności) | **kod własny: MIT** 🟢, ale **zależy od Swiss Ephemeris** 🔴 | 🟡→🔴 Sam kod flatlib jest permisywny (MIT), lecz obliczenia robi Swiss Ephemeris — więc realnie obowiązuje Cię AGPL/komercyjna SE. Słabo utrzymywane. Dobra **referencja** projektowa. |
| **libephemeris** (`g-battaglia/libephemeris`) | API zgodne z pyswisseph, ale liczy Skyfieldem (NASA); 25 systemów domów, weryfikowane względem pyswisseph | **AGPL3.0** | 🔴 Ciekawe technicznie (permisywny fundament: Skyfield+JPL), ale autor wybrał AGPL → i tak copyleft. Można potraktować jako **wzorzec**, jak dołożyć domy do Skyfielda. |
---
## 3. JavaScript / frontend (jeśli warstwa prezentacji będzie webowa)
| Rozwiązanie | Co robi | Licencja | Komentarz / ryzyko |
|---|---|---|---|
| **CircularNatalHoroscopeJS** (`0xStarcat/...`) | Liczy natalny wykres (Asc/MC, domy, tropikalny/syderyczny) — **na Moshierze, bez Swiss Ephemeris** | **MIT** | 🟢 Rzadki przypadek: obliczenia astrologiczne bez „skażenia" AGPL. Dobry wzorzec/komponent dla JS. |
| **AstroChart** (`Kibo/AstroChart`) | Renderowanie kosmogramu (SVG) — tylko rysowanie, bez efemeryd | opensource (zweryfikować plik LICENSE) | 🟡 Brak obliczeń = brak ryzyka efemeryd. Sprawdzić dokładnie licencję przed użyciem. |
| **swissephwasm** | (jak w sekcji 1) | AGPL/komercyjna | 🔴 |
---
## 4. Geolokalizacja i strefy czasowe (dla pola „miejscowość → timezone → Asc/MC")
Notatki opisują obliczenie strefy czasowej z większej miejscowości, potem przeliczenie dla dokładnej lokalizacji. Przydatne:
| Rozwiązanie | Co robi | Licencja | Komentarz / ryzyko |
|---|---|---|---|
| **timezonefinder** (`jannikmi/timezonefinder`) | Strefa czasowa z lat/long, offline | **kod: MIT** 🟢 / **dane: ODbL** 🟡 | 🟡 Dane (timezoneboundarybuilder, ODbL) — przy dystrybucji bazy wymagana atrybucja i klauzula sharealike **dla samej bazy** (nie dla Twojego kodu). |
| **GeoNames** (baza miejscowości) | Miejscowości + współrzędne + strefy (do dropdowna lokalizacji) | **CCBY 4.0** | 🟡 Wolne, ale **wymaga atrybucji** „GeoNames". Idealne do tabeli miejscowości z notatek. |
| **IANA tz database (tzdata)** | Reguły stref i czasu letniego (DST) | **Public domain** | 🟢 W Pythonie przez `zoneinfo` (stdlib) — bez zależności. |
---
## 5. Pełne programy referencyjne (do nauki algorytmów, nie do kopiowania kodu)
| Program | Licencja | Komentarz / ryzyko |
|---|---|---|
| **Astrolog** (Walter Pullen, `astrolog.org`) | **GPL2.0+** (od wersji 6.00) | 🔴 do kopiowania kodu (GPL), ale **bezcenny jako referencja** algorytmów (domy, dyrekcje, techniki). Opcjonalnie używa Swiss Ephemeris. Można czytać i uczyć się metod, nie wklejać kodu do produktu zamkniętego. |
| **Maitreya / Morinus** (opensource) | GPL (zweryfikować) | 🟡 Jw. — referencja, nie źródło kodu do zamkniętego produktu. |
---
## 6. Strategia licencyjna — trzy ścieżki (z rekomendacją)
Wybór zależy od jednej decyzji z arkusza wymagań (**Q01**: produkt zamknięty/hostowany czy nie) i **Q07** (SQL/architektura). Przy założeniu **produktu zamkniętego z prywatnymi bazami** (co wynika z notatek — hosting online, własność kolaboratora):
**Ścieżka A — permisywna, „zbuduj sam" 🟢 (rekomendowana longterm)**
Skyfield (MIT) + dane JPL DE440 (public domain) lub Moshier (PD) do pozycji; domy/aspekty/lots/dyrekcje implementujemy sami (wzorując się na Astrologu i libephemeris — czytając, nie kopiując).
- Pełna swoboda licencyjna, produkt może być zamknięty i hostowany.
- Najwięcej pracy w warstwie logicznej (ale dokładnie tę warstwę i tak projektujemy modułowo).
**Ścieżka B — Swiss Ephemeris na licencji komercyjnej 🟡 (najszybsza „pełna dokładność")**
Kupujemy licencję komercyjną SE (CHF 750) i wołamy silnik C bezpośrednio (z cienkim własnym wrapperem, by ominąć AGPLowy pyswisseph).
- Od razu komplet funkcji i najwyższa dokładność; produkt zamknięty OK.
- Koszt + formalności licencyjne; trzeba uważać, żeby nie wciągnąć AGPLowych wrapperów.
**Ścieżka C — AGPL „na całość" 🔴 (tylko jeśli produkt może być otwarty)**
kerykeion/immanuel/flatlib+pyswisseph — najszybszy development.
- Gotowiec, mało kodu.
- Wymusza **otwarcie całej aplikacji** (w tym kodu obsługującego prywatne bazy). Przy hostingu online (AGPL!) praktycznie wykluczone dla tego projektu.
**Ścieżka D — hostowane API (np. Astrologer API)** jako uzupełnienie: korzystanie z REST nie narzuca copyleft, ale to zależność zewnętrzna, koszt, prywatność danych urodzeniowych i może nie pokrywać egzotycznych technik (firdaria, zodiacal releasing, warianty primary directions).
> **Rekomendacja:** zarezerwować w architekturze warstwy logicznej **interfejs silnika efemeryd** (jak `DataProvider` w warstwie danych), tak by dało się podmienić backend: `MoshierEngine` / `SkyfieldEngine` (ścieżka A) ↔ `SwissEphCommercialEngine` (ścieżka B). Wtedy decyzję A vs B można podjąć później bez przepisywania logiki.
---
## 7. Osobna kwestia: prawa autorskie do TREŚCI baz (to NIE są licencje software!)
To dotyczy zawartości baz interpretacji, nie kodu — i jest tu realne ryzyko do rozważenia:
- **Bazy kolaboratora.** Jeśli to **oryginalna twórczość** współpracownika, to on jest dysponentem praw — potrzebne jasne ustalenie (umowa/licencja) na wykorzystanie ich w produkcie. To załatwia sprawę po stronie tych danych.
- **Źródła z opublikowanych książek.** Przykładowy plik „The Encyclopaedia of Medical Astrology" to **wydana książka** (H. L. Cornell, 1933). Cyfrowy przedruk obszernych opisów interpretacyjnych z chronionego dzieła = **ryzyko naruszenia praw autorskich**, niezależnie od licencji oprogramowania. Status zależy od jurysdykcji i daty (część dawnych dzieł może być już w domenie publicznej — wymaga sprawdzenia per tytuł).
- **Fakt vs. twórczość.** Krótkie formuły sygnifikatorów (np. „Ma Ari = …") to raczej **fakty/metoda** — trudniej objąć je prawem autorskim. Natomiast rozbudowana **proza interpretacyjna** (kolumna Effect) jest chroniona jako utwór.
- **Zalecenie:** dla każdej bazy odnotować **proweniencję** (autor/źródło/status praw) — najlepiej jako kolumnę/metadane w warstwie danych (pasuje do `book-#per-txt`, `author`, `lang` z notatek). Oddzielić materiał: własny kolaboratora / public domain / cytowany za zgodą / wymagający usunięcia.
---
## Źródła
- [Swiss Ephemeris — licencja (astro.com)](https://www.astro.com/swisseph/swephinfo_e.htm) · [LICENSE](https://www.astro.com/ftp/swisseph/LICENSE) · [repo `aloistr/swisseph`](https://github.com/aloistr/swisseph)
- [pyswisseph (`astrorigin/pyswisseph`)](https://github.com/astrorigin/pyswisseph)
- [kerykeion (PyPI)](https://pypi.org/project/kerykeion/) · [immanuel (PyPI)](https://pypi.org/project/immanuel/) · [flatlib LICENSE (MIT)](https://raw.githubusercontent.com/flatangle/flatlib/master/LICENSE)
- [Skyfield (PyPI, MIT)](https://pypi.org/project/skyfield/) · [libephemeris (`g-battaglia/libephemeris`)](https://github.com/g-battaglia/libephemeris)
- [NASA JPL DE440/DE441](https://ssd.jpl.nasa.gov/doc/de440_de441.html) · [Moshier ephemeris](http://www.moshier.net/) · [MoshierEphemerisJS](https://github.com/0xStarcat/Moshier-Ephemeris-JS)
- [CircularNatalHoroscopeJS](https://github.com/0xStarcat/CircularNatalHoroscopeJS) · [AstroChart (`Kibo/AstroChart`)](https://github.com/Kibo/AstroChart) · [swissephwasm](https://github.com/prolaxu/swisseph-wasm)
- [timezonefinder (PyPI, MIT)](https://pypi.org/project/timezonefinder/) · [Astrolog (Wikipedia)](https://en.wikipedia.org/wiki/Astrolog)
+174
View File
@@ -0,0 +1,174 @@
# Warstwa logiczna — analiza, rozwinięcie wymagań i przypisanie rozwiązań
Dokument roboczy. Rozwinięcie 24 wymagań warstwy logicznej (LOG01…LOG24 z `astrololo_wymagania.xlsx`) wraz z: przypisaniem gotowych **permisywnych** rozwiązań, oceną złożoności implementacji **od zera** (bo idziemy **ścieżką A**) oraz wskazaniem, gdzie narzędzia AGPL/komercyjne (B/C) służą jako **referencja i walidacja**. Stan: czerwiec 2026.
---
## 0. Założenia ścieżki A
**Fundament permisywny (co dostajemy „za darmo"):**
- **Skyfield** (MIT) + dane **NASA JPL DE440/DE441** (public domain) → geocentryczne pozycje planet i Księżyca, RA/Dec, długość/szerokość ekliptyczna, prędkości, wschody/zachody, wyszukiwanie zdarzeń (stacje, powroty, syzygia).
- **pyerfa / ERFA** (BSD; pochodna IAU SOFA) → precesja, nutacja, czas gwiazdowy (LST), nachylenie ekliptyki (obliquity) — prymitywy potrzebne do osi (Asc/MC) i ayanams.
- **Katalog Hipparcos** (ESA, swobodnie dostępny; ładuje go Skyfield) → gwiazdy stałe.
- **Lark / pyparsing** (MIT) → infrastruktura parsera składni sygnifikatorów.
- **numpy / pandas** (BSD) → cała matematyka i tabele.
Wniosek: **~7080% astronomicznego fundamentu jest pokryte permisywnie**. Sami budujemy **warstwę astrologiczną** (domy, dyrekcje, techniki, parser, dopasowanie) — a jej algorytmy/wzory są **wiedzą publiczną** (opisane w literaturze astronomicznej i astrologicznej), więc **nie podlegają prawu autorskiemu** — chronione są tylko konkretne implementacje kodu. Czytamy więc Astrolog/Morinus/dokumentację Swiss Ephemeris, by **zrozumieć metodę**, a piszemy **własny** kod permisywny.
**Rola ścieżek B/C (Swiss Ephemeris, Morinus, astro.com):**
1. **Wyrocznia walidacyjna** — nasz silnik porównujemy liczbowo (do łuku sekundy) z SE/astro.com. Użycie pyswisseph **w samych testach/CI** (nie dystrybuowane, nie wystawiane użytkownikom przez sieć) **nie uruchamia obowiązków AGPL** — to legalne i bardzo przydatne.
2. **Dane referencyjne** — tabele kontrolne (np. dla profekcji, primary directions) do testów regresyjnych.
3. **Porównanie funkcjonalne** — które warianty technik liczy „konkurencja".
Skala złożoności (implementacja od zera): **Trywialna · Niska · Średnia · Wysoka · Bardzo wysoka (XL)**.
---
## 1. Macierz zbiorcza
| ID | Wymaganie | Gotowe permisywne (ścieżka A) | Złożoność od zera | Rola B/C |
|----|-----------|-------------------------------|-------------------|----------|
| LOG01 | Pozycje obiektów | **Skyfield** (komplet) | Niska | walidacja pozycji |
| LOG02 | Taksonomia obiektów | Skyfield (planety, Księżyc), Hipparcos (gwiazdy) | **Wysoka** (true Node/Lilith, gwiazdy) | walidacja węzłów/Lilith |
| LOG03 | Kierunek i stacje | Skyfield (find_discrete) | Średnia | walidacja dat stacji |
| LOG04 | Systemy zodiaku / ayanamsy | Skyfield (tropikalny, RA), pyerfa (precesja) | Średnia | walidacja ayanams |
| LOG05 | Systemy domów (wiele) | pyerfa (LST, obliquity); reszta własna | **Wysoka** | walidacja cusps (kluczowa) |
| LOG06 | Aspekty + orby | własne (czysta matematyka) | Niska | walidacja list aspektów |
| LOG07 | Aspekty pozazodiakalne | Skyfield (deklinacja) | Niska | walidacja |
| LOG08 | Lots | własne (arytmetyka + DSL formuł) | Niska–Średnia | porównanie wartości |
| LOG09 | **Primary Directions** | brak permisywnego | **Bardzo wysoka (XL)** | **krytyczna** (Morinus) |
| LOG10 | Profekcje | własne (arytmetyka + tabele władców) | Niska–Średnia | tabela kontrolna |
| LOG11 | Zod. Releasing / Firdaria / Decennials | brak permisywnego | Średnia (tabledriven) | walidacja sekwencji |
| LOG12 | Returns (Solar/Lunar) | Skyfield (rootfinding) | Niska–Średnia | walidacja czasu powrotu |
| LOG13 | Ascensional Times | pyerfa/własne (oblique ascension) | Średnia | tabela kontrolna |
| LOG14 | Zbiorcza tabela dat | własne (orkiestracja) | Średnia | — |
| LOG15 | **Parser sygnifikatorów** | Lark/pyparsing (infrastruktura) | **Wysoka** (unikalne IP) | brak referencji |
| LOG16 | Precompute „atomów" | własne | Średnia | — |
| LOG17 | Interpretacja „asp"/„asp±" | własne (config + LOG06) | Niska–Średnia | — |
| LOG18 | Warstwy 1A/1B | własne (orkiestracja ↔ warstwa danych) | Średnia | — |
| LOG19 | Wypis interpretacji 2B | własne (↔ DataProvider) | Średnia | — |
| LOG20 | Kolumny sygnif. + „no of hits" | własne | Niska–Średnia | — |
| LOG21 | Scoring siły efektu | własne (rule/datadriven) | Średnia | — |
| LOG22 | Konwersja tekst ↔ symbol | własne (tablice z notes3) | Niska | — |
| LOG23 | Pozostałe wyliczenia | Skyfield (zdarzenia) | Niska | walidacja |
| LOG24 | Abstrakcja silnika efemeryd | architektura | Niska | — |
**Tylko 3 pozycje są naprawdę trudne:** LOG09 (primary directions, XL), LOG05 (pełny zestaw domów, Wysoka), LOG15 (parser — Wysoka, ale to nasze unikalne IP, więc gotowca i tak nie ma). Reszta jest Niska/Średnia, w dużej części pokryta przez Skyfield.
---
## 2. Rozwinięcia pogrupowane
### Grupa A — Fundament astronomiczny (LOG01, 02, 03, 24)
**LOG01 · Pozycje obiektów.**
*Rozwinięcie:* długość i szerokość ekliptyczna (ofdate), prędkość (różniczkowanie pozycji), kierunek; formaty: DMS w znaku (Tau 28°12'57''), absolutne 0360°, dziesiętne. *Ścieżka A:* Skyfield daje to wprost (`ecliptic_latlon`, prędkość z dwóch chwil). Formatowanie/podział na znaki = trywialne. *Złożoność: Niska.* *B/C:* porównać długości z astro.com do ~0,1″.
**LOG02 · Taksonomia obiektów.**
*Rozwinięcie:* światła+planety (Skyfield 🟢), planety nowożytne (🟢), gwiazdy stałe (precesja+ruch własny — Hipparcos w Skyfield, 🟢 średnia), obiekty wirtualne:
- **Węzły księżycowe** — *mean* (wzór analityczny, Niska) i *true/osculating* (z wektora stanu Księżyca → elementy oskulacyjne → węzeł wstępujący; **ŚredniaWysoka**).
- **Lilith (Black Moon = apogeum Księżyca)** — *mean* (Niska–Średnia) i *true* (oskulacyjne apogeum; **Wysoka** — tu implementacje notorycznie się różnią, walidacja względem SE obowiązkowa).
- **Osie (Asc/MC → Dsc/IC)** — patrz LOG05.
*Złożoność całości: Wysoka* (przez gwiazdy + true Node/Lilith; reszta Niska). *B/C:* SE jako wzorzec dla true Node/Lilith (różne definicje!).
**LOG03 · Kierunek i stacje.**
*Rozwinięcie:* znak prędkości → D/Rx; stacja = przejście prędkości przez 0 (SD vs SR z przyspieszenia); „dni od stacji ścisłej" + flaga <7 dni; „stacja umowna" jako konfigurowalny próg % ruchu typowego. *Ścieżka A:* Skyfield `find_discrete`/`find_maxima` znajduje momenty stacji; średnie prędkości precompute. *Złożoność: Średnia.* *B/C:* daty stacji z SE.
**LOG24 · Abstrakcja silnika.**
*Rozwinięcie:* interfejs `EphemerisEngine` z implementacjami `SkyfieldEngine` (główna, MIT+JPL), `MoshierEngine` (fallback w pełni publicdomain, bez plików danych) oraz — opcjonalnie po decyzji — `SwissEphCommercialEngine`. *Złożoność: Niska* (architektura, analogicznie do `DataProvider`). To wymaganie **spina ścieżki A/B** bez przepisywania logiki.
### Grupa B — Układy odniesienia (LOG04, 05)
**LOG04 · Systemy zodiaku / ayanamsy.**
*Rozwinięcie:* tropikalny (domyślny, 🟢), syderyczny = tropikalny ayanamsa(t) (Lahiri, FaganBradley… ~40 w SE; my zaczynamy od kilku + tabela rozszerzalna), draconic = tropikalny długość NN (🟢 po LOG02), RA (Skyfield daje wprost). *Ścieżka A:* precesja z pyerfa; ayanamsy to znane stałe odniesienia + tempo precesji. *Złożoność: Średnia* (głównie tabela ayanams). *B/C:* wartości ayanams z SE.
**LOG05 · Systemy domów (wiele, równolegle).** ⚠️ najcięższa matematyka astronomiczna.
*Rozwinięcie:*
- Whole Sign / Equal (Asc/MC) / Porphyry → **Niska** (trywialne podziały).
- Placidus, Koch, Regiomontanus, Campanus, Topocentric, Alcabitus, Meridian, Morinus, Vehlow → znane wzory trygonometrii sferycznej, każdy **Średnia**, w komplecie **Wysoka** (przypadki brzegowe na wysokich szerokościach — Placidus/Koch nieokreślone za kołem podbiegunowym).
*Ścieżka A:* Asc/MC z LST (pyerfa) + obliquity + szerokość geo (standardowe wzory). `libephemeris` (AGPL) dowodzi, że pełny zestaw 25 systemów **da się** policzyć na Skyfieldzie — czytamy go jako dowód wykonalności, ale piszemy własny kod. *Złożoność: Wysoka.* *B/C:* **walidacja cusps krytyczna** — tu kryją się błędy, szczególnie na dużych szerokościach.
### Grupa C — Aspekty (LOG06, 07)
**LOG06 · Aspekty + orby.**
*Rozwinięcie:* separacja kątowa vs kąt aspektu ± orb; aplikacja/separacja z różnicy prędkości; schematy orbów (domyślne, bonus dla luminarzy, fixed, ±%, perplaneta/szkoła); aspekt „przez znak" vs „przez stopień"; major/minor/harmonic. *Ścieżka A:* czysta matematyka na policzonych długościach — **żadna biblioteka nie jest potrzebna**. *Złożoność: Niska–Średnia* (szerokość konfiguracji, nie trudność algorytmu). *B/C:* listy aspektów z astro.com do testów.
**LOG07 · Aspekty pozazodiakalne.**
*Rozwinięcie:* parallel/contraparallel (po deklinacji — Skyfield 🟢), antiscia/contraantiscia (lustro względem osi przesileń — arytmetyka). *Złożoność: Niska.*
### Grupa D — Lots (LOG08)
**LOG08 · Lots.**
*Rozwinięcie:* silnik formuł `Lot = C + A B` z odwracaniem dzień/noc (sekta); By Degree (domyślnie) i By Sign; 7 Hermetic Lots; cały znak działa jak Lot. *Ścieżka A:* mały DSL/parser formuł + ewaluacja na policzonych pozycjach. *Złożoność: Niska–Średnia* (logika sekty prosta; trudność tkwi w **tożsamości/wariantach** Lots — to kuracja danych, nie obliczenia; patrz Q03). *B/C:* porównanie wartości stopni.
### Grupa E — Techniki predykcyjne (LOG09…14)
**LOG09 · Primary Directions.** ⚠️ **najtrudniejsze (XL).**
*Rozwinięcie:* łuk dyrekcji (semiarc/Placidean, Regiomontanus, …) między sygnifikatorem a promisorem w ruchu dobowym; klucz łuk→czas (Ptolemeusz/Naibod/Cardan/Solar arc); direct/converse; mundo/zodiacal; z szerokością/bez. ~160 wariantów — robimy 58 (Q02), framework rozszerzalny. *Ścieżka A:* brak gotowca permisywnego — **w całości własna trygonometria sferyczna** + obsługa wariantów. *Złożoność: Bardzo wysoka.* *B/C:* **Morinus (GPL) słynie z dokładnych primary directions** — kluczowa wyrocznia; uwaga: nawet komercyjne programy różnią się w wariantach (do udokumentowania, który wariant odwzorowujemy).
**LOG10 · Profekcje.**
*Rozwinięcie:* annual (Asc +1 znak/rok, mod 12), monthly/daily, continuous (klucz, domyślnie 30°/rok), Lord of Year, Lord of Orb (kolejność chaldejska, mod 84/12). *Ścieżka A:* arytmetyka dat + tabele władców domicilnych — **bez bibliotek**. *Złożoność: Niska* (annual) * Średnia* (continuous + Lord of Orb). *B/C:* tabela kontrolna (jak ta w notes3).
**LOG11 · Zodiacal Releasing / Firdaria / Decennials.**
*Rozwinięcie:* deterministyczne sekwencje okresów z tabel hellenistycznych/perskich (ZR: długości okresów Valensa + „loosing of the bond"; Firdaria: stałe sekwencje diurnal/nocturnal z podokresami; Decennials: kolejność planetarna). *Ścieżka A:* logika sekwencjonowania na tabelach — algorytmy publiczne, niepodlegające prawu autorskiemu. *Złożoność: Średnia.* *B/C:* walidacja sekwencji z SE/Astrolog.
**LOG12 · Returns (Solar/Lunar).**
*Rozwinięcie:* rootfinding momentu powrotu Słońca/Księżyca do długości natalnej; 2 warianty (osobny horoskop vs tranzyt do natalu); monthly revolutions (Słońce + n×30°). *Ścieżka A:* Skyfield znajduje moment (🟢), reszta = ponowne użycie LOG01…06. *Złożoność: Niska–Średnia.* *B/C:* czas powrotu z astro.com.
**LOG13 · Ascensional Times.**
*Rozwinięcie:* oblique ascension znaków wg szerokości (różnica ascensjonalna z RA) → lata życia; sumy i ułamki dla dodatkowych dat. Częściowo datadriven (tabela DAN07). *Ścieżka A:* wzory powiązane z LOG05 (trygonometria sferyczna) + pyerfa. *Złożoność: Średnia.*
**LOG14 · Zbiorcza tabela dat z technik.**
*Rozwinięcie:* uruchom wszystkie techniki w zakresie czasu → jedna datowana tabela (`technique | significator | start | exact | end`), sortowanie, deduplikacja, daty dokładne (wspólny rootfinding z LOG12). *Ścieżka A:* orkiestracja/glue. *Złożoność: Średnia.*
### Grupa F — Parser sygnifikatorów i dopasowanie (LOG15…20) — **rdzeń unikalnego IP**
**LOG15 · Parser składni „Significator".** ⚠️ **Wysoka — i tak nie ma gotowca.**
*Rozwinięcie:* gramatyka skrótów (obiekty, znaki, żywioły, jakości, domy, aspekty+cel, retro/combust/waxing/heliacal, opcje A/B, warunki, „rAsc", whole sign+orb); 7 poziomów złożoności — od poziomu „obiekt" po sygnifikatory opisowe/niejasne (poziomy 57 mogą wymagać fallbacku człowiek/AI). *Ścieżka A:* tokenizer + gramatyka PEG (Lark, MIT) + słownik SIGNIFICATORS KEY (warstwa danych) jako mapa znaczeń tokenów. *Złożoność: Wysoka* — tu idzie najwięcej oryginalnej inżynierii; to **wartość intelektualna projektu**. *B/C:* brak — nie istnieje zewnętrzny parser tej składni.
**LOG16 · Precompute „atomów" obecnych w horoskopie.**
*Rozwinięcie:* wygeneruj zbiór wszystkich cząstek prawdziwych dla danego horoskopu (Ma Ari, Ma fire, Ma h2, Ma opp Sa, rAsc Ari…); dopasowanie rekordu bazy = przynależność do zbioru. Zależy od LOG01…08 (policzony horoskop) + LOG15 (słownik). *Złożoność: Średnia* (generacja kombinatoryczna + indeks; wrażliwe wydajnościowo — łączy się z DAN22).
**LOG17 · Interpretacja „asp"/„asp+"/„asp"/„asp malefic".**
*Rozwinięcie:* rozwinięcie wg konfigurowalnej tabeli pomocniczej (jakie aspekty, orby, active) + LOG06. *Złożoność: Niska–Średnia.*
**LOG18/19/20 · Dopasowanie i wypis.**
*Rozwinięcie:* silnik dopasowania: atomy → zapytanie do **warstwy danych (nasz DataProvider!)** → złożenie outputu: 1A (natal, unikalny), 1B (predykcyjne z datami), 2B (wypis interpretacji tylko dla obecnych rekordów), 2 kolumny sygnifikatora (z bazy / asisinchart), „no of hits" (1/2/3/…/?/2+?). To **styk warstwy logicznej z danych** — pasuje do istniejącej architektury. *Złożoność: Średnia.*
### Grupa G — Scoring, symbole, pozostałe (LOG21, 22, 23)
**LOG21 · Scoring siły efektu.**
*Rozwinięcie:* punktacja wg tabel relacyjnych (np. „no air" poziomy */**/***), punkty za domniemaną prawdziwość w syntezie; logika indywidualna (nie zawsze rzadkość = siła). *Ścieżka A:* silnik reguł na danych (tabele relacyjne — warstwa danych). *Złożoność: Średnia*, otwarte (autor przygotuje punktację indywidualnie).
**LOG22 · Konwersja tekst ↔ symbol.**
*Rozwinięcie:* dwukierunkowe mapowanie z tablic Unicode (kompletne w notes3); przechowywanie tekstem, wyświetlanie symbolami; tylko „text" Unicode, nigdy emoji; ℞ dla retro. *Złożoność: Niska* (tablice lookup). Ligatury vs 2 kolumny — patrz Q05 (prezentacja).
**LOG23 · Pozostałe wyliczenia.**
*Rozwinięcie:* tally żywiołów/jakości (trywialne), planetary day & hours (wschód/zachód ze Skyfielda + podział na 12 godzin dnia/nocy + władcy chaldejscy), stopnie krytyczne/wrażliwe (lookup), faza Księżyca (elongacja SuMo), syzygia prenatalna (rootfind ostatniej pełni/nowiu przed urodzeniem), divisional (12th/9th parts — arytmetyka). *Ścieżka A:* Skyfield obsługuje zdarzenia astronomiczne. *Złożoność: Niska* (lokalnie Niska–Średnia).
---
## 3. Proponowana kolejność budowy (fazy)
1. **Fundament (MVP silnika):** LOG24 (interfejs) → LOG01 → LOG02 (bez gwiazd/trueLilith) → LOG04 (tropikalny+RA) → LOG06/07 → LOG05 (Whole/Equal/Porphyry+Placidus) → LOG23 (podstawy). *Tu Skyfield daje ogromny skok.*
2. **Astrologia rdzenia:** LOG08 (Lots) → LOG15 (parser) → LOG16 → LOG18/19/20 (dopasowanie ↔ warstwa danych). *Tu powstaje unikalna wartość i pełny przepływ „horoskop → interpretacje".*
3. **Predykcja prosta:** LOG10 (profekcje) → LOG12 (returns) → LOG14 (agregacja) → LOG11 (timelordy) → LOG13.
4. **Predykcja trudna i dopieszczenie:** LOG09 (primary directions, XL) → LOG02 dokończenie (gwiazdy, true Node/Lilith) → LOG04 (ayanamsy) → LOG21 (scoring) → LOG05 (pełny zestaw domów).
Logika: najpierw to, co Skyfield daje tanio i co odblokowuje przepływ endtoend; najtrudniejsze (primary directions, pełne domy) na koniec, gdy mamy już solidną wyrocznię walidacyjną.
---
## 4. Stos technologiczny (permisywny) i walidacja
**Produkcyjne (ścieżka A):** Skyfield (MIT), pyerfa (BSD), dane JPL DE440 (PD) lub Moshier (PD), Lark (MIT), numpy/pandas (BSD). Zero copyleft → produkt może być zamknięty i hostowany.
**Tylko dev/test (B/C jako wyrocznia — NIE w produkcie, NIE wystawiane przez sieć):** pyswisseph (AGPL) i Morinus (GPL) do porównań liczbowych w testach regresyjnych. To legalne, bo nie dystrybuujemy ich i nie udostępniamy użytkownikom — AGPL/GPL nie zostają uruchomione.
**Wymaganie pochodne (do dopisania w arkuszu):** **LOG25 — harness walidacyjny**: zestaw testów porównujących nasz silnik z SE/astro.com/Morinus na bazie znanych horoskopów (np. przykład z notes3: 30.04.1984, Warszawa), z progami tolerancji per wielkość. To nasza polisa ubezpieczeniowa przy budowie od zera.
---
## 5. Wniosek
Ścieżka A jest **w pełni wykonalna**. Skyfield + pyerfa + JPL/Moshier zdejmują z nas całą trudną astronomię (pozycje, zdarzenia, czas gwiazdowy, precesja). Realny ciężar własnej implementacji to **trzy obszary**: primary directions (XL), pełny zestaw domów (Wysoka) i parser sygnifikatorów (Wysoka — ale unikalny, gotowca i tak nie ma). Wszystkie pozostałe wymagania są Niskie/Średnie. Narzędzia AGPL/komercyjne zostają jako **wyrocznia walidacyjna i dane referencyjne** — używane legalnie poza produktem.
+12 -3
View File
@@ -5,12 +5,21 @@ przekazuje dane z formularza do warstwy logicznej i renderuje opracowane wyniki.
Brak logiki biznesowej i dostępu do danych. Brak logiki biznesowej i dostępu do danych.
## Trasy ## Trasy
- `GET /`strona z formularzem - `GET /`**Horoskop**: formularz danych momentu (data, godzina, strefa, opcjonalnie lokalizacja)
- `POST /`wysłanie formularza → warstwa logiczna → render wyników - `POST /`liczy pozycje obiektów (woła logic `/chart/positions`) i renderuje czytelną tabelę
- `GET /significators`, `POST /significators` — wyszukiwarka w bazach interpretacji (warstwa danych)
- `GET /health` - `GET /health`
Strona „Horoskop" to widok do **ręcznego testowania**: podstawowe dane wejściowe →
to, co policzyła warstwa logiczna (znak, pozycja w znaku, absolutna, kierunek, prędkość).
Przycisk „Tu i teraz" uzupełnia bieżącą datę/godzinę i strefę przeglądarki.
## Zależności w dół ## Zależności w dół
Zna wyłącznie `LOGIC_URL` (adres warstwy logicznej). Zna wyłącznie `LOGIC_URL` (adres warstwy logicznej) i jej kontrakty `/chart/positions`
oraz `/api/query`. Nie wie nic o silniku ani bazach.
> Uwaga: pełne liczenie pozycji wymaga warstwy logicznej z silnikiem efemeryd
> (gałąź `feat/logic-engine`). Bez niego strona „Horoskop" pokaże czytelny komunikat.
## Uruchomienie ## Uruchomienie
```bash ```bash
@@ -1,7 +1,7 @@
"""Klient HTTP do warstwy logicznej. """Klient HTTP do warstwy logicznej.
Jedyny punkt styku prezentacji w dół. Przekazuje dane z formularza i odbiera Jedyny punkt styku prezentacji w dół. Przekazuje dane z formularza i odbiera
opracowane wyniki. Prezentacja nie sięga bezpośrednio do bazy. opracowane wyniki. Prezentacja nie sięga bezpośrednio do bazy ani do silnika.
""" """
from __future__ import annotations from __future__ import annotations
@@ -22,3 +22,13 @@ class LogicClient:
r = client.post(f"{self.base_url}/api/query", json=payload) r = client.post(f"{self.base_url}/api/query", json=payload)
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
def positions(
self, when_utc_iso: str, lat: float, lon: float, objects: list[str] | None = None
) -> dict[str, Any]:
"""Pozycje obiektów dla danego momentu — woła logic /chart/positions."""
payload = {"when_utc": when_utc_iso, "lat": lat, "lon": lon, "objects": objects}
with httpx.Client(timeout=settings.http_timeout) as client:
r = client.post(f"{self.base_url}/chart/positions", json=payload)
r.raise_for_status()
return r.json()
+60 -5
View File
@@ -2,9 +2,15 @@
W dół: przekazuje dane z formularza do warstwy logicznej i odbiera opracowane W dół: przekazuje dane z formularza do warstwy logicznej i odbiera opracowane
wyniki. Nie zawiera logiki biznesowej ani dostępu do danych — tylko UI. wyniki. Nie zawiera logiki biznesowej ani dostępu do danych — tylko UI.
Strona główna „/" = wprowadzenie danych horoskopu i podgląd policzonych pozycji
(do ręcznego testowania aplikacji). Wyszukiwarka sygnifikatorów przeniesiona pod
„/significators".
""" """
from __future__ import annotations from __future__ import annotations
from datetime import datetime, timedelta, timezone
import httpx import httpx
from fastapi import FastAPI, Form, Request from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@@ -19,13 +25,62 @@ templates = Jinja2Templates(directory="app/templates")
logic = LogicClient() logic = LogicClient()
def _build_utc(date: str, time: str, tz_offset: float) -> tuple[str, str]:
"""Z lokalnej daty/godziny + przesunięcia strefy → moment UTC.
Zwraca (iso_utc, etykieta_czytelna). UTC = czas lokalny offset.
"""
local = datetime.fromisoformat(f"{date}T{time}")
utc = (local - timedelta(hours=tz_offset)).replace(tzinfo=timezone.utc)
label = utc.strftime("%Y-%m-%d %H:%M UTC")
return utc.isoformat(), label
def _logic_error(e: Exception) -> str:
if isinstance(e, httpx.HTTPStatusError) and e.response.status_code == 404:
return (
"Warstwa logiczna działa, ale nie ma endpointu /chart/positions. "
"Uruchom warstwę logiczną z silnikiem efemeryd (gałąź feat/logic-engine)."
)
return f"Warstwa logiczna niedostępna: {e}"
# ---------------- Horoskop: pozycje (strona główna) ----------------
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
def index(request: Request): def chart_form(request: Request):
return templates.TemplateResponse(request, "index.html", {"result": None, "form": {}}) return templates.TemplateResponse(request, "chart.html", {"result": None, "form": {}})
@app.post("/", response_class=HTMLResponse) @app.post("/", response_class=HTMLResponse)
def search( def chart_compute(
request: Request,
date: str = Form(...),
time: str = Form(...),
tz_offset: float = Form(0.0),
lat: float = Form(0.0),
lon: float = Form(0.0),
):
form = {"date": date, "time": time, "tz_offset": tz_offset, "lat": lat, "lon": lon}
ctx: dict = {"form": form, "result": None, "error": None, "moment": None}
try:
iso_utc, label = _build_utc(date, time, tz_offset)
ctx["moment"] = label
ctx["result"] = logic.positions(when_utc_iso=iso_utc, lat=lat, lon=lon)
except (httpx.HTTPError,) as e:
ctx["error"] = _logic_error(e)
except ValueError as e:
ctx["error"] = f"Niepoprawne dane wejściowe: {e}"
return templates.TemplateResponse(request, "chart.html", ctx)
# ---------------- Sygnifikatory (wyszukiwarka w bazach) ----------------
@app.get("/significators", response_class=HTMLResponse)
def significators_form(request: Request):
return templates.TemplateResponse(request, "significators.html", {"result": None, "form": {}})
@app.post("/significators", response_class=HTMLResponse)
def significators_search(
request: Request, request: Request,
query: str = Form(...), query: str = Form(...),
field: str = Form("name"), field: str = Form("name"),
@@ -37,8 +92,8 @@ def search(
try: try:
ctx["result"] = logic.query(query=query, field=field, exact=exact, limit=limit) ctx["result"] = logic.query(query=query, field=field, exact=exact, limit=limit)
except httpx.HTTPError as e: except httpx.HTTPError as e:
ctx["error"] = f"Warstwa logiczna niedostępna: {e}" ctx["error"] = _logic_error(e)
return templates.TemplateResponse(request, "index.html", ctx) return templates.TemplateResponse(request, "significators.html", ctx)
@app.get("/health") @app.get("/health")
@@ -15,6 +15,23 @@ body {
main { width: 100%; max-width: 880px; } main { width: 100%; max-width: 880px; }
h1 { margin: 0; font-size: 2rem; letter-spacing: .5px; } h1 { margin: 0; font-size: 2rem; letter-spacing: .5px; }
.sub { color: var(--muted); margin: .25rem 0 2rem; } .sub { color: var(--muted); margin: .25rem 0 2rem; }
.topbar { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: .25rem; }
.topbar nav { display: flex; gap: .25rem; }
.topbar nav a { color: var(--muted); text-decoration: none; padding: .4rem .8rem; border-radius: 8px; border: 1px solid transparent; }
.topbar nav a:hover { color: var(--ink); }
.topbar nav a.active { color: #fff; background: var(--panel); border-color: var(--line); }
.foot { color: var(--muted); font-size: .8rem; margin-top: 2.5rem; opacity: .7; }
.grid { display: flex; gap: .6rem; flex-wrap: wrap; }
.grid label { display: flex; flex-direction: column; gap: .25rem; color: var(--muted); font-size: .85rem; flex: 1; min-width: 9rem; }
.grid input { width: 100%; }
.loc { margin-top: .8rem; }
.loc summary { color: var(--muted); cursor: pointer; font-size: .85rem; margin-bottom: .6rem; }
.actions { display: flex; gap: .5rem; margin-top: 1rem; }
button.ghost { background: transparent; color: var(--accent); border: 1px solid var(--line); }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; }
td.retro { color: #ff9b6a; font-weight: 600; }
form { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 1.25rem; } form { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 1.25rem; }
.row { display: flex; gap: .5rem; } .row { display: flex; gap: .5rem; }
.row input[type=text] { flex: 1; } .row input[type=text] { flex: 1; }
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>astrololo · {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<main>
<header class="topbar">
<h1>astrololo</h1>
<nav>
<a href="/" class="{% block nav_chart %}{% endblock %}">Horoskop</a>
<a href="/significators" class="{% block nav_sig %}{% endblock %}">Sygnifikatory</a>
</nav>
</header>
{% block content %}{% endblock %}
<footer class="foot">
prezentacja → logika → dane · widok testowy
</footer>
</main>
</body>
</html>
@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block title %}Horoskop{% endblock %}
{% block nav_chart %}active{% endblock %}
{% block content %}
<p class="sub">Wpisz podstawowe dane momentu — program policzy pozycje obiektów (silnik efemeryd warstwy logicznej).</p>
<form method="post" action="/">
<div class="grid">
<label>Data
<input type="date" name="date" value="{{ form.date or '' }}" required>
</label>
<label>Godzina (lokalna)
<input type="time" name="time" value="{{ form.time or '' }}" required>
</label>
<label>Strefa (offset h)
<input type="number" name="tz_offset" step="0.25" value="{{ form.tz_offset if form.tz_offset is not none else 0 }}">
</label>
</div>
<details class="loc">
<summary>Lokalizacja (opcjonalnie — przyda się później do osi i domów)</summary>
<div class="grid">
<label>Szerokość (lat)
<input type="number" name="lat" step="0.0001" value="{{ form.lat if form.lat is not none else 0 }}">
</label>
<label>Długość (lon, + na wschód)
<input type="number" name="lon" step="0.0001" value="{{ form.lon if form.lon is not none else 0 }}">
</label>
</div>
</details>
<div class="actions">
<button type="button" id="nowBtn" class="ghost">Tu i teraz</button>
<button type="submit">Policz pozycje</button>
</div>
</form>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if result %}
<div class="meta">
Silnik: <strong>{{ result.engine }}</strong> ·
obiektów: {{ result.positions | length }}
{% if moment %}· moment: {{ moment }}{% endif %}
</div>
<table>
<thead>
<tr>
<th>Obiekt</th><th>Znak</th><th>W znaku</th><th>Absolutna</th><th>Kier.</th><th>Prędkość °/d</th>
</tr>
</thead>
<tbody>
{% for p in result.positions %}
<tr>
<td>{{ p.name }}</td>
<td>{{ p.sign }}</td>
<td class="mono">{{ p.in_sign }}</td>
<td class="mono">{{ p.absolute }}</td>
<td class="{{ 'retro' if p.direction == 'Rx' else '' }}">{{ p.direction }}</td>
<td class="mono">{{ '%+.4f' | format(p.speed) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<script>
// „Tu i teraz": uzupełnia datę/godzinę bieżącą i offset lokalny przeglądarki.
document.getElementById('nowBtn').addEventListener('click', function () {
const d = new Date();
const pad = n => String(n).padStart(2, '0');
document.querySelector('input[name=date]').value =
d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
document.querySelector('input[name=time]').value = pad(d.getHours()) + ':' + pad(d.getMinutes());
document.querySelector('input[name=tz_offset]').value = (-d.getTimezoneOffset() / 60);
});
</script>
{% endblock %}
@@ -1,62 +0,0 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>astrololo</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<main>
<h1>astrololo</h1>
<p class="sub">Warstwa prezentacji → logiczna → bazodanowa</p>
<form method="post" action="/">
<div class="row">
<input type="text" name="query" placeholder="Szukana fraza…"
value="{{ form.query or '' }}" autofocus required>
<select name="field">
{% for f in ["name", "id", "symbol", "category", "value"] %}
<option value="{{ f }}" {{ 'selected' if form.field == f else '' }}>{{ f }}</option>
{% endfor %}
</select>
<button type="submit">Szukaj</button>
</div>
<div class="opts">
<label><input type="checkbox" name="exact" value="true"
{{ 'checked' if form.exact else '' }}> dokładne</label>
<label>limit
<input type="number" name="limit" min="1" max="200" value="{{ form.limit or 25 }}">
</label>
</div>
</form>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if result %}
<div class="meta">
Znaleziono <strong>{{ result.count }}</strong> ·
provider: {{ result.meta.data_provider }} ·
cache: {{ result.meta.data_cache }} ·
{{ result.meta.data_elapsed_ms }} ms
</div>
{% if result.results %}
<table>
<thead>
<tr>{% for col in result.results[0].keys() %}<th>{{ col }}</th>{% endfor %}</tr>
</thead>
<tbody>
{% for row in result.results %}
<tr>{% for v in row.values() %}<td>{{ v }}</td>{% endfor %}</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="empty">Brak wyników.</p>
{% endif %}
{% endif %}
</main>
</body>
</html>
@@ -0,0 +1,54 @@
{% extends "base.html" %}
{% block title %}Sygnifikatory{% endblock %}
{% block nav_sig %}active{% endblock %}
{% block content %}
<p class="sub">Wyszukiwanie w bazach interpretacji (warstwa danych). Wymaga wgranych baz.</p>
<form method="post" action="/significators">
<div class="row">
<input type="text" name="query" placeholder="Szukana fraza…"
value="{{ form.query or '' }}" autofocus required>
<select name="field">
{% for f in ["name", "id", "symbol", "category", "value"] %}
<option value="{{ f }}" {{ 'selected' if form.field == f else '' }}>{{ f }}</option>
{% endfor %}
</select>
<button type="submit">Szukaj</button>
</div>
<div class="opts">
<label><input type="checkbox" name="exact" value="true"
{{ 'checked' if form.exact else '' }}> dokładne</label>
<label>limit
<input type="number" name="limit" min="1" max="200" value="{{ form.limit or 25 }}">
</label>
</div>
</form>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if result %}
<div class="meta">
Znaleziono <strong>{{ result.count }}</strong> ·
provider: {{ result.meta.data_provider }} ·
cache: {{ result.meta.data_cache }} ·
{{ result.meta.data_elapsed_ms }} ms
</div>
{% if result.results %}
<table>
<thead>
<tr>{% for col in result.results[0].keys() %}<th>{{ col }}</th>{% endfor %}</tr>
</thead>
<tbody>
{% for row in result.results %}
<tr>{% for v in row.values() %}<td>{{ v }}</td>{% endfor %}</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="empty">Brak wyników.</p>
{% endif %}
{% endif %}
{% endblock %}