Zurück zum Blog

Wie ich RFP Search gebaut habe, einen KI-gestützten RFP-Aggregator

2026-03-256 min read

Wenn Sie jemals versucht haben, öffentliche Ausschreibungen (RFPs) für digitale Dienstleistungen zu finden, wissen Sie, wie mühsam das ist. Angebote sind über SAM.gov, staatliche Beschaffungsportale, Grants.gov, die UN, die EU und ein Dutzend anderer Orte verstreut. Jede Seite hat ihre eigene Suchoberfläche, ihr eigenes Format und ihre eigenen Besonderheiten. Sie alle manuell zu überprüfen ist mühsam, also habe ich RFP Search gebaut, um das für mich zu erledigen.

Was es tut

RFP Search durchforstet jede Nacht 11 Beschaffungsquellen von Regierungen und gemeinnützigen Organisationen, verarbeitet jede Ausschreibung durch ein KI-Modell, um strukturierte Daten zu extrahieren, und stellt alles über eine einzige durchsuchbare Oberfläche mit Filtern und einer interaktiven Karte bereit.

Zu den Quellen gehören SAM.gov, Grants.gov, das Texas ESBD, Cal eProcure aus Kalifornien, Florida MFMP, das TED-Portal der EU, der UN Global Marketplace, NYC Open Data, das Federal Register, USAspending und Brave Search für eine breitere Abdeckung. Jeden Morgen warten frische Ergebnisse auf Sie.

Die Architektur

Das gesamte System läuft auf Cloudflare. Drei Workers, eine D1-Datenbank und null traditionelle Server. Ich habe es als Monorepo mit Turborepo und npm workspaces strukturiert.

Die drei Workers haben jeweils eine klare Aufgabe.

  • rfp-web ist das Frontend, erstellt mit React Router v7 (dem Nachfolger von Remix). Es verwaltet die Suchoberfläche, Filter und eine interaktive Leaflet-Karte mit Marker-Clustering.
  • rfp-api ist eine Hono REST API, die Suchanfragen, Paginierung, Statistiken und Geodaten für die Karte verarbeitet.
  • rfp-scraper ist ein geplanter Worker, der nächtliche Cron-Jobs ausführt, um RFPs von allen Quellen abzurufen und zu verarbeiten.

Der Web-Worker kommuniziert über Cloudflare Service Bindings mit dem API-Worker. Kein HTTP, kein CORS, kein öffentlicher API-Endpunkt. Die Bindung ruft die API direkt innerhalb des Cloudflare-Netzwerks auf, was sowohl schneller als auch sicherer ist.

Der Scraper

Dies ist der interessanteste Teil. Jede Quelle ist ein Plugin, das eine einfache Schnittstelle implementiert. Möchten Sie eine neue Quelle hinzufügen? Erstellen Sie eine Datei, implementieren Sie die Schnittstelle, registrieren Sie sie und fügen Sie eine Datenbankzeile hinzu. Das war's.

Der Scraper läuft in drei gestaffelten Batches (9:00, 9:15 und 9:30 Uhr UTC), um die CPU-Zeitlimits des Workers einzuhalten. Jeder Batch verarbeitet eine Teilmenge der Quellen.

Batch 1 (9:00 UTC)  SAM.gov, TED EU, USAspending
Batch 2 (9:15 UTC) Texas ESBD, Cal eProcure, Brave Search, Grants.gov, Federal Register
Batch 3 (9:30 UTC) Florida MFMP, UNGM, NYC Open Data

Jedes Quellen-Plugin weiß, wie es seine eigenen Datenformate abrufen und parsen kann. Einige greifen auf REST-APIs zu, andere parsen HTML, wieder andere verwenden RSS-Feeds. Die Plugin-Architektur hält diese Komplexität eingegrenzt. Wenn eine Quelle ihr Format ändert, muss ich nur eine Datei aktualisieren.

Der Scraper verfolgt auch Fehler. Wenn eine Quelle dreimal hintereinander fehlschlägt, wird sie automatisch deaktiviert, damit sie keine Zyklen verschwendet oder die Protokolle verunreinigt.

KI-Extraktion

Rohe RFP-Einträge sind unübersichtlich. Einige enthalten detaillierte Beschreibungen, andere nur einen Titel und einen Link. Ich verwende Cloudflare Workers AI mit Llama 3.1 70B Instruct, um alles in eine konsistente Struktur zu normalisieren.

