Private, source-based system with FastAPI, Next.js, Postgres schema, ethics constraints, RSS/API ingest into a review queue, and 24 seeded cases.
3.7 KiB
Quellenatlas — Architektur
Privates, selbst gehostetes Recherche-System für quellenbasierte Dokumentation politischer und gesellschaftlicher Auffälligkeiten. Kein öffentliches Portal, kein Meinungsprodukt.
Ziele
- Hierarchische Navigation über Jurisdiktionen, Entitäten, Kategorien und Einzelfälle
- Jede faktische Aussage ist einer Quelle zugeordnet
- Klare Status-Trennung: bestätigt / Vorwurf / widerlegt / laufend; zusätzlich
disputed - Erhöhte Sorgfalt für priorisierte Cluster: Ursula von der Leyen, Ukraine, EU-Spitzen
- Halbautomatische Aktualisierung nur über RSS/API in eine Review-Queue (kein Medien-HTML-Scraping)
- Export zitierfähiger Einträge (JSON, CSV, Markdown)
Nicht-Ziele (v1)
- Öffentlicher Zugriff ohne Login
- LLM-generierte Zusammenfassungen
- HTML-Crawler gegen Nachrichtenportale
- Neo4j (Graph ist eine SQL-abgeleitete JSON-Sicht)
- Multi-Tenancy, Kommentare, Social Sharing
Systemübersicht
[Browser / Next.js]
| HTTP + Cookie (JWT)
v
[FastAPI /api/v1]
| [APScheduler]
| |
+-- CRUD/Search/Export +-- IngestAdapter (RSS/API)
| |
v v
PostgreSQL 16 SourceEntry + ReviewItem
^
|
data/sources.yaml (Katalog, kanonisch)
data/seed/** (YAML-Fälle)
Drei Prozesse in Docker Compose: postgres, backend, frontend. Der Ingest-Scheduler läuft im Backend-Prozess (kein Redis in v1).
Schichten (Backend)
| Schicht | Pfad | Verantwortung |
|---|---|---|
| HTTP | backend/app/api/v1/ |
Auth, Serialisierung, Statuscodes |
| Services | backend/app/services/ |
Orchestrierung, Audit-Log |
| Ethik | backend/app/core/ethics.py |
Harte Regeln; von Services aufgerufen |
| ORM | backend/app/models/ |
Schema |
| Ingest | backend/app/ingest/ |
Adapter, Dedup, Keyword-Priority |
Admin-UI ist kein separates SQLAdmin. Alle Schreibzugriffe laufen durch dieselben Services, damit Ethik-Regeln nicht umgangen werden.
Auth
- Rollen:
admin,editor,viewer - JWT in httpOnly-Cookie (
quellenatlas, SameSite=Lax) - Bootstrap-User aus
ADMIN_USERNAME/ADMIN_PASSWORD - Alle API-Routen außer
POST /api/v1/auth/loginund/healtherfordern Session - Frontend-Middleware blockt unauthentifizierte Seiten außer
/login
Datenfluss: Fall anlegen
- Editor erstellt Case (Status Default
allegation) - Mindestens ein
SourceEntrymitaccessed_at - Claims mit je ≥ 1 Quelle
ethics.validate_*— sonst HTTP 422CaseRevision-Snapshot +UpdateLog
Datenfluss: Ingest
- Scheduler oder
POST /api/v1/ingest/run/{slug} - Adapter holt Feed/API, kanonisiert URL, hasht Inhalt
- Upsert
SourceEntry(nie ein Case) - Keyword-Match (VdL / Ukraine / Kategorien) →
ReviewItem, bei Trefferpriority=high - Mensch reviewed: accept / reject / merge in bestehenden Fall
Cluster-Navigation
Keine Sonder-Tabellen. Cluster sind Tags auf Entity/Case (vdl, ukraine, eu-top, dach) plus care_level=high. Endpoints /api/v1/clusters/vdl und /clusters/ukraine filtern darauf.
Graph (v1)
GET /api/v1/graph?entity=slug|case=slug baut Knoten und Kanten aus SQL (Case↔Entity, Case↔Category, Entity↔Jurisdiction, Claim↔Source). Kein Graph-Store.
Erweiterbarkeit
- Neue Quelle: Eintrag in
data/sources.yaml, bei neuem Protokoll ein Adapter iningest/adapters/ - Neue Kategorie/Jurisdiktion: Seed-YAML oder Admin-API
- Später Neo4j: denselben Graph-Endpoint als Quelle für ein Read-Model nutzen
Deployment
Siehe Root-README.md. Lokal: docker compose up --build. Persistenz nur in der Postgres-Volume. Backups = pg_dump.