286 lines
16 KiB
Markdown
286 lines
16 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. |
|
|
|
|
## 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**
|
|
|
|
## 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.
|
|
|
|
---
|
|
|
|
## 16. Deployment-Referenz (NAS)
|
|
* **Pfad:** `/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`
|
|
|
|
---
|
|
|
|
## 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
|
|
|
|
**Ziel:** Zwei hoch-personalisierte, schlagkräftige Einleitungssätze (1-2 Sätze) zu generieren, die eine operative Herausforderung implizieren, ohne die Lösung zu nennen.
|
|
|
|
* **Zwei getrennte Kontexte:** Es werden zwei Sätze für zwei Personas generiert:
|
|
1. **`ai_opener` (Primär):** Zielt auf den **Infrastruktur-Entscheider** (z.B. Facility Manager, Technischer Leiter).
|
|
2. **`ai_opener_secondary` (Sekundär):** Zielt auf den **Operativen Entscheider** (z.B. Produktionsleiter, Pflegedienstleitung).
|
|
|
|
* **Persona-spezifische Produktauswahl:**
|
|
* Der primäre Opener (Infrastruktur) bezieht sich **immer** auf das `primary_category` der Branche.
|
|
* Der sekundäre Opener (Operativ) bezieht sich:
|
|
* Standardmäßig ebenfalls auf das `primary_category`.
|
|
* **Ausnahme:** Wenn in der Branche `ops_focus_secondary = True` gesetzt ist, bezieht er sich auf das `secondary_category`.
|
|
|
|
* **Der "1komma5°"-Prompt:**
|
|
* Die Generierung nutzt einen bewährten Prompt, der das Sprachmodell anweist, das Geschäftsmodell des Unternehmens zu analysieren und eine wertschätzende Beobachtung zu formulieren.
|
|
* **"Munition":** Der Prompt wird dynamisch mit den hoch-spezifischen, vordefinierten `pains` und `gains` aus der jeweiligen Branche angereichert.
|
|
* **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.
|
|
|
|
---
|
|
|
|
## 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"). |