1. E2E-Test erfolgreich: Das neue Test-Skript (test_e2e_full_flow.py) hat den kompletten Prozess automatisiert verifiziert:
* Provisionierung: Anlage eines Test-Unternehmens ("Klinikum Landkreis Erding (E2E Test)") über die API.
* Discovery & Analyse: Das System hat die Website gefunden, gescrapt und die Branche ("Healthcare - Hospital") korrekt
klassifiziert.
* Fehlerbehebung (Crash-Fix): Während des Tests wurde ein kritischer AttributeError im Backend identifiziert (Absturz bei
fehlenden Einheiten) und von mir behoben. Seitdem läuft die Analyse ohne Unterbrechung durch.
* Opener-Generierung: Es wurden erfolgreich zwei hoch-personalisierte Einleitungssätze (Primary & Secondary Opener) generiert.
* SuperOffice-Payload: Der finale Abgleich lieferte die korrekte Rolle ("Wirtschaftlicher Entscheider") für den
"Geschäftsführer" zurück.
15 KiB
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: 1. Strict Industry Classification. 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
- Identifizierte Komponente:
- 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
- Frontend "Settings" Komponente:
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: 1. Prüfen: Stellt fest, ob ein Unternehmen bereits existiert. 2. Erstellen: Legt das Unternehmen an, falls es neu ist. 3. Anstoßen: Startet den asynchronen discover-Prozess. 4. Warten (Polling): Überwacht den Status des Unternehmens, bis eine Website gefunden wurde. 5. Analysieren: Startet den asynchronen analyze-Prozess. 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
companiesersetzt.
Tabelle contacts (Ansprechpartner)
id(PK)account_id(FK -> companies.id)gender,title,first_name,last_name,emailjob_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_statusundwiki_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:
- PLZ-Handling: Zwingend als String einlesen (führende Nullen erhalten).
- Normalisierung: Website bereinigen (kein
www.,https://). - Matching: Kaskade über CRM-ID, VAT, Domain, Fuzzy Name.
- 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
MetricParsereinen plausiblen Wert, wird dieser direkt instandardized_metric_valuegeschrieben und der Prozess ist für diese Stufe beendet.calculated_metric_valueist 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
MetricParserextrahiert den Rohwert (z.B.250). Dieser wird incalculated_metric_valuegespeichert. - Standardisierung: Die Formel aus
standardization_logic(z.B.wert * 100) wird auf den Rohwert angewendet. Das Ergebnis wird instandardized_metric_valuegeschrieben.
- Nur wenn Stufe 1 fehlschlägt, wird die branchenspezifische Logik aus den
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:
ai_opener(Primär): Zielt auf den Infrastruktur-Entscheider (z.B. Facility Manager, Technischer Leiter).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_categoryder Branche. - Der sekundäre Opener (Operativ) bezieht sich:
- Standardmäßig ebenfalls auf das
primary_category. - Ausnahme: Wenn in der Branche
ops_focus_secondary = Truegesetzt ist, bezieht er sich auf dassecondary_category.
- Standardmäßig ebenfalls auf das
- Der primäre Opener (Infrastruktur) bezieht sich immer auf das
-
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
painsundgainsaus 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.
-
"Phantom"
NameErrorfürjoinedload:- Problem: Trotz korrekter
import-Anweisung wurde einNameErrorausgelöst. - Lösung: Ein erzwungener Neustart des Containers (
--force-recreate) ist nach kritischen Code-Änderungen (besonders Imports) unerlässlich.
- Problem: Trotz korrekter
-
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
MetricParserwurde 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_logicentfernt nun konsequent alles in Klammern und alle Nicht-Rechenzeichen, bevor siesafe_eval_mathaufruft. Dies verhindertSyntaxErrordurch Datenbank-Reste.
- 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.
-
Persona-spezifische Pains:
- Erkenntnis: Damit die Opener wirklich zwischen Infrastruktur und Betrieb unterscheiden, müssen die
painsin der Datenbank mit Markern wie[Primary Product]und[Secondary Product]versehen werden. Die Logik wurde entsprechend angepasst, um diese Segmente gezielt zu extrahieren.
- Erkenntnis: Damit die Opener wirklich zwischen Infrastruktur und Betrieb unterscheiden, müssen die
-
Backend-Absturz durch
NoneType-Fehler:- Problem: Während der Analyse stürzte der Backend-Worker ab (
AttributeError: 'NoneType' object has no attribute 'lower'), weilcalculated_metric_unitin der DatenbankNULLwar. - Lösung: Robuste Prüfung auf
Nonevor 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.
- Problem: Während der Analyse stürzte der Backend-Worker ab (
Diese Punkte unterstreichen die Notwendigkeit von robusten Deployment-Prozessen, aggressiver Datenbereinigung und der Schaffung von dedizierten Test-Tools zur Isolierung komplexer Anwendungslogik.