Architektur-Überblick
Dieses Dokument beschreibt das System als Ganzes: Dienste, Datenhaltung, Funktionsbereiche und die wichtigsten Datenflüsse. Entscheidungen stehen in den ADRs, Details zu einzelnen Abläufen in den Steckbriefen. Den fachlichen Stand und die Grenzen des Prototyps beschreibt die Projektzusammenfassung.
Systembild
VergabeFit ist eine Webanwendung aus fünf Diensten und einer selbst betriebenen Supabase-Instanz. Der Browser spricht ausschließlich mit dem Frontend. Das Frontend leitet Chat-Anfragen über das AG-UI-Protokoll (Server-Sent Events) und alle Aufrufe unter /api/* an den Agent-Service weiter. Der Agent-Service nutzt Werkzeuge des MCP-Servers, ruft Sprachmodelle auf und liest und schreibt die Datenbank. Zwei Hintergrunddienste füllen den Bestand an Bekanntmachungen.
Browser
-> Frontend (Next.js, Port 3000)
-> Agent-Service (FastAPI, Port 8000) /agui, /api/*
-> MCP-Server (FastMCP, Port 8001) Suche, CPV, eForms, Webseiten
-> Sprachmodelle (Gemini, Claude, OpenAI)
-> Supabase (Postgres, pgvector, Auth, Storage)
-> Supabase Auth Anmeldung, Sitzung
Processor-Scraper und Processor-Worker -> Supabase (Bekanntmachungen, Vektoren)
Alle Dienste laufen als Docker-Container (docker-compose.yml). Für die Entwicklung lassen sie sich auch einzeln starten (siehe AGENTS.md).
Dienste
Frontend
Next.js 16 mit React 19, TypeScript, Tailwind CSS 4, Zustand und Radix UI unter services/frontend/. Die Anwendung liegt unter /app, die öffentliche Startseite unter /. Der Chat-Proxy steht in app/agui/route.ts, die Weiterleitung von /api/* in next.config.mjs. Fachlogik, die im Browser läuft (Fit-&-Gap-Regeln, Marktcheck, Partnersuche), liegt unter lib/vergabefit/ und lib/partners/.
Agent-Service
Python 3.12 mit FastAPI und Pydantic AI unter services/agent/. Er stellt den Chat-Endpunkt POST /agui (src/api/agui_routes.py) und die Fach-API unter /api/vergabefit/* bereit. Der Agent (src/agent/procur_mcp_agent.py) nutzt 15 Werkzeuge: 8 im Prozess, 7 über den MCP-Server. Die Kriterienextraktion liegt in src/criteria_extraction/, die Annahme von Dateien und ZIP-Archiven in src/document_intake/. Der Service prüft jede Anfrage gegen eine gültige Supabase-Sitzung.
MCP-Server
FastMCP unter services/mcp-server/. Er bietet dem Agenten sieben Werkzeuge: hybride Suche über Bekanntmachungen und eigene Dokumente (retrieve_context, fetch_related_chunks), CPV-Suche (search_cpv_codes, get_cpv_code_details), eForms-Wissen (retrieve_eforms_knowledge) sowie das Abrufen ausdrücklich genannter Webseiten (fetch_webpage, crawl_website). Eine allgemeine Websuche gibt es nicht. Für die Suche in eigenen Dokumenten reicht der Agent die Sitzung des Nutzers weiter, damit die Zugriffsregeln der Datenbank gelten.
Datenimport
services/processor-scraper/ liest die TED-Ablage der ProcurDat-Plattform ausschließlich lesend und trägt neue Bekanntmachungen in procur_notices.ted_notice_tracker ein. services/processor-worker/ verarbeitet sie: eForms-XML lesen, nach Losen aufteilen, Embeddings berechnen und in procur_notices.notice_vectorstore speichern. Gemeinsamer Code liegt in packages/processor-shared/. Dazu kommen Skripte für Einzelimporte: scripts/import_fit_data.py (CSV-Daten, siehe Importformate), scripts/load_add_info/ (Vergabeunterlagen zu TED-Beispielen) und scripts/import_notice_criteria/ (Kriterien aus diesen Unterlagen, ADR).
Datenbank
Selbst betriebenes Supabase mit Postgres, pgvector, Auth und Storage. Die Zeilensicherheit (Row-Level Security) der Datenbank ist die eigentliche Berechtigungsgrenze. Sieben Schemas trennen die Bereiche:
procur_core: Erweiterungen, gemeinsame Funktionen, Migrationsprotokoll.procur_auth: Nutzerprofile und Freigabestatus.procur_notices: Bekanntmachungen, Lose, Vektoren, Kriterienkatalog und Unterlagen.procur_docs: eigene Dokumente, private Vektoren, Chats und Ergebnisse der Kriterienextraktion.procur_guru: CPV-Codes und Fachwissen.procur_regx: Unternehmen, Mitgliedschaften, Ansprüche, Einladungen, Benachrichtigungen.procur_fit: Projekte, Projektdokumente, Anforderungslisten, Nachweisprofile, Auswertungsläufe.
Die Referenzdateien unter database/<bereich>/ beschreiben das Zielschema, neue Änderungen kommen als Migration mit Zeitstempel unter database/migrations/. Lange Aufgaben laufen über Job-Tabellen in Postgres mit Leases statt über zusätzliche Infrastruktur. Details stehen in database/README.md und database/SUPABASE_PLAN.md.
Sprachmodelle
Die Sprachmodelle wählt ein Plattform-Admin in der Oberfläche. Der Katalog procur_regx.llm_models führt Modelle der Anbieter Google Gemini, Anthropic Claude und OpenAI; beim Hinzufügen prüft der Agent-Service das Modell mit einem Testaufruf. Die Plattform-Einstellungen halten je Zweck eine Auswahl: agent_model_id für den Chat, die Zusammenfassung langer Chats, die Zertifikatserkennung und den Website-Import, criteria_model_id für die Kriterienextraktion sowie die Projekt-Extraktion und Dokumentanalyse (/api/vergabefit/projects/{id}/extract, /api/vergabefit/documents/analyze). Ohne Auswahl gilt das Ersatzmodell aus LLM_MODEL im Format anbieter:modell, standardmäßig Gemini 3.8 Flash. Fehlt der API-Schlüssel des gewählten Anbieters, schlägt der Aufruf mit einer Meldung fehl; ein anderer Anbieter springt nicht ein. OpenAI-kompatible Endpunkte wie Ollama oder vLLM sind über den Anbieter openai und OPENAI_BASE_URL nutzbar. Alle Anbieter laufen über Pydantic AI; die Kriterienextraktion lädt große Dateien bei Gemini über die Files API hoch und nutzt dort einen expliziten Prompt-Cache, bei Claude das Caching des System-Prompts. Embeddings (1536 Dimensionen) und Transkription bleiben bei Gemini, GOOGLE_API_KEY ist daher immer nötig. Die Modelle schlagen vor; Entscheidungen über Eignung oder Zuschlag treffen sie nicht. Begründung im ADR.
Funktionsbereiche
Agent-Chat
Ein Chat unter /app/agent beantwortet Fragen zu Ausschreibungen und eigenen Unterlagen mit Quellenkarten und Folgevorschlägen. Antworten stützen sich auf die hybride Suche des MCP-Servers. Änderungen, die der Agent in der Anwendung vornehmen will, muss der Nutzer bestätigen. Chats liegen in der Datenbank, Projektchats sieht die ganze Organisation. Einen Chat löscht seine Erstellerin oder sein Ersteller nach einer Rückfrage, einen Projektchat auch OWNER und EDITOR der Organisation. Im Profil lassen sich alle eigenen Chats in allen Organisationen löschen. Dieser Bereich ist echt; der Bestand an Bekanntmachungen im Suchindex hängt vom Datenimport ab. Einstieg: services/frontend/lib/store.ts, services/frontend/lib/agui/, services/agent/src/agent/.
Dokumentenprüfung
Nutzer laden Vergabeunterlagen privat (/app/vergabefit/dokumentenpruefung) oder in einem Projekt hoch. Eine gemeinsame Pipeline erkennt Auftrag und Lose, extrahiert Kriterien und Nachweise je Dokument mit Fundstelle, führt sie über alle Dokumente zusammen und legt sie zur menschlichen Prüfung vor. Neue Unterlagen lassen sich nachträglich ergänzen. Dieser Bereich ist echt; die fachliche Qualität wird noch an Referenzfällen gemessen. Einstieg: services/agent/src/criteria_extraction/, services/frontend/lib/criteria-extraction/. Siehe Steckbrief und ADR; eine frühere Fassung beschreibt v2.
Fit-Gap-Analyse
Fit & Gap vergleicht die bestätigten Anforderungen einer Bewerbung mit dem Nachweisprofil des eigenen Unternehmens und markiert jede Anforderung als erfüllt, nicht erfüllt oder unklar. Die Auswertung ist regelbasiert, läuft im Browser und nutzt keine KI. Die Regeln decken wenige Kriterienarten ab (Leistung, Mindestumsatz, Referenzen, ISO 27001, Leistungsgebiet); alles andere bleibt zur manuellen Prüfung offen. Die Anforderungsliste übernimmt ein Angebotsprojekt per Knopf aus dem gespeicherten Review der gemeinsamen Kriterienextraktion (services/agent/src/criteria_extraction/fit_requirements.py, siehe Steckbrief). Den Regelvertrag führt services/agent/src/vergabefit/models.py; die Frontend-Typen werden daraus generiert, und Golden-Fixtures halten rules.py und rules.ts gleich, bis die Bewertung serverseitig läuft (ADR). Einstieg: services/frontend/lib/vergabefit/fit-gap.ts, rules.ts.
Marktcheck
Für eigene Ausschreibungsentwürfe zeigt der Marktcheck, wie viele Firmenprofile die Anforderungen erfüllen, welche Anforderungen den Kreis am stärksten verengen und wie sich geänderte Schwellen (Varianten) auswirken. Ein Bericht fasst das Ergebnis zusammen. Der Bereich ist eine Demo: Er rechnet im Browser auf einem synthetischen Firmenpool (dataset = 'demo_pool'). Einstieg: services/frontend/components/vergabefit/market-view.tsx, variant-step.tsx, report-step.tsx, services/frontend/lib/vergabefit/rules.ts.
Partnersuche
Die Partnersuche schlägt Firmen vor, die offene Anforderungen einer Bewerbung schließen könnten, mit Karte und Umkreisfilter. Eine Teamansicht zeigt, was eine Bietergemeinschaft gemeinsam abdeckt. Der Bereich ist eine Demo: Er nutzt synthetische Firmen mit angenäherten Standorten, das Team wird nur im Browser gespeichert, und die Kombinationsregeln sind rechtlich ungeprüft. Einstieg: /app/partners, services/frontend/lib/partners/, services/frontend/lib/vergabefit/consortium.ts.
Unternehmensverwaltung
Unternehmen werden als gemeinsame Einträge mit Rollen (OWNER, EDITOR, VIEWER) verwaltet. Nutzer können bestehende Einträge beanspruchen, Mitglieder per E-Mail einladen, den Beitritt anfragen und erhalten Benachrichtigungen; Plattform-Admins geben Firmen, Ansprüche und Nutzer frei. Eingeladene nehmen offene Einladungen unter /app/companies an oder lehnen sie ab, eine angenommene Organisation wird sofort die aktive. Beitrittsanfragen entscheiden OWNER und EDITOR im Team-Abschnitt der Unternehmensseite. Die Benachrichtigungen führen jeweils dorthin, eine nicht mehr offene Einladung oder Anfrage meldet die Oberfläche als solche. Einladungen werden erst an ein Konto mit bestätigter E-Mail-Adresse gebunden; eigene Einladungsmails gibt es noch nicht. Eine Anmeldung über CKAN ist optional über die Edge Function supabase/functions/ckan-bridge/ möglich. Jedes Unternehmen hat eine eigene Seite mit Abschnitten, die OWNER, EDITOR und Plattform-Admins direkt bearbeiten und VIEWER nur lesen, sowie eine öffentliche Leseansicht. Dieser Bereich ist echt. Einstieg: services/frontend/lib/regx/, services/frontend/app/app/companies/, services/frontend/app/app/platform/, database/regx/.
Unternehmensprofil und Nachweise
Das Profil besteht aus Fakten in einer einzigen Nachweisquelle, profile_facts. Jeder Fakt trägt eine Qualitätsstufe: derived (automatisch gefunden: Vergabedaten, Website, Upload), self_declared (vom Unternehmen angegeben) oder verified (durch ein Dokument belegt). Teammitglieder haben Rollen und Zertifikate, die als Nachweise an das Unternehmen gebunden sind. Namen und Personennachweise sehen nur OWNER, EDITOR und die Person selbst. Öffentliche Ansichten zeigen nur Nachweise des Unternehmens, der Agent sieht Teamnachweise nur als anonyme Anzahl. Der Website-Import liest Seiten über die abgesicherten MCP-Webwerkzeuge und extrahiert strukturiert mit Pydantic AI. Gefundene Zertifikate werden als nicht bestätigte Nachweise (derived) gespeichert und lassen sich bestätigen oder verwerfen; ohne Gültigkeitsdatum zeigt Fit & Gap sie als "unklar". Gefundene Leistungen ergänzen leere Schlagwörter und die CPV-Codes. Beschreibung und Stammdaten bleiben Vorschläge, bis OWNER oder EDITOR sie übernehmen. Siehe ADR. Einstieg: services/agent/src/, services/frontend/lib/regx/.
Projekte und Wissensbasis
Projekte bündeln eine Bewerbung oder eine eigene Ausschreibung mit Unterlagen, Arbeitsdokumenten, Chats und Prüfschritten. Sie entstehen manuell oder aus der Ausschreibungssuche (/app/notices), die standardmäßig die veröffentlichten Ausschreibungen (procur_fit.published_tenders) durchsucht; NEXT_PUBLIC_NOTICE_SEARCH schaltet auf Demo-Bestand oder Suchindex um (Veröffentlichung: Kriterienextraktion). OWNER und EDITOR können ein Projekt löschen. Seine Dokumente bleiben in der Wissensbasis, und eigene Chats der Mitglieder bleiben ohne Projektbezug erhalten. Den generierten Entwurf einer eigenen Ausschreibung löscht der Dialog auf Wunsch mit. Ein archiviertes Projekt verschwindet aus Projektliste, Seitenleiste und Fristenübersicht, bis es wiederhergestellt wird; die Projektliste kann archivierte Projekte einblenden. Die Wissensbasis (/app/knowledge) verwaltet Nachweise mit Gültigkeit und Dokumente des Unternehmens; der Agent kann sie lesen. Sie erscheint als Reiter der Organisation neben deren Übersichtsseite; mit aktiver Organisation führt der Eintrag „Dokumente“ der Symbolleiste direkt zu ihren Dokumenten. Ein weiterer Reiter zeigt die Vorlagen der Organisation (/app/knowledge?tab=templates) über als Demo gekennzeichneten Beispielvorlagen: OWNER und EDITOR legen eigene Vorlagen an, bearbeiten sie im Dokumenteditor und erzeugen daraus neue Arbeitsdokumente; für die Suche des Agenten werden diese Vorlagen nicht eingelesen. Persönliche Dokumente ohne Organisation haben eine eigene Seite (/app/personal), die die Symbolleiste mit aktiver Organisation nur zeigt, solange es welche gibt. Ein neues Dokument im Editor bleibt persönlich, wenn der Nutzer in der aktiven Organisation nur lesen darf oder keine hat; Editor und Dokumentansicht weisen darauf hin. OWNER und EDITOR können ein persönliches Dokument in die Organisation übernehmen: Eine hochgeladene Datei zieht dabei in deren Speicherbereich und wird für den Agenten eingelesen, Dateien einer privaten Dokumentenprüfung bleiben persönlich. Projekte, Wissensbasis und Dokumenteditor sind echt; die Aufgabenplanung (/app/tasks) speichert nur im Browser und führt nichts aus. Einstieg: services/frontend/components/projects/, services/frontend/lib/fit/, services/frontend/lib/knowledge/. Siehe ADR zum Schema und ADR zu Matching-Merkmalen.
Vorlagen sind vom Dokumente-Reiter getrennt, nicht vom Dokumenteditor: Der Editor lädt die Editor-Dokumente der aktiven Organisation einschließlich ihrer Vorlagen, damit eine Vorlage dort geöffnet und bearbeitet werden kann. Deshalb erscheinen Vorlagen auch in der Dokumentauswahl der Agenten-Seitenleiste. Dass sie nicht eingelesen werden, sichert procur_fit.request_document_ingest ab (siehe SUPABASE_PLAN).
Datenflüsse
- Dokumentenprüfung. Browser lädt Dateien in privaten Storage, startet einen Job über
/api/vergabefit/criteria-extractions, der Agent-Service speichert den Auftrag vor jedem Modellaufruf, verarbeitet Auftrag und Lose, dann jedes Dokument, dann den Vergleich, und veröffentlicht das Ergebnis gemeinsam. Der Browser fragt den Status ab. Nach einem Neustart setzt das System kostenpflichtige Schritte nicht selbst fort. - Agent-Chat mit Suche. Browser sendet die Nachricht an
/agui, der Agent wählt Werkzeuge, der MCP-Server sucht hybrid (Vektorsuche mit pgvector und Volltextsuche, zusammengeführt per Reciprocal Rank Fusion) in Bekanntmachungen und eigenen Dokumenten, der Agent antwortet im Stream mit Quellen und Zustandsänderungen. - Fit & Gap und Marktcheck im Browser. Das Frontend lädt Anforderungsliste, eigenes Nachweisprofil und Vergleichspool aus
procur_fitund wertet sie mit deterministischen Regeln aus. Gespeicherte Auswertungen landen als Lauf inprocur_fit.analysis_runs. - Datenimport. Der Scraper meldet neue Bekanntmachungen aus der ProcurDat-Ablage, der Worker zerlegt sie nach Losen, berechnet Embeddings und schreibt sie in den Suchindex. CSV-Importe und Kriterienimporte laufen als Skripte mit Probelauf.
Sicherheit und Betrieb
- Anmeldung über Supabase Auth. Der Agent-Service prüft das Bearer-Token jeder Anfrage und vertraut keinen vom Client gesetzten Nutzerangaben. Neue Konten bestätigen ihre E-Mail-Adresse und brauchen eine Freigabe, die nur für bestätigte Adressen möglich ist.
- Mailversand über ein SMTP-Relay mit einheitlichen
SMTP_*-Variablen: Supabase Auth versendet Bestätigung und Passwort-Reset mit Vorlagen ausservices/frontend/public/email-templates/, der Agent-Service versendet Benachrichtigungen aus der Tabellenotifications(notification_email_worker.py), das Frontend den Claim-Code (lib/email/send.ts). Lokal fängt Mailpit (Compose-Profilmail) alles ab. Siehe ADR. - Zugriffsrechte setzt die Datenbank per Row-Level Security durch, auch für Projekte, Dokumente und Ergebnisse.
- Secrets liegen nur in lokalen
.env-Dateien und Deployment-Variablen, nie im Repository. - Lange Modellaufträge sind dauerhafte Jobs mit Leases, Revisionsschutz und Idempotenzschlüsseln.
- Produktion: Container-Images aus der GitHub Container Registry, Caddy als TLS-Proxy. Eine öffentliche Instanz für den Pilot ist in Vorbereitung.
- CI: GitHub Actions für Tests, Datenbank-Synchronisation und Sicherheits-Scan.
Weiterführende Dokumente
- Projektzusammenfassung und Roadmap
- Architekturentscheidungen
- Steckbrief: Kriterienextraktion; der frühere v2-Ablauf liegt im Archiv
- Fachkonzept Extraktion
- Importformate
- Datenbank:
database/README.md,database/SUPABASE_PLAN.md,database/MATCHING_CRITERIA.md