feat(scanner): Funde mit Akquise und Opportunities abgleichen
Neu gefundene Projekte mit Treffer werden einmalig per gpt-5.6-luna mit den Akquise-Eintraegen und Opportunities der letzten N Tage (Vorgabe 14) verglichen. Passt ein Eintrag, zeigt der Scanner "bereits beworben!" samt Begruendung. Schalter und Zeitraum (Tage/Wochen) in Einstellungen -> Scanner, Neupruefung von Hand im Detail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
87ca52a34b
commit
af3ca33743
@@ -332,6 +332,9 @@ erDiagram
|
||||
varchar rate
|
||||
varchar url
|
||||
datetime published_at
|
||||
tinyint already_applied "in Akquise/Opportunities gefunden"
|
||||
text applied_refs "JSON: type, id, title, consultant, reason"
|
||||
datetime applied_check_at "NULL = noch nicht abgeglichen"
|
||||
timestamp created_at "gescrapt am, Aufbewahrung 7 Tage"
|
||||
}
|
||||
matches {
|
||||
|
||||
+71
-1
@@ -40,12 +40,81 @@ sequenceDiagram
|
||||
P->>M: E-Mail an Agent oder Standardempfaenger
|
||||
end
|
||||
end
|
||||
|
||||
opt Abgleich aktiv, Projekt hat Treffer, noch nicht geprüft
|
||||
P->>P: Akquise und Opportunities der letzten N Tage laden
|
||||
P->>AI: gpt-5.6-luna: Projekt vs. CRM-Einträge
|
||||
AI-->>P: { matches: [{ type, id, reason }] }
|
||||
P->>DB: already_applied, applied_refs, applied_check_at
|
||||
end
|
||||
```
|
||||
|
||||
Zwei Sparmassnahmen stecken bewusst **vor** dem OpenAI-Aufruf: der vorhandene
|
||||
Treffer (Unique-Key `agent_id, project_id`) und die Ausschlussbegriffe. Ein
|
||||
Projekt, das „Arbeitnehmerüberlassung" enthält, kostet damit keinen Cent.
|
||||
|
||||
## Abgleich „bereits beworben!"
|
||||
|
||||
Dieselbe Ausschreibung kommt oft mehrfach: über zwei Börsen, über mehrere
|
||||
Vermittler, mit anderem Titel und anderem Link. Damit niemand ein Projekt ein
|
||||
zweites Mal anbietet, gleicht `services/appliedCheck.js` jeden Fund mit der
|
||||
**Akquise-Liste** (`crm_acquisitions`) und den **Opportunities**
|
||||
(`crm_opportunities`) ab. Steht er dort schon, trägt der Treffer im Dashboard,
|
||||
unter *Unpassend* und im Detail die Markierung **bereits beworben!**; das Detail
|
||||
nennt die passenden Einträge samt Begründung.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Projekt im Lauf verarbeitet"] --> B{"applied_check_active?"}
|
||||
B -->|nein| X["nichts tun"]
|
||||
B -->|ja| C{"schon geprüft?<br/>applied_check_at"}
|
||||
C -->|ja| X
|
||||
C -->|nein| D{"mindestens ein Treffer?"}
|
||||
D -->|nein| X
|
||||
D -->|ja| E["Kandidaten laden:<br/>Akquise found_at, Opportunities created_at<br/>der letzten N Tage, ohne Papierkorb"]
|
||||
E --> F{"Kandidaten?"}
|
||||
F -->|nein| N["nicht beworben"]
|
||||
F -->|ja| G{"gleicher Link?"}
|
||||
G -->|ja| Y["bereits beworben"]
|
||||
G -->|nein| H["gpt-5.6-luna, reasoning_effort low"]
|
||||
H -->|Treffer mit bekannter Id| Y
|
||||
H -->|keine| N
|
||||
N --> S["am Projekt speichern"]
|
||||
Y --> S
|
||||
```
|
||||
|
||||
| Einstellung | Vorgabe | Bedeutung |
|
||||
| --- | --- | --- |
|
||||
| `applied_check_active` | `true` | Schalter in *Einstellungen → Scanner* |
|
||||
| `applied_check_days` | `14` | Zeitraum zurück, in Tagen (Oberfläche: Tage oder Wochen), höchstens 365 |
|
||||
|
||||
Entscheidungen dahinter:
|
||||
|
||||
- **Eigenes Modell.** Die Bewertung CV gegen Projekt bleibt bei `gpt-4o-mini`;
|
||||
der Abgleich nutzt `gpt-5.6-luna` (Konstante `APPLIED_CHECK_MODEL`). Er muss
|
||||
umformulierte Ausschreibungen als dieselbe erkennen und ähnliche Rollen bei
|
||||
anderem Kunden auseinanderhalten - das ist ein Urteil, kein Textvergleich.
|
||||
Der Schlüssel ist derselbe `openai_api_key`.
|
||||
- **Einmal je Projekt, nur mit Treffer.** Die Börsen liefern bei jedem Lauf
|
||||
dieselben Projekte erneut; `applied_check_at` verhindert, dass sie jedes Mal
|
||||
bezahlt werden. Projekte ohne Treffer zeigt die Oberfläche nie an.
|
||||
- **Gleicher Link braucht keine KI.** Verglichen wird ohne Protokoll, `www`,
|
||||
Query und Schrägstrich am Ende.
|
||||
- **Nur Ids, die wir geschickt haben.** Nennt das Modell eine unbekannte Id,
|
||||
zählt sie nicht.
|
||||
- **Fehler setzen aus, nicht ab.** Scheitert der Aufruf (Schlüssel, Quote,
|
||||
Netz), bleibt der Abgleich für den Rest des Laufs aus und das Projekt
|
||||
ungeprüft - der nächste Lauf versucht es wieder. Die Bewertung läuft davon
|
||||
unberührt weiter.
|
||||
- **Momentaufnahme.** Geprüft wird beim Fund. Wird die Bewerbung erst danach
|
||||
in der Akquise erfasst, holt *Auf Bewerbung prüfen* im Detail den Abgleich
|
||||
von Hand nach (`POST /projects/:id/applied-check`, auch bei abgeschaltetem
|
||||
Automatismus).
|
||||
|
||||
Die drei Spalten an `projects` legt `initScannerDatabase()` per
|
||||
`ALTER TABLE ... ADD COLUMN IF NOT EXISTS` an - `sync()` ohne `alter` tut das in
|
||||
bestehenden Tabellen nicht.
|
||||
|
||||
## Wann läuft er
|
||||
|
||||
```mermaid
|
||||
@@ -215,7 +284,7 @@ Zertifikatswarnung. Der DNS-Eintrag muss beim Anbieter gelöscht werden.
|
||||
| Unpassend | `ScannerRejected.tsx` | Treffer mit Status `rejected` |
|
||||
| Suchagenten | `ScannerAgents.tsx` | Beraterprofile, CV-Upload |
|
||||
| Börsen-Konten | `ScannerPortals.tsx` | Verbindungsstatus, Zugangsdaten, Cookie-Import |
|
||||
| Einstellungen | `settings/ScannerSettings.tsx` | Intervall, Schwellenwerte, OpenAI, Ton, E-Mail |
|
||||
| Einstellungen | `settings/ScannerSettings.tsx` | Intervall, Schwellenwerte, Abgleich mit Akquise/Opportunities, OpenAI, Ton, E-Mail |
|
||||
|
||||
**Wichtig zum Ton:** Er hängt an der Dashboard-Komponente. Wer auf einen anderen
|
||||
Menüpunkt wechselt, hört nichts - die Komponente wird ausgebaut und die
|
||||
@@ -240,6 +309,7 @@ Alle unter `/api/scanner`, alle hinter `requireAuth`:
|
||||
| POST | `/portals/test-connection/:id` | Mini-Scrape bzw. Session-Prüfung |
|
||||
| GET | `/matches` | Filter: `status`, `agentId`, `minPercentage`, `limit` |
|
||||
| PUT/DELETE | `/matches/:id` | Status setzen, löschen |
|
||||
| POST | `/projects/:id/applied-check` | Abgleich „bereits beworben!" von Hand wiederholen |
|
||||
| GET/POST | `/settings` | Geheimnisse maskiert |
|
||||
| POST | `/scan` | Lauf starten, `409` wenn schon einer läuft |
|
||||
| GET | `/status` | läuft ein Lauf, Scheduler an, Datenbankname |
|
||||
|
||||
@@ -819,6 +819,17 @@ wissen:
|
||||
3. **Ist der Tab geschlossen, passiert nichts.** Wer unabhängig vom Browser
|
||||
benachrichtigt werden will, schaltet die **E-Mail** in den Einstellungen ein.
|
||||
|
||||
### „bereits beworben!"
|
||||
|
||||
Jeder neue Treffer wird mit der **Akquise-Liste** und den **Opportunities**
|
||||
abgeglichen. Steht das Projekt dort schon - auch unter anderem Titel oder von
|
||||
einer anderen Börse -, trägt es in der Liste die rote Markierung **bereits
|
||||
beworben!**. Im Detail steht, welcher Eintrag passt, für welchen Berater und
|
||||
warum die KI ihn für dasselbe Projekt hält.
|
||||
|
||||
Geprüft wird einmal, beim Fund. Wer die Bewerbung erst danach in der Akquise
|
||||
einträgt, klickt im Detail auf **Auf Bewerbung prüfen**.
|
||||
|
||||
### Unpassend
|
||||
|
||||
Alles, was über *Passt nicht* aussortiert wurde. Von hier bringt *Zurück ins
|
||||
@@ -900,6 +911,11 @@ nicht mehr: kein Lauf im Hintergrund, und *Jetzt scannen* antwortet mit einem
|
||||
Hinweis statt zu starten. Der Zustand steht auch oben im Scanner-Dashboard, damit
|
||||
man nicht rätselt, warum keine neuen Treffer kommen.
|
||||
|
||||
Unter **Abgleich mit Akquise und Opportunities** lässt sich die Markierung
|
||||
*bereits beworben!* ein- und ausschalten. **Zeitraum zurück** bestimmt, wie alt
|
||||
ein Akquise-Eintrag oder eine Opportunity höchstens sein darf, um verglichen zu
|
||||
werden - in Tagen oder Wochen, Vorgabe zwei Wochen.
|
||||
|
||||
Im Reiter **Postfach** steht oben, ob die Verbindung zu Microsoft 365 steht.
|
||||
**Verbindung testen** prüft beides: die Anmeldung *und* den tatsächlichen
|
||||
Zugriff auf `crm@perinco.de`. Schlägt etwas fehl, nennt die Meldung den
|
||||
|
||||
@@ -229,6 +229,7 @@ Die portierte Scanner-API unter `/api/scanner`, vollständig in
|
||||
| `models/Setting.js` | Schlüssel-Wert mit `getVal`/`setVal` |
|
||||
| `services/scraper.js` | Playwright je Portal; unverändert aus der alten App |
|
||||
| `services/matchingEngine.js` | OpenAI `gpt-4o-mini`, `response_format: json_object`, `temperature: 0.1` |
|
||||
| `services/appliedCheck.js` | Abgleich mit Akquise/Opportunities, OpenAI `gpt-5.6-luna`, Ergebnis an `projects` |
|
||||
| `services/pipeline.js` | ein Lauf: scrapen, speichern, bewerten, benachrichtigen |
|
||||
| `services/scheduler.js` | node-cron jede Minute, Zeitfenster, Sperrfrist, `last_scan_at` |
|
||||
| `services/cleanup.js` | Projekte älter als 7 Tage löschen (Treffer kaskadieren) |
|
||||
|
||||
Reference in New Issue
Block a user