Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3195d9b003 | |||
| 9bf297d461 | |||
| 561d95c7f4 | |||
| 4231f640b8 | |||
| f86ab55b29 | |||
| 21442b35b9 | |||
| 3655b39acf | |||
| 7bda39ab46 | |||
| 269c89152e |
@@ -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** (engine‑agnostic). 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 = de‑facto 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 side‑by‑side 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 (dual‑run)
|
||||
|
||||
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 side‑by‑side** w prezentacji na żądanie.
|
||||
|
||||
---
|
||||
|
||||
## 5. Wpływ na warstwy i mapowanie na wymagania
|
||||
|
||||
| Warstwa | Zmiana |
|
||||
|---|---|
|
||||
| Prezentacja | brak (opcjonalnie: widok porównawczy side‑by‑side) |
|
||||
| Dane | brak |
|
||||
| **Logika** | `EngineProvider` + 2 backendy (EngineA in‑proc, EngineB jako klient usługi AGPL) + komparator |
|
||||
| **Nowa usługa** | `engine-swisseph` (AGPL, osobno, opcjonalna) |
|
||||
|
||||
Wymagania (arkusz „Warstwa logiczna"):
|
||||
- **LOG‑24** — interfejs `EngineProvider` z **dwoma** backendami (zrewidowane).
|
||||
- **LOG‑25** — framework walidacyjno‑porównawczy (zrewidowane).
|
||||
- **LOG‑26** — tryb dual‑run + raport różnic (nowe).
|
||||
- **LOG‑27** — izolacja licencyjna silnika AGPL jako osobnej usługi (nowe).
|
||||
- **LOG‑28** — 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.
@@ -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ą **AGPL‑3.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`) | De‑facto standard: pozycje obiektów, domy, aspekty, gwiazdy stałe, lots — pełna astrologia, najwyższa dokładność | **Dual: AGPL‑3.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 | **AGPL‑3.0** | 🔴 To prawdopodobnie „kalkulator z GitHuba", o którym wiesz. Sam wrapper jest AGPL niezależnie od licencji silnika. |
|
||||
| **swisseph‑wasm** (`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 (research‑grade), 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 1550–2650; DE441: −13200 do +17191. |
|
||||
| **Moshier ephemeris** (Steve Moshier, `moshier.net`) | Semi‑analityczna 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) | **BSD‑3** | 🟢 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 | **AGPL‑3.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 | **GPL‑3.0+** | 🔴 GPL (sieciowo łagodniejsze niż AGPL, ale wciąż copyleft); ciągnie AGPL‑owy 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 | **AGPL‑3.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 / front‑end (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 | open‑source (zweryfikować plik LICENSE) | 🟡 Brak obliczeń = brak ryzyka efemeryd. Sprawdzić dokładnie licencję przed użyciem. |
|
||||
| **swisseph‑wasm** | (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 (timezone‑boundary‑builder, ODbL) — przy dystrybucji bazy wymagana atrybucja i klauzula share‑alike **dla samej bazy** (nie dla Twojego kodu). |
|
||||
| **GeoNames** (baza miejscowości) | Miejscowości + współrzędne + strefy (do dropdowna lokalizacji) | **CC‑BY 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`) | **GPL‑2.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** (open‑source) | 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ń (**Q‑01**: produkt zamknięty/hostowany czy nie) i **Q‑07** (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 long‑term)**
|
||||
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ąć AGPL‑owy 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ąć AGPL‑owych 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/) · [Moshier‑Ephemeris‑JS](https://github.com/0xStarcat/Moshier-Ephemeris-JS)
|
||||
- [CircularNatalHoroscopeJS](https://github.com/0xStarcat/CircularNatalHoroscopeJS) · [AstroChart (`Kibo/AstroChart`)](https://github.com/Kibo/AstroChart) · [swisseph‑wasm](https://github.com/prolaxu/swisseph-wasm)
|
||||
- [timezonefinder (PyPI, MIT)](https://pypi.org/project/timezonefinder/) · [Astrolog (Wikipedia)](https://en.wikipedia.org/wiki/Astrolog)
|
||||
@@ -0,0 +1,174 @@
|
||||
# Warstwa logiczna — analiza, rozwinięcie wymagań i przypisanie rozwiązań
|
||||
|
||||
Dokument roboczy. Rozwinięcie 24 wymagań warstwy logicznej (LOG‑01…LOG‑24 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: **~70–80% 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 |
|
||||
|----|-----------|-------------------------------|-------------------|----------|
|
||||
| LOG‑01 | Pozycje obiektów | **Skyfield** (komplet) | Niska | walidacja pozycji |
|
||||
| LOG‑02 | Taksonomia obiektów | Skyfield (planety, Księżyc), Hipparcos (gwiazdy) | **Wysoka** (true Node/Lilith, gwiazdy) | walidacja węzłów/Lilith |
|
||||
| LOG‑03 | Kierunek i stacje | Skyfield (find_discrete) | Średnia | walidacja dat stacji |
|
||||
| LOG‑04 | Systemy zodiaku / ayanamsy | Skyfield (tropikalny, RA), pyerfa (precesja) | Średnia | walidacja ayanams |
|
||||
| LOG‑05 | Systemy domów (wiele) | pyerfa (LST, obliquity); reszta własna | **Wysoka** | walidacja cusps (kluczowa) |
|
||||
| LOG‑06 | Aspekty + orby | własne (czysta matematyka) | Niska | walidacja list aspektów |
|
||||
| LOG‑07 | Aspekty pozazodiakalne | Skyfield (deklinacja) | Niska | walidacja |
|
||||
| LOG‑08 | Lots | własne (arytmetyka + DSL formuł) | Niska–Średnia | porównanie wartości |
|
||||
| LOG‑09 | **Primary Directions** | brak permisywnego | **Bardzo wysoka (XL)** | **krytyczna** (Morinus) |
|
||||
| LOG‑10 | Profekcje | własne (arytmetyka + tabele władców) | Niska–Średnia | tabela kontrolna |
|
||||
| LOG‑11 | Zod. Releasing / Firdaria / Decennials | brak permisywnego | Średnia (table‑driven) | walidacja sekwencji |
|
||||
| LOG‑12 | Returns (Solar/Lunar) | Skyfield (root‑finding) | Niska–Średnia | walidacja czasu powrotu |
|
||||
| LOG‑13 | Ascensional Times | pyerfa/własne (oblique ascension) | Średnia | tabela kontrolna |
|
||||
| LOG‑14 | Zbiorcza tabela dat | własne (orkiestracja) | Średnia | — |
|
||||
| LOG‑15 | **Parser sygnifikatorów** | Lark/pyparsing (infrastruktura) | **Wysoka** (unikalne IP) | brak referencji |
|
||||
| LOG‑16 | Precompute „atomów" | własne | Średnia | — |
|
||||
| LOG‑17 | Interpretacja „asp"/„asp±" | własne (config + LOG‑06) | Niska–Średnia | — |
|
||||
| LOG‑18 | Warstwy 1A/1B | własne (orkiestracja ↔ warstwa danych) | Średnia | — |
|
||||
| LOG‑19 | Wypis interpretacji 2B | własne (↔ DataProvider) | Średnia | — |
|
||||
| LOG‑20 | Kolumny sygnif. + „no of hits" | własne | Niska–Średnia | — |
|
||||
| LOG‑21 | Scoring siły efektu | własne (rule/data‑driven) | Średnia | — |
|
||||
| LOG‑22 | Konwersja tekst ↔ symbol | własne (tablice z notes3) | Niska | — |
|
||||
| LOG‑23 | Pozostałe wyliczenia | Skyfield (zdarzenia) | Niska | walidacja |
|
||||
| LOG‑24 | Abstrakcja silnika efemeryd | architektura | Niska | — |
|
||||
|
||||
**Tylko 3 pozycje są naprawdę trudne:** LOG‑09 (primary directions, XL), LOG‑05 (pełny zestaw domów, Wysoka), LOG‑15 (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 (LOG‑01, 02, 03, 24)
|
||||
|
||||
**LOG‑01 · Pozycje obiektów.**
|
||||
*Rozwinięcie:* długość i szerokość ekliptyczna (of‑date), prędkość (różniczkowanie pozycji), kierunek; formaty: DMS w znaku (Tau 28°12'57''), absolutne 0–360°, 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″.
|
||||
|
||||
**LOG‑02 · 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; **Średnia–Wysoka**).
|
||||
- **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 LOG‑05.
|
||||
*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!).
|
||||
|
||||
**LOG‑03 · 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.
|
||||
|
||||
**LOG‑24 · Abstrakcja silnika.**
|
||||
*Rozwinięcie:* interfejs `EphemerisEngine` z implementacjami `SkyfieldEngine` (główna, MIT+JPL), `MoshierEngine` (fallback w pełni public‑domain, 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 (LOG‑04, 05)
|
||||
|
||||
**LOG‑04 · Systemy zodiaku / ayanamsy.**
|
||||
*Rozwinięcie:* tropikalny (domyślny, 🟢), syderyczny = tropikalny − ayanamsa(t) (Lahiri, Fagan‑Bradley… ~40 w SE; my zaczynamy od kilku + tabela rozszerzalna), draconic = tropikalny − długość NN (🟢 po LOG‑02), 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.
|
||||
|
||||
**LOG‑05 · 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 (LOG‑06, 07)
|
||||
|
||||
**LOG‑06 · 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, ±%, per‑planeta/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.
|
||||
|
||||
**LOG‑07 · Aspekty pozazodiakalne.**
|
||||
*Rozwinięcie:* parallel/contraparallel (po deklinacji — Skyfield 🟢), antiscia/contra‑antiscia (lustro względem osi przesileń — arytmetyka). *Złożoność: Niska.*
|
||||
|
||||
### Grupa D — Lots (LOG‑08)
|
||||
|
||||
**LOG‑08 · 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 Q‑03). *B/C:* porównanie wartości stopni.
|
||||
|
||||
### Grupa E — Techniki predykcyjne (LOG‑09…14)
|
||||
|
||||
**LOG‑09 · Primary Directions.** ⚠️ **najtrudniejsze (XL).**
|
||||
*Rozwinięcie:* łuk dyrekcji (semi‑arc/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 5–8 (Q‑02), 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).
|
||||
|
||||
**LOG‑10 · 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).
|
||||
|
||||
**LOG‑11 · 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.
|
||||
|
||||
**LOG‑12 · Returns (Solar/Lunar).**
|
||||
*Rozwinięcie:* root‑finding 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 LOG‑01…06. *Złożoność: Niska–Średnia.* *B/C:* czas powrotu z astro.com.
|
||||
|
||||
**LOG‑13 · 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 data‑driven (tabela DAN‑07). *Ścieżka A:* wzory powiązane z LOG‑05 (trygonometria sferyczna) + pyerfa. *Złożoność: Średnia.*
|
||||
|
||||
**LOG‑14 · 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 root‑finding z LOG‑12). *Ścieżka A:* orkiestracja/glue. *Złożoność: Średnia.*
|
||||
|
||||
### Grupa F — Parser sygnifikatorów i dopasowanie (LOG‑15…20) — **rdzeń unikalnego IP**
|
||||
|
||||
**LOG‑15 · 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, „r‑Asc", whole sign+orb); 7 poziomów złożoności — od poziomu „obiekt" po sygnifikatory opisowe/niejasne (poziomy 5–7 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.
|
||||
|
||||
**LOG‑16 · 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, r‑Asc Ari…); dopasowanie rekordu bazy = przynależność do zbioru. Zależy od LOG‑01…08 (policzony horoskop) + LOG‑15 (słownik). *Złożoność: Średnia* (generacja kombinatoryczna + indeks; wrażliwe wydajnościowo — łączy się z DAN‑22).
|
||||
|
||||
**LOG‑17 · Interpretacja „asp"/„asp+"/„asp‑"/„asp malefic".**
|
||||
*Rozwinięcie:* rozwinięcie wg konfigurowalnej tabeli pomocniczej (jakie aspekty, orby, active) + LOG‑06. *Złożoność: Niska–Średnia.*
|
||||
|
||||
**LOG‑18/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 / as‑is‑in‑chart), „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 (LOG‑21, 22, 23)
|
||||
|
||||
**LOG‑21 · 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).
|
||||
|
||||
**LOG‑22 · 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 Q‑05 (prezentacja).
|
||||
|
||||
**LOG‑23 · 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 Su‑Mo), syzygia prenatalna (root‑find 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):** LOG‑24 (interfejs) → LOG‑01 → LOG‑02 (bez gwiazd/true‑Lilith) → LOG‑04 (tropikalny+RA) → LOG‑06/07 → LOG‑05 (Whole/Equal/Porphyry+Placidus) → LOG‑23 (podstawy). *Tu Skyfield daje ogromny skok.*
|
||||
2. **Astrologia rdzenia:** LOG‑08 (Lots) → LOG‑15 (parser) → LOG‑16 → LOG‑18/19/20 (dopasowanie ↔ warstwa danych). *Tu powstaje unikalna wartość i pełny przepływ „horoskop → interpretacje".*
|
||||
3. **Predykcja prosta:** LOG‑10 (profekcje) → LOG‑12 (returns) → LOG‑14 (agregacja) → LOG‑11 (time‑lordy) → LOG‑13.
|
||||
4. **Predykcja trudna i dopieszczenie:** LOG‑09 (primary directions, XL) → LOG‑02 dokończenie (gwiazdy, true Node/Lilith) → LOG‑04 (ayanamsy) → LOG‑21 (scoring) → LOG‑05 (pełny zestaw domów).
|
||||
|
||||
Logika: najpierw to, co Skyfield daje tanio i co odblokowuje przepływ end‑to‑end; 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):** **LOG‑25 — 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.
|
||||
@@ -5,12 +5,21 @@ przekazuje dane z formularza do warstwy logicznej i renderuje opracowane wyniki.
|
||||
Brak logiki biznesowej i dostępu do danych.
|
||||
|
||||
## Trasy
|
||||
- `GET /` — strona z formularzem
|
||||
- `POST /` — wysłanie formularza → warstwa logiczna → render wyników
|
||||
- `GET /` — **Horoskop**: formularz danych momentu (data, godzina, strefa, opcjonalnie lokalizacja)
|
||||
- `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`
|
||||
|
||||
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ół
|
||||
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
|
||||
```bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Klient HTTP do warstwy logicznej.
|
||||
|
||||
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
|
||||
|
||||
@@ -22,3 +22,13 @@ class LogicClient:
|
||||
r = client.post(f"{self.base_url}/api/query", json=payload)
|
||||
r.raise_for_status()
|
||||
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()
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
|
||||
W dół: przekazuje dane z formularza do warstwy logicznej i odbiera opracowane
|
||||
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 datetime import datetime, timedelta, timezone
|
||||
|
||||
import httpx
|
||||
from fastapi import FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
@@ -19,13 +25,62 @@ templates = Jinja2Templates(directory="app/templates")
|
||||
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)
|
||||
def index(request: Request):
|
||||
return templates.TemplateResponse(request, "index.html", {"result": None, "form": {}})
|
||||
def chart_form(request: Request):
|
||||
return templates.TemplateResponse(request, "chart.html", {"result": None, "form": {}})
|
||||
|
||||
|
||||
@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,
|
||||
query: str = Form(...),
|
||||
field: str = Form("name"),
|
||||
@@ -37,8 +92,8 @@ def search(
|
||||
try:
|
||||
ctx["result"] = logic.query(query=query, field=field, exact=exact, limit=limit)
|
||||
except httpx.HTTPError as e:
|
||||
ctx["error"] = f"Warstwa logiczna niedostępna: {e}"
|
||||
return templates.TemplateResponse(request, "index.html", ctx)
|
||||
ctx["error"] = _logic_error(e)
|
||||
return templates.TemplateResponse(request, "significators.html", ctx)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
|
||||
@@ -15,6 +15,23 @@ body {
|
||||
main { width: 100%; max-width: 880px; }
|
||||
h1 { margin: 0; font-size: 2rem; letter-spacing: .5px; }
|
||||
.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; }
|
||||
.row { display: flex; gap: .5rem; }
|
||||
.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 %}
|
||||
Reference in New Issue
Block a user