"""Warstwa PREZENTACJI — usługa HTTP serwująca stronę WWW. 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 from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from app.clients.logic_client import LogicClient app = FastAPI(title="astrololo · warstwa prezentacji") app.mount("/static", StaticFiles(directory="app/static"), name="static") 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 chart_form(request: Request): return templates.TemplateResponse(request, "chart.html", {"result": None, "form": {}}) @app.post("/", response_class=HTMLResponse) 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"), exact: bool = Form(False), limit: int = Form(25), ): form = {"query": query, "field": field, "exact": exact, "limit": limit} ctx: dict = {"form": form, "result": None, "error": None} try: ctx["result"] = logic.query(query=query, field=field, exact=exact, limit=limit) except httpx.HTTPError as e: ctx["error"] = _logic_error(e) return templates.TemplateResponse(request, "significators.html", ctx) # ---------------- Interpretacje (wynik obliczeń szukany w bazie) ---------------- @app.get("/interpret", response_class=HTMLResponse) def interpret_form(request: Request): return templates.TemplateResponse(request, "interpret.html", {"result": None, "form": {}}) @app.post("/interpret", response_class=HTMLResponse) def interpret_run( 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.report(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, "interpret.html", ctx) @app.get("/health") def health() -> dict: return {"status": "ok", "layer": "presentation"}