"""Warstwa BAZODANOWA — usługa HTTP. Jedyne zadanie: przyjąć znormalizowane zapytanie z warstwy logicznej, wyszukać dane (w Excelu z cache lub w SQL) i zwrócić je w górę. Nie zna warstwy logicznej ani prezentacji. """ # build-marker: 2026-07-25 wymuszenie nowego obrazu po incydencie z tagiem :latest from __future__ import annotations from contextlib import asynccontextmanager from fastapi import FastAPI from app import canary, link_crypto, security from app.config import settings from app.models import HealthInfo, SearchQuery, SearchResult from app.providers.factory import build_provider provider = build_provider(settings) @asynccontextmanager async def lifespan(app: FastAPI): provider.warmup() # zbuduj/odśwież indeks i cache przy starcie yield app = FastAPI(title="astrololo · warstwa bazodanowa", lifespan=lifespan) security.install(app, "danych") # token międzywarstwowy (LOG-32) # Szyfrowanie łącza od logiki. 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_LOGIC_DATA, "danych") @app.post("/search", response_model=SearchResult) def search(query: SearchQuery) -> SearchResult: result = provider.search(query) # Rekordy-pułapki (DAN-26) odsiewamy TU, na wyjściu z warstwy danych — dzięki # temu nie dotrą ani wyżej, ani do promptu LLM (LOG-30), niezależnie od dostawcy. visible, report = canary.screen(result.rows, query.value) if report["active"]: result.rows = visible result.total = len(visible) return result @app.get("/bases") def bases() -> dict: """Bazy dostępne na udziale + metaopis i stan włączenia (DAN-15/PRE-09). Same METADANE — nazwy plików, rozmiar, data. Żadnej treści baz, więc podgląd listy nie jest kolejną drogą do ich wyniesienia.""" items = provider.list_bases() return {"bases": items, "enabled": sum(1 for b in items if b["enabled"]), "total": len(items)} @app.get("/health", response_model=HealthInfo) def health() -> HealthInfo: return provider.health()