VakanzPuls ist eine B2B HR-Intelligence-Plattform für den DACH-Stellenmarkt. Die Plattform aggregiert Stellenanzeigen, reichert sie mit direkten HR-Kontaktdaten an und gibt Personalvermittlern einen entscheidenden Wettbewerbsvorteil bei der Kundenakquise.
Problem
Personalvermittler verbringen Stunden mit manueller Suche nach Kunden mit offenen Stellen. Kontaktdaten fehlen oder sind veraltet. Der Marktüberblick ist fragmentiert.
Lösung
VakanzPuls crawlt automatisch alle relevanten Job-Quellen im DACH-Raum, reichert Firmen mit direkten HR-Kontaktdaten an und liefert täglich aktuelle Intelligence — vollautomatisch.
ROI
Statt 2–4h täglicher Recherche: 5 Minuten Dashboard-Check. Direkte Kontaktdaten erhöhen Kaltakquise-Erfolgsrate signifikant. Wochenbericht DACH montags 08:00 automatisch.
Der DACH-Stellenmarkt umfasst über 50 Millionen Beschäftigte. Täglich werden zehntausende neue Stellen ausgeschrieben. VakanzPuls ist die erste Plattform die diesen gesamten Markt systematisch aggregiert und für Personalvermittler aufbereitet.
| Merkmal | VakanzPuls | LinkedIn/Xing | Indeed/StepStone | Manuelle Recherche |
|---|---|---|---|---|
| Echtzeit-Marktdaten | ✓ Ja | teilweise | teilweise | ✗ Nein |
| Direkte HR-Kontakte | ✓ automatisch | manuell | ✗ Nein | aufwändig |
| DACH-Vollabdeckung | ✓ 25+ ATS | unvollständig | teilweise | ✗ Nein |
| Behörden & Sozial | ✓ 25K+ Stellen | ✗ Nein | teilweise | ✗ Nein |
| Outreach-Integration | ✓ direkt | InMail (kostenpfl.) | ✗ Nein | ✗ Nein |
| Automatisierungsgrad | ✓ vollautomatisch | ✗ manuell | ✗ manuell | ✗ manuell |
Typische Anwendungsfälle
Kaltakquise & Neukundengewinnung
Personalvermittler exportieren täglich eine Liste von Unternehmen mit neuen Stellen — inkl. HR-Kontakt-E-Mail. Outreach startet automatisch.
Marktbeobachtung & Frühwarnung
Watchlist-Funktion: Bei neuen Stellen eines Wunschkunden sofortige Telegram-Benachrichtigung. Akquise beginnt bevor der Wettbewerb reagiert.
Branchenanalyse & Wochenbericht
Automatischer DACH-Report jeden Montag: Top-10 Branchen, wachsende Arbeitgeber, Engpassberufe, regionale Hotspots.
Kundenpflege & Bestandsmonitoring
Bestandskunden auf Watchlist — sobald Stellenvolumen sinkt oder neue Positionen entstehen: sofortige Benachrichtigung.
Kern-Features (Partner-Dashboard)
- Vollzugriff Markt & Quellen
- Schnelltest (50/Tag)
- Watchlist (max. 20 Firmen)
- DACH-Report wöchentlich
- E-Mail Support
- Alles aus Starter
- Outreach-Engine (80/Tag)
- Kundensegmentierung & Export
- Watchlist unbegrenzt
- Intelligence & Seismograph
- API-Zugang (read-only)
- Priority Support
- Alles aus Professional
- Outreach unbegrenzt
- Vollständiger API-Zugang
- Multi-User (5 Seats)
- White-Label Option
- Dedizierter Account Manager
- SLA-Garantie
✓ Umgesetzt
- Landing Page DSGVO-konform (Cookie-Banner, Datenschutzerklärung)
- Outreach mit Unsubscribe-Link (DSGVO Art. 17)
- Opt-out-Verwaltung in Datenbank
- Datenspeicherung ausschließlich auf EU-Servern (Contabo DE)
- HTTPS/TLS auf allen Endpunkten
⏳ Ausstehend (nach Gründung)
- EU-Vertreter Art.27 — DRINGEND
- Firmenname & Adresse im Impressum
- datenschutz@vakanzpuls.de (nach Domain)
- AV-Vertrag Contabo + SendGrid
- Echte Telefonnummer (Sandro)
Quality Score Verteilung
Government-Jobs (756K, 65% aller Jobs): government_description_fetcher läuft 4× täglich zur Verbesserung.
| ATS-System | Aktive Quellen | Jobs (aktiv) | Methode | Status |
|---|---|---|---|---|
| SmartRecruiters | 86 | 69.201 | API | Aktiv |
| Softgarden | 1.082 | 57.518 | HTML (LB) | Aktiv |
| Greenhouse | 900 | 41.409 | API + Webhook | Aktiv |
| Lever | 869 | 27.588 | API | Aktiv |
| Personio | 2.361 | 24.971 | API + Webhook | Aktiv |
| Workday | 76 | 21.614 | HTML | Aktiv |
| Workable | 1.459 | 15.633 | API | Aktiv |
| iCIMS | 163 | 7.915 | HTML (LH) | Aktiv |
| Pinpoint | 359 | 7.409 | JSON API (LF) | Aktiv |
| BambooHR | 1.873 | 6.956 | JSON API (LD) | Aktiv |
| Breezy | 549 | 5.376 | JSON API (LF) | Aktiv |
| Ashby | 1.493 | 4.272 | API (LM) | Aktiv |
| Recruitee | 64 | 2.133 | API (LN) | Aktiv |
| Teamtailor | 48 | 590 | HTML (LN) | Aktiv |
| Haufe | 66 | 553 | HTML (LN) | Aktiv |
| JOIN.com | 1.413 | 469 | API (LN) | Aktiv |
| dvinci | 28 | 360 | HTML (LN) | Aktiv |
| hr4you | 10 (+6) | 2.256 | JSON-API | NEU 18.04 |
| Talention | 13 (+10) | 45 | Sitemap+JSON-LD | NEU 18.04 |
| Coveto | 15 (+5) | 86 | HTML+JSON-LD | NEU 18.04 |
| + Concludis, Schema.org, RSS | ~600 | ~5.000 | Verschiedene | Aktiv |
1. Query-Generator erzeugt täglich 05:00 neue Such-Kombinationen (v3: 12.271 Queries/Zyklus)
2. gosom-Docker-Container (13 parallel: W1=3, W3=2, W6=8) crawlen Google Maps mit per-Container-Finalisierung
3. GMaps-Reaktor verarbeitet Rohdaten → Firmenprofile (done_1h nach 8-17min)
4. Impressum-Crawler reichert E-Mail, Telefon, Inhaber an
Status 19.04: 595K Discoveries, daemon_keeper via server_caps.json SSOT, Load-Guard 80% vCPU
ATS / Direkt (Kernformel)
| Titel + Location | +20 Pkt |
| Description (>100 Zeichen) | +20 Pkt |
| Employment Type | +10 Pkt |
| E-Mail-Kontakt (Hauptfaktor) | +30 Pkt |
| Telefonnummer | +10 Pkt |
| Karriereseite + Gehalt | +10 Pkt |
| Maximum | 100 Pkt |
Behörden / Sozial (angepasst)
| Titel, Location, Description, Emptype | +50 Pkt |
| E-Mail (Bonus, nicht Pflicht) | +15 Pkt |
| Telefonnummer | +15 Pkt |
| PLZ / Adresse vollständig | +10 Pkt |
| Gehaltsangabe (TVöD) | +10 Pkt |
| Maximum | 100 Pkt |
Separate Formel verhindert künstliche Score-Senkung durch Behörden-Jobs.
| Komponente | Status | Details |
|---|---|---|
| SendGrid API Key | ✓ Aktiv | Konfiguriert |
| E-Mail-Templates A/B | ✓ Fertig | Direkt + Frage-Variante |
| Outreach Engine | ✓ Fertig | 80/Tag, Mo–Fr 09:00 Cron |
| Unsubscribe + Webhook | ✓ Fertig | DSGVO-konform |
| 3 Outreach-Segmente | ✓ Bereit | Staffing (201) · Mittelstand (384) · Behörden (301) |
| Domain vakanzpuls.de | ⏳ Sandro | Nach Ostern → Prompt CP |
| OUTREACH_ENABLED | false | Aktivierung nach Domain |
Tech-Stack
| Server | IP | RAM | Rolle | Daemons | AD-Worker |
|---|---|---|---|---|---|
| Master | 161.97.79.192 | 16GB | API + Orchestrator + Nginx | 15+ | 5 |
| Worker-1 | 173.212.217.37 | 16GB | ATS + Enrichment + Quality | 12 | 8 |
| Worker-2 | 161.97.170.205 | 16GB | Career + Triple + BA-Fetcher | 10 | 8 |
| Worker-3 | 95.111.233.0 | 47GB | Description Shards + Booster | 5 | 12 |
| Worker-4 (DB) | 5.189.174.152 | 48GB | PostgreSQL Primary + PgBouncer | 0 | 0 |
| Worker-5 | 173.212.253.115 | 16GB | Advertsdata dediziert + HR | 2 | 12 |
| Tabelle | Rows | Beschreibung | Kategorie |
|---|---|---|---|
job_advertisements | 1.41M | VP-Stellenanzeigen mit Enrichment (76+ Spalten) | Core |
advertsdata_jobs | 1.09M | Advertsdata-Stellenanzeigen (Email 82%, Phone 87%) | Core |
placement_scores | 1.41M | Placement-Score pro Stelle (0-100, Grades S/A/B/C/D) | Analytics |
hr_companies | 260K | Firmendatenbank mit HRB, Domain, Grade | HR |
hr_contacts | 162K | HR-Kontakte mit Name, Email, Phone, Grade (KQI 80.8%) | HR |
crawler_sources | 272K | Alle registrierten Datenquellen | Core |
gmaps_discoveries | 595K | Google-Maps-Firmendatenbank (W6 dediziert) | GMaps |
hiring_signals | 19.6K | Hiring-Signale (5-Faktor Composite Score) | Analytics |
gvp_members | 8.6K | GVP-Personalvermittler (5 Layer Enrichment) | Outreach |
company_momentum | 2.4K | Woche-über-Woche Hiring-Momentum | Analytics |
offeneregister_raw | 2.39M | Handelsregister-Daten (JSONL Import) | HR |
| + 70 weitere Tabellen | — | Users, Watchlist, Outreach, Notifications, MVs... | Support |
18 Materialized Views (Refresh alle 2-15 Min)
DB-Host: W4 (5.189.174.152) — PostgreSQL Primary
Zugriff: 127.0.0.1:5433 (PgBouncer auf Master) — NIEMALS Port 5432!
Worker: 161.97.79.192:5433 (Master-PgBouncer)
Pool: 80 Verbindungen · Session-Mode
Backup: W4 täglich 03:00
| Daemon | Server | Funktion |
|---|---|---|
backend (FastAPI) | Master | API :8003, 200+ Endpoints, Background-Refresh, Warmup |
orchestrator | Master | Koordination, Heartbeat-Check, Worker-Monitoring |
telegram_control_bot | Master | Bot v2: 10 Commands, 39+ Aktionen |
briefing_auto_analyst | Master | 3-Stufen (Regel-Engine → Claude API → Telegram) |
ats_crawler | W1 | 16 ATS-Systeme (Personio, Softgarden, Greenhouse...) |
enrichment_pipeline | W1 | Impressum + E-Mail/Phone-Enrichment 24/7 |
quality_score | W1 | Quality-Scoring (5K Batch/60s) |
shadow_monitor | W1 | Master-Failover (20s Zyklus, auto-Promotion) |
career_page + career_html | W2 | Career-Page-Checks + HTML-Parsing |
triple_enricher | W2 | GMaps + Impressum + Phone kombiniert |
ba_api_fetcher | Master | BA REST-API (aiohttp, 15 concurrent, ~100/s, 65% OK) |
description_daemon | W3 | Sharded Description-Fetching (Shard 4+5) |
advertsdata_scraper | W5 | 20 dedizierte Playwright-Worker |
vp_hr_matcher | Cron | VP↔HR Trigram-Matching (Daemon, 4 Partitionen) |
ad_vp_matcher | Cron | Advertsdata↔VP Trigram-Matching (GIN-optimiert) |
Öffentliche Endpunkte
GET /api/public/briefing — System-Briefing (kern.jobs_combined)GET /api/public/signals — Hiring-SignaleGET /api/public/stats — Basis-StatistikenGET /api/monitor/full — Monitor-Dashboard (15 Sektionen)POST /api/v1/auth/login — JWT-AuthentifizierungCustomer Portal (/api/v1/)
GET /dashboard/kern — Fast KPIs (<300ms)GET /dashboard/overview — Full DashboardGET /jobs/placement — Placement EngineGET /jobs/placement/by-job — Job→FirmenGET /jobs/placement/by-company — Firma→StellenGET /stellenanzeigen/enriched — HR-enriched JobsGET /hr/contacts — HR-KontaktdatenbankGET /qualitaet/overview — DatenqualitätGET /qualitaet/dach-report — DACH-Markt-ReportGET /companies/momentum — Hiring-MomentumGET /contacts/ — Contact IntelligenceGET /gvp/ — GVP-AnalysenGET /analytics/shortage — Engpass-Radar+ 30 weitere EndpointsAdmin Portal (/admin/api/)
GET /admin/api/dashboard — Admin-KPIsGET /admin/api/monitor/live — Live-StatusGET /admin/api/intelligence/* — Quality, Salary, ShortageGET /admin/api/crawler/* — Crawler-StatsGET /admin/api/hr-kontakte — HR-DashboardGET /admin/api/advertsdata — Advertsdata-KPIsPOST /admin/api/auto-analyst/trigger+ 40 weitere EndpointsPerformance
| Zeit | Script | Funktion |
|---|---|---|
| Monitoring (alle 2-30 Min) | ||
*/2 | vakanzpuls_watchdog + MV-Refresh (KPIs) | Daemon-Überwachung + Dashboard-MVs |
*/5 | worker_scheduler + MV-Refresh (Trends) | Phase-A/B + Trends-MV |
*/15 | system_health_check + MV-Refresh (7 MVs) | Health + Branchen/ATS/Region/Email MVs |
| Tägliche Pipeline-Kette (chronologisch) | ||
02:30 | shortage_radar | Engpass-Radar (241 Gruppen) |
03:00 | hiring_signals | Hiring-Signale (5-Faktor, 19.6K) |
04:30 | advertsdata_hr_import | AD→HR Import Pipeline |
05:30 | Matcher Daemon | VP↔HR Trigram (4 Partitionen) |
05:45 | name_extractor | GF-Namen aus raw_text (130K) |
05:50 | gf_email_enricher | GF-Email Pattern-Erkennung |
05:55 | advertsdata_vp_matcher | AD↔VP Trigram (GIN-optimiert) |
06:15 | momentum_tracker | Woche-über-Woche (2K Firmen) |
06:30 | placement_scorer + wakeup_checker | Score 0-100 für 1.36M Jobs + Pipeline-Wakeup |
07:00 | stier_digest | Neue S-Tier Jobs → Telegram |
07:30 | skill_extractor | 28 Skills, 200K Jobs/Batch |
08:30 | morning_briefing | Telegram-Statusbericht |
| Spider (06:00-09:00 auf W1+W2) | ||
06:00-09:00 | 12 Spider (Interamt, Stellenwerk, AWO, DRK...) | 25K+ öffentliche Stellen täglich |
Seit April 2026 sorgt das Always-Working System dafür, dass alle Crawling- und Enrichment-Prozesse permanent arbeiten. Kein Daemon bleibt unbemerkt idle — automatische Erkennung und Wiederherstellung in unter 60 Sekunden.
Briefing Auto-Analyst — Regel-Engine
| Bedingung | Aktion (LOW-Risk, sofort) | Cooldown |
|---|---|---|
apollo.daemon = false | Apollo-Daemon starten | 6h |
emails_pro_stunde = 0 | Enrichment-Pipeline prüfen + Restart | 2h |
| GMaps Queue pending=0 + idle | gmaps_jobs auf 'pending' zurücksetzen → Zyklus startet | 1h |
| BA-Fetcher nicht aktiv | BA-Fetcher starten | 4h |
| desc_pct < 25% + Booster down | Description-Booster neu starten | 4h |
| E-Mail-Rate < 60% | Email-Booster-Status prüfen | 6h |
| GMaps-Karriere-Funnel < 100/24h | Ungecheckte Karriereseiten analysieren | 24h |
| GVP ohne E-Mail > 50% | GVP-Kategorien analysieren | 24h |
| Alle Daemons aktiv aber Output=0 | Prozess-Status-Snapshot | 3h |
JN — Heartbeat-System
Alle Server schreiben alle 30s in worker_heartbeats: Load, RAM, Daemons, Docker-Count, Queue. Orchestrator prüft alle 60s.
JO — Adaptive LB
adaptive_lb.py routet Tasks zum besten Worker. GMaps-Docker-Load herausgerechnet. RAM + Slots entscheiden.
JP — Master-Failover
shadow_monitor.py auf W1: bei 2× Ausfall (~40s) → W1 startet Backend :8004 + Celery-Beat. Auto-Recovery.
# Deployment-Workflow:
git add -A && git commit -m "feat(KN): ..." && git push origin master
# Frontend rebuild (immer mit rm -rf .next!):
cd /home/zerocool/crawler/admin
rm -rf .next && npm run build
fuser -k 3003/tcp; sleep 8; nohup npx next start -p 3003 &
# Backend restart:
bash scripts/backend_restart.sh
Kritische Regeln
- Immer
rm -rf .nextvor Build - DB-Host Worker:
161.97.79.192(NICHT 127.0.0.1) - PgBouncer Port:
5433(NICHT 5432) - Git-Branch:
master - Backend NUR über
scripts/backend_restart.sh
Daemon-Verteilung (42)
- Master (13): orchestrator, reaktor, harvester, celery, heartbeat, bot, never-idle, auto-analyst, tis, aoe-executor, doku-updater, docker-monitor, apollo
- Worker-1 (12): ats-crawler, mass-crawler, salary, phone, description, enrichment, quality, desc-booster, shadow, emptype, heartbeat, gmaps-daemon
- Worker-2 (12): career-page, career-html, triple-enricher, ba-fetcher, description, desc-booster, domain-resolver, data-propagator, contact-completer, career-miner, heartbeat, gmaps-daemon
- Worker-3 (7): description(shard4+5), desc-booster, career-page, triple-enricher, career-html, heartbeat, gmaps-daemon
+ 2 hiberniert: ats-email-enricher, domain-email-booster (reaktiviert bei >500 neuen GMaps-Domains/Tag)
Abgeschlossene Meilensteine
/personaldienstleister zwischen Firmendatenbank und HR-Kontakte. Klassifikation 3-Schicht auf unified_companies.is_personaldienstleister: Schicht 1 Name-Keywords (3.020 Treffer: personaldienstleist/zeitarbeit/recruiting/staffing/headhunter/etc), Schicht 2 industry-Match (91), Schicht 3 GVP-Member-Match (1.170) → 4.281 PDL-Firmen total (4.118 mit Jobs, 161.103 Stellen). Backend /api/v1/personaldienstleister + /personaldienstleister/kontakte (1.843 HR-Kontakte bei PDL). Frontend Light Theme analog /firmen, Tabs Firmen/Kontakte, KPI-Row, 2-Spalten Card-Grid, Copy-Buttons, Nav-Eintrag mit "Neu" Badge. Commit f7efeb4.PGPASSWORD → mv_advertsdata_stats / mv_matcher_stats / mv_source_buckets / mv_firmen_distinct waren bis 580 Min stale. Fix: ~/.pgpass + alle Crons mit PGPASSWORD=. NEU scripts/mv_health_check.py: scannt 18 MVs alle 10 Min via refreshed_at-Spalte oder Filesystem-mtime. Pro MV erwartetes Refresh-Intervall (2-30 Min). Stale (>3× erwartet) → Auto-REFRESH (CONCURRENTLY mit Fallback non-concurrent). Telegram-Alert bei Fehler (1h Cooldown pro MV). Daily-Report 09:10. Plus 4 UNIQUE INDEX auf refreshed_at für alle 4 betroffenen MVs (vorherige (1)-Expression-Indexe waren für CONCURRENTLY ungeeignet). Crons: */10 auto-refresh + 09:10 daily-status. Commit c0e5a47.telegram_message_log + Patch in telegram_utils._send_raw persistiert jede gesendete Nachricht (fail-silent). Backend _read_telegram_log(20) → telegram_log-Feld in /api/monitor/full. Frontend rTgLog(): scrollable Liste, kategorie-farbig (rot=critical, amber=watchdog, gold=quota, violett=mv_*, grün=daily), relative Zeit. Layout: Paid APIs+IPRoyal jetzt rechts oben (col 7, row 1) statt unten, Live Throughput ausgeblendet. IPRoyal-Tile: zeigt MB statt 0.00 GB bei kleinem Verbrauch, 1px-Mindest-Bar. Grid-Compactness: grid-template-rows minmax(170/190/190px, max-content) + align-content:start — kein Freiraum mehr zwischen Enrichment↔Matcher und Signals↔HR-GVP. Commits 4fbf015 / 123820b / 59791ad / c0e5a47.description='[nicht extrahierbar]' markiert (557k davon BA). Playwright-Fetcher hatte nur 3% Hit-Rate — ~50% BA-Jobs sind expired (404 auf SPA-Shell) + Anti-Bot auf den Rest. Lösung: offizielle BA REST-API https://rest.arbeitsagentur.de/jobboerse/jobsuche-service/pc/v4/jobdetails/{base64(job_id)} mit Header X-API-Key: jobboerse-jobsuche, Response-Feld stellenangebotsBeschreibung. scripts/ba_api_fetcher.py: aiohttp + 15 concurrent, 2000/Batch, ~100/s Throughput, 65% OK / 35% expired (404 → description='[expired]' + is_active=false) / 0 Err bei c=15. ETA: 557k Backlog in ~1.5-2h durch. KPI-Impact: jobs_combined 2.431k → ~2.225k (-206k expired deaktiviert), desc_pct 50.3% → ~75-80%. Swap: alter ba_description_fetcher gestoppt (PID 715779), Cron live + crons/master.crontab*, 10 Refs geswappt (main.py, admin_api.py, heartbeat_sender, auto_healer, telegram_control_bot, critical_alert, pipeline_wakeup_checker, briefing_auto_analyst, tis_scanner, never_idle_monitor). Admin-Daemon-Label: "BA-API-Fetcher / BA REST-API (110/s, 65% OK) / master".scripts/workday_api_fetcher.py, nutzt /wday/cxs/{tenant}/{site}/job/{path} statt Playwright. Site-Fallback External → Careers → External_Career_Site. Result: 36/s, 72% OK, 5% expired, 23% unbekannte site-slugs. Backlog: 23.119 Workday-Jobs als [nicht extrahierbar] → ETA ~11 Min. Cron */10, LoadGuard. Alter workday_desc_fetcher.py hatte 0% Hit-Rate (Regex-Bug: erwartete {career_path}/job/ statt /job/).w4_load_alert.py */5min Telegram. W1-Zombie-Intervention: 25 gmaps-worker-Container vom docker_watchdog auto-restarted, Load 354. Fix: docker_watchdog Cron-Line entfernt, Container gewiped, Load 354→10. Stufe 3+5 canceled (Generator saturiert / W4-Schutz). Stufe 4 pending (IPRoyal-Upgrade-Entscheidung).gmaps_query_generator_v3.py reiht die 161k offenen Kombinationen ein (prio=3, nach S1-S3). Default OFF (env GMAPS_FULL_MATRIX=1). Historische Yield: median 2 Firmen/Query → Prognose +320-480k Firmen in 4-7 Tagen bei Aktivierung. gmaps.total 438k → ~800k. Bundle C wartet auf Sandro-Freigabe.Teil 1 Auto-Solver-Tuning:
LOAD_THRESHOLD 12→8 (W4 hat 8 vCPUs, Load 8 = 100%). fix_idle_in_tx() Whitelist: application_name NOT ILIKE '%enricher%' + query-pattern Filter (contact_company_email/phone) → verhindert Termination legitimer Enricher-SELECT→HTTP-Cycles (Lektion #65/#67-Schutz).Teil 2a Contact-Completer 70/30: 2 Sub-Queries — 70% fresh (<7d, DESC) + 30% aging (>7d, ASC).
ORDER BY hashtext(id) %% 10000 Sampling auf 591k Rows war zu teuer (statement_timeout 30s+, Seq-Scan). Lösung: ORDER BY crawled_at ASC nutzt vorhandenes idx_jobs_active_crawled → Index-Scan 38ms. Live-Verify: Batch: fresh=350 + aging=150 = 500.Teil 3 Zufluss-Analyse: Discovery-Crons bereits konservativ (1×/Tag) — kein Drosseln nötig. Hauptzufluss ats_recruitee 1426/h ohne Email = spider-driven, nicht discovery-driven. Aging-Bias ist primärer Lever.
Worker-Sync: contact_completer + w4_auto_solver auf W1+W2+W5, daemon_keeper restartet binnen 1min. Lektionen #90 (Aging-Bias Index-Scan) + #91 (LOAD_THRESHOLD = vCPUs).
Teil 1 Heartbeat-Partitionen:
heartbeat_sender.EXPECTED_DAEMONS 1:1 mit daemon_keeper — alle _pN.sh-Wrapper individuell trackbar (vorher generic Pattern → keine Partition-Crash-Detection). W1 +6, W2 +4, W3 +4, W5 +8 Wrapper. Sync 5 Worker."6 offline"-Bug-Fix (Backend-Normalisierung): Heartbeat schreibt _pN.sh in
daemons_running-Set. Backend ALL_DAEMONS hat generic Names. Set-membership 'phone_enricher' in {'phone_enricher_p0.sh',...} = False. Fix in _get_all_daemon_status(): re.sub(r'_p\d+\.(sh|py)$','',x) → base-name auch ins Set. Verify: Online 27 | Offline 0 (vorher 6 offline). Lektion #88.Teil 2 W4 Auto-Solver
scripts/w4_auto_solver.py Cron */2: (a) pg_terminate_backend WHERE state='idle in tx' >60s, (b) wenn >2 parallele MV-REFRESHes → jüngste terminate, (c) bei load > 12 VP-Matcher pkill (daemon_keeper restartet). Telegram-Alert nur bei Action. Live-Verify: W4 Load 12.27 → 9.87 in 5min, killed_idle_tx=1. Lektion #89.Teil 3+4 implizit: alle 27 Daemons online, Throughput 259/h → 615/h (2.4×), Phone-Enricher bereits max-config. CC-Backlog 591k > 7d alt → eigene Iteration.
main.py:_read_iproyal_quota(): 2-Tier-Lookup (Tabelle iproyal_budget_history bevorzugt → State-File mit OVERHEAD_FACTOR=2.16 Fallback). Source-Indikator (state_file_corrected/iproyal_budget_history). Live-Verifikation: used_gb 4.875 → 2.257 GB. Monitor v6.7 (monitor/index.html): Grid-Row 4 mit 3 neuen Tiles + JavaScript-Loop alle 30s — (1) 📊 Pipeline Trends 48h (4 SVG-Sparklines: email_pct/desc_pct/phone_pct/quality_avg) via /api/monitor/trends, (2) 🌐 IPRoyal Burn-Rate 7d (SVG-Liniendiagramm + cap-Linie + Forecast) via /api/monitor/iproyal, (3) 📋 Audit 48h (Top-Categories Table mit ACTION_REQUIRED-Markierung) via /api/monitor/telegram-audit. Footer-Bump v6.6→v6.7. LLM-Classifier W2 scripts/telegram_audit_classifier.py: Claude-Haiku-4.5-API, BATCH=50, MAX_BATCHES=4, self-contained (chdir + .env-Loader). Cron 0 */4 auf W2. Smoke-Test: 12 Alerts klassifiziert (5 INFO, 4 FALSE_POSITIVE, 2 ACTION_REQUIRED, 1 INFO). Phase 4.2 Quad-Sync scripts/daemon_config_sync_check.py: 4-Way-Sync keeper↔heartbeat↔main.py↔critical_alert. Norm-Helpers: Partition-Wrapper auf Basisname, Shard-Suffix gestrippt, main.py nur auf Master. Cron */30 auf W2. 2 echte Drifts erkannt auf W1+W5 (heartbeat_sender.EXPECTED_DAEMONS fehlen 4-5 Enricher). Phase 4.3 Heartbeat-Completeness scripts/heartbeat_completeness_check.py: vergleicht heartbeat_sender vs. worker_heartbeats.daemons_running. Cron */15 auf W2. Smoke-Test: alle 6 Worker 100% Coverage. NOTIFICATION_CONFIG +6 Categories auf alle 5 Worker synced. Lektionen #84+#85+#86 neu.pipeline_metrics_hourly + 11-Metriken-Collector (Cron hourly). Phase 2 Telegram-Audit: telegram_audit-Tabelle + send_alert-Hook. Phase 3 Deploy-Drift-Audit: md5-Vergleich Master↔5 Worker (Cron */4h). Phase 4.1 Code-Freshness: mtime(script) vs. starttime(proc) (Cron */15). Phase 5 IPRoyal Burn-Rate mit 3-Tier-Fallback (API→Playwright→state_file, Skeleton-Mode aktiv). 3 neue API-Endpoints: /api/monitor/{trends,iproyal,telegram-audit}. Sofort-Befunde: 4 stale Daemons + 5 Worker mit Drift gefunden. Follow-ups: Playwright-Install auf W2, LLM-Classifier (Claude API), Frontend-Sparkline-Tiles, Phase 4.2/4.3.30 2 * * * auf daemon_keeper-verwalteten Daemon auf W3 (Load 1.84, 45GB RAM frei). 4-Way-Sync (Lektion #50): daemon_keeper EXPECTED + heartbeat_sender EXPECTED_DAEMONS + main.py ALL_DAEMONS + critical_alert WORKER_DAEMONS. PyMuPDF (fitz) nachinstalliert auf W3 via pip --break-system-packages. DB_HOST=161.97.79.192-env explizit gesetzt im restart-Command. Live-Betrieb: 295 PDF-Extractions in 10min, State-File unverändert (Cache-Extraktion, kein neuer Proxy-Traffic). Kill-Recovery-Test: pkill + 75s warten → daemon_keeper (Cron */1 auf Master) hat automatisch restartet. Neue Lektion #80 (Logger-Duplizität ist false-positive von Lektion #71/#76 Doppelstart — hier FileHandler + StreamHandler + 2>&1-Redirect).scripts/eures_fetcher.py als Skeleton angelegt: API-Discovery-Loop + Safe-Abort bei unerreichbarer API (Telegram-Warning + Exit 0, keine DB-Inserts). Script ist Auth-Ready via EURES_API_TOKEN env-var — Re-Enable ohne Code-Change wenn API wieder öffnet. Kein Cron installiert (wäre Telegram-Spam). DACH-Abdeckung ausreichend via BA-API (744k), Adzuna (161k), Arbeitnow (1.1k) = 906k Jobs. Neue Lektion #79 (API-Discovery + Skeleton-Pattern).scripts/arbeitnow_fetcher.py angelegt, Schema auf echtes job_advertisements adaptiert (content_hash statt job_uid, position statt title). Full-Run: 1.051 Jobs in 40s, 100% desc-coverage, 165 Remote, 556 mit employment_type. API liefert real ~1.1k (nicht 50-100k wie Prompt erwartete). Cron 0 */6 installiert mit cd-Prefix. UPSERT-Pattern konform zu Lektion #78 (DO UPDATE SET crawled_at=NOW()).ba_api_fetcher läuft (PID 1371505). Backlog 7.7k → 116 (98.5% Reduktion seit Prompt-Erstellung). Total BA-Jobs: 744.023, davon 743.907 mit Description = 99.98%. 88 residual permanent-nodesc (BA-API liefert für diese Jobs keine Description). Keine Code-Änderung nötig, Config war optimal.p_ats_matcher_optimize.p27 vermuteten Upsert-Bug bei personio/softgarden/lever. Lektion #78 erstellt.ON CONFLICT (content_hash) DO UPDATE SET last_seen_at=NOW(), is_active=true — aber crawled_at wurde nicht mit-upgedatet. Konsequenz: Bei Duplikat-Crawls (selber content_hash) bleibt crawled_at beim ersten-Insert-Zeitpunkt → Monitor-Query MAX(crawled_at) > NOW()-48h erkennt Spider als "tot" obwohl er aktiv läuft. Fix: DO UPDATE SET last_seen_at=NOW(), crawled_at=NOW(), is_active=true in crawl_haufe_dvinci.py:50 + icims_spider.py:228. rsync auf 5 Worker (md5 identisch). Re-Run: dvinci 6→358 inserts/10min ✔, MAX crawled_at 13:27→13:33 UTC. Monitor grün: last_48h=358 (>> Threshold 20). Lektion #78: Audit-Befehl grep -rn "ON CONFLICT.*DO UPDATE" | grep -v crawled_at für weitere Spider-Audits. Das ist auch die bei p27 vermutete Upsert-Ursache bei personio/softgarden/lever!534240513821bd...), Remote-Import-Test auf allen 5 Workern grün. Smoke-Test: icims findet Actalentservices 20 jobs + Bridgecrestsuites 5 jobs + 404-Handling; dvinci iteriert 884 Jobs erfolgreich. 0 OperationalError + 0 InterfaceError. Beide laufen via Master-Cron 0 12 + 30 12 daily — kein Daemon-Restart nötig.fcntl in advertsdata_pdf_extractor.py:_acquire_singleton_lock() — Aufruf als erste Zeile in _safe_main(). Lock-File /tmp/advertsdata_pdf_extractor.lock. Sanity-Test: erster Start OK, zweiter Start liefert Lock belegt — andere Instanz läuft. Abort. Exit 0. Cron konsolidiert: genau 1 PDF-Cron (30 2 re-enabled mit Lock-Safety) + 1 Proxy-Cron (0 */6 --limit 2). AD-Cluster-Klarstellung: Watchdog meldet inserts/60min=0 seit 15:05 — das ist by-design nach Emergency-Drossel (8 Jobs/Tag, 6h-Intervall). BANNED-Flag des Watchdogs irreführend bei Emergency-Config, Follow-up zum adjust. Lektionen #76+#77 (umnummeriert wg. Konflikt mit paralleler Session zu ATS-Inventur) als gefixt markiert.[11:16:27] Turbo-Lauf #12 startet (20,000 Queries). Actions: pkill gmaps_daemon + gmaps_turbo + docker stop/rm aller 13 Container + Queue-Reset UPDATE gmaps_jobs SET status=pending WHERE status=running (14.800 rows). daemon_keeper */1 restartete automatisch → neuer Daemon PID 2559721 (14:41:54). Log: [14:41:54] === Turbo-Lauf #1 startet (3,000 Queries) === ✔ NEUER CODE AKTIV. Docker-Worker spawnen, Queue 15000→9000+6000 in <10min.ats_crawler_daemon auf W1 lief seit 14.04 mit ALTER Code-Version (Prozess-Alter ≠ Code-Alter). Nach Force-Restart: No module named crawler.spiders.ats.join_spider. Master hatte 11 Spider im Dir, W1 nur 7 — join_spider.py, breezy_spider.py, pinpoint_spider.py fehlten. Alle 6 Spider-Imports im gemeinsamen try-Block → 1 fehlt = 5 andere skippen = stuck-state 40h+. Fix: rsync vakanzpuls/crawler/spiders/ Master→W1 + Daemon-Force-Restart. Spider laufen wieder: Hogast 7 Jobs, Minor Figures 3, Viataurus 12. Pending-Counter 23.699→22.896 in 4 Zyklen. Zweites Problem: 0 DB-inserts für personio/softgarden/lever trotz gefundener Jobs → UPSERT-Bug separat zu fixen. Lektionen #73 (Prozess≠Code) + #74 (try-Block All-or-Nothing) + #75 (Deploy-Drift-Audit).monitor_health_alert.py:check_idle_txn() mit ResilientConn + statement_timeout=5s live. Smoke-Test 5× green: monitor/full 0.07s, briefing 0.00s, tg-log 0.01s, idle-tx OK, remote 0.02s. External HTTPS 5× parallel 0.013-0.019s (DoD <5s um Faktor 250× unterboten). 0 KRITISCH-Alerts in 30min (vs. 95+ in Ausgangslage). Audit: kein Monitor-Script nutzt noch psycopg2.connect direkt. Follow-up bleibt Cold-Miss-Refactoring (Parkplatz #45) + Backend Legacy-Router (Low-Prio, SQLAlchemy bereits resilient).30 2 + 0 14), zusätzlich lief der 02:30-Prozess 11h statt zu terminieren (MAX_BATCHES=5 respect-issue). Log zeigte jede Zeile doppelt als Indikator. Sofort-Fix: pkill aller PDF-/Proxy-Prozesse, beide PDF-Crons auskommentiert, Proxy */15 --limit 25 → 0 */6 --limit 2 = 184 MB/Tag Safe-Rate. 14 Tage × 184 MB = 2.6 GB, Puffer 1.9 GB bis 01.05. Cron-Backup /tmp/crontab_pre_budget_emergency.bak. Lektionen #71 (PDF-Doppelstart-Detection) + #72 (Counter-Overhead ≠ Volumen-Problem).jobs.awo.org + jobs.caritas.de weiter tot (seit >3 Wochen), 4 zentrale Sources bleiben korrekt deaktiviert. Caritas-Nachfolge-URL liefert HTTP 200, aber ohne Schema.org JSON-LD → separater Parser nötig. Keine Aktion erforderlich. Empfehlung: Status-Briefing-Wording anpassen (Lektion #69: Zentral-Jobbörse vs. Einzelstandort differenzieren).--limit 75→25 (Budget-kritisch, pre-drossel war 27 GB/Tag bei 9 GB-Budget). Zusätzlich entdeckt: IPRoyal Dashboard zeigte 4.98 GB used, unser State-File zählte 8.05 GB → echter Overhead-Faktor empirisch 2.16× (nicht 3.5). State auf IPRoyal-Ground-Truth gesetzt (4.98 GB = 55% von 9 GB Cap). Code-Faktor 3.5 bleibt als Safety-Puffer (konservativ = budget-safe). inserts/60min = 155 ✔ Scraper liefert. Rest-Budget 4.02 GB für 15 Tage = 268 MB/Tag Safe-Rate.default_pool_size 40→80 + server_idle_timeout 600→1800. Backup pgbouncer.ini.20260416_131555.bak bleibt für Rollback. Wichtig: PgBouncer läuft auf Master (161.97.79.192), nicht auf W4 — die W4-Instanz läuft zwar, aber mit 0 xacts/s (ungenutzt).{meta, result:[...]}) — HTML-Detection-Patch bleibt für eigene Iteration p_bamboohr_html_detect.default_pool_size 40→80 + server_idle_timeout 600→1800. Diagnose: pooler error: query_wait_timeout-Flood, 21 idle-in-tx + 10 active = 31/40 Slots permanent blockiert. Fix eliminierte Pin #95 "KRITISCH: Datenbank nicht erreichbar" (95+ Events → 0 neue in 30 min). p16: monitor_health_alert.py:check_idle_txn() auf ResilientConn + statement_timeout=5s. Backend SQLAlchemy pool_pre_ping bereits resilient. p05: 5 Scripts Syntax-Check + breezy/pinpoint/jazzhr/bamboohr Smoke-Tests. bamboohr DIAG: Schema unverändert ({meta, result:[]}), niedrige Job-Rate durch Source-Churn (HTML-Redirects auf Landing). p03: Bandwidth-Counter 9.02 GB→0 reset, manueller Testlauf 5/5 saved, Faktor 3.5 bestätigt. p09: awo/caritas-Deaktivierung korrekt (DNS-fail der 2 Zentralbörsen), 878/899 Sources (97.7%) aktiv, Status-Briefing-Wording irreführend. p28: gmaps_daemon.py QUERIES_PER_RUN 20000→3000 + timeout 4h→1h + Queue-Reset bei TimeoutExpired. Deploy auf W6, greift bei nächstem Daemon-Restart. p27: Baseline erfasst — nur ats_personio (0/24h, 40h stuck), softgarden/greenhouse/lever/BA liefern normal. 24h-Recheck 17.04 13:42. Lektionen #68 (pool_size vs. idle_timeout), #69 (Zentral-Jobbörse vs. Einzelstandort), #70 (gmaps Chunking-Kalibrierung). Reports .claude/report_shadowfix_p*.md.career_email_miner: Connection hielt Transaction 5+ Min während HTTP-Crawl → PgBouncer-Slot blockiert → monitor/full timeoutet. Fix: conn.commit() nach cur.fetchall() in career_email_miner (Z.109) + contact_completer (Z.107) + data_propagator get_db() in try-Block + main-Exception-Handling für Reconnect-Safety. Deploy auf alle 5 Worker. Lektion #67: conn.commit() nach SELECT Pflicht wenn danach HTTP-Loop folgt (Unterschied zu #65 async: gilt auch synchron).advertsdata_vp_matcher_daemon.connect(): eigene Funktion → nutzt jetzt _dbutils_connect(DB_DSN) + SET statement_timeout='45s' (schützt Trigram-JOINs vor runaway). (0.2) briefing_auto_analyst.py: 4× psycopg2.connect(**DB_CONFIG) durch Fallback-Pattern mit f-string-DSN-Konstruktion für db_utils ersetzt. (0.3) Audit SELECT DISTINCT company_domain — career_email_miner + phone_enricher haben bereits try/finally+timeout (aus NP-AO-v5). Cluster-Status: W1=26, W2=10, W3=15, W5=14 Daemons, W4 Load 6.3, Backend 200.import psycopg2 + fehlendes from db_utils import → Fallback-Block einfügen. psycopg2.connect(DB|DB_DSN|DB_URL|DSN|dsn|DATABASE_URL|PG_DSN|PG_URL) → (_dbutils_connect(X) if _dbutils_connect else psycopg2.connect(X)). AST-parse vor Write verhindert Broken-Code. 168/235 Scripts (71%) mit db_utils. Deploy auf alle 5 Worker via deploy.sh ✅. 67 Scripts nicht automatisch migriert (dict-Config wie `psycopg2.connect(**DB_CONFIG)` oder exotische Import-Forms) — Original-Code unverändert, null Regression-Risiko. Backend 200 in 2ms.psycopg2.connect() ohne TCP-Keepalives → strukturelle Connection-Leak-Quelle. Phase 1 (6 Daemons: ats_crawler, career_page, vp_matcher, aoe_executor, briefing_auto_analyst, apollo_enricher) + Phase 2 (4 Enricher: contact_completer, career_email_miner, phone_enricher, ats_email_enricher) migriert. Pattern: from db_utils import connect as _dbutils_connect + (_dbutils_connect(DB) if _dbutils_connect else psycopg2.connect(DB)) mit Fallback. scripts/deploy.sh neu — einheitliches Rsync auf alle 5 Worker inkl. db_utils.py. 12/12 kritische Scripts haben db_utils ✅. W4 Load 6.11 stabil. Lektion #66. Phase 3-6 (~50 Scripts: Advertsdata/Spider/Monitoring) folgt. Commit 4aca747.firm_chunk 500→100, sleep_batch 0.3→2.0s, sleep_empty 30→60s), Nacht+Übergang eigene Configs. Phase 3: safe_mv_refresh.sh mit SSH-Load-Check (skip wenn W4 >10), Cron `4-59/10` ersetzt. Phase 4: ats_email_enricher conn.commit() nach get_batch() vor async HTTP (schließt Transaction, kein idle-in-txn mehr). Matcher PID 1498743 neu. Final 6/6: W4 12.5 sinkend, JB=887k/ATS=343k, Offline=0, idle-in-tx=0. Lektionen #63 (firm_chunk tageszeit) + #64 (MV Load-Check) + #65 (commit() vor async). Commit 3b2d005.mv_source_buckets war seit 14:10 stale (7h!). Ursache: Cron REFRESH MATERIALIZED VIEW CONCURRENTLY erforderte UNIQUE INDEX — MV hatte nur expr_idx → silent-fail bei jedem Cron-Run. Fix: CONCURRENTLY aus Cron entfernt (1-Row-MV, Lock-Dauer marginal). Manueller REFRESH → jobboersen=882k / ATS=342k / career=68k. W5: 14 Daemons verifiziert (Monitor-UI zeigte fälschlich 1). W2 career_email_miner läuft. Backend 200 in 2ms, Offline=0, idle-in-tx=4 (transient). Lektionen #61 (CONCURRENTLY ohne UNIQUE INDEX = silent-fail) + #62 (ps aux nach Daemon-Start). Commit folgt.SET statement_timeout='30s'. Plus: rsync --include='*.sh' + chmod +x auf alle 5 Worker (W3 hatte .sh-Scripts noch nicht). Audit: grep -c "finally:" aller Enricher → ALLE ✅. Worker-Daemons gekillt → daemon_keeper Restart mit neuem Code. W5 11 Python-Daemons laufen. Backend HTTP 200 in 7ms. Lektionen #59 (statement_timeout Pflicht in Enrichern) + #60 (rsync *.py+*.sh). Commits cea7ab0 + 693adf8.idle in transaction von domain_email_booster Partitionen (4× 2-5min alt). Root Cause: get_domains()/save_emails() öffneten Connections ohne try/finally — Crash zwischen execute/close → Connection bleibt idle in transaction. Fix: (3) try/finally um beide Funktionen + SET statement_timeout='30s'. (6) Global ALTER DATABASE vakanzpuls SET statement_timeout='120s'. (1+2) Backend-Restart + 4× pg_terminate_backend auf Master+W4. (5) Master description_daemon war sauber (kein Dup). Worker-Booster gekillt → daemon_keeper restart mit try/finally-Code. Lektionen #57 (try/finally Pflicht) + #58 (statement_timeout 3 Ebenen). Commit 2d36379.rsync scripts/ auf alle 5 Worker (partition-scripts komplett synchron). Fix 2: W1 Duplikate manuell gekillt (ats_crawler 3→1, shadow_monitor 3→1, quality_score_v2 2→1, ältestes PID behalten). Fix 4: daemon_keeper Duplikat-Detection jetzt AKTIV KILL statt nur melden — Pattern pgrep -af PATTERN | sort -n | tail -n +2 | xargs kill -15. Briefing-Endpoint-Timeout bleibt: W4 DB-Load (MV-Refreshes), Monitor-Endpoint läuft (7ms). Lektionen #55 (rsync nach push) + #56 (Kill statt Melden). Commit 71f0431.briefing_auto_analyst._check_email_rate_stagnation startet keinen email_booster mehr auf Master — Ghost-Kollision mit Worker-Partition-Management. Nur noch Warnung. (2) pkill domain_email_booster.py auf Master, Worker-Partitionen laufen weiter. (3) auto_healer log_max_age: TIS 10→30, Analyst 60→120, Heartbeat 10→20 (Batch-Jobs sind idle zwischen Runs). (4) critical_alert.py:588 filtert real_healer_actions (nur FEHLGESCHLAGEN/down/CRITICAL) vor Send. (5) daemon_keeper Exponential-Backoff: ≥5 Misses → Cooldown 10min→1h. W5 venv verifiziert OK. Lektionen #51-#54. Commit 273f140.crawl_pinpoint.py category="spider_error" ergänzt. (c) critical_alert.py WORKER_DAEMONS komplett synchronisiert mit daemon_keeper EXPECTED: W2 CareerHTML entfernt (nach W3 migriert, erzeugte permanente False-Alerts), +W5 7 Daemons, +W6 2. (d) ATS-Output-Warnings nur Mo-Fr (now.weekday()<5). (e) Spider-Stale-Threshold für Batch-Crawls 3d→7d. Final-Audit: 0 missing categories. Lektion #49 (send_alert MUSS category) + #50 (4-Stellen-Sync für Daemons). Commit 743ddc6.mv_source_buckets Query mit 2s statement_timeout crashed unter DB-Last → source_breakdown={} ging in 30min Stale-Cache (verletzt Lektion #20). Fix: (a) statement_timeout 2s→5s für MV-Reads (vorberechnet, darf länger), (b) Stale-Cache-Write nur wenn bool(data.source_breakdown) — leere Dicts gehen NIE in Stale. Fresh-Cache akzeptiert weiter leere Dicts (20s TTL). Verify: 17 keys, jobboersen=882k, ats=342k, ad=1.13M, meta.version 1.2. Lektion #48: Nach Redis-Flush IMMER MV-Refresh + Cache-Warmup-Checkliste. Commit ae10774.source_breakdown={}, 4 Daemons offline. Diagnose: W2 hatte 5× contact_completer (statt 2), data_propagator komplett tot, PgBouncer query_wait_timeout durch DB-Überlast. Fix: (1) W2 alle contact_completer killed + clean P0+P1 Wrapper-Restart, (2) data_propagator manuell gestartet, (3) Redis flush + Backend-Restart. Plus Phase 4: telegram_utils._msg_fingerprint() normalisiert jetzt _p0.sh/_p1.sh/... → _pX.sh, partition=0/4 → partition=X, P0/4 → PX/X. Dadurch 4 Partitionen desselben Daemons = 1 Alert statt 4 (Telegram-Spam-Fix). Final: Offline=0, Jobs=2.5M, Jobbörsen=882k, ATS=342k, AD=1.13M ✅. Commit a07a7ef.smi-Item statt als separatem Badge unter der Karte — garantiert sichtbar. Tooltip mit ↓rx ↑tx MB/s, Farbe nach Durchsatz (grün >10, cyan >1). Fix 2: AD-Scraper Cron --limit 50→75, scraper Code-Default 50→75, iproyal_budget_watchdog CAP_PCT 90→95. Budget-Prognose: 75×4/h×24h×200KB×8× = 9.2 GB/Monat = 92% von 10 GB Plan. Commit 6716adc.[ -f venv/bin/python3 ] && PYTHON=venv/bin/python3 — W5 nutzt venv (andere Worker system-python). Plus: phonenumbers pip auf W5-venv nachinstalliert (phone_enricher crash war ModuleNotFoundError). Fix 2: triple_enricher_daemon.py PAUSE_BETWEEN 120→30s, PAUSE_IDLE 3600→120s (4× busy, 30× idle schneller). Fix 3: GMaps WORKER_COUNT 10→13 an allen 3 Stellen (gmaps_turbo default, daemon_keeper, W6 @reboot cron). Verifiziert: W5 6 Partition-Daemons + Load 1.23, W6 14 Container + Load fällt 34→15, triple_enricher auf W2+W3 aktiv. Commit f25c0b7._background_refresh Sleep von 120s auf 12s gesetzt. Aber _monitor_full_sync() braucht 3-5s für ~20 DB-Queries. Bei 12s-Intervall startet der nächste Build bevor der alte fertig ist → Queue-Explosion, Backend unresponsive. Fix: 12→30s. Chain final: Frontend-Poll 15s → Redis-Cache 20s → Refresher 30s. HTTP 200 in 8ms verifiziert. Lektion #47: Refresher-Sleep > max(DB-Build×3, Cache-TTL+10). Commit d22b08a.RF 30→15, backend setex Redis-Cache TTL 300→20, _briefing_cache in-memory 300→20, _background_refresh Sleep 120s→12s, meta.version 1.1→1.2 (sentinel für Verify). Chain: Poll 15s → Cache 20s → Refresher 12s = Daten nie älter als ~12s. Commit bad4294.TOTAL_PARTITIONS 2→4 für 6 Scripts. 24 Wrapper-Scripts (6×P0-P3) + daemon_keeper umverteilt: W1 +5 (CC/Booster/Resolver/CEM P2), W5 +8 (Phone P2+P3, CC P3, ATS-Email P2+P3, Booster P3, Resolver P0, CEM P0). Partitionen auf 4 Worker verteilt. I/O-Badge: font-size 9px→11px, font-weight 700, ⚡-Emoji, eigene Zeile mit flex-wrap. Verifiziert: W1 Load 0.5→3.86, W5 Load 0.10→1.16, W2 7 / W3 5 / W5 7 partition-daemons. Commit 4bb8fa8.data_propagator.py hardcoded 127.0.0.1 → env-aware (Master-Detection + VP_DB_HOST Override). War auf W2 gecrashed.Teil B: Netzwerk-I/O pro Server. Schema
ALTER TABLE server_metrics ADD net_rx_mbps/net_tx_mbps. system_metrics_collector.py mit psutil.net_io_counters() + State-File pro Server für MB/s-Delta (Filter: kein lo/docker0/veth*/br*). Backend-Query + servers-dict erweitert. Frontend: neuer I/O-Badge pro Server-Kachel (grün >10 MB/s, cyan >1, grau sonst) mit RX/TX im Tooltip. Live: Master 1.69/1.64, W2 3.69/0.11, W3 3.49/0.15 MB/s. Commit 52ea430.PARTITION_ID/TOTAL_PARTITIONS + abs(hashtext(company_domain))%2=PID Clause. Für 2 Scripts implementiert: phone_enricher und domain_email_booster. 4 Wrapper-Scripts (phone_enricher_p0.sh, _p1.sh + dito booster), daemon_keeper W1 + W3 startet jetzt jeweils 2 Instanzen. Wrapper-Pattern statt env-var-direkt damit pgrep sie unterscheiden kann. Follow-Up NP-AI-2: career_email_miner + domain_resolver + ats_email_enricher. Commit 12c4c29.ats_crawler_daemon.py alle 6 ATS-Typen (personio, greenhouse, lever, join, breezy, pinpoint) LIMIT auf 500. (2) ats_slug_discovery.py SLEEP_IDLE 3600→300s (1h→5min). Diagnose: 0 reaktivierbare ATS-Sources mit health≥80+DACH (bereits alle wichtigen aktiv), BA-Backlog=0 (clean), Adzuna 86.6% coverage OK. Nichts weiter zu reaktivieren. Commit 9f48023.data_propagator INTERVAL 900→120s, career_email_miner 600→120s, description_daemon EMPTY_SLEEP 150→60s, ba_api_fetcher 300→60s, pdf_extractor CONCURRENT 10→20 + IDLE 600→120s, domain_resolver CONCURRENT 20→50, phone_enricher 40→80 (Ziel phone_pct 75%), gmaps_ats_fingerprinter CONCURRENT 20→60 + BATCH 400 + SLEEPs drastisch runter, description_enricher THREADS 3→8, gmaps_daemon QUEUE_EMPTY 1800→300s. Auf 5 Worker deployed + pkill für daemon_keeper-Restart. Commit 5e43b79.1-59/3, 2-59/5, 7,22,37,52. (2) VP-Matcher stage1_batch von Row-by-Row (N Trigram-Queries) auf SET-basiert (TEMP-Tabelle + JOIN, 1× GIN-Scan). (3) DB-Load-Check smarter: nur pausieren wenn REFRESH MATERIALIZED aktiv (echter Blocker), nicht bei generischem state=active. Lektion #46. Commit 327852d.monitor/index.html DG hatte tote Aliase (orchestrator, celery) und fehlende W5-Gruppe. Fixes: (1) backend/app/main.py Shard-Normalisierung — Heartbeat meldet description_daemon.py --shard N einzeln, Monitor aggregiert zu einem description_daemon. (2) ALL_DAEMONS um description_daemon erweitert, career_html_extractor W2→W3 korrigiert. (3) DG komplett umgebaut: W5-Gruppe (unsichtbar!) hinzu, Shard-Daemons auf Master/W1/W3/W5. (4) SRVS nd-Counts: M:9, W1:7, W2:6, W3:6, W5:1, W6:2. Ergebnis: 26/26 online, 0 offline ✅. Lektion #45: Daemon-Status wird an 3 Stellen definiert (keeper+main.py+monitor.html) — alle synchron halten. Commit 8fffa43.main.py:1758 career_email_miner backlog war hardcoded 0 (Kommentar "DISTINCT query below" aber nie implementiert) → Live-Query mit 15s Timeout, 15.731 Domains. Bug 2: admin_api.py:3494 HIBERNATED_PATTERNS enthielt ats_email_enricher + domain_email_booster trotz NP-Y Reaktivierung → leeres Set, "2 hiberniert" Badge weg. Bug 3: monitor/full hatte keinen fetchers.adzuna_npd Key (nur briefing) → neuer Block mit source IN ('Adzuna','adzuna_at','adzuna_ch','adzuna_de') statt ILIKE für Index-Nutzung → 161.485 Jobs, 86.6% coverage. Bug 4/5/6 waren Backend-seitig korrekt. Lektionen #38-#40. Commit 002230c.pdf_extractor MAX_BATCHES 0→10, proxy_scraper --limit 500→50. (2) Telegram category= ergänzt in crawl_breezy, crawl_icims, pipeline_wakeup_checker. (3) heartbeat_sender.EXPECTED_DAEMONS 1:1 zu daemon_keeper (worker-5 hinzu, präzise Shard-Pattern). (4) daemon_keeper Duplikat-Detection: python-only count (bash-wrapper excluded). (5) db_utils.py selbst env-aware — Master-Detection via hostname, VP_DB_HOST Override, Fallback 161.97.79.192. Behebt stillen W3 domain_email_booster Bug. Lektionen #34-#37. Commit 7dcd7fa.gmaps-daemon.service SYSTEMD auf W1 aktiv (NP-W hatte nur pkill gemacht, systemd-Unit übersehen) → respawn-loop spawned 25 Docker-Container + 502 Chrome. Fix: sudo systemctl stop+disable gmaps-daemon.service + docker kill/rm all + pkill chrome. Load 307→63 in 3 min. Zusätzlich: W5 description_daemon Shard 7 doppelt, jüngere PID 3746574 killed. W6 Load 64 verifiziert OK (10 Container, turbo env GMAPS_WORKER_COUNT=10). Parkplatz #58/#59/#60 ✅. Lektion #33: GMaps-Respawn-Quellen sind DREI (cron + systemd + docker-restart-policy), nicht nur eine.scripts/phone_enricher.py → enrichment/ auf Master+W1 (#51 ✅), (4) 5 Worker-Scripts DSN+TCP-keepalives: ats_email_enricher, career_html_extractor, domain_resolver, quality_score_v2, ats_crawler_daemon. Alle auf 4 Worker rsynced. Phase 1 Auffälligkeiten: W1 Load 324 🔴 (#58 NP-AB dringend), W5 Shard 7 doppelt (#59), W6 Load 64 (#60). Lektion #32. Commit 96e7459.gmaps_turbo.py Code-Default 25, (b) daemon_keeper Restart-Command ohne env, (c) W6 lokal docker_watchdog */3 (W2-Pattern geerbt), (d) @reboot Cron ohne env. Fixes: Code-Default 25→10 (Commit 559710a), daemon_keeper restart-command mit GMAPS_WORKER_COUNT=10 (Commit 3aa5d18), docker_watchdog auf W6 DISABLED, @reboot-Cron mit explicit env. Load 296→21.77 in 2min, 25 Container→0. Lektion #31: GMaps Worker-Count an 3 Stellen sync halten.docker_watchdog */3 + worker_watchdog */10 aktiv (NP-F hatte nur Master-Cron deaktiviert). docker_watchdog respawn'd alle 3min die NP-W-gekillten gmaps-Container = Endlos-Loop. Fixes: career_html_extractor W2→W3 migriert (daemon_keeper EXPECTED), W2 lokale Watchdogs auskommentiert, W6 GMAPS_WORKER_COUNT=10 (default 25 = Load 314, jetzt Load 1.33). ATS-Revert NP-V Init 4: 18/20 reactivated waren US-Sources (Auto-Deactivated Non-DACH, health=30, Filter-Bug `=100` matched AVG nicht Einzel). Final-Loads: M 2.45 / W1 0.23 / W2 0.86 / W3 0.08 / W5 0.09 / W6 1.33 — alle <80% ✅. Pipeline OK: phone_pct 23.71→28.24% (+4.5 Pkt/h), 880 Jobs/30min, 134k GMaps done. Lektionen #27-29. Commit fdb615a.gmaps_turbo.py finalize() markierte ALLE Queries erst nach dem letzten Container als done → done_1h=0 für gesamten Batch (83-167min). Alle 5 Worker (W1/W2/W3/W6/Master) zeigten 0/h im Monitor. Fix 1 (Permanent): Per-Container-Finalisierung: split_and_write() gibt chunk_ids per Split zurück → durch start_workers() propagiert → wait_for_completion() ruft sofort nach Container-Exit import_csv_file() + finalize(chunk_ids) auf. done_1h steigt nun nach 8-17min statt 83-167min. Fix 2: gmaps_daemon.py QUERIES_PER_RUN 3000→600 (600÷4=150/Container @9 Queries/min = 17min, 600÷8=75/Container = 8min). Fix 3: /api/monitor/gmaps-cluster +started_1h-Spalte als sofortiger Aktivitäts-Proxy (wird bei prepare_batch() sofort gesetzt, auch wenn noch kein Container fertig). Monitor zeigt jetzt "~N lfd" (gold) statt "0/h" (rot). 15.200 stuck-running Jobs (aus altem Batch-Run) auf pending zurückgesetzt. W5 erklärt: dedizierter advertsdata-Scraper, NICHT im GMaps-Cluster. Deployed: rsync auf alle 4 Workers (W1/W2/W3/W6), gmaps_turbo pkill, daemon_keeper respawn mit neuem Code. Queue: 110k pending bereit. Lektionen #141 (Batch-Finalize erst nach ALL-Containers = done_1h=0), #142 (Batch-Größe kalibrieren an Container-Finish-Zeit), #143 (started_1h als Aktivitäts-Proxy). Commit ffcba51..claude/HANDOFF_18apr_session.md fuer nächste Session. 18 Commits Session dokumentiert, Cluster-Snapshot (jobs_active 1.411.813, enriched 1.385/h, W4 Load 12.70 chronisch, GMaps Queue 0/5000, IPRoyal 7.32/10 GB, 98 UNKNOWN Telegram-Audits). 9 neue Lektionen #88-#97. 5 Top-Follow-ups priorisiert (W4-Load, ats_join 13.6d off, UNKNOWN-Audit-Classifier, CC-Backlog 591k, Email-Pct 65.1%→80%). CLAUDE.md komplett für 17.04-Stand. Commit 01790d4.quality-backend, 6 Commits, +2.359 Zeilen: Phase 0 (0c84382): mv_quality_tiers MV (1.41M Rows), validation_results + dedupe_candidates Tables (job_id UUID angepasst), trust_score SMALLINT-Spalte + Partial-Index, MV-Refresh-Cron */10 (statement_timeout 120s). Phase 1 (00a1a88): scripts/quality_score_v3.py (neue Gewichtungen Email25/generic:10, Desc15/10, Domain10, ATS10) + 8 neue Endpoints /api/v1/quality/* (tiers, tiers/history, inspector, inspector/{id}, inspector/export xlsx+csv, inspector/bulk PATCH, validation-stats, dedupe-stats). Redis-shared Cache via app.cache (qa: prefix). Phase 2 (d314221): email_validator_free.py (W2 Daemon, 5 free Checks syntax/MX/not_generic/name_match), dedupe_engine.py (3 Strategien email_exact/firma_domain/titel_ort), gf_name_matcher.py, Trust-Score-Berechnung in v3. Phase 3 (f653c30): gvp_validation.py (8.406 GVP-Emails in 3:26min, 97.8% MX, 58.4% not_generic). Phase 4 (c11cd34): /customer-overview + /trust-score/{id} Endpoints + min_quality/min_trust/exclude_generic Filter auf /jobs/search. Live-Demo-Zahlen: astrein 47.266 (×3 über Prompt-Ziel 15k) / gut 443.681 (×5 über 80k) / Validation 18.805 Emails / Dedupe 217.665 email_exact. trust_score 1.560→10.041. Lektionen #100-#104.filters.py build_order_clause auf COALESCE(col, 0) DESC umgestellt, damit Planner den Expression-Index matcht (vorher "quality_score DESC NULLS LAST" triggerte Bitmap Scan). EXPLAIN 7.491ms → 0.254ms = 29.500× schneller. Plus trust_score als Sort-Key whitelisted. Lektion #132 (Expression-Index-Matching erfordert identische ORDER-BY-Expression). Fix 4 HIGH Sidebar: Datenqualitaet aus INTELLIGENCE (Item 9/11) → MARKT (nach HR-Kontakte, Badge "Neu"). globals.css --sidebar-w 240px→260px + textOverflow:ellipsis entfernt — Personaldienstleister ist voll lesbar. Fix 5: FALSE-ALARM nach Diagnose (Monitor=Briefing=DB exakt, heartbeat_sender Master+W2 aktiv). Lektion #133 (Vor Fix erst Source-of-Truth-Vergleich).localhost:8003/api/v1 → /api/v1. stFetch normalisiert Legacy-Paths. FU4: 9 weitere Files frontend-wide gefixt (lib/api.ts, akquise/tagesplanung, pricing, personaldienstleister, firmen, firma, hr-kontakte, hr-kontakte/firma, stellenanzeigen/pdf). Fix 2 HIGH daten-qualitaet/page.tsx: 4 Fetches (overview/checks/customer-overview/history) mit Bearer-Token — vorher silent 401 bei auth-geschuetzten Endpoints. Fix 3 MEDIUM trust_score vollstaendig aufgefuellt: 10.041 → 57.560 Jobs (+47.519, 5.7× Coverage). compute_trust mit 3-Retry+Reconnect gegen PgBouncer-Drops. _build_trust_update_sql: DISTINCT ON(job_id) + JOIN + Diff-Filter IN der Subquery (nicht UPDATE-WHERE — sonst picked LIMIT immer dieselben Rows, Daemon-Loop no progress). Return-Tuple (updated, conn). FU1 placement/page.tsx trust_verified: 10 Factor ergaenzt (Backend-Scorer separater TODO). FU3 Dedupe alle 3 Strategien: email_exact 217.665 + firma_domain +17.437 NEU + titel_ort +35 NEU. Lektionen #127 (Filter in Subquery bei UPDATE+LIMIT) + #128 (Return-Tuple fuer Reconnect-Pattern).LOAD_THRESHOLD 8.0 → 13.0 (nproc auf W4 = 12, nicht 8 wie angenommen). Vorher 30 aufeinanderfolgende w4_auto_solver.log-Runs mit matcher_killed=True (Kill/Restart-Loop alle 2min reduzierte VP-Matcher-Durchsatz statt zu verbessern). Nachher: letzte Runs (Load 12.67, 11.56) ohne Matcher-Kill. Lektion #125. W6: gmaps_jobs hatte 5.000 stuck-running (worker-6 seit 19:49, 0 Docker-Container, gmaps_turbo-Log 11h alt in "30min-Pause"). gmaps_queue_refill --check-only Guard (pending<200 AND running<100) blockiert bei 5k-stuck. Fix: UPDATE gmaps_jobs SET status='pending' WHERE status='running' AND started_at < NOW() - INTERVAL '1 hour' + pkill gmaps_turbo (daemon_keeper spawned neu, PID 2558456→2915586). Nachher: 13 gmaps-worker Docker-Container live, pipeline aktiv (pending=2000/running=3000). Lektion #126 (Refill-Guard sollte Zombie-Trigger running>300 AND no_progress_1h bekommen).quality_benchmark.py): check_kqi_alert() nach run_benchmark(). CRIT<78%, FAIL<80%, WARN<82%, TREND>3pp/24h aus kqi_history. Test: prev=83.9%→80.8% → WARNING+TREND korrekt gefeuert. Cron 23:55 (vorhanden). Alert 2+3 — Desc+Dedupe Backlog (pipeline_health_alert.py): check_backlog_alerts() neu. Desc via mv_dashboard_kpis.jobs_active-jobs_mit_description (kein 1.4M-Scan). Dedupe via dedupe_candidates WHERE status=pending. Je WARN>150k/CRIT>250k. Test: 234k desc → WARNING, 235k dedupe → WARNING. Cron */15 (vorhanden). Lektion #163 (MV statt COUNT auf 1.4M Rows). Alert 4 — Spamhaus Check (scripts/spamhaus_check.py NEU): 4 Blacklists (Spamhaus ZEN/SpamCop/Barracuda/SORBS) via dig +short reverse_ip.zone. Test: Master 161.97.79.192 → CSS 127.0.0.3 detected, Alert gesendet. W6 clean. Cron 0 6 * * * neu. Lektion #164 (127.0.0.3=CSS vs 127.0.0.2=SBL). Alert 5 — ACTION_REQUIRED Eskalation (telegram_audit_classifier.py): Am Ende von main(), >50/24h → ESKALATION mit Top-3-Snippets. Test: 448/24h → Eskalation gefeuert. Rsync W2. Cron */2 (vorhanden). Lektion #165 (send_alert category throttle verhindert Spam).GMAPS_WORKER_COUNT=13→8 — Root-Cause Env-Drift war @reboot-Cron (nicht daemon_keeper). Container 13→8 verifiziert. Fix 2 (P1): VACUUM FULL crawler_sources 948 MB → 184 MB (-764 MB). autovacuum_scale_factor=0.01 permanent. Fix 3 (P1): 5 unused Indexes gedroppt: idx_vp_position_trgm(433MB) + idx_jobs_position_fts(326MB) + idx_quality_score(129MB) + idx_quality_grade(129MB) + idx_jobs_processed(44MB) = -1.060 MB. Fix 4+5 (P1): ATS-Crawler Reihenfolge greenhouse+lever VOR personio. personio LIMIT 500→1000, Intervall 12h→8h. Root-Cause war Cycle-Zeit >24h. Fix 6 (P1): heartbeat_sender.py: gmaps_daemon + gmaps_reaktor aus Master-EXPECTED entfernt (Lektion #144-Drift). Eliminiert ~37 false-positive ACTION_REQUIRED/Tag. Fix 7 (P2): .gitignore +gmaps/results/*.csv +turbo_batch_*.txt. 51 → 0 uncommitted files. Lektionen #159-#162.touch logs/batch_ats_discovery.log — Datei fehlte, Cron 30 */2 appendet jetzt. (2) Briefing ATS-Block 24→27 Typen: Redis DB2-Key war seit 15:25 stale (2.5h). Root-Cause: Build-Dauer ~85s bei Lock-TTL 90s => Race-Conditions servten stale Key perpetuell. Fix: redis-cli -n 2 DEL der 3 Briefing-Keys + fresh=1 über localhost:8003. Verify: 27 ATS-Typen inkl. hr4you ✅ talention ✅ coveto ✅ (generated_at=18:13). (3) Monitor ATS-Kachel: CSS 2-col → 3-col Grid (.atsg) → 27 Typen = 9 Reihen ~198px (ragt nicht mehr in benachbarte Rows). Kompakte Items (padding 2px/5px, font-size 9px, text-overflow ellipsis). .ats-idle{opacity:0.35} für Inaktive. JS rATS(): active-first Sort (aktiv_24h>0 zuerst), Header-Badge "N/M aktiv", Info-Line "M Systeme · N aktiv 24h". Lektion #151 (Redis DB-Key-Namespace mit -n 2 prüfen) + #152 (Lock-TTL 90s bei 85s-Build = Stampede-Risiko).30 */2 auf Master (3000 URLs/Run).quality_engine/api/job_store.py (verify_jobs Tabelle, ensure_table/create/update/complete/fail/get mit auto-expire 1h + set_webhook_status) + job_worker.py (process_bulk_job async background + _fire_webhook HMAC-SHA256 via hmac.new, aiohttp, delays=[0,5,30]s, 10s timeout). GET /verify/job/{id} + /webhook live. httpbin-Test: Delivery Attempt 1 OK. QS-5 quality_score v3 Migration: v2 auf W1 gekilled, daemon_keeper v2→v3 (W1 line 72), heartbeat_sender+critical_alert 4-Way-Sync, score avg 48.8 (Parität v2), v3 aktiv. P3 Briefing-Metriken (Commit b0cc3e9): _get_scheduler_state_safe() liest nicht mehr stale JSON-Datei (4 Tage alt, worker_scheduler.py seit 14.04 deaktiviert) sondern queried worker_heartbeats live (6 Worker, last_seen <10min). active_workers: finished_at-Query (immer 0 bei laufendem Batch) → COUNT(*) WHERE gmaps_docker_count>0 AND last_seen<5min. Verify: source=worker_heartbeats, 6 workers, active_workers=6, total_docker=32. Lektionen #150-#152.daemons.online/total waren None → jetzt populiert (29/29) via _get_all_daemon_status() Summary-Counts. ANO-2: w2-reaktor-1 Orphan-Record gelöscht. 8 neue Alerts: (1) Table Bloat >50% WARNING / >200% CRITICAL, (2) Unused Indexes >500MB (via pg_class.relpages statt pg_relation_size I/O), (3) HR Name-Coverage <40% WARNING, (4) Telegram Unclassified >200, (5) GMaps Discoveries/24h <10k, (6) Description Throughput/h <5k, (7) Reaktor-Workers down, (8) Enrichment-Daemons (8 kritische gegen Heartbeats). Smoke-Test: desc_backlog 234k WARNING, dedupe 235k WARNING, HR Name 38.1% WARNING korrekt gefeuert. Plus: check_db_unused_indexes() in system_audit_full.py integriert (täglicher Vollaudit). Source-Freshness per-query statement_timeout='5s' gegen 30s-Timeout gefixt.quality-frontend, 4 Commits, +1.293 Zeilen: Admin NX-84 Inspector (b4068c7): admin/src/pages/quality-inspector.tsx (+831) — Tier-Pyramide klickbar, Filter-Bar (Search, has_email/phone, source_category), Paginierte Tabelle mit Multi-Select, Detail-Drawer mit Score-Breakdown + Validation-Status + Dedupe-Matches + Actions (Email kopieren, Stelle öffnen), Bulk-Aktionen (dismiss_dedupe / mark_verified / set_tier_override), Excel+CSV Export (StreamingResponse), Demo-Mode ?view=demo (kuratierter astrein+email+phone), URL-Deep-Linking für alle Filter, 30s Auto-Refresh. 3 neue Components: QualityBadge + TierPyramid + ExportButton. Sidebar-Link in Layout.tsx + PartnerLayout.tsx. Customer Portal Quality-Upgrade (8a51642): 2 shared Components (QualityBadge + TrustBadge mit Verifiziert/Direkt/MX/Generisch/Ungeprueft). daten-qualitaet +Tier-Pyramide-Section + Trust-Metriken-Grid aus /customer-overview. search: min_quality=40 Default (Schrott raus) + neue Sort-Option "Beste Kontaktqualität" (trust_score). stellenanzeigen: min_score=40 Default. contacts: Default Min-Score 50→60 (zeigt nur okay+). hr-kontakte: Generisch-Warning ⚠ / Direkt-Kontakt-Badge ✓. Merge beider Branches (1f00141 + f505562): admin+frontend builds gruen, /quality-inspector HTTP 200 in 14ms, /customer-overview liefert astrein=47.266+validation=18.805. Sales-Demo ready für Sandro: /quality-inspector?view=demo. Lektionen #120-#124.0 2 * * *. Phase 1: 12.167 Garbage rejected (.png-URLs als Email). Phase 2: 13.800 resolved, 10.589 Jobs deaktiviert (höherer quality_score gewinnt). Phase 2b: firma_domain (sim≥80). Phase 2c: titel_ort (sim≥85). Phase 3: 203.348 not_duplicate (verschiedene Jobs, gleiche Firmen-Email). Erkenntnis: 98.9% der email_exact waren False Positives (Firmen-Sammel-Emails)./monitor-v2 (ohne Login). Commit 45d7981: Auth-Wrapper entfernt.git checkout monitor-pre-refactor -- monitor/index.html. Ersatz CC19b (2-Zeilen-Fix): (1) zoom-Media-Queries aggressiver: 1.0 default / 1.6 @2560px+ / 2.2 @3840px+ (vorher 1.5 @2560-3199 + 2.2 @3200+ + body{zoom:0.80} override), (2) body{overflow-x:hidden}html{overflow-x:hidden} statt body{zoom:0.80}. Keine Layout-Änderung, keine px→vw, kein clamp(). scripts/monitor_viewport_refactor.py gelöscht. Tag monitor-pre-refactor bleibt. Lektion #184: Viewport-Refactor setzt fluide-designed Layout voraus — bei fixed-width-Design nur zoom-Media-Queries sicher.550 Service unavailable, Client host blocked using Spamhaus = systematische False-Negatives. Master-Batch-Verify seit 18.04 11:21 stillgelegt. ~46k hr_contacts-Emails fehlerhaft als invalid/timeout/refused gespeichert. Fix: Pipeline migriert nach W1 (173.212.217.37 Spamhaus-clean). quality_engine/ rsynced, dnspython via pip --break-system-packages, config.py DB_DSN host-aware, Tier grade_bc_promote in batch_verify.py (B/C-Kontakte mit email+phone+name+position, non-generic). NOT EXISTS-Filter nur gegen overall_status='valid' → Re-Verify der Master-False-Negatives. Smoke 50: 9s/5.6/s/42 valid. Full-Run 5000: 463s/10.8/s/2726 valid (54.5%)/0 Errors. grade_after_verify.sql: 3.040 B→S, 3.235 email_verified neu. Cron W1: 15 3 * * * 2000 Emails/Tag + automatic grade_after_verify. Pool ~48k Rest (∼24 Tage). KQI: 82.40% → 83.0% (+0.6pp). Master-SMTP bleibt deaktiviert bis Spamhaus-Delisting (Sandro A2). Lektionen #180-#182.dokumente.anzeigedaten.de erreichbar via Proxy (HTTP 200), aber extract(pdf) returnte alle Felder None → [nicht extrahierbar]-Writes. Deep-Diagnose (step-by-step): (1) curl direct HTTP 000 (IP-Ban erwartet), (2) curl via Proxy HTTP 200, (3) aiohttp lädt PDFs (73 KB DE-Kleinanzeige, 426 KB Castorama Chambéry FR), (4) extract(pdf) alle None, (5) Inspection: PDFs sind DE-Kleinanzeigen + FR + EN gemischt, aber anchors-Liste nur DE-Enterprise ("Ihre Aufgaben", "Stellenbeschreibung"). Fix: Anker erweitert auf DE+FR+EN (22 Terme): DE "Beschreibung" + FR "Les missions du poste"/"Description du poste"/"Profil recherché"/"Responsabilités" + EN "Job description"/"Responsibilities"/"Tasks". Live-Verify W3 08:01-08:03: Batch 1 39/50 (78%), Batch 2 40/50 (79%), Batch 3 48/50 (84.7%). Lektion #191: Bei 0% Success niemals vorschnell "Source DOWN" — immer Parse-Logik step-by-step. Multi-Lang-Pipelines brauchen DE+FR+EN-Anker (advertsdata importiert EU-weit).info@) nicht im Benchmark zählen → Lektion #186. CC21 data_sources API: /api/monitor/data-sources + pipeline_health_alert.check_data_sources_down() (HTTP-Call statt Direct-SQL). 5 ATS-Spider als DOWN aufgedeckt → Lektion #187. CC22 Spider-Freshness: MAX(GREATEST(crawled_at, last_seen_at, updated_at)) statt nur MAX(crawled_at). 5 False-DOWN → LIVE. UPSERT-Pfade sind inkonsistent (talention SELECT-then-UPDATE setzt nur last_seen_at) → Lektion #188. CC23 DSN-Cleanup + Workday-Retry: crawl_workday_fix.py/fix_softgarden_ashby.py host-aware. Workday 7d-Retry-Gate (Pool 20k→6k, err-rate 99.6%→normal) → Lektion #189. CC24 2 Builder-Functions: data_sources in BEIDEN (_monitor_full_sync + _build_full_status) synchronisiert → Lektion #190. Plus: Jobware Spider live W5 (Cron 0 */6, URL /job/<slug>-<id>). CC25/25b Monitor max-height:420px zurück + overflow-y:auto (Layout safe). CC26 batch_ats_discovery defaultdict-Init + Fingerprinter 22→49 Patterns. CC27 successfactors Pattern entfernt (matched SAP-Infra-Subdomains wie boomi/sftp/community, 39 Junk-URLs deaktiviert). 49→48. CC28 Empty-Tiles Fix + Umantis Spider (Cron 15 */6) + ATS idle-dim opacity 0.35.ats_smartrecruiters 12.222 (15.5% der SR-Jobs) · kleinanzeigen 5.178 · ats_workable 4.054. Kein Handlungsbedarf — Normal-Pipeline-Lag, wird laufend abgebaut.Aktueller Stand: 41 Portal-Pages + 15 Landing-Pages + 40 Admin-Seiten
Dashboard, Stellenanzeigen, HR-Kontakte, HR-Firmenprofil, Schnellsuche, Radar, Radio, Unternehmen, Engpass
Info-Agenten, Kunden-Radar, Export
Momentum, Placement, Evolution, Contacts, GVP, Skill-Map, DACH-Report, Analytics, Quellen-Health, Innovationen, Daten-Qualität v2
Outreach-Center, Template-Editor, Setup-Guide, Tagesplanung
User-Management, Portal-Analytics, NX-84 Quality Inspector
Nächste Schritte (A–Z sortiert)
grep -rn "DB_DSN.*127.0.0.1" scripts/.admin.../quality-inspector?view=demo. 47k Astrein-Jobs + Excel-Export. Dedupe Backlog 235k→5.8k erledigt.Sandro-Blocker (ohne ihn kein Fortschritt)
- Domain vakanzpuls.de — blockt Outreach, SMTP, Landing, Impressum
- Spamhaus CSS-Delisting — check.spamhaus.org, blockt SMTP-Outreach
- NeverBounce (~$750/250k) für Bulk-Email-Verification
- AV-Verträge (Contabo, SendGrid, IPRoyal)
- Impressum + DSGVO + AGB Texte
- EU Art.27 Vertreter (~50€/Mo)
Technisch offen
- GMaps Phase 3+4 (Query-Generator v4 + Jobs-Bridge)
- HR Name-Coverage 36.8% → 50%+
- Monitor-v2 Ausbau (Sparklines + 24h-Trends)
- ATS-Spider Audit (ats_join 13.6d off)
- desc_pct 84.8% → stabil >85%
- 10 Redis-backed Endpoints ohne Stampede-Lock (P3)
Hintergrund — Was passierte am 13.04.2026
Um 14:29 UTC stoppten alle direkten Inserts aus db.advertsdata.com. Diagnose via tcpdump: SYN-Pakete gehen raus, kein SYN-ACK zurück — silent drop am Ziel. Test aller 6 Server: M, W1, W2, W3, W5 alle blockiert; nur W6 (164.68.123.85, kein Advertsdata-History) kam noch durch ⇒ klassischer IP-Bann nach intensivem Scraping.
Lösung — IPRoyal Residential Proxy
Plan
- 10 GB / $52.50/Monat Pay-As-You-Go
- Endpoint:
geo.iproyal.com:12321 - Hard-Cap:
9 GB(1 GB Puffer) - Residential rotating IPs (echte deutsche Nutzer-IPs)
- Reach: ~2 Mio Pages bei 5 KB/Page
Performance
- ~5 KB pro Page (statt ~500 KB Playwright)
- Live: 50/50 OK in 97s, 31 req/min @ 2 Worker
- Hochskaliert (4W): ~85k Pages/Tag
- Backlog (1.06M) bei 85k/Tag = ~12 Tage
Komponenten
Scraper
scripts/advertsdata_proxy_scraper.py
Lean requests + BeautifulSoup. Modi: --gap-filler (default) / --range START-END / --ids 1,2. Threading via --workers N. Backoff bei 403/429 (3× sleep, neue Session).
Cron: */15 * * * * — 200 IDs, 2 Worker
Quota-Tracker
scripts/iproyal_quota_check.py + Cron */30
State-File /tmp/advertsdata_proxy_bandwidth (Bytes | Monat). Telegram-Alerts 1× pro Schwelle/Monat: 50% / 75% / 90% / 100%. Tagesreport 09:00 mit verbleibendem Kontingent.
Watchdog-Härtung
scripts/advertsdata_watchdog.py
Pre-Check is_source_reachable(). Bei Source-Down: SIGSTOP auf alle Worker + EIN Info-Alert. Bei Source-Up: SIGCONT + Recovery-Alert. State-File /tmp/advertsdata_source_state — idempotent, kein Spam.
Monitor-Tile
Rechts unten in der p-right Spalte. Progress-Bar mit Farb-Schwellen (grün <50% / gold <75% / gelb <90% / rot ≥90%).
Endpoint: /api/monitor/full → Feld iproyal (used_gb, plan_gb, cap_gb, pct_plan, remaining_gb).
Direkte Worker-Strategie
Die 5 alten direkten Worker (M+W1+W2+W3+W5) sind via SIGSTOP pausiert. Watchdog prüft regelmäßig Source-Reachability — bei Auto-Lift des IP-Banns (typisch 24-48h) erfolgt automatisch SIGCONT + Recovery-Telegram. W6 bleibt unberührt (GMaps-only, keine Advertsdata-Last).
Commits
6b098ceWatchdog Pre-Check + SIG-Pausef47232fIPRoyal Proxy + Quota-Tracker + Monitor-Tile
Stand: 01.06.2026 (~18:20 CEST) · Nächste ID: Off-Peak-Bundle 02:00 02.06 (Auto-Trigger) · Lektionen #32–#395 · Mo-Marathon: 13 Commits, 12+ Briefe, 3 Workflows, 20 Tasks · Customer-Portal: 6/6 Sektionen LIVE post-Recovery, q=bosch liefert Bosch-Jobs · Re-Audit: 4 RESOLVED + 1 FALSE-POSITIVE + 2 OFF-PEAK + 3 QUICK-FIX (alle gefixt) · SMTP-cleanup: 24.657→2.775 (-89%) · OPEN: Off-Peak Bundle 02:00 Di (Auto), #69 Hevelio-Search Off-Peak, Domain vakanzpuls.de (Sandro)
GitHub: 0xxCool/vakanzpuls · Branch: master
Session 19.04: CC16-CC19+Rollback (Monitor-v2, CC19b zoom-only, W1-SMTP-Bypass, Alerts, Telegram, Stampede, Dedupe)