c1f5bea9f7
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m46s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Trzeci z pięciu kroków. Zmieniłem jego zakres wobec planu i warto wiedzieć dlaczego: pierwotnie miała to być deduplikacja pięciu kopii link_crypto.py, ale po zrobieniu PR 2 widać, że astroklienta blokuje co innego — ekrany, których nie ma mieć, siedzą wewnątrz jednego main.py. Deduplikacja kryptografii jest realnym długiem, ale niczego nie blokuje. PODZIAŁ. main.py (1023 linie) rozpadł się na podstawa.py (wspólne obiekty i pomocnicy), jedenaście modułów w app/ekrany/ i main.py, który jest już samym ZŁOŻENIEM: lista importów JEST definicją produktu. Podział zrobiony mechanicznie, z osobnym sprawdzeniem, że żadna sekcja nie wołała pomocnika z innej (nie wołała). KATALOG Z REJESTRACJI. Dotąd wszystkie funkcje były wypisane w features.py, więc obraz produktu, który części z nich nie ma, i tak niósł ich nazwy — spis funkcji, których nie ma jak włączyć. Teraz ekran zgłasza siebie, swoje trasy i swoje zasoby przy imporcie własnego modułu, a features.py nie wymienia ani jednego ekranu. Kolejność w nawigacji jest jawna (`kolejnosc`), żeby nie rządziła nią kolejność importów. To samo dotyczy nawigacji administratora: odsyłacz do ekranu kont był wpisany na sztywno w base.html, więc w węższym produkcie zostawał martwy link i nazwa ekranu, którego nie ma. EKSPORT JAKO MODUŁ. Zgodnie z ustaleniem eksport jest funkcją administracyjną, więc musi dać się usunąć. app/moduly/eksport/ zabiera arkusz, trasę PDF-a i akcję formularza. „Można, ale nie temu kontu" i „nie ma takiej możliwości" to dwie różne gwarancje, a eksport wynosi najwięcej treści baz naraz. MOST ODKRYWA MODUŁY. Skoro modułów jest więcej niż jeden, most nie może ich znać z nazwy — nazwa nieobecnego modułu jechałaby do obrazu, w którym go nie ma. Przechodzi więc po podkatalogach app/moduly/ i pyta każdy, co wnosi. Katalog generowania przeniesiony z app/dodatki na app/moduly/dodatki. ZNALEZIONE PRZY OKAZJI. Po wydzieleniu eksportu okazało się, że jego ścieżki SZCZĘŚLIWEJ nie sprawdzał żaden test — badano wyłącznie odmowę dla konta bez uprawnienia. Moduł dostaje zależności z wywołania montującego, więc brak jednej z nich wyszedłby dopiero przy pierwszym kliknięciu. Dopisany test funkcjonalny (realny arkusz, sprawdzany aż do nagłówka ZIP-a) i brakujące zależności. Cztery komentarze w plikach współdzielonych wymieniały zakładkę „Skompiluj", w tym wheelzoom.js ze wzmianką o „przyszłej zakładce" — to samo zgłoszenie, które audyt podnosił wcześniej. TEST ZŁOŻENIA. Buduje węższy produkt NAPRAWDĘ: kopiuje drzewo, usuwa cztery ekrany i oba moduły, uruchamia aplikację w OSOBNYM PROCESIE (importy są zapamiętywane, więc sprawdzanie tego w procesie, który moduł już zaimportował, dawałoby wynik fałszywie pozytywny) i sprawdza, że wstaje, że zachowane ekrany oddają 200, że usunięte oddają 404 (nie 403 i nie 500), że katalog opisuje ten obraz, i że w nawigacji nie ma martwych odsyłaczy. Napisałem najpierw ostrzejszy test — „nazwa ekranu nie pada poza jego modułem" — i go wyrzuciłem: zgłaszał wzmianki o Horoskopie w plikach współdzielonych, choć astroklient Horoskop MA. Ślad ma znaczenie wyłącznie wobec konkretnego złożenia, więc sprawdzenie należy do produktu, nie do mechanizmu. Testy: presentation 368, logic 342, data 37, render 41. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
115 lines
5.1 KiB
Python
115 lines
5.1 KiB
Python
"""Trasy wnoszone przez moduł. Bez tego katalogu w obrazie nie istnieją."""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import httpx
|
|
from fastapi.responses import JSONResponse, Response
|
|
|
|
from app.moduly.eksport import katalog
|
|
|
|
_logic: Any = None
|
|
_szablony: Any = None
|
|
_buduj_utc: Any = None
|
|
_blad: Any = None
|
|
_kolo: Any = None
|
|
|
|
|
|
def zamontuj(app, *, logic: Any, templates: Any, buduj_utc: Any, blad: Any,
|
|
security: Any = None, licz_rekordy: Any = None, kolo: Any = None,
|
|
**_reszta) -> None:
|
|
"""Podpięcie trasy i przekazanie zależności. Idą z zewnątrz, bo zależność ma
|
|
biec w JEDNĄ stronę: aplikacja wie o modułach opcjonalnych (przez neutralny
|
|
most), moduł nie wie nic o aplikacji."""
|
|
global _logic, _szablony, _buduj_utc, _blad, _kolo
|
|
_logic, _szablony, _buduj_utc, _blad = logic, templates, buduj_utc, blad
|
|
_kolo = kolo
|
|
katalog.ustaw_zaleznosci(logic=logic, security=security, licz_rekordy=licz_rekordy)
|
|
|
|
# Nazwy, których używa przeniesiona trasa. `chartwheel` to czysty rysownik
|
|
# bez stanu, więc importujemy go wprost — wstrzykiwanie ma sens dla rzeczy
|
|
# związanych z aplikacją (klient, szablony), nie dla funkcji rysujących.
|
|
from app import chartwheel as chartwheel_mod
|
|
|
|
_build_utc = buduj_utc
|
|
_logic_error = blad
|
|
|
|
@app.post("/compile/pdf")
|
|
def compile_pdf(payload: dict):
|
|
"""Składa raport PDF (PRE-24) — woła usługę render po szyfrowanym łączu.
|
|
|
|
Wejście z przeglądarki, bo część kawałków (interpretacja natalna i predykcje)
|
|
mieszkają w magazynie lokalnym. Kosmogram i tabele liczymy TU, żeby PDF
|
|
zawierał dokładnie to, co widać na stronie.
|
|
|
|
Kosmogram idzie w motywie DRUKU: samodzielny konwerter SVG→PDF nie zna
|
|
naszego arkusza, więc zmienne CSS i font glifów muszą być w samym rysunku.
|
|
"""
|
|
from fastapi.responses import Response
|
|
|
|
from app.clients.render_client import RenderClient
|
|
|
|
data = payload.get("data") or {}
|
|
try:
|
|
iso_utc, label = _build_utc(
|
|
str(data.get("date") or ""), str(data.get("time") or ""),
|
|
float(data.get("tz_offset") or 0.0),
|
|
)
|
|
except (ValueError, TypeError) as e:
|
|
return JSONResponse({"detail": f"Niepoprawne dane wejściowe: {e}"}, status_code=422)
|
|
|
|
figures: list[dict] = []
|
|
warnings: list[str] = []
|
|
try:
|
|
chart = logic.positions(
|
|
when_utc_iso=iso_utc,
|
|
lat=float(data.get("lat") or 0.0), lon=float(data.get("lon") or 0.0),
|
|
house_system=str(data.get("house_system") or "whole_sign"),
|
|
house_systems=[s for s in (data.get("house_systems") or []) if s],
|
|
aspect_orb=float(data.get("aspect_orb") or 8.0),
|
|
aspect_luminary_bonus=float(data.get("aspect_luminary_bonus") or 2.0),
|
|
aspect_minor=bool(data.get("aspect_minor")),
|
|
stations=bool(data.get("stations")), tables=bool(data.get("tables")),
|
|
zodiac=str(data.get("zodiac") or "tropical"),
|
|
)
|
|
# Fallback systemu domów musi dojechać do PDF-a — z samego kosmogramu
|
|
# nie da się poznać, że podział jest z innego systemu, niż zamówiono.
|
|
warnings = [w for w in (chart.get("house_warnings") or []) if w]
|
|
|
|
from app import chartwheel
|
|
|
|
# Zasada: co pokazujemy na stronie, ma trafić do PDF-a. Wszystkie rysunki
|
|
# w motywie DRUKU — samodzielny konwerter SVG→PDF nie zna arkusza, więc
|
|
# zmienne CSS i font glifów muszą być wprost w rysunku.
|
|
for svg, caption in (
|
|
(chartwheel.render(chart, theme="print",
|
|
orientation=str(data.get("wheel_orientation") or chartwheel_mod.ASC_LEFT)),
|
|
"Kosmogram"),
|
|
(chartwheel.render_aspectarian(chart, theme="print"), "Aspektarian — siatka aspektów"),
|
|
(chartwheel.render_declination(chart, theme="print"), "Wykres deklinacji"),
|
|
(chartwheel.render_antiscia(chart, theme="print"), "Oś antyscji"),
|
|
):
|
|
if svg:
|
|
figures.append({"svg": svg, "caption": caption})
|
|
except httpx.HTTPError as e:
|
|
# Brak rysunków nie może zablokować raportu — tekst jest ważniejszy.
|
|
data = {**data, "wheel_error": _logic_error(e)}
|
|
|
|
report = {
|
|
"person": payload.get("person") or "",
|
|
"data": {**data, "moment_utc": label},
|
|
"figures": figures,
|
|
"warnings": warnings,
|
|
"natal": payload.get("natal") or {},
|
|
"predictions": payload.get("predictions") or [],
|
|
}
|
|
try:
|
|
pdf = RenderClient().pdf(report)
|
|
except httpx.HTTPError as e:
|
|
return JSONResponse(
|
|
{"detail": f"Usługa render niedostępna albo nie złożyła PDF-a: {e}"},
|
|
status_code=502,
|
|
)
|
|
return Response(content=pdf, media_type="application/pdf",
|
|
headers={"Content-Disposition": 'attachment; filename="raport.pdf"'})
|