f5dec15e4d
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m13s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m49s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 29s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 20s
build-render / build (push) Successful in 44s
build / build (push) Successful in 54s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m19s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m59s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 34s
Testy / Kontrola składni wszystkich warstw (push) Successful in 25s
Zasada: co składamy, dodajemy i wyświetlamy, MA trafiać do finalnego raportu
(«Skompiluj») i do PDF-a — nie tylko na stronę /chart. Do tej pory do raportu i
druku szło samo koło; aspektarian (etap 5) i deklinacja/antyscja (etap 6) były
tylko w podglądzie horoskopu.
RAPORT «Skompiluj» (podgląd): handler /compile liczy teraz wszystkie cztery
rysunki, a szablon je pokazuje (koło, aspektarian, deklinacja, antyscja).
PDF (usługa render) — uogólnienie z jednego rysunku na LISTĘ:
- Kontrakt raportu: `figures = [{svg, caption}]` (uporządkowana). `wheel_svg`
zostaje dla zgodności wstecznej jako pojedynczy rysunek.
- `compile.py`: każdy SVG osobno przez rsvg-convert → PDF (fig0…figN); braki/
błędy pomijane (rysunek nie może wywalić raportu, tekst ważniejszy).
- `latex.py build()`: rysunki w sekcji 3 (po danych, przed natalną) w PODANEJ
kolejności, każdy z podpisem. Jedna reguła składania: keepaspectratio z limitem
szerokości i wysokości — kwadratowe (koło, aspektarian) ogranicza wysokość,
szerokie (deklinacja, antyscja) szerokość, bez zniekształceń.
- `compile_pdf` (prezentacja): renderuje komplet w motywie DRUKU i wysyła jako
`figures`.
Testy: render +5 (kolejność, podpisy, zgodność wsteczna, pomijanie pustych),
prezentacja +3 (raport pokazuje komplet, PDF składa komplet). Render 32,
prezentacja 173.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
"""Warstwa RENDER — usługa składająca raport PDF (PRE-24).
|
|
|
|
OSOBNA usługa, nie część prezentacji, z tego samego powodu co silnik swisseph
|
|
(LOG-27): TeX Live waży setki megabajtów. Trzymanie go w obrazie produktu
|
|
spowalniałoby każdy build i deploy, a tak aktualizuje się niezależnie i awaria
|
|
renderu nie kładzie aplikacji.
|
|
|
|
Rozmawia TYLKO z prezentacją, po szyfrowanym łączu (własny, trzeci klucz —
|
|
dostaje cały raport: dane urodzeniowe i opisy z baz). W klastrze wystawiona jako
|
|
ClusterIP, bez wejścia z zewnątrz.
|
|
|
|
Świadomie nie zapisuje niczego na dysk poza katalogiem tymczasowym, który sprząta
|
|
po sobie — raport nie może zostawiać śladu w kontenerze.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.responses import Response
|
|
|
|
from app import link_crypto, security
|
|
from app.compile import RenderError, to_pdf, toolchain_ready
|
|
|
|
log = logging.getLogger("astrololo.render")
|
|
|
|
app = FastAPI(title="astrololo · usługa render (PDF)")
|
|
security.install(app, "render") # token międzywarstwowy (LOG-32)
|
|
# Szyfrowanie łącza od prezentacji. PO security.install, żeby także odmowa
|
|
# tokenowa wracała zaszyfrowana — inaczej klient nie umiałby jej odczytać.
|
|
link_crypto.install(app, link_crypto.ENV_PRESENTATION_RENDER, "render")
|
|
|
|
|
|
@app.post("/pdf")
|
|
def build_pdf(report: dict) -> Response:
|
|
"""Raport (JSON) → PDF. Wejście: person, data, figures[{svg,caption}] (albo
|
|
starsze wheel_svg — pojedynczy rysunek), natal, predictions.
|
|
|
|
Zwracamy surowe bajty PDF-a, nie base64 — łącze i tak jest szyfrowane, więc
|
|
kodowanie tylko rozdmuchałoby odpowiedź o trzecią część.
|
|
"""
|
|
if not isinstance(report, dict):
|
|
raise HTTPException(422, "Oczekiwano obiektu JSON z raportem.")
|
|
try:
|
|
pdf = to_pdf(report)
|
|
except RenderError as e:
|
|
raise HTTPException(500, str(e))
|
|
except FileNotFoundError as e: # brak xelatex/rsvg w obrazie
|
|
raise HTTPException(503, f"Brak narzędzia w obrazie usługi render: {e}")
|
|
return Response(
|
|
content=pdf,
|
|
media_type="application/pdf",
|
|
headers={"Content-Disposition": 'attachment; filename="raport.pdf"'},
|
|
)
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> dict:
|
|
"""Raportuje też obecność narzędzi — dzięki temu zepsuty obraz widać od razu,
|
|
a nie dopiero przy pierwszym raporcie."""
|
|
tools = toolchain_ready()
|
|
return {"status": "ok" if all(tools.values()) else "degraded",
|
|
"layer": "render", "tools": tools}
|