Hustler: Name in der Oberfläche, Budget höchstens 150 $/Monat (6 $/Tag) mit hartem Deckel

Kenso, 17.10.: "150 € im Monat max", "nenne es Hustler". Technische
Namen (workers*) bleiben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-17 20:26:42 +00:00
1 parent 740089c997
commit 11e4280a92
8 files changed
+184 -156

No files matched your search

@@ -53,7 +53,7 @@ Die P6-Ergänzungen E1–E8 stehen jetzt verbindlich in Übersicht §11.1 (P6-E1
| E4 | siehe Übersicht §11, P6-E4 und A1: `deps.places`, `deps.osm`, `deps.dns = { resolveMx, resolve4 }`, kein `llm`; das Impressum-Lesen nutzt `deps.fetch` (H2). | P6-T08 (Nutzung), P6-T24 (`erweitereDeps`) |
| E5 | siehe Übersicht §11, P6-E5 und §6.1 (Ränge, H1): P6 ergänzt in `src/schedule.js` nur die Tick-Arten `top3` (05:30) und `scouts` (alle 2 Minuten); `kuerzen` (03:00) steht dort seit P1-T08 und wird nicht noch einmal angelegt; Einhängen aller drei Tick-Einträge über `buildTickJobs()` (A4). | P6-T05, P6-T24 |
| E6 | siehe Übersicht §11, P6-E6 und §6 (`gate.js`): Zusatzprüfung `coldChecks` über `registerGateCheck` (Gründe `datenschutz`, `adresse_nicht_belegt`, `kein_kaltkontakt`, `kaltmail_ohne_antwort`, `status_unpassend`), `appendCompliance` Zweig `cold:true`. | P6-T15 |
| E7 | siehe Übersicht §11, P6-E7: `GET /botleads?id=<prospectId>`, Ereignislink `/workers?tab=botleads&bot=<prospectId>`, Route über `routes`/`buildRoutes()` (A4). | P6-T18, P6-T19, P6-T24 |
| E7 | siehe Übersicht §11, P6-E7: `GET /botleads?id=<prospectId>`, Ereignislink `/hustler?tab=botleads&bot=<prospectId>` (WOMS-Route `/hustler`, Weiterleitung von `/workers` und `/leads`), Route über `routes`/`buildRoutes()` (A4). | P6-T18, P6-T19, P6-T24 |
| E8 | siehe Übersicht §11, P6-E8: `deps.drafters.erstmail/nachfass(deps, job)` liefert P6; die Aufträge legen `selectNightTop3` bzw. `scheduleFollowups` an, versendet P4s Tick `senden`. | P6-T16 |
## File Structure
@@ -156,7 +156,7 @@ test('makeDeps: Uhr in Millisekunden, Einstellungen über den Standardwerten, do
assert.equal(typeof deps.clock.now(), 'number')
assert.equal(new Date(deps.clock.now()).toISOString(), '2026-10-20T03:30:00.000Z')
const s = deps.db.docs('workersSettings')[0]
assert.deepEqual([s.$id, s.connectedRooms, s.dailyBudgetUsd, s.privacyInteressentenLive], ['main', ['handwerk'], 10, true])
assert.deepEqual([s.$id, s.connectedRooms, s.dailyBudgetUsd, s.privacyInteressentenLive], ['main', ['handwerk'], 6, true]) // 6 $/Tag = Standard (P1, Kenso 17.10.: max. 150 € im Monat)
const p = await seedProspect(deps.db, {})
await deps.db.update('workersProspects', p.$id, { score: 1 })
assert.deepEqual(deps.db.writes.slice(-1)[0], { op: 'update', col: 'workersProspects', id: p.$id })
@@ -872,7 +872,7 @@ test('fileBotLead: Status, Grund, Kontaktdaten weg, Ereignis ohne Namen oder Adr
const d = await deps.db.get('workersProspects', p.$id)
assert.deepEqual([d.status, d.botLeadReason, d.email, d.emailSource, d.contactName, d.phone, d.score], ['botlead', grund, '', '', '', '0631 111', 80])
const ev = deps.db.docs('workersEvents').find((e) => e.kind === 'botlead')
assert.deepEqual([ev.workerId, ev.text, ev.prospectId, ev.link], ['arne', `Bot-Lead (Handwerk, Kaiserslautern): ${grund}`, p.$id, `/workers?tab=botleads&bot=${p.$id}`])
assert.deepEqual([ev.workerId, ev.text, ev.prospectId, ev.link], ['arne', `Bot-Lead (Handwerk, Kaiserslautern): ${grund}`, p.$id, `/hustler?tab=botleads&bot=${p.$id}`])
assert.ok(!/Beispiel|@/.test(ev.text))
await fileBotLead(deps, { prospectId: p.$id, reason: grund })
assert.equal(deps.db.docs('workersEvents').filter((e) => e.kind === 'botlead').length, 1, 'zweiter Aufruf ändert nichts')
@@ -889,7 +889,7 @@ test('Grund wird auf 500 Zeichen gekürzt; weitergeschrittene oder für Mail vor
})
```
- [ ] **Step 2: Fehlschlag sehen.** `timeout 300 node --max-old-space-size=1024 --test test/p6-botleads-ablage.test.js` ⇒ FAIL, Modul fehlt.
- [ ] **Step 3: Implementieren.** Interessenten lesen (`db.get` liefert `null` ⇒ `Error('Interessent fehlt')`); ist `status` schon `botlead` ⇒ zurück ohne Schreiben; ist `status` nicht `gefunden` oder `bewertet` oder `threadId` gesetzt ⇒ `Error('Bot-Lead nur für Interessenten ohne Kontakt')`; `update` mit `status`, `botLeadReason`, bei `dropContact` die drei Felder auf `''`, `updatedAt`; Ereignis `emit(deps, { workerId: 'arne', kind: 'botlead', prospectId, link: '/workers?tab=botleads&bot=<id>', text: 'Bot-Lead (<Raumbeschriftung>, <Ort oder „Ort unbekannt“>): <Grund, höchstens 200 Zeichen>' })`.
- [ ] **Step 3: Implementieren.** Interessenten lesen (`db.get` liefert `null` ⇒ `Error('Interessent fehlt')`); ist `status` schon `botlead` ⇒ zurück ohne Schreiben; ist `status` nicht `gefunden` oder `bewertet` oder `threadId` gesetzt ⇒ `Error('Bot-Lead nur für Interessenten ohne Kontakt')`; `update` mit `status`, `botLeadReason`, bei `dropContact` die drei Felder auf `''`, `updatedAt`; Ereignis `emit(deps, { workerId: 'arne', kind: 'botlead', prospectId, link: '/hustler?tab=botleads&bot=<id>', text: 'Bot-Lead (<Raumbeschriftung>, <Ort oder „Ort unbekannt“>): <Grund, höchstens 200 Zeichen>' })`.
- [ ] **Step 4: Bestehen sehen.** Derselbe Befehl ⇒ `# fail 0`.
- [ ] **Step 5: Commit.** `git add src/archive/botleads.js test/p6-botleads-ablage.test.js && git commit -m "p6: Arne legt Bot-Leads ab, ohne Kontaktdaten" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"`
@@ -956,7 +956,7 @@ test('Register zuerst, nur der frische Kandidat kostet; Bewertung 82; R1: keine
})
test('Budget aus ⇒ kein Places-Aufruf', async () => {
const deps = szenario()
await deps.db.create('workersDaily', { costUsd: 10, mailsCold: 0, mailsReply: 0, searches: 0, found: 0, contacted: 0, deals: 0, countersJson: '{}', warned: false, stopped: false }, '2026-10-20')
await deps.db.create('workersDaily', { costUsd: 6, mailsCold: 0, mailsReply: 0, searches: 0, found: 0, contacted: 0, deals: 0, countersJson: '{}', warned: false, stopped: false }, '2026-10-20') // Tagesgrenze 6 $ erreicht
let n = 0
deps.places = { textSearch: async () => { n++; return { places: [], nextPageToken: null } } }
const r = await runScoutJob(deps, await jobIn(deps))
@@ -1689,7 +1689,7 @@ test('legal_datenschutz_interessenten: Geltungsbereich, Empfänger und Art. 22 p
**Interfaces:**
- Consumes aus `rechtliches-hochladen.mjs` (neu exportiert): `client(fetchFn, schluessel)`, `alleDokumente(api, sammlung)`, `versionen(api, documentKey)`, `neueId()`, `ddmmjjjj(iso)`, `planVersion({ alle, source, validFrom })`, `BUCKET`, `DB`, `KEY_DATEI`, `ROOT`.
- Produces: `parseArgs(argv) → { modus: 'probe'|'hochladen', freigabe: string|null }`; `main(argv, deps) → Promise<0|1>` mit `deps = { fetch, schluessel, log, git, dateien }` wie das Vorbild. Karte `datenschutz`; Dateien `docs/WEBklar-Datenschutzerklaerung.pdf` und `public/datenschutz.html`; `source = kundenbereich@<Commit>:public/datenschutz.html`; `changeNote = 'Fassung vom <TT.MM.JJJJ>: Abschnitt 11 Interessenten (Workers, Kaltmail)'`. Der Aufruf `dokumentFuer('datenschutz')` des Vorbilds bleibt unverändert ein Fehler (bestehender Test).
- Produces: `parseArgs(argv) → { modus: 'probe'|'hochladen', freigabe: string|null }`; `main(argv, deps) → Promise<0|1>` mit `deps = { fetch, schluessel, log, git, dateien }` wie das Vorbild. Karte `datenschutz`; Dateien `docs/WEBklar-Datenschutzerklaerung.pdf` und `public/datenschutz.html`; `source = kundenbereich@<Commit>:public/datenschutz.html`; `changeNote = 'Fassung vom <TT.MM.JJJJ>: Abschnitt 11 Interessenten (Ansprache per E-Mail)'` (steht in WOMS „Rechtliches“ und kann für Kunden sichtbar werden: darum weder „Workers“ noch „Hustler“). Der Aufruf `dokumentFuer('datenschutz')` des Vorbilds bleibt unverändert ein Fehler (bestehender Test).
- [ ] **Step 1: Test schreiben.**
@@ -2036,7 +2036,7 @@ Reihenfolge ist wichtig: Erst das Fundament (1–4), dann Probe (5), dann der Da
6. **Datenschutztext live.** (a) Kenso gibt den Text aus P6-T20 frei und nennt den Mail-Anbieter. (b) Der Ausführende committet in `dev-workers-datenschutz`; Kenso pusht: `git -C /home/webklar/apps/kundenbereich.webklar.com/dev-workers-datenschutz push origin feature/workers-datenschutz:main`; nach 1–2 Minuten `curl -s https://project.webklar.com/datenschutz.html | command grep -c 'id="interessenten"'` ⇒ `1`. (c) Website: `git -C /home/webklar/apps/webklar.com/dev-workers-ds push origin fix/datenschutz-anker:main`; deployt der Webhook nicht binnen 3 Minuten: `docker exec webklar-webclaw-build sh -c 'DEPLOY_TRIGGER=manual sh "${WEBCLAW_DEPLOY_SCRIPT:-/deploy.sh}"'`. Prüfen im Browser: `https://www.webklar.com/datenschutz#interessenten` landet auf `https://project.webklar.com/datenschutz.html#interessenten` und zeigt „11. Interessenten“. (d) Rechtliches: Kenso erzeugt in der Appwrite-Konsole den kurzlebigen Schlüssel „Rechtliches-Upload“ (files.read, files.write, documents.read, documents.write, Ablauf 1 Tag) und legt ihn nach `/root/.appwrite-rechtliches.key` (chmod 600); der Ausführende (Claude) führt im Portal-Worktree `timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --probe` und dann `timeout 300 node --max-old-space-size=1024 scripts/datenschutz-hochladen.mjs --freigabe=<Stand-Datum>` aus; Prüfen in WOMS „Rechtliches“: Karte „Datenschutzerklärung“ zeigt die neue Version als „aktuell“, die alte als „abgelöst“; danach löscht Kenso den Schlüssel in der Konsole.
7. **Schalter umlegen (erst jetzt dürfen Kaltmails raus).** `docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --check` ⇒ „Seite ok“; dann `… datenschutz --set` ⇒ „Schalter privacyInteressentenLive: an“ (derselbe Schalter wie P3s `datenschutz-schalter.mjs --an`, aber erst nach der Seitenprüfung; P3s Schritt „Clara einschalten“ ist damit mit erledigt).
8. **Räume verbinden, einen nach dem anderen.** `docker exec webklar-workers node scripts/p6-freischalten.mjs raeume --set handwerk` ⇒ „Verbundene Räume: …handwerk“. Ab der nächsten Nacht (22:00 Suche, 05:30 Auswahl, ab 09:00 Versand) arbeitet Hanna; weitere Räume erst, wenn Zustellung und Antworten gut aussehen (`aerzte`, `selbst`, `laden` mit demselben Befehl).
9. **WOMS.** `git -C /home/webklar/apps/ticket.webklar.com/dev-workers push origin feature/workers:test` (wenn P2/P3 mit dran sind, laut Übersicht §1.1). Prüfen: `curl -s -o /dev/null -w '%{http_code}' https://ticket.webklar.com/api/workers/botleads` ⇒ `401`; in WOMS `/workers` ⇒ Reiter „Bot-Leads“.
10. **Erste Nacht beobachten.** Morgens: Morgenbericht-Mail (06:30); „Bot-Leads“ zeigt rund 7 Einträge mit Grund; im Absender-Postfach „Gesendet“ höchstens 3 Mails, alle zwischen 09:00 und 16:00, jede mit genau einem Link im Text (`https://www.webklar.com`), Fußblock und Abmelde-Link (Klick testen: erscheint der Eintrag in der Sperrliste?); `workersDaily` des Tages zeigt `mailsCold` ≤ 3. Beschwerden und Antworten landen wie in P4 beschrieben im Pult. Zeigt das Board „Versand gestoppt“ (Bounce-Notbremse, H4), sind harte Bounces aufgetreten: Ursache prüfen (falsche Adressen? Absender-Reputation?), dann gibt ein Admin im Einstellungs-Dialog „Versand wieder freigeben“; die Erstmail-Aufträge blieben solange offen. Verweigert das Tor eine Kaltmail aus P6 (Pult „Mail nicht gesendet: …“, z. B. `text_zu_lang` bei einem sehr langen Angebotssatz), gehört der Entwurf geprüft, nicht das Tor gelockert.
9. **WOMS.** `git -C /home/webklar/apps/ticket.webklar.com/dev-workers push origin feature/workers:test` (wenn P2/P3 mit dran sind, laut Übersicht §1.1). Prüfen: `curl -s -o /dev/null -w '%{http_code}' https://ticket.webklar.com/api/workers/botleads` ⇒ `401`; in WOMS `/hustler` ⇒ Reiter „Bot-Leads“.
10. **Erste Nacht beobachten.** Morgens: Morgenbericht-Mail „Hustler-Nachtbericht TT.MM.JJJJ“ (06:30); „Bot-Leads“ zeigt rund 7 Einträge mit Grund; im Absender-Postfach „Gesendet“ höchstens 3 Mails, alle zwischen 09:00 und 16:00, jede mit genau einem Link im Text (`https://www.webklar.com`), Fußblock und Abmelde-Link (Klick testen: erscheint der Eintrag in der Sperrliste?); `workersDaily` des Tages zeigt `mailsCold` ≤ 3. Beschwerden und Antworten landen wie in P4 beschrieben im Pult. Zeigt das Board „Versand gestoppt“ (Bounce-Notbremse, H4), sind harte Bounces aufgetreten: Ursache prüfen (falsche Adressen? Absender-Reputation?), dann gibt ein Admin im Einstellungs-Dialog „Versand wieder freigeben“; die Erstmail-Aufträge blieben solange offen. Verweigert das Tor eine Kaltmail aus P6 (Pult „Mail nicht gesendet: …“, z. B. `text_zu_lang` bei einem sehr langen Angebotssatz), gehört der Entwurf geprüft, nicht das Tor gelockert.
11. **Not-Aus und Rückbau.** Sofort: Pausenschalter in WOMS. Nur Kaltmail aus (stoppt auch Claras Erstantworten, P3): `docker exec webklar-workers node scripts/p6-freischalten.mjs datenschutz --off`. Suche aus: `… raeume --clear`. Beides ist ohne Neustart wirksam. Die Bounce-Notbremse (H4) stoppt den Versand außerdem von selbst, siehe Schritt 10.