⚙️ Worker & Pipelines
Vier Pipelines, jeweils mehrere Worker als Stufen. Jede Zeile ist Selbstauskunft — der Worker meldet bei jedem Lauf selbst, was er an welcher Search getan hat. „Backlog" = der eigene Eingangsstapel dieses Workers (nicht die Gesamt-Seeds).
🎯 Lead-Gewinnung
Firma finden → filtern → crawlen → Kontakt & Job-Signal
| Stufe | Worker | Quelle | Status | Letzter Lauf | 24h Läufe | erzeugt 24h | Backlog | Searches 24h |
|---|---|---|---|---|---|---|---|---|
| Discovery (Tavily) | Firmen-Suche (Tavily) · city_search | Tavily | ⏳ wartet | — | — | — | — | — |
| Discovery (Tavily) | Jobseiten-Suche (Tavily) · jobsearch | Tavily | ⏳ wartet | — | — | — | — | — |
| Filter |
Seed-Filter (raw→legit)
· seed_filter
LLM-Precheck — rohe URLs aus seed_urls_raw als legit Seeds übernehmen oder verwerfen. |
— | 🔴 still | — | — | — | 212 | — |
| Crawl & Anreichern |
Crawl & Validate
· crawl_validate
Crawlt legit Seeds, extrahiert Kontakte und validiert die Firmenseite. |
Firmen-Website | 🔴 still | — | — | — | 2.594 | — |
| Crawl & Anreichern |
Adress-Parser
· address_parse
Zerlegt Freitext-Anschriften (seed_urls.company_address) deterministisch in street/plz/ort/land der Kontakte. |
— | 🔴 still | — | — | — | — | — |
| Job-Signal |
Stellen-/Signal-Radar
· jobscan
Crawlt Firmen-Homepage + Karriereseite, extrahiert offene Stellen und Opportunity-Signale (Jobs/Digitalisierung/Ausschreibung/Beratung) je Firma. |
Firmen-Website | ⚠️ hängt? | 1 h vor | 132 | 0 | 10.983 | |
| Nutzen | Pitch-Scoring · pitch_score | — | 💤 dormant | — | — | — | — | — |
💼 Externe Jobs & Ausschreibungen
Direkt aus Behörden-/EU-APIs — kein Crawl nötig
| Stufe | Worker | Quelle | Status | Letzter Lauf | 24h Läufe | erzeugt 24h | Backlog | Searches 24h |
|---|---|---|---|---|---|---|---|---|
| Discovery (API) | Stellen-Scanner · jobportals | Bundesagentur-API | ⏳ wartet | — | — | — | — | — |
| Discovery (API) | Ausschreibungen · tenders | TED-API | ⏳ wartet | — | — | — | — | — |
🌍 Wealth-Atlas
Validierte Seeds öffentlich schalten
| Stufe | Worker | Quelle | Status | Letzter Lauf | 24h Läufe | erzeugt 24h | Backlog | Searches 24h |
|---|---|---|---|---|---|---|---|---|
| Publish & Wartung |
Atlas-Auto-Promotion
· atlas_promote
Promotet validierte Atlas-Seeds automatisch in den oeffentlichen Wealth-Atlas (wealth_offices). |
— | 🔴 still | — | — | — | — | — |
| Publish & Wartung | Atlas Geo-Wartung · atlas_revert_geo | — | 💤 dormant | — | — | — | — | — |
📧 Kampagnen-Versand (separat)
Alter Kampagnen-Mailer — NICHT der Pitch-Outreach (der läuft im delta-Container)
| Stufe | Worker | Quelle | Status | Letzter Lauf | 24h Läufe | erzeugt 24h | Backlog | Searches 24h |
|---|---|---|---|---|---|---|---|---|
| Sender |
Kampagnen-Versand
· campaign_send
Verteilt Kampagnen-Mails ueber das eingestellte Zeitfenster. |
— | 🔴 still | — | — | — | 110 | — |
Status: 🟢 produziert · ⚪ idle (nichts zu tun) · 🟠 idle trotz Backlog (erschöpft/blockiert) · 🔵 läuft · ⏳ wartet auf Cron · 💤 dormant (nur manuell) · 🔴 still trotz Cron · ⚠️ Fehler. ⏸ orange Searches = pausiert, werden aber noch bearbeitet (verschwendetes Budget).
🔗 SSOT — Crawl-Ergebnis-Kontrakt
Beide Crawler-Lanes schreiben über EINE Funktion
ingest_seed_result() (in lib/seed_result.php) in die DB:
der DeepSeek-Worker ruft sie direkt, Eva sendet das JSON an den HTTP-Endpoint.
So ist der A/B-Vergleich fair — gleicher Schreibpfad, gleiche Validierung.
| Claim-Endpoint (Eva) | POST /webresearch/seed_claim.php — atomar claimen + sofort auf is_craweled=1 stellen (kein SELECT-Pattern mehr!) |
| Ingest-Endpoint (Eva) | POST /webresearch/seed_result_ingest.php (Body: JSON + Feld token) |
| Funktion (für Worker) | ingest_seed_result($db, $payload) in lib/seed_result.php |
| Token-Quelle | lib/ingest_config.php (nicht im Web sichtbar; Eva liest die Datei) |
| Batch | mehrere Ergebnisse auf einmal: {"token":"…","batch":[ {…}, {…} ]} |
{
"seed_id": "int — PFLICHT, ID aus WEBRESEARCH_seed_urls",
"crawler": "'worker' | 'bot' — wer das Ergebnis erzeugt hat",
"status": "'done' (Daten gefunden) | 'give_up' (nichts brauchbar)",
"reason": "string — give_up-Grund oder done-Notiz",
"company": {
"name": "string — Firmenname",
"summary": "string — STECKBRIEF (-> legit_note): Taetigkeit, Spezialisierungen, Team/Mitarbeiter, Lage/Standort-Besonderheit, USP/Auffaelliges. So konkret wie moeglich, damit GPT daraus eine personalisierte Mail bauen kann. Mehrere Saetze erlaubt.",
"address": "string — Postadresse (Strasse Nr, PLZ Ort), aus Impressum/Kontakt. Leer wenn nicht gefunden.",
"language": "'de' | 'en' | 'es' | 'other'"
},
"contacts": [
{
"person_name": "string oder leer",
"role": "z.B. 'Geschaeftsfuehrer' / 'Einkauf' / 'Allgemein'",
"email": "string oder leer",
"phone": "string oder leer",
"found_on_url": "auf welcher URL gefunden",
"rank": "int 1..N — 1 = BESTER Ansprechpartner fuer einen Software-Pitch (Einkauf/Entscheider), 2 = naechster, ... (du entscheidest die Reihenfolge)"
}
],
"pages_visited": [
"array von URLs — optional, fuer Audit"
]
}
{
"seed_id": 2720,
"crawler": "bot",
"status": "done",
"reason": "Impressum + Team-Seite ausgewertet",
"company": {
"name": "Junkersdorf Hausverwaltung GmbH",
"summary": "Hausverwaltung in Koeln, ~1.700 Wohneinheiten, WEG- und Mietverwaltung.",
"address": "Aachener Str. 1253, 50858 Koeln",
"language": "de"
},
"contacts": [
{
"person_name": "Kenny Roteweit",
"role": "Geschaeftsfuehrer",
"email": "info@junkersdorf-koeln.de",
"phone": "0221 6802598",
"found_on_url": "https://www.junkersdorf-hausverwaltung.de/impressum",
"rank": 1
},
{
"person_name": "",
"role": "Buchhaltung",
"email": "buchhaltung@junkersdorf-koeln.de",
"phone": "",
"found_on_url": "https://www.junkersdorf-hausverwaltung.de/kontakt",
"rank": 2
}
],
"pages_visited": [
"https://www.junkersdorf-hausverwaltung.de/",
"https://www.junkersdorf-hausverwaltung.de/impressum"
]
}
Eva wacht alle paar Minuten auf, ruft den Claim-Endpoint auf,
bekommt N Seeds IHRER Lane (crawler='bot') zurück — atomar geclaimt
und sofort auf is_craweled=1 umgestellt. Der Worker (DeepSeek) macht
es identisch mit crawler='worker'.
Nur AKTIVE Searches — pausierte/abgeschlossene/archivierte werden nicht ausgegeben.
POST /webresearch/seed_claim.php
Content-Type: application/json
{
"token": "<ingest_token aus lib/ingest_config.php>",
"crawler": "bot", // Worker nutzt "worker"
"count": 3, // 1..10 Seeds in einem Claim
"worker_id": "eva-1", // Audit
"orphan_min": 15 // hängende Claims nach N Minuten freigeben
}
→ Response: { ok, claimed, orphans_released, claim_token,
seeds: [ {id, url, project_id, question, ...}, ... ] }
Pro Seed crawlen → Ergebnis-JSON (oben) bauen → an
POST /webresearch/seed_result_ingest.php senden. Der Endpoint markiert
is_craweled=2 und schreibt Profile + Kontakte. Eva muss NICHT selbst per SQL schreiben.
⚠️ Veraltetes Anti-Pattern: SELECT direkt aus WEBRESEARCH_seed_urls + manuell is_craweled=1 setzen.
Das war race-anfällig (doppel-Claims, hängende Seeds bei Crash). Nicht mehr verwenden —
immer den Claim-Endpoint oben. Insbesondere: nicht gegen eine lokale SQLite-Kopie arbeiten,
die enthält nur einen alten Snapshot ohne crawler-Spalte.
Volle Anweisung in ~/.openclaw/workspace-eva/WEBRESEARCH_PIPELINE.md.