Přeskočit obsah

Architektura — Přehled

Tato kapitola popisuje co je za komponentu zodpovědné za co a jak spolu komunikují. Detail per komponenta je v Komponenty, data flow v Pipeline.

High-level diagram

graph TB
    User([Uživatel])
    NetteApp([Nette aplikace<br/>Phase E])

    subgraph CloudflareFE["☁️ Cloudflare Workers"]
        direction LR
        FE[Frontend<br/>Next.js 16 SSR]
    end

    subgraph DockerContainers["🐳 Docker kontejnery"]
        direction LR
        BE[Backend<br/>FastAPI]
        Redis[(Redis<br/>queue + rate limit)]
        Worker[Worker<br/>taskiq]
    end

    subgraph External["🤖 Externí AI služby"]
        direction LR
        Mistral[Mistral OCR]
        Gemini[Gemini 3 Flash<br/>+ Embedding 2]
        Cohere[Cohere Rerank 4.0]
    end

    subgraph Supabase["⚡ Supabase"]
        direction LR
        Auth[Auth<br/>magic link]
        DB[(Postgres + RLS<br/>nemoreport schema)]
        Storage[Storage<br/>4 buckety]
        Realtime[Realtime<br/>WebSocket]
    end

    %% INVISIBLE EDGES — force vertical stacking order
    CloudflareFE ~~~ DockerContainers
    DockerContainers ~~~ External
    External ~~~ Supabase

    %% User-facing flow
    User -->|HTTPS| FE
    NetteApp -.->|Phase E<br/>JWT bridge + HMAC| BE

    %% Frontend → Backend / Supabase
    FE -->|JWT auth REST| BE
    FE -->|signInWithOtp| Auth
    FE <-->|WebSocket| Realtime

    %% Backend internal
    BE -->|enqueue| Redis
    Worker -->|consume stream| Redis

    %% Docker → External AI (vertical down)
    BE -->|embed query| Gemini
    BE -->|rerank| Cohere
    Worker -->|OCR + annotations| Mistral
    Worker -->|figure annot.<br/>+ embed| Gemini

    %% Docker → Supabase (verify, DB, storage)
    BE -->|verify JWT| Auth
    BE -->|service_role + signed URLs| DB
    BE -->|signed URLs| Storage
    Worker -->|service_role| DB
    Worker -->|read/write files| Storage

    %% Realtime publish
    DB -->|publish UPDATE| Realtime

    %% Color coding
    classDef userClass fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#000
    classDef containerClass fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
    classDef aiClass fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px,color:#000
    classDef supabaseClass fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000
    class User,NetteApp userClass
    class BE,Worker,Redis,FE containerClass
    class Mistral,Gemini,Cohere aiClass
    class DB,Auth,Storage,Realtime supabaseClass

    %% Invisible link styling (poslední 3 linky = invisible edges)
    linkStyle 0 stroke-width:0px,fill:none
    linkStyle 1 stroke-width:0px,fill:none
    linkStyle 2 stroke-width:0px,fill:none

Princip designu

1. Multi-tenant first

Každý uživatel má personal_tenant vytvořený automaticky při registraci (přes handle_new_user trigger v auth.users). RLS policy na všech nemoreport.* tabulkách scope-uje data per tenant_id.

JWT custom claims (custom JWT hook v migraci 0007):

  • personal_tenant_id — vždy vlastní tenant
  • active_tenant_id — aktuální workspace (default = personal, později team workspaces)

Backend extrahuje tyto claims, předává active_tenant_id při insertech, RLS pak enforce-uje v DB.

2. Embed-first frontend

Frontend obsahuje dva typy stránek:

  • Parent UI (/, /reports, /upload, /admin, /migrate) — testing shell pro testery; v produkci ho nahradí Nette aplikace
  • Embed chat (/chat?id=...) — iframe child, komunikuje s parent přes postMessage protokol nemoreport-ai:*

Vše se vyvíjí jako embed — i když parent je teď náš testing UI, chat je vždy iframe. Phase E změní jen parent origin + JWT source.

3. Async ingestion s durable queue

Upload PDF nemá čekat na 5min OCR. Backend:

  1. Validuje + uloží do storage + DB row
  2. Enqueue worker job přes Redis Stream (taskiq + RedisStreamBroker)
  3. Vrátí 202 Accepted

Worker:

  1. Konzumuje stream s ack-based delivery
  2. Provádí 5-stage pipeline (scan → parse → annotate → embed → finalize)
  3. Updatuje reports.status po každém stage
  4. Realtime emit přes Postgres WAL → supabase_realtime publication

