Files
Brancheneinstufung2/MIGRATION_PLAN.md
Floke e43e129771 feat: robust metric extraction with confidence score and proof snippets
- fixed Year-Prefix Bug in MetricParser
- added metric_confidence and metric_proof_text to database
- added Entity-Check and Annual-Priority to LLM prompt
- improved UI: added confidence traffic light and mouse-over proof tooltip
- restored missing API endpoints (create, bulk, wiki-override)
2026-01-23 21:16:07 +00:00

187 lines
10 KiB
Markdown

# Migrations-Plan: Legacy GSheets -> Company Explorer (Robotics Edition v0.7.0)
**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 |
### 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.
* **View 3: "List Matcher":** Upload einer Excel-Liste -> Anzeige von Duplikaten -> Button "Neue importieren".
* **View 4: "Settings":** Konfiguration von Branchen, Rollen und Robotik-Logik.
## 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)
* **[STABILITY] v0.7.2: Robust Metric Parsing (Jan 23, 2026) [RESOLVED]**
* **Legacy Logic Restored:** Re-implemented the robust, regex-based number parsing logic (formerly in legacy helpers) as `MetricParser`.
* **German Formats:** Correctly handles "1.000" (thousands) vs "1,5" (decimal) and mixed formats.
* **Citation & Year Cleaning:** Filters out Wikipedia citations like `[3]` and years in parentheses.
* **Wolfra Fix:** Specifically detects and fixes the "802020" bug by stripping concatenated years from the end of numeric strings.
* **Hybrid Extraction:** The ClassificationService now asks the LLM for the *text segment* and parses the number deterministically, fixing the LLM hallucinations.
* **[SUCCESS] v0.6.4: Wolfra Metric Extraction Bug (Jan 23, 2026)**
* **Problem:** Mitarbeiterzahl für "Wolfra" wurde fälschlicherweise als "802020" anstatt "80" ausgelesen.
* **Gelöst durch:** Hybrid-Extraktion (LLM Segment + Python Cleanup) und Wiederherstellung der `MetricParser` Logik.
* **Status:** Problem **vollständig gelöst**. Alle Core-API Endpunkte (Import, Override, Create) wurden ebenfalls wiederhergestellt.
* **[STABILITY] v0.7.1: AI Robustness & UI Fixes (Jan 21, 2026)**
* **SDK Stabilität:** Umstellung auf `gemini-2.0-flash` im Legacy-SDK zur Behebung von `404 Not Found` Fehlern bei `1.5-flash-latest`.
* **API-Key Management:** Implementierung eines robusten Ladevorgangs für den Google API Key (Fallback von Environment-Variable auf lokale Datei `/app/gemini_api_key.txt`).
* **Classification Prompt:** Schärfung des Prompts auf "Best-Fit"-Entscheidungen, um zu konservative "Others"-Einstufungen bei klaren Kandidaten (z.B. Thermen) zu vermeiden.
* **Frontend Rendering:** Fix eines UI-Crashs im Inspector. Metriken werden jetzt auch angezeigt, wenn nur der standardisierte Wert (Fläche) vorhanden ist. Null-Safety für `.toLocaleString()` hinzugefügt.
* **Scraping:** Wiederherstellung der Stabilität durch Entfernung fehlerhafter `trafilatura` Abhängigkeiten; Nutzung von `BeautifulSoup` als robustem Standard.
* **[MAJOR] v0.7.0: Quantitative Potential Analysis (Jan 20, 2026)**
...
...
## 11. Lessons Learned (Retrospektive Jan 21, 2026)
1. **KI statt Regex für Zahlen:** Anstatt komplexe Python-Funktionen für deutsche Zahlenformate ("1,7 Mio.") zu schreiben, ist es stabiler, das LLM anzuweisen, den Wert direkt als Integer (1700000) zu liefern.
2. **Abhängigkeiten isolieren:** Änderungen an zentralen `core_utils.py` führen schnell zu Import-Fehlern in anderen Modulen. Spezifische Logik (wie Metrik-Parsing) sollte lokal im Service bleiben.
3. **UI Null-Safety:** Quantitative Daten sind oft unvollständig (z.B. Fläche vorhanden, aber Besucherzahl nicht). Das Frontend muss robust gegen `null`-Werte in den Metrik-Feldern sein, um den Render-Prozess nicht zu unterbrechen.
4. **SDK-Versionen:** Die Google-API ist in stetigem Wandel. Der explizite Rückgriff auf stabile Modelle wie `gemini-2.0-flash` ist im Legacy-SDK sicherer als die Nutzung von `-latest` Tags.
* **Zweistufige Analyse:**
1. **Strict Classification:** Ordnet Firmen einer Notion-Branche zu (oder "Others").
2. **Metric Cascade:** Sucht gezielt nach der branchenspezifischen Metrik ("Scraper Search Term").
* **Fallback-Kaskade:** Website -> Wikipedia -> SerpAPI (Google Search).
* **Standardisierung:** Berechnet vergleichbare Werte (z.B. m²) aus Rohdaten mit der `Standardization Logic`.
* **Datenbank:** Erweiterung der `companies`-Tabelle um Metrik-Felder, Deprecation der `signals`-Tabelle.
* **Backend Fixes (Finalized Jan 20, 2026):**
* Behebung diverser `NameError` und `ImportError` in `backend/lib/core_utils.py`, `backend/services/scraping.py` und `backend/services/classification.py`.
* Korrektur der `ClassificationService` Initialisierung und des Aufrufs in `backend/app.py` zur Behebung des `TypeError: missing 1 required positional argument: 'db'`.
* Wiederherstellung fehlender API-Endpunkte in `backend/app.py` (z.B. `/api/companies/bulk`) nach zu aggressivem Refactoring.
* **[UPGRADE] v0.6.1: Notion Sync Fixes**
* **Mapping:** Korrektur des Mappings für `Metric Type` und `Scraper Search Term` (Notion Select Fields).
* **Truncate-and-Reload:** Sync-Skript löscht alte Daten vor dem Import (für `industries`), behält aber `robotics_categories` bei (Upsert), um FK-Constraints zu schützen.
* **Frontend:** Korrektur der Einheiten-Anzeige ("Unit") im Settings-Dialog.
* **[UPGRADE] v0.6.0: Notion Single Source of Truth**
* Synchronisation von Branchen und Kategorien direkt aus Notion.
* **[UPGRADE] v0.5.1: Robustness**
* Logging, Wikipedia-Optimierung, UI-Fixes.
## 8. Eingesetzte Prompts (Account-Analyse v0.7.0)
### 8.1 Strict Industry Classification
Ordnet das Unternehmen einer definierten Branche zu.
```python
prompt = r"""
Du bist ein präziser Branchen-Klassifizierer.
...
--- ZU VERWENDENDE BRANCHEN-DEFINITIONEN (STRIKT) ---
{industry_definitions_json}
...
Wähle EINE der folgenden Branchen... Wenn keine zutrifft, wähle "Others".
"""
```
### 8.2 Metric Extraction
Extrahiert den spezifischen Zahlenwert ("Scraper Search Term").
```python
prompt = r"""
Analysiere den folgenden Text...
--- KONTEXT ---
Branche: {industry_name}
Gesuchter Wert: '{search_term}'
...
Gib NUR ein JSON-Objekt zurück:
'raw_value', 'raw_unit', 'area_value' (falls explizit m² genannt).
"""
```
## 9. Notion Integration
Das System nutzt Notion als SSoT für `Industries` und `RoboticsCategories`.
Sync-Skript: `backend/scripts/sync_notion_industries.py`.
## 10. Database Migration
Bei Schema-Änderungen ohne Datenverlust: `backend/scripts/migrate_db.py`.