535 lines
31 KiB
Markdown
535 lines
31 KiB
Markdown
# Migrations-Plan: Legacy GSheets -> Company Explorer (Robotics Edition v0.8.5)
|
|
|
|
**Kontext:** Neuanfang für die Branche **Robotik & Facility Management**.
|
|
**Ziel:** Ablösung von Google Sheets/CLI durch eine Web-App ("Company Explorer") mit SQLite-Backend.
|
|
|
|
## 1. Strategische Neuausrichtung
|
|
|
|
| Bereich | Alt (Legacy) | Neu (Robotics Edition) |
|
|
| :--- | :--- | :--- |
|
|
| **Daten-Basis** | Google Sheets | **SQLite** (Lokal, performant, filterbar). |
|
|
| **Ziel-Daten** | Allgemein / Kundenservice | **Quantifizierbares Potenzial** (z.B. 4500m² Fläche, 120 Betten). |
|
|
| **Branchen** | KI-Vorschlag (Freitext) | **Strict Mode:** Mapping auf definierte Notion-Liste (z.B. "Hotellerie", "Automotive"). |
|
|
| **Bewertung** | 0-100 Score (Vage) | **Data-Driven:** Rohwert (Scraper/Search) -> Standardisierung (Formel) -> Potenzial. |
|
|
| **Analytics** | Techniker-ML-Modell | **Deaktiviert**. Fokus auf harte Fakten. |
|
|
| **Operations** | D365 Sync (Broken) | **Excel-Import & Deduplizierung**. Fokus auf Matching externer Listen gegen Bestand. |
|
|
|
|
## 2. Architektur & Komponenten-Mapping
|
|
|
|
Das System wird in `company-explorer/` neu aufgebaut. Wir lösen Abhängigkeiten zur Root `helpers.py` auf.
|
|
|
|
### A. Core Backend (`backend/`)
|
|
|
|
| Komponente | Aufgabe & Neue Logik | Prio |
|
|
| :--- | :--- | :--- |
|
|
| **Database** | Ersetzt `GoogleSheetHandler`. Speichert Firmen & "Enrichment Blobs". | 1 |
|
|
| **Importer** | Ersetzt `SyncManager`. Importiert Excel-Dumps (CRM) und Event-Listen. | 1 |
|
|
| **Deduplicator** | Ersetzt `company_deduplicator.py`. **Kern-Feature:** Checkt Event-Listen gegen DB. Muss "intelligent" matchen (Name + Ort + Web). | 1 |
|
|
| **Scraper (Base)** | Extrahiert Text von Websites. Basis für alle Analysen. | 1 |
|
|
| **Classification Service** | **NEU (v0.7.0).** Zweistufige Logik: <br> 1. Strict Industry Classification. <br> 2. Metric Extraction Cascade (Web -> Wiki -> SerpAPI). | 1 |
|
|
| **Marketing Engine** | Ersetzt `generate_marketing_text.py`. Nutzt neue `marketing_wissen_robotics.yaml`. | 3 |
|
|
|
|
**Identifizierte Hauptdatei:** `company-explorer/backend/app.py`
|
|
|
|
### B. Frontend (`frontend/`) - React
|
|
|
|
* **View 1: Der "Explorer":** DataGrid aller Firmen. Filterbar nach "Roboter-Potential" und Status.
|
|
* **View 2: Der "Inspector":** Detailansicht einer Firma. Zeigt gefundene Signale ("Hat SPA Bereich"). Manuelle Korrektur-Möglichkeit.
|
|
* **Identifizierte Komponente:** `company-explorer/frontend/src/components/Inspector.tsx`
|
|
* **View 3: "List Matcher":** Upload einer Excel-Liste -> Anzeige von Duplikaten -> Button "Neue importieren".
|
|
* **View 4: "Settings":** Konfiguration von Branchen, Rollen und Robotik-Logik.
|
|
* **Frontend "Settings" Komponente:** `company-explorer/frontend/src/components/RoboticsSettings.tsx`
|
|
|
|
### C. Architekturmuster für die Client-Integration
|
|
|
|
Um externen Diensten (wie der `lead-engine`) eine einfache und robuste Anbindung an den `company-explorer` zu ermöglichen, wurde ein standardisiertes Client-Connector-Muster implementiert.
|
|
|
|
| Komponente | Aufgabe & Neue Logik |
|
|
| :--- | :--- |
|
|
| **`company_explorer_connector.py`** | **NEU:** Ein zentrales Python-Skript, das als "offizieller" Client-Wrapper für die API des Company Explorers dient. Es kapselt die Komplexität der asynchronen Enrichment-Prozesse. |
|
|
| **`handle_company_workflow()`** | Die Kernfunktion des Connectors. Sie implementiert den vollständigen "Find-or-Create-and-Enrich"-Workflow: <br> 1. **Prüfen:** Stellt fest, ob ein Unternehmen bereits existiert. <br> 2. **Erstellen:** Legt das Unternehmen an, falls es neu ist. <br> 3. **Anstoßen:** Startet den asynchronen `discover`-Prozess. <br> 4. **Warten (Polling):** Überwacht den Status des Unternehmens, bis eine Website gefunden wurde. <br> 5. **Analysieren:** Startet den asynchronen `analyze`-Prozess. <br> **Vorteil:** Bietet dem aufrufenden Dienst eine einfache, quasi-synchrone Schnittstelle und stellt sicher, dass die Prozessschritte in der korrekten Reihenfolge ausgeführt werden. |
|
|
|
|
### 2.1 Der End-to-End Datenfluss (Lead-Fabrik)
|
|
|
|
Diese Grafik visualisiert den gesamten Prozess von der Anlage eines Kontakts im CRM über die KI-Analyse bis zur fertigen Marketing-Automation.
|
|
|
|
```mermaid
|
|
graph TD
|
|
%% Nodes
|
|
User((Vertriebs-User))
|
|
SO_CRM[SuperOffice CRM]
|
|
Connector[Connector Service]
|
|
CE_Core[Company Explorer Core]
|
|
CE_AI[AI Analysis Engine]
|
|
CE_DB[(SQLite DB)]
|
|
MA_System[Marketing Automation]
|
|
|
|
%% Flow
|
|
User -- Erstellt Kontakt --> SO_CRM
|
|
SO_CRM -- Webhook (New Contact) --> Connector
|
|
Connector -- POST /provision --> CE_Core
|
|
|
|
subgraph "Intelligence Phase (Asynchron)"
|
|
CE_Core -- 1. Scrape & Research --> CE_AI
|
|
CE_AI -- 2. Vertical & Metriken (Potential) --> CE_Core
|
|
CE_AI -- 3. Generiere Atomic Opener --> CE_Core
|
|
end
|
|
|
|
subgraph "Matrix Logic (Matching)"
|
|
CE_Core -- 4. Rolle & Branche Identifizieren --> CE_DB
|
|
CE_DB -- 5. Hole Matrix-Texte (Subject/Intro) --> CE_Core
|
|
Note[Logik: Primary vs Secondary Product<br>z.B. Healthcare: Pflege -> Transport]
|
|
end
|
|
|
|
CE_Core -- Angereichertes Profil + Texte --> Connector
|
|
Connector -- UPDATE Person (UDFs) --> SO_CRM
|
|
|
|
SO_CRM -- Daten verfügbar --> MA_System
|
|
MA_System -- Ersetze Variablen im Template --> Email(Finale E-Mail)
|
|
```
|
|
|
|
**Prozess-Schritte:**
|
|
1. **Trigger:** Ein Vertriebsmitarbeiter legt eine Person oder Firma in SuperOffice an.
|
|
2. **Transport:** Der Connector empfängt den Webhook und beauftragt den Company Explorer (`/provision`).
|
|
3. **Intelligence:**
|
|
* Die Website wird gescraped und analysiert.
|
|
* Die KI bestimmt das **Vertical** (z.B. "Healthcare - Hospital") und berechnet das **Potenzial** (z.B. Bettenanzahl).
|
|
* Ein individueller **Atomic Opener** wird generiert, der auf die spezifische Situation des Unternehmens eingeht.
|
|
4. **Matrix Match:**
|
|
* Basierend auf der Job-Rolle (z.B. "Pflegedienstleitung") wird die **Persona** ("Operativer Entscheider") bestimmt.
|
|
* Die Engine prüft das `Ops Focus: Secondary` Flag (z.B. bei Krankenhäusern).
|
|
* Die passenden Textbausteine (Betreff, Intro, Social Proof) werden aus der vor-generierten Matrix geladen.
|
|
5. **Sync Back:** Alle Texte (Opener + Matrix-Bausteine) werden in die benutzerdefinierten Felder (UDFs) der Person in SuperOffice zurückgeschrieben.
|
|
6. **Execution:** Die Marketing-Automation nutzt diese Felder (`{udf_opener}`, `{udf_intro}`), um hoch-personalisierte E-Mails zu versenden.
|
|
|
|
## 3. Umgang mit Shared Code (`helpers.py` & Co.)
|
|
|
|
Wir kapseln das neue Projekt vollständig ab ("Fork & Clean").
|
|
|
|
* **Quelle:** `helpers.py` (Root)
|
|
* **Ziel:** `company-explorer/backend/lib/core_utils.py`
|
|
* **Aktion:** Wir kopieren nur relevante Teile und ergänzen sie (z.B. `safe_eval_math`, `run_serp_search`).
|
|
|
|
## 4. Datenstruktur (SQLite Schema)
|
|
|
|
### Tabelle `companies` (Stammdaten & Analyse)
|
|
* `id` (PK)
|
|
* `name` (String)
|
|
* `website` (String)
|
|
* `crm_id` (String, nullable - Link zum D365)
|
|
* `industry_crm` (String - Die "erlaubte" Branche aus Notion)
|
|
* `city` (String)
|
|
* `country` (String - Standard: "DE" oder aus Impressum)
|
|
* `status` (Enum: NEW, IMPORTED, ENRICHED, QUALIFIED)
|
|
* **NEU (v0.7.0):**
|
|
* `calculated_metric_name` (String - z.B. "Anzahl Betten")
|
|
* `calculated_metric_value` (Float - z.B. 180)
|
|
* `calculated_metric_unit` (String - z.B. "Betten")
|
|
* `standardized_metric_value` (Float - z.B. 4500)
|
|
* `standardized_metric_unit` (String - z.B. "m²")
|
|
* `metric_source` (String - "website", "wikipedia", "serpapi")
|
|
|
|
### Tabelle `signals` (Deprecated)
|
|
* *Veraltet ab v0.7.0. Wird durch quantitative Metriken in `companies` ersetzt.*
|
|
|
|
### Tabelle `contacts` (Ansprechpartner)
|
|
* `id` (PK)
|
|
* `account_id` (FK -> companies.id)
|
|
* `gender`, `title`, `first_name`, `last_name`, `email`
|
|
* `job_title` (Visitenkarte)
|
|
* `role` (Standardisierte Rolle: "Operativer Entscheider", etc.)
|
|
* `status` (Marketing Status)
|
|
|
|
### Tabelle `industries` (Branchen-Fokus - Synced from Notion)
|
|
* `id` (PK)
|
|
* `notion_id` (String, Unique)
|
|
* `name` (String - "Vertical" in Notion)
|
|
* `description` (Text - "Definition" in Notion)
|
|
* `metric_type` (String - "Metric Type")
|
|
* `min_requirement` (Float - "Min. Requirement")
|
|
* `whale_threshold` (Float - "Whale Threshold")
|
|
* `proxy_factor` (Float - "Proxy Factor")
|
|
* `scraper_search_term` (String - "Scraper Search Term")
|
|
* `scraper_keywords` (Text - "Scraper Keywords")
|
|
* `standardization_logic` (String - "Standardization Logic")
|
|
|
|
### Tabelle `job_role_mappings` (Rollen-Logik)
|
|
* `id` (PK)
|
|
* `pattern` (String - Regex für Jobtitles)
|
|
* `role` (String - Zielrolle)
|
|
|
|
## 7. Historie & Fixes (Jan 2026)
|
|
|
|
* **[CRITICAL] v0.7.4: Service Restoration & Logic Fix (Jan 24, 2026)**
|
|
* **[STABILITY] v0.7.3: Hardening Metric Parser & Regression Testing (Jan 23, 2026)**
|
|
* **[STABILITY] v0.7.2: Robust Metric Parsing (Jan 23, 2026)**
|
|
* **[STABILITY] v0.7.1: AI Robustness & UI Fixes (Jan 21, 2026)**
|
|
* **[MAJOR] v0.7.0: Quantitative Potential Analysis (Jan 20, 2026)**
|
|
* **[UPGRADE] v0.6.x: Notion Integration & UI Improvements**
|
|
|
|
## 8. Eingesetzte Prompts (Account-Analyse v0.7.4)
|
|
|
|
### 8.1 Strict Industry Classification
|
|
|
|
Ordnet das Unternehmen einer definierten Branche zu.
|
|
|
|
```python
|
|
prompt = f"""
|
|
Act as a strict B2B Industry Classifier.
|
|
Company: {company_name}
|
|
Context: {website_text[:3000]}
|
|
|
|
Available Industries:
|
|
{json.dumps(industry_definitions, indent=2)}
|
|
|
|
Task: Select the ONE industry that best matches the company.
|
|
If the company is a Hospital/Klinik, select 'Healthcare - Hospital'.
|
|
If none match well, select 'Others'.
|
|
|
|
Return ONLY the exact name of the industry.
|
|
"""
|
|
```
|
|
|
|
### 8.2 Metric Extraction
|
|
|
|
Extrahiert den spezifischen Zahlenwert ("Scraper Search Term") und liefert JSON für den `MetricParser`.
|
|
|
|
```python
|
|
prompt = f"""
|
|
Extract the following metric for the company in industry '{industry_name}':
|
|
Target Metric: "{search_term}"
|
|
|
|
Source Text:
|
|
{text_content[:6000]}
|
|
|
|
Return a JSON object with:
|
|
- "raw_value": The number found (e.g. 352 or 352.0). If text says "352 Betten", extract 352. If not found, null.
|
|
- "raw_unit": The unit found (e.g. "Betten", "m²").
|
|
- "proof_text": A short quote from the text proving this value.
|
|
|
|
JSON ONLY.
|
|
"""
|
|
```
|
|
|
|
## 9. Notion Integration (Single Source of Truth)
|
|
|
|
Das System nutzt Notion als zentrales Steuerungselement für strategische Definitionen.
|
|
|
|
### 9.1 Datenfluss
|
|
1. **Definition:** Branchen und Robotik-Kategorien werden in Notion gepflegt (Whale Thresholds, Keywords, Definitionen).
|
|
2. **Synchronisation:** Das Skript `sync_notion_industries.py` zieht die Daten via API und führt einen Upsert in die lokale SQLite-Datenbank aus.
|
|
3. **App-Nutzung:** Das Web-Interface zeigt diese Daten schreibgeschützt an. Der `ClassificationService` nutzt sie als "System-Anweisung" für das LLM.
|
|
|
|
### 9.2 Technische Details
|
|
* **Notion Token:** Muss in `/app/notion_token.txt` (Container-Pfad) hinterlegt sein.
|
|
* **DB-Mapping:** Die Zuordnung erfolgt primär über die `notion_id`, sekundär über den Namen, um Dubletten bei der Migration zu vermeiden.
|
|
|
|
## 10. Database Migration
|
|
|
|
Wenn die `industries`-Tabelle in einer bestehenden Datenbank aktualisiert werden muss (z.B. um neue Felder aus Notion zu unterstützen), darf die Datenbankdatei **nicht** gelöscht werden. Stattdessen muss das Migrations-Skript ausgeführt werden.
|
|
|
|
**Prozess:**
|
|
|
|
1. **Sicherstellen, dass die Zieldatenbank vorhanden ist:** Die `companies_v3_fixed_2.db` muss im `company-explorer`-Verzeichnis liegen (bzw. via Volume gemountet sein).
|
|
2. **Migration ausführen:** Dieser Befehl fügt die fehlenden Spalten hinzu, ohne Daten zu löschen.
|
|
```bash
|
|
docker exec -it company-explorer python3 backend/scripts/migrate_db.py
|
|
```
|
|
3. **Container neu starten:** Damit der Server das neue Schema erkennt.
|
|
```bash
|
|
docker-compose restart company-explorer
|
|
```
|
|
4. **Notion-Sync ausführen:** Um die neuen Spalten mit Daten zu befüllen.
|
|
```bash
|
|
docker exec -it company-explorer python3 backend/scripts/sync_notion_industries.py
|
|
```
|
|
|
|
## 11. Lessons Learned (Retrospektive Jan 24, 2026)
|
|
|
|
1. **API-Routing-Reihenfolge (FastAPI):** Ein spezifischer Endpunkt (z.B. `/api/companies/export`) muss **vor** einem dynamischen Endpunkt (z.B. `/api/companies/{company_id}`) deklariert werden. Andernfalls interpretiert FastAPI "export" als eine `company_id`, was zu einem `422 Unprocessable Entity` Fehler führt.
|
|
2. **Nginx `proxy_pass` Trailing Slash:** Das Vorhandensein oder Fehlen eines `/` am Ende der `proxy_pass`-URL in Nginx ist kritisch. Für Dienste wie FastAPI, die mit einem `root_path` (z.B. `/ce`) laufen, darf **kein** Trailing Slash verwendet werden (`proxy_pass http://company-explorer:8000;`), damit der `root_path` in der an das Backend weitergeleiteten Anfrage erhalten bleibt.
|
|
3. **Docker-Datenbank-Persistenz:** Das Fehlen eines expliziten Volume-Mappings für die Datenbankdatei in `docker-compose.yml` führt dazu, dass der Container eine interne, ephemere Kopie der Datenbank verwendet. Alle Änderungen, die außerhalb des Containers an der "Host"-DB vorgenommen werden, sind für die Anwendung unsichtbar. Es ist zwingend erforderlich, ein Mapping wie `./companies_v3_fixed_2.db:/app/companies_v3_fixed_2.db` zu definieren.
|
|
4. **Code-Integrität & Platzhalter:** Es ist kritisch, bei Datei-Operationen sicherzustellen, dass keine Platzhalter (wie `pass` oder `# omitted`) in den produktiven Code gelangen. Eine "Zombie"-Datei, die äußerlich korrekt aussieht aber innerlich leer ist, kann schwer zu debuggende Logikfehler verursachen.
|
|
5. **Formel-Robustheit:** Formeln aus externen Quellen müssen vor der Auswertung bereinigt werden (Entfernung von Einheiten, Kommentaren), um Syntax-Fehler zu vermeiden.
|
|
|
|
## 12. Deployment & Access Notes
|
|
|
|
* **Pfad auf NAS:** `/volume1/homes/Floke/python/brancheneinstufung/company-explorer`
|
|
* **DB:** `/app/companies_v3_fixed_2.db`
|
|
* **Sync:** `docker exec -it company-explorer python backend/scripts/sync_notion_to_ce_enhanced.py`
|
|
* **Restart:** `docker-compose restart company-explorer`
|
|
* **Build:** `docker-compose up -d --build company-explorer`
|
|
|
|
## 13. Feature: Report Mistakes
|
|
|
|
### Aufgabenbeschreibung:
|
|
Benutzer können Fehler in den Unternehmensdaten (z.B. falscher Umsatz, falsche Branche) direkt im Inspector melden. Diese Korrekturen werden in einer separaten Tabelle `reported_mistakes` gespeichert und im Settings-Bereich angezeigt.
|
|
|
|
### Implementierung:
|
|
* **Tabelle `reported_mistakes`**: Speichert `company_id`, `field_name`, `corrected_value` und `status`.
|
|
* **API**: Endpunkte für `POST /report` und `GET /mistakes`.
|
|
* **UI**: Button im Inspector und Übersichtstabelle in den Settings.
|
|
|
|
## 14. Upgrade v2.0 (Feb 18, 2026): "Lead-Fabrik" Erweiterung
|
|
|
|
Dieses Upgrade transformiert den Company Explorer in das zentrale Gehirn der Lead-Generierung (Vorratskammer).
|
|
|
|
### 14.1 Detaillierte Logik der neuen Datenfelder
|
|
|
|
Um Gemini CLI (dem Bautrupp) die Umsetzung zu ermöglichen, hier die semantische Bedeutung der neuen Spalten:
|
|
|
|
#### Tabelle `companies` (Qualitäts- & Abgleich-Metriken)
|
|
|
|
* **`confidence_score` (FLOAT, 0.0 - 1.0):** Indikator für die Sicherheit der KI-Klassifizierung. `> 0.8` = Grün.
|
|
* **`data_mismatch_score` (FLOAT, 0.0 - 1.0):** Abweichung zwischen CRM-Bestand und Web-Recherche (z.B. Umzug).
|
|
* **`crm_name`, `crm_address`, `crm_website`, `crm_vat`:** Read-Only Snapshot aus SuperOffice zum Vergleich.
|
|
* **Status-Flags:** `website_scrape_status` und `wiki_search_status`.
|
|
|
|
#### Tabelle `industries` (Strategie-Parameter)
|
|
|
|
* **`pains` / `gains`:** Strukturierte Textblöcke (getrennt durch `[Primary Product]` und `[Secondary Product]`).
|
|
* **`ops_focus_secondary` (BOOLEAN):** Steuerung für rollenspezifische Produkt-Priorisierung.
|
|
|
|
---
|
|
|
|
## 15. Offene Arbeitspakete (Bauleitung)
|
|
|
|
Anweisungen für den "Bautrupp" (Gemini CLI).
|
|
|
|
### Task 1: UI-Anpassung - Side-by-Side CRM View & Settings
|
|
(In Arbeit / Teilweise erledigt durch Gemini CLI)
|
|
|
|
### Task 2: Intelligenter CRM-Importer (Bestandsdaten)
|
|
|
|
**Ziel:** Importieren der `demo_100.xlsx` in die SQLite-Datenbank.
|
|
|
|
**Anforderungen:**
|
|
1. **PLZ-Handling:** Zwingend als **String** einlesen (führende Nullen erhalten).
|
|
2. **Normalisierung:** Website bereinigen (kein `www.`, `https://`).
|
|
3. **Matching:** Kaskade über CRM-ID, VAT, Domain, Fuzzy Name.
|
|
4. **Isolierung:** Nur `crm_` Spalten updaten, Golden Records unberührt lassen.
|
|
|
|
---
|
|
|
|
## 17. Analyse-Logik v3.0 (Feb 2026): Quantitative Potenzialanalyse & "Atomic Opener"
|
|
|
|
Nach mehreren instabilen Iterationen wurde die Kernlogik des `ClassificationService` finalisiert. Dieser Abschnitt dient als "Single Source of Truth", um zukünftige Fehlentwicklungen zu vermeiden.
|
|
|
|
### 17.1 Das Gesamtbild: Vom Content zur fertigen Analyse
|
|
|
|
Der Prozess ist streng sequenziell und baut aufeinander auf.
|
|
|
|
```
|
|
1. Branchen-Klassifizierung
|
|
|
|
|
-> Erkannte Branche: "Healthcare - Hospital"
|
|
|
|
|
2. Quantitative Potenzialanalyse (Zweistufige Kaskade)
|
|
|
|
|
--> 2a. Stufe 1: Direkte Flächensuche ("Fläche in m²")
|
|
| |
|
|
| --> Ergebnis: FEHLSCHLAG
|
|
|
|
|
--> 2b. Stufe 2: Branchenspezifische Proxy-Suche
|
|
|
|
|
--> Suchbegriff (aus Branche): "Anzahl Betten"
|
|
--> Formel (aus Branche): "wert * 100"
|
|
|
|
|
-> Ergebnis: 250 Betten -> 25000 m²
|
|
|
|
|
3. "Atomic Opener" Generierung (Zwei getrennte Personas)
|
|
|
|
|
--> 3a. Opener 1 (Primär): Fokus auf Infrastruktur-Entscheider
|
|
| |
|
|
| --> Produkt-Kontext: Nassreinigungsroboter (Primärprodukt)
|
|
| --> Pain-Kontext: Hygiene-Audits, Keimbelastung
|
|
|
|
|
--> 3b. Opener 2 (Sekundär): Fokus auf Operativen Entscheider
|
|
|
|
|
--> Produkt-Kontext: Serviceroboter (Sekundärprodukt, da "ops_focus_secondary" aktiv)
|
|
|
|
|
--> Pain-Kontext: Personalmangel, Entlastung der Pflegekräfte
|
|
|
|
|
4. FINALES COMMIT
|
|
```
|
|
|
|
### 17.2 Quantitative Potenzialanalyse im Detail
|
|
|
|
**Ziel:** Für jedes Unternehmen einen `standardized_metric_value` in `m²` zu ermitteln.
|
|
|
|
* **Stufe 1: Direkte Flächensuche (Direct Hit)**
|
|
* Das System sucht **immer** zuerst nach direkten Flächenangaben (Keywords: "Fläche", "m²", "Quadratmeter").
|
|
* Findet der `MetricParser` einen plausiblen Wert, wird dieser direkt in `standardized_metric_value` geschrieben und der Prozess ist für diese Stufe beendet. `calculated_metric_value` ist in diesem Fall identisch.
|
|
|
|
* **Stufe 2: Proxy-Metrik-Suche (Fallback)**
|
|
* **Nur wenn Stufe 1 fehlschlägt**, wird die branchenspezifische Logik aus den `industries`-Settings angewendet.
|
|
* **Suchbegriff:** `scraper_search_term` (z.B. "Anzahl Betten", "Anzahl Passagiere").
|
|
* **Extraktion:** Der `MetricParser` extrahiert den Rohwert (z.B. `250`). Dieser wird in `calculated_metric_value` gespeichert.
|
|
* **Standardisierung:** Die Formel aus `standardization_logic` (z.B. `wert * 100`) wird auf den Rohwert angewendet. Das Ergebnis wird in `standardized_metric_value` geschrieben.
|
|
|
|
### 17.3 "Atomic Opener" Generierung im Detail (Zweistufiger Prozess, Feb 22, 2026)
|
|
|
|
**Ziel:** Zwei hoch-personalisierte, schlagkräftige Einleitungssätze (2-3 Sätze) zu generieren, die eine operative Herausforderung als wertschätzende Beobachtung formulieren.
|
|
|
|
Um die Qualität der Ergebnisse zu maximieren und "Rauschen" aus den Website-Texten zu eliminieren, wurde ein zweistufiger Generierungsprozess implementiert.
|
|
|
|
#### Schritt 1: Das Website-Dossier (Extraktion & Komprimierung)
|
|
Zunächst analysiert die KI den Rohtext der Website und erstellt ein strukturiertes Dossier. Dieser Zwischenschritt dient dazu, die wesentlichen Informationen über das Geschäftsmodell und das Reinigungspotenzial (Hygieneanforderungen, Flächengröße) zu isolieren.
|
|
|
|
**Prompt (Website-Dossier - Fokus Reinigung):**
|
|
```text
|
|
**Rolle:** Du bist ein erfahrener B2B-Marktanalyst mit Fokus auf Facility Management und Gebäudereinigung.
|
|
**Aufgabe:** Analysiere den Website-Text des Unternehmens '{company_name}' und erstelle ein prägnantes Dossier.
|
|
|
|
**Deine Analyse besteht aus ZWEI TEILEN:**
|
|
|
|
**TEIL 1: Geschäftsmodell-Analyse**
|
|
1. Identifiziere die Kernprodukte und/oder Dienstleistungen des Unternehmens.
|
|
2. Fasse in 2-3 prägnanten Sätzen zusammen, was das Unternehmen macht und für welche Kunden.
|
|
|
|
**TEIL 2: Reinigungspotenzial & Hygiene-Analyse**
|
|
1. Scanne den Text gezielt nach Hinweisen auf große Bodenflächen, Publikumsverkehr oder hohe Hygieneanforderungen (Schlüsselwörter: Reinigung, Sauberkeit, Hygiene, Bodenpflege, Verkaufsfläche, Logistikhalle, Patientenversorgung, Gästeerlebnis).
|
|
2. Bewerte das Potenzial für automatisierte Reinigungslösungen auf einer Skala (Hoch / Mittel / Niedrig).
|
|
3. Extrahiere die 1-2 wichtigsten Sätze, die diese Anforderungen oder die Größe der Einrichtung belegen.
|
|
|
|
**Antworte AUSSCHLIESSLICH im folgenden exakten Format:**
|
|
GESCHÄFTSMODELL: <Deine 2-3 Sätze über das Kerngeschäft des Unternehmens.>
|
|
REINIGUNGSPOTENZIAL: <Hoch / Mittel / Niedrig / Kein Hinweis>
|
|
HYGIENE-BEWEISE: <Die 1-2 aussagekräftigsten Sätze als Bullet Points (* Satz 1...)>
|
|
```
|
|
|
|
#### Schritt 2: Formulierung des Openers (Scharfsinnige Beobachtung)
|
|
Basierend auf dem Dossier aus Schritt 1 und den branchenspezifischen Pains/Gains wird der finale Opener formuliert.
|
|
|
|
**Prompt (Finaler Opener - Optimiert Feb 22, 2026):**
|
|
```text
|
|
Du bist ein scharfsinniger Marktbeobachter und Branchenexperte. Formuliere eine wertschätzende Einleitung (genau 2-3 Sätze) für ein Anschreiben an das Unternehmen {company.name}.
|
|
|
|
DEINE PERSONA:
|
|
Ein anerkennender Branchenkenner, der eine scharfsinnige Beobachtung teilt. Dein Ton ist wertschätzend, professionell und absolut NICHT verkäuferisch.
|
|
|
|
STRATEGISCHER HINTERGRUND (Nicht nennen!):
|
|
Dieses Unternehmen wird kontaktiert, weil sein Geschäftsmodell perfekt zu folgendem Bereich passt: "{product_name}" ({product_desc}).
|
|
Ziel des Schreibens ist es, die Branchen-Herausforderungen "{relevant_pains}" zu adressieren und die Mehrwerte "{relevant_gains}" zu ermöglichen.
|
|
|
|
DEINE AUFGABE:
|
|
1. Firmenname kürzen: Kürze "{company.name}" sinnvoll (meist erste zwei Worte). Entferne UNBEDINGT Rechtsformen wie GmbH, AG, gGmbH, e.V. etc.
|
|
2. Struktur: Genau 2 bis 3 flüssige Sätze.
|
|
3. Inhalt:
|
|
- Satz 1: Eine wertschätzende Beobachtung zum Geschäftsmodell oder einem aktuellen Fokus des Unternehmens (siehe Analyse-Dossier).
|
|
- Satz 2-3: Leite elegant zu einer spezifischen operativen Herausforderung über, die für das Unternehmen aufgrund seiner Größe oder Branche relevant ist (orientiere dich an "{relevant_pains}").
|
|
4. STRENGES VERBOT: Nenne KEIN Produkt ("{product_name}") and biete KEINE "Lösungen", "Hilfe" oder "Zusammenarbeit" an. Der Text soll eine reine Beobachtung bleiben.
|
|
5. KEINE Anrede (kein "Sehr geehrte Damen und Herren", kein "Hallo").
|
|
|
|
KONTEXT (Analyse-Dossier):
|
|
{context_text}
|
|
```
|
|
|
|
* **Regel:** Das Produkt selbst wird **nicht** im Opener genannt. Der Satz fokussiert sich rein auf die Formulierung der Herausforderung. Die Auflösung erfolgt in den nachfolgenden, persona-spezifischen Textbausteinen.
|
|
|
|
### 17.4 Debugging & Lessons Learned (Feb 21, 2026)
|
|
|
|
Die Implementierung der v3.0-Logik war von mehreren hartnäckigen Problemen geprägt, deren Behebung wichtige Erkenntnisse für die zukünftige Entwicklung lieferte.
|
|
|
|
1. **"Phantom" `NameError` für `joinedload`:**
|
|
* **Problem:** Trotz korrekter `import`-Anweisung wurde ein `NameError` ausgelöst.
|
|
* **Lösung:** Ein erzwungener Neustart des Containers (`--force-recreate`) ist nach kritischen Code-Änderungen (besonders Imports) unerlässlich.
|
|
|
|
2. **Die "Krankenhaus-Schlacht" (Proxy-Metriken & Parser-Interferenz):**
|
|
* **Problem:** Bei Kliniken wurde oft der Wert "100" extrahiert (aus "100%ige Trägerschaft"), anstatt der korrekten Bettenanzahl. Zudem scheiterte die Standardisierung an Resten von Einheiten in der Formel (z.B. `wert * 100 (m²)`).
|
|
* **Lösung 1 (Targeted Matching):** Der `MetricParser` wurde so umgebaut, dass er einen "Hint" (erwarteter Wert vom LLM) priorisiert. Er sucht nun im Volltext exakt nach der Ziffernfolge, die das LLM identifiziert hat, und ignoriert alle anderen plausiblen Zahlen.
|
|
* **Lösung 2 (Aggressive Formula Cleaning):** Die `_parse_standardization_logic` entfernt nun konsequent alles in Klammern und alle Nicht-Rechenzeichen, bevor sie `safe_eval_math` aufruft. Dies verhindert `SyntaxError` durch Datenbank-Reste.
|
|
|
|
3. **Persona-spezifische Pains:**
|
|
* **Erkenntnis:** Damit die Opener wirklich zwischen Infrastruktur und Betrieb unterscheiden, müssen die `pains` in der Datenbank mit Markern wie `[Primary Product]` und `[Secondary Product]` versehen werden. Die Logik wurde entsprechend angepasst, um diese Segmente gezielt zu extrahieren.
|
|
|
|
4. **Backend-Absturz durch `NoneType`-Fehler:**
|
|
* **Problem:** Während der Analyse stürzte der Backend-Worker ab (`AttributeError: 'NoneType' object has no attribute 'lower'`), weil `calculated_metric_unit` in der Datenbank `NULL` war.
|
|
* **Lösung:** Robuste Prüfung auf `None` vor der String-Manipulation (`(value or "").lower()`) implementiert.
|
|
* **Test:** Ein vollständiger E2E-Test (`test_e2e_full_flow.py`) wurde etabliert, der Provisioning, Analyse und Opener-Generierung automatisiert verifiziert.
|
|
|
|
Diese Punkte unterstreichen die Notwendigkeit von robusten Deployment-Prozessen, aggressiver Datenbereinigung und der Schaffung von dedizierten Test-Tools zur Isolierung komplexer Anwendungslogik.
|
|
|
|
### 17.5 Lessons Learned: SuperOffice Address Sync (Feb 22, 2026)
|
|
|
|
Die Synchronisation von Stammdaten (Adresse, VAT) erforderte ein tiefes Eintauchen in die API-Struktur.
|
|
|
|
1. **Field Naming:** Die REST-API verlangt strikt `OrgNr` für die Umsatzsteuer-ID, nicht `OrgNumber` oder `VatNo`.
|
|
2. **Nested Updates:** Adressen müssen tief verschachtelt übergeben werden (`Address.Postal.City`), nicht flach (`PostalAddress`).
|
|
3. **Atomic Strategy:** Getrennte Updates für UDFs und Standardfelder führen zu Race Conditions. **Nur ein gebündelter PUT-Request** auf den Haupt-Endpunkt garantiert, dass keine Daten (durch veraltete Reads) überschrieben werden.
|
|
|
|
### 17.6 Lessons Learned: Full E-Mail Simulation & Persona Hardening (Feb 22, 2026)
|
|
|
|
Der End-to-End-Test der E-Mail-Simulation im SuperOffice-Kalender deckte subtile UI-Herausforderungen auf.
|
|
|
|
1. **Die "42-Zeichen-Falle" (Appointment Title):**
|
|
* **Problem:** Das `MainHeader`-Feld in der SuperOffice-Listenansicht ist auf ca. 42 Zeichen begrenzt. Längere Betreffzeilen werden abgeschnitten, und der Überhang wird oft als erste Zeile in den Textkörper verschoben.
|
|
* **Lösung:** Strikte serverseitige Kürzung des Titels auf 40 Zeichen. Zusätzlich wird der **vollständige Betreff** als allererste Zeile in die Description geschrieben, gefolgt von zwei Newlines. Dies stellt sicher, dass SuperOffice den Betreff "stiehlt" und die Anrede ("Hallo...") sicher im Body bleibt.
|
|
|
|
2. **Rollen-Dynamik & "Sticking":**
|
|
* **Problem:** Wenn ein Nutzer den Jobtitel ändert, blieb oft die alte Persona (z.B. "Infrastruktur") aktiv, wenn für den neuen Titel kein exakter Match vorlag.
|
|
* **Lösung:** Implementierung eines **Rollen-Resets**. Bei jeder Namens- oder Funktionsänderung wird die Persona im Company Explorer gelöscht und basierend auf den neuesten Mappings (z.B. neue Regeln für "Geschäftsleitung" -> "Wirtschaftlicher Entscheider") neu berechnet.
|
|
|
|
3. **Kaskadierung & Loop-Schutz:**
|
|
* **Problem:** Änderungen am Unternehmen (Contact) müssen alle Personen aktualisieren. Dies triggerte jedoch Endlosschleifen, da SuperOffice nach jedem Update einen eigenen Webhook sendete.
|
|
* **Lösung:** Implementierung einer **Deep-Filter-Logik**. Der Worker ignoriert nun Felder wie `updated`, `updated_associate_id` und `registered`. Nur inhaltliche Änderungen (Name, Branche, Funktion) lösen die Verarbeitungslogik aus.
|
|
|
|
### 17.7 Marketing Matrix Schärfung (v3.2 - Feb 22, 2026)
|
|
|
|
Die Qualität der Marketing-Matrix (Subject, Intro, Social Proof) ist entscheidend für den Erfolg des Outreachs. Daher wurde die Generierungslogik in `generate_matrix.py` massiv geschärft.
|
|
|
|
**Kern-Konzept: Der Strategische Brückenschlag**
|
|
Die KI agiert nicht mehr als reiner Copywriter, sondern als **scharfsinniger B2B-Strategieberater** ("Lösungsberater"). Der Prompt erzwingt die Verknüpfung von drei Ebenen:
|
|
1. **Persönlicher Druck (Persona):** Was hält den Entscheider nachts wach? (Pains der Rolle)
|
|
2. **Strategische Notwendigkeit (Branche):** Welchen externen Anforderungen muss das Unternehmen gerecht werden? (Pains/Gains der Branche)
|
|
3. **Technologischer Enabler (Produkt):** Wie genau löst die spezifische Robotik-Kategorie (Cleaning, Service, etc.) diesen Konflikt?
|
|
|
|
**Neues Feature: "Ops Focus: Secondary" (Die Rollen-Weiche)**
|
|
Für bestimmte Branchen (z.B. **Healthcare - Hospital**) ist das Primärprodukt (Reinigung) zwar für das Facility Management relevant, aber für die operative Leitung (Pflegedienstleitung) uninteressant.
|
|
* **Logik:** Wenn in Notion das Flag `Ops Focus: Secondary` aktiv ist, wechselt die Engine für die Persona **"Operativer Entscheider"** automatisch auf das **Sekundärprodukt** (z.B. Service-Roboter/Transport).
|
|
* **Resultat:** Der Pflegedienstleiter bekommt Texte über "Entlastung von Routinetätigkeiten" (Transport), während der Technische Leiter weiterhin Texte über "Effiziente Flächenreinigung" (Cleaning) erhält.
|
|
* **Technische Umsetzung:** Die Pains/Gains in Notion sind mit Tags `[Primary Product: ...]` und `[Secondary Product: ...]` versehen. Der Parser (`extract_segment`) extrahiert gezielt den für die Rolle relevanten Block.
|
|
|
|
**Der "Lösungsberater" Prompt (Auszug):**
|
|
```python
|
|
prompt = f"""
|
|
Du bist ein kompetenter Lösungsberater und brillanter Texter.
|
|
AUFGABE: Erstelle 3 Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly) für eine E-Mail an einen Entscheider.
|
|
|
|
--- KONTEXT ---
|
|
ZIELBRANCHE: {industry.name}
|
|
BRANCHEN-HERAUSFORDERUNGEN (PAIN POINTS): {industry_pains}
|
|
FOKUS-PRODUKT (LÖSUNG): {target_scope} ({product_context})
|
|
ANSPRECHPARTNER (ROLLE): {persona.name}
|
|
PERSÖNLICHE HERAUSFORDERUNGEN: {persona_pains}
|
|
|
|
--- DEINE AUFGABE ---
|
|
1. **Subject:** Formuliere eine kurze Betreffzeile (max. 6 Wörter). Richte sie **direkt an einem der persönlichen Pain Points** des Ansprechpartners.
|
|
2. **Introduction_Textonly:** Formuliere einen Einleitungstext (2-3 Sätze).
|
|
- **Satz 1 (Die Brücke):** Knüpfe an die (uns unbekannte) operative Herausforderung an.
|
|
- **Satz 2 (Die Relevanz):** Schaffe die Relevanz für die Zielperson ("Für Sie als {persona.name} ist dabei entscheidend...").
|
|
3. **Industry_References_Textonly:** Formuliere einen **strategischen Referenz-Block** ("Social Proof").
|
|
"""
|
|
```
|
|
|
|
**Prompt-Anforderungen (Matrix):**
|
|
* **Betreff:** "Finger in die Wunde" (Max. 6 Wörter).
|
|
* **Einleitung:** Sofortiges Gefühl von "Verstanden-Werden" durch Verbindung von Rollen-Verantwortung und Branchen-Herausforderung.
|
|
* **Social Proof:** Quantifizierbare Effekte statt leerer Versprechungen, abgestimmt auf die Branche.
|
|
* **Produkt-Kontext:** Nutzung der offiziellen Kategorie-Beschreibungen aus der Datenbank zur Vermeidung von generischen Floskeln.
|
|
|
|
---
|
|
|
|
## 18. Next Steps & Todos (Post-Migration)
|
|
Nach Abschluss der Kern-Migration stehen folgende Optimierungen an:
|
|
|
|
### Task 1: Monitoring & Alerting
|
|
* **Dashboards:** Ausbau des Connector-Dashboards (`/connector/dashboard`) um Fehler-Statistiken und Retry-Logik.
|
|
* **Alerting:** Benachrichtigung (z.B. Slack/Teams) bei wiederholten Sync-Fehlern.
|
|
|
|
### Task 2: Robust Address Parsing
|
|
* **Scraper:** Derzeit verlässt sich der Scraper auf das LLM für die Adress-Extraktion. Eine Validierung gegen Google Maps API oder PLZ-Verzeichnisse würde die Datenqualität ("Golden Record") massiv erhöhen.
|
|
|
|
### Task 3: "Person-First" Logic
|
|
* **Aktuell:** Trigger ist meist das Unternehmen.
|
|
* **Zukunft:** Wenn eine Person ohne Firma angelegt wird, sollte der CE proaktiv die Domain der E-Mail-Adresse nutzen, um das Unternehmen im Hintergrund zu suchen und anzulegen ("Reverse Lookup"). |