Jeder RFP erhält einen einzigen KI-Aufruf, der strukturierte Felder (Frist, Budget, Standort, Kontaktinformationen), Metadaten zur Entscheidungsfindung (erforderliche Zertifizierungen, Vertragsart, Eignung für Remote-Arbeit, geschätzte Teamgröße, Technologie-Stack), Kategorien (Webentwicklung, CMS, Cloud, KI, Migration und mehr) sowie eine 2-3-sätzige Zusammenfassung extrahiert.

Ein Aufruf pro RFP, ein Prompt, der alles erledigt. Dies hält die Kosten für Workers AI niedrig und liefert dennoch eine gute Extraktionsqualität. Das Modell ist überraschend fähig, strukturierte Daten aus unstrukturierten Texten öffentlicher Ausschreibungen zu ziehen.

Die Datenbank

Cloudflare D1 (verwaltetes SQLite am Edge) speichert alles. Die Haupttabelle rfps enthält über 30 Felder, die die Kerndaten, KI-extrahierten Felder und Metadaten zur Entscheidungsfindung abdecken.

Für die Suche verwende ich SQLite's FTS5 (Full-Text Search 5) mit Triggern, die den Suchindex automatisch synchronisieren, wann immer eine Zeile eingefügt, aktualisiert oder gelöscht wird. Keine manuelle Neuindizierung, kein separater Suchdienst. Es funktioniert einfach.

CREATE VIRTUAL TABLE rfps_fts USING fts5(
title, description, ai_summary, categories,
content=rfps, content_rowid=id
);

Die Deduplizierung erfolgt über eine eindeutige Einschränkung für (source_name, external_id). Wenn also dasselbe RFP in mehreren Scraping-Durchläufen erscheint, wird es aktualisiert und nicht dupliziert.

Der RFP-Status (offen, läuft bald ab, geschlossen) wird zur Abfragezeit aus dem Fristfeld berechnet und nicht gespeichert. Das bedeutet, dass der Status immer korrekt ist, ohne dass ein Hintergrundjob veraltete Datensätze aktualisieren muss.

Das Frontend

Die Benutzeroberfläche wurde mit React Router v7 auf Cloudflare Workers erstellt und mit Tailwind CSS v4 gestaltet. Sie verfügt über eine Suchleiste mit Volltextsuche in Titeln, Beschreibungen, Zusammenfassungen und Kategorien. Dropdown-Filter ermöglichen die Eingrenzung nach Organisationstyp, Kategorie, Friststatus und geschätztem Wert.

Die interaktive Karte verwendet Leaflet mit Marker-Clustering. Jedes RFP mit geokodierten Standortdaten wird als Pin angezeigt. Die Geokodierung selbst erfolgt im Scraper mithilfe von Nominatim (dem Geokodierungsdienst von OpenStreetMap), um Ortsangaben in Breiten-/Längengrade umzuwandeln.

Eine Statusleiste oben zeigt die Anzahl der aktuell offenen RFPs und aktiven Quellen an, sodass Sie auf einen Blick erkennen können, wie aktuell die Daten sind.

Was ich anders machen würde

Wenn ich noch einmal von vorne beginnen würde, würde ich wahrscheinlich von Anfang an E-Mail-Benachrichtigungen hinzufügen. Derzeit muss man die Seite besuchen, um nach neuen Möglichkeiten zu suchen. Eine einfache tägliche Zusammenfassung für gespeicherte Suchen würde es viel nützlicher machen.

Ich würde auch mehr in das Testen der Zuverlässigkeit der Quellen investieren. Regierungswebsites ändern sich ohne Vorwarnung, und das Scraping ist von Natur aus fehleranfällig. Bessere Überwachung und automatische Benachrichtigungen, wenn eine Quelle beginnt, unerwartete Daten zurückzugeben, würden Debugging-Zeit sparen.

Probieren Sie es aus

Die Plattform ist unter rfp.davidloor.com live. Sie konzentriert sich auf digitale Dienstleistungen, Webentwicklung, CMS, Cloud und KI-Möglichkeiten, aber die Architektur könnte jede Kategorie von RFPs unterstützen.

Auf dem Laufenden bleiben

Erhalten Sie die neuesten Beiträge und Einblicke direkt in Ihren Posteingang.

Unsubscribe anytime. No spam, ever.