⚙️ Worker & Pipeline
Die Pipeline besteht aus 4 Phasen. Phase 0 holt URLs, Phase 1 filtert auf echte Firmen, Phase 2 extrahiert Profile + Kontakte, Phase 3 bewertet pro Pitch. 🟢 = aktiv im Cron · 🟡 = altes Skript (deaktiviert oder Fallback)
🌱 Phase 0 — Seed-Harvesting (Tavily)
Sucht neue Firmen-URLs ueber Tavily-API und legt sie in WEBRESEARCH_seed_urls_raw ab.
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_city_search.php · 9,7 KB · geaendert 2026-05-20 12:12 |
| Cron | 30 6-21 * * * (16×/Tag, 06-21h) |
| Aufruf | docker exec bots-admin php /var/www/html/webresearch/worker_city_search.php |
| Tavily-Last | ~240 Calls/Tag (50% Budget) |
| Log | /var/log/openclaw/webresearch_city_search.log |
| Letzte Aktivität (DB) | 22 min vor (2026-06-05 00:00:20) |
| Output | 2406 Runs · 23608 In raw eingefuegt |
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_seed_urls_raw_ingest.py · 3,5 KB · geaendert 2026-04-27 12:09 |
| Cron | — (kein Cron, nur on-demand) |
| Aufruf | python worker_seed_urls_raw_ingest.py <project_id> "<query>" |
| Tavily-Last | on-demand |
| Letzte Aktivität (DB) | 4 h vor (2026-06-04 20:00:21) |
| Output | 23612 Raws insgesamt |
🔬 Phase 1 — Seed-Filter (LLM-Vetting raw → legit)
Prueft jede raw-URL: echte Firma oder Directory/SERP/Wiki/Spam? DeepSeek-Verdict.
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_seed_filter.php · 7,7 KB · geaendert 2026-05-20 16:58 |
| Cron | */15 * * * * (alle 15min) |
| Aufruf | docker exec bots-admin php /var/www/html/webresearch/worker_seed_filter.php --batch=100 |
| Tavily-Last | — (nur DeepSeek, ~$0.0002/raw) |
| Log | /var/log/openclaw/webresearch_seed_filter.log |
| Letzte Aktivität (DB) | 4 h vor (2026-06-04 20:15:42) |
| Output | 12875 akzeptiert · 10663 abgewiesen · 0 pending |
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_seed_urls_raw_precheck.py · 10,0 KB · geaendert 2026-04-28 16:00 |
| Cron | — (deaktiviert) |
| Aufruf | python worker_seed_urls_raw_precheck.py <project_id> |
| Tavily-Last | — |
| Letzte Aktivität (DB) | — |
🕵️ Phase 2 — Seed-Profile (Multi-Page LLM-Agent)
Pro legit-Seed: DeepSeek-Agent-Loop mit Tools (fetch_url, save_company_profile, save_contact). Findet Impressum/Kontakt/Team und extrahiert Profil + Personen.
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_seed_profile.php · 21,4 KB · geaendert 2026-05-21 16:47 |
| Cron | */3 * * * * (alle 3min, batch=5) |
| Aufruf | docker exec bots-admin php /var/www/html/webresearch/worker_seed_profile.php --batch=5 --worker_id=1 |
| Tavily-Last | — (nur DeepSeek, ~$0.003/seed) |
| Log | /var/log/openclaw/webresearch_seed_profile.log |
| Letzte Aktivität (DB) | 4 h vor (2026-06-04 20:19:10) |
| Output | 7184 Runs gesamt · 5378 done · 89 give_up · 270 error · 11721 Kontakte gesp. |
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_contact_extract.php · 22,7 KB · geaendert 2026-05-19 00:02 |
| Cron | — (durch seed_profile abgeloest, kein Cron) |
| Aufruf | docker exec bots-admin php /var/www/html/webresearch/worker_contact_extract.php |
| Tavily-Last | — |
| Letzte Aktivität (DB) | 52 min vor (2026-06-04 23:30:53) |
| Output | 25150 Kontakte gesamt |
🎯 Phase 3 — Pitch-Scoring (KI-Match)
Bewertet pro Pitch wie gut jeder Kontakt zur Zielfunktion passt (0–100). Triggered on-demand vom Dashboard.
| Pfad | /srv/openclaw/sites/admin_bots/webresearch/worker_pitch_score.php · 6,9 KB · geaendert 2026-05-19 12:30 |
| Cron | — (Button "⚡ Scoring starten" oder CLI) |
| Aufruf | docker exec bots-admin php /var/www/html/webresearch/worker_pitch_score.php --pitch_id=N --project_id=N |
| Tavily-Last | — (nur DeepSeek, ~$0.0003/Kontakt) |
| Letzte Aktivität (DB) | 16 d vor (2026-05-19 12:35:39) |
| Output | 5 Scores insgesamt |
🕒 Cron-Aufstellung (Tavily-Budget)
| Worker | Frequenz | Calls/Tag | Calls/Monat |
|---|---|---|---|
| wealth_atlas_harvester (phi) | 0 * * * * (stuendlich) | ~240 | ~7.200 (48 %) |
| worker_city_search (webresearch) | 30 6-21 * * * (16×/Tag) | ~240 | ~7.200 (48 %) |
| Total Tavily-Plan: 15.000/Monat | — | ~14.400 (96 %) | |
🔗 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 Projekte — 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.