Frontend subscribe na Realtime channel → vidí progress live.

Retry strategy: každý stage je @broker.task(retry_on_error=True, max_retries=3). Idempotentní (DELETE existing data před re-run).

4. Defense-in-depth security

Vrstvy ochrany dat:

  • Network: HTTPS, internal Docker DNS pro privát komunikaci mezi kontejnery
  • Auth: Supabase JWT s RS256, JWKS rotation, custom claims
  • Authz: RLS policies per table, private.user_has_tenant_access(tenant_id) helper
  • Defense-in-depth grants: revoke insert/update/delete from authenticated na worker-managed tabulky (figures, parsed_*, chunks, retrieval_log) — i kdyby se omylem přidala permissive policy, table grant by ji blokoval
  • Sequence grants: explicitní USAGE grants per sequence (zachyceno v Phase A.1 hotfixu)
  • HMAC: Nette attachments verify HMAC nad canonical string <report_id>|<nette_id>|<attachment_type>|<sha256(file_bytes)>

Viz Bezpečnost.

5. Soft-fail graceful degradation

Phase C decision D7: pokud embed_target stage selže (např. Gemini API outage), report přejde do status='ready' přesto. parsed_metadata.embedding_status='failed' se zapíše. BM25 leg pořád funguje, vector leg ne. Phase D chat bude fallbackovat na full-report dump pro reporty bez chunks.

Stejný pattern u Cohere reranku v /retrieve — pokud API down/rate limit, fallback na hybrid RRF order.

Data flow zjednodušeně

1. UPLOAD     User → FE → BE → DB row + Storage upload + Redis enqueue → 202
2. WORKER     Redis stream → Worker → 5 stages → DB updates → Realtime
3. STATUS     Realtime → FE → progress bar
4. READY      User vidí "Hotovo" → /reports/{id} detail
5. RETRIEVE   User query → FE → BE → embed query → Postgres RPC (hybrid) → Cohere rerank → top-K chunks
6. CHAT       (Phase D) → BE volá retrieve interně → injektuje chunks do LLM prompt → SSE stream

Detail viz Pipeline (data flow).

Folder model

Klíčový koncept Phase B post-deploy iterace.

Report = container, ne single file. Reálný NemoReport obsahuje:

  • 1 main report (PDF / MHTML s 12-18 sekcemi)
  • 5-15 attachments (vyjádření CETIN, ČEZ, geometrický plán, scan razítka, foto pozemku...)
  • Cross-source figures (mapy povodní, územního plánu, technické výkresy)

Implementace v DB:

  • nemoreport.reports = parent container
  • nemoreport.attachments = file rows uvnitř reportu (FK report_id, source ∈ {nette, user_upload})
  • nemoreport.parsed_sectionsattachment_id (NULL = main report)
  • nemoreport.figures.report_id je VŽDY parent (i pro figures z attachmentu); attachment_id je optional FK
  • nemoreport.chunks.report_id = parent → folder retrieval jediným SELECTem

Worker _Target dataclass sjednocuje "report" a "attachment" cesty — všechny stages pracují generic.

Phase rozdělení

Phase A — Auth + multi-tenant (✅ hotovo)

Supabase Auth setup, JWT custom claims, RLS policies, 12 migrací (0001-0012), pgTAP testy, custom magic link template.

Phase A.1 — UI port (✅ hotovo)

v1 → v2 frontend port: /reports, /chat, /admin přepsány na v2 schema, Vitest scaffold, TS strict.

Phase B — Ingestion pipeline (✅ hotovo)

5-stage worker pipeline. PDF → Mistral OCR. MHTML → BS4 + trafilatura. DOCX → python-docx. Gemini fallback pro figures s thin/no annotation. Folder model post-deploy. Cost tracking per stage.

Phase C — Vector RAG (✅ hotovo)

pgvector 0.8 + halfvec(1536) + Gemini Embedding 2 multimodal. Hybrid retrieval (vector + BM25 + RRF) + Cohere Rerank 4.0. HyDE pro krátké queries. Per-source diversity. Retrieval observability.

Phase D — Chat s RAG (⏳ plánováno)

/chat endpoint přepsat na use /retrieve interně. Injektovat top-K chunks místo full textu. Streamovaný SSE response s citacemi.

Phase E — Nette integration (⏳ plánováno)

Embed chat do Nette aplikace (parent iframe). JWT bridge (Nette signed RS256 JWT → Supabase session). HMAC webhooks pro automatické attachment uploady.

Detail per fázi viz Vývojové fáze.