# Fundus Interner Wissens-Sammler: zieht Dokumente aus Watchfoldern und Datenbank-Views, anonymisiert sie fail-closed, und macht sie ueber eine OpenAI-kompatible API sowie MCP durchsuchbar — als lokales, on-prem Wissens-Backend fuer KI-Assistenten wie Claude Code oder Claudi. Mit eurem Wissen chatten, ohne dass Klartext-Patientendaten oder andere PII je die Maschine verlassen, auf der Fundus laeuft. ## Architektur ``` ┌───────────────┐ ┌──────────────────┐ Watchfolder ──▶│ │ │ │ (Dateien) │ Ingestion │──────▶ │ Anonymisierung │ │ (alle 5 Min)│ │ (fail-closed) │ DB-View ──▶│ │ │ │ (read-only) └───────────────┘ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Chunking + │ │ Embedding │ │ (nomic v1.5) │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ Postgres + │ │ pgvector + FTS │ └────────┬─────────┘ │ ┌────────────────────────────┼────────────────────────────┐ ▼ ▼ ▼ POST /v1/search POST /v1/chat/completions MCP-Server (stdio/SSE) (Hybrid-Suche + Zitate) (OpenAI-kompatibel + Zitate) fundus_search / fundus_get_document ``` Vier Stufen: **Sammeln** (Watchfolder, DB-Views) → **Aufbereiten** (Docling-Konvertierung, struktur-erhaltendes Chunking) → **Anonymisieren** (Regex-/Presidio-Detektor, Policy-Engine, Pseudonym-Vault, fail-closed → Quarantaene) → **Bereitstellen** (Hybrid-Suche FTS+pgvector, OpenAI-kompatible API, MCP). ## Quick Start ```bash git clone https://github.com/astockma2/fundus.git && cd fundus cp .env.example .env # Secrets ausfuellen (siehe unten) docker compose -f docker-compose.prod.yml up -d --build ``` Admin-Oberflaeche danach unter `http://127.0.0.1:8310` (Login `admin` / `FUNDUS_ADMIN_PASSWORD`). ## API-Beispiele ### Suche (`/v1/search`) ```bash curl -s https://fundus.c3po42.de/v1/search \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"query": "Wie wird ein Projekt auf den VPS deployt?", "top_k": 5}' | jq ``` Antwort enthaelt pro Treffer `content`, `origin`, `heading_path` und `provenance_sha256` (Zitat-Nachweis, welches Ursprungsdokument mit welchem Hash den Chunk erzeugt hat). ### Chat (`/v1/chat/completions`, OpenAI-kompatibel) ```bash curl -s https://fundus.c3po42.de/v1/chat/completions \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "fundus-context", "messages": [{"role": "user", "content": "Wie wird ein Projekt auf den VPS deployt?"}] }' | jq ``` `model: "fundus-context"` liefert reinen Kontext mit Zitierpflicht (kein Upstream-LLM noetig). Ist `FUNDUS_UPSTREAM_LLM` gesetzt (z.B. auf das KI-Gateway oder einen lokalen Ollama), reicht jedes andere `model` den Kontext als System-Prompt an das Upstream-LLM weiter. Nur Requests mit `Authorization: Bearer ` bekommen reversible Pseudonyme in der Antwort wieder aufgeloest; `X-Api-Key` liefert immer die anonymisierte Fassung. ### Authentifizierung - **Menschen:** `POST /api/login` (Body `{"username": "admin", "password": "..."}`) → JWT, 480 Min gueltig, `deanonymize: true`. - **Maschinen/Andere Dienste:** `X-Api-Key: ` — darf NIE de-anonymisieren. ### MCP-Einbindung in Claude Code ```bash claude mcp add fundus -- python -m backend.app.api.mcp_server ``` Stellt drei Tools bereit: `fundus_search(query, top_k)`, `fundus_get_document(document_id)`, `fundus_stats()`. Fuer Remote-Betrieb (SSE hinter Nginx) siehe Kopfkommentar in `backend/app/api/mcp_server.py`. ## Pflicht-Warnung: DB-View-Quellen brauchen einen Read-only-DB-User Der DB-View-Konnektor (`backend/app/ingest/dbviews.py`) fuehrt die in der Quellen-Konfiguration hinterlegte SQL-Query **exakt so aus, wie sie konfiguriert ist** — es gibt kein eigenes Rechte-Modell und kein Schema-Crawling innerhalb von Fundus. Der DSN, den ihr fuer eine `dbview`-Quelle hinterlegt, MUSS auf einen Datenbank-User mit **ausschliesslich Lesezugriff** zeigen. Ein Schreibzugriff-DSN in einer Quellen-Konfiguration ist ein Fehlkonfigurations-Risiko, kein Feature — Fundus selbst schreibt nie in die Quell-DB, aber ein falsch konfigurierter User kann das theoretisch tun, wenn eine Query mal fehlerhaft ist oder erweitert wird. ## Sicherheitsmodell (Kurzfassung) - Anonymisierung ist **fail-closed**: schlaegt sie fehl, landet das Dokument in Quarantaene statt unanonymisiert in der DB. - Persistiert wird ausschliesslich der anonymisierte Markdown-Text — Rohtext existiert nur kurzlebig waehrend der Ingestion. - Irreversible Entitaeten (KVNR, IBAN, Geburtsdatum, Diagnose/ICD-10) werden nie im Klartext gespeichert, auch nicht verschluesselt. - Reversible Entitaeten (Person, Telefon, E-Mail, Adresse) sind nur mit JWT-Claim `deanonymize=true` aufloesbar — nie ueber API-Key. - Audit-Log ist hash-verkettet (`prev_hash`/`entry_hash`), Manipulation ist erkennbar (`audit.verify_chain()`). Details zu Architektur und Konventionen: `CLAUDE.md`. Bedienungsanleitung fuer Quellen, Policy, Golden Questions: `BENUTZERHANDBUCH.md`.