# 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.