179 lines
9.7 KiB
Markdown
179 lines
9.7 KiB
Markdown
# SuperOffice Connector ("The Butler") - GTM Engine
|
|
|
|
Dies ist der Microservice zur Anbindung von **SuperOffice CRM** an die **GTM-Engine**.
|
|
Der Connector agiert als "Butler": Er bereitet im Hintergrund hyper-personalisierte Marketing-Texte vor und legt sie im CRM ab, damit der Vertrieb sie mit minimalem Aufwand versenden kann.
|
|
|
|
## 1. Architektur: Das "Butler-Modell"
|
|
|
|
Wir haben uns gegen eine komplexe Automatisierung *innerhalb* von SuperOffice (via CRMScript-Trigger) entschieden, da die DEV-Umgebung limitiert ist und die Logik extern flexibler ist.
|
|
|
|
**Der Datenfluss:**
|
|
1. **Master-Daten (Notion):** Pains & Gains werden pro Vertical in Notion gepflegt.
|
|
2. **Text-Matrix (Lokal):** Das Skript `build_matrix.py` "multipliziert" die Vertical-Daten mit den Rollen-Daten, generiert via Gemini die finalen Texte und speichert sie in einer lokalen SQLite DB (`marketing_matrix.db`).
|
|
3. **Polling & Injection (Daemon):** Das Skript `polling_daemon_final.py` läuft periodisch, prüft auf Änderungen an Kontakten in SuperOffice und "injiziert" die passenden Texte aus der lokalen Matrix-DB in die UDF-Felder.
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph "A: Strategie & Content"
|
|
NotionDB["🏢 Notion DB<br/>(Industries & Roles)"]
|
|
end
|
|
|
|
subgraph "B: Lokale Engine (Python)"
|
|
MatrixBuilder["⚙️ build_matrix.py"]
|
|
GeminiAPI["🧠 Gemini API"]
|
|
MatrixDB[("💾 marketing_matrix.db")]
|
|
PollingDaemon["🤖 polling_daemon_final.py"]
|
|
end
|
|
|
|
subgraph "C: CRM-System"
|
|
SuperOfficeAPI["☁️ SuperOffice API"]
|
|
Contact["👤 Contact / Person<br/>(mit UDFs)"]
|
|
end
|
|
|
|
NotionDB -->|1. Liest Pains/Gains| MatrixBuilder
|
|
MatrixBuilder -->|2. Promptet| GeminiAPI
|
|
GeminiAPI -->|3. Generiert Texte| MatrixBuilder
|
|
MatrixBuilder -->|4. Speichert in DB| MatrixDB
|
|
|
|
PollingDaemon -->|5. Prüft Änderungen| SuperOfficeAPI
|
|
PollingDaemon -->|6. Holt passende Texte| MatrixDB
|
|
PollingDaemon -->|7. Schreibt in UDFs| SuperOfficeAPI
|
|
SuperOfficeAPI -->|8. Aktualisiert| Contact
|
|
```
|
|
|
|
## 2. Kern-Komponenten (Skripte)
|
|
|
|
* **`superoffice_client.py`:**
|
|
* Das Herzstück. Eine Python-Klasse, die die gesamte Kommunikation mit der SuperOffice API kapselt (Auth, GET, PUT, Search).
|
|
* Beherrscht Paginierung, um auch große Datensätze zu verarbeiten.
|
|
|
|
* **`build_matrix.py`:**
|
|
* Der "Content Generator".
|
|
* Liest Pains/Gains aus Notion.
|
|
* Iteriert über definierte Verticals und Rollen.
|
|
* Nutzt Gemini, um die spezifischen Textbausteine (`Subject`, `Intro`, `SocialProof`) zu erstellen.
|
|
* Speichert das Ergebnis in der `marketing_matrix.db`.
|
|
|
|
* **`polling_daemon_final.py`:**
|
|
* Der Automatisierungs-Motor.
|
|
* Läuft in einer Schleife (z.B. alle 15 Minuten) nur zu Geschäftszeiten (Mo-Fr, 7-18 Uhr).
|
|
* Fragt SuperOffice nach kürzlich geänderten Kontakten.
|
|
* Nutzt ein **Hash-System**, um zu prüfen, ob sich eine *relevante* Eigenschaft (Vertical oder Rolle) geändert hat, um unnötige API-Schreibvorgänge zu vermeiden.
|
|
* Wenn eine Änderung erkannt wird, stößt er den Update-Prozess an.
|
|
|
|
* **`normalize_persona.py`:**
|
|
* Ein Hilfs-Skript (Classifier), um Jobtitel auf unsere 4 Archetypen (Operativ, Infrastruktur, Wirtschaftlich, Innovation) zu mappen.
|
|
|
|
## 3. Setup & Konfiguration
|
|
|
|
### Installation
|
|
```bash
|
|
# In einer Python 3.11+ Umgebung:
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
### Konfiguration (`.env`)
|
|
Der Connector benötigt eine `.env` Datei im Root-Verzeichnis mit folgendem Inhalt:
|
|
```ini
|
|
# Gemini API Keys (DEV für Tests, PROD für Live-Tools)
|
|
GEMINI_API_KEY_DEV="AIza..."
|
|
GEMINI_API_KEY_PROD="AIza..."
|
|
GEMINI_API_KEY=${GEMINI_API_KEY_DEV} # Setzt den Default
|
|
|
|
# Notion
|
|
NOTION_API_KEY="secret_..."
|
|
|
|
# SuperOffice Client Configuration
|
|
SO_CLIENT_ID="<Client ID / App ID>"
|
|
SO_CLIENT_SECRET="<Client Secret>"
|
|
SO_CONTEXT_IDENTIFIER="CustXXXXX"
|
|
SO_REFRESH_TOKEN="<Refresh Token>"
|
|
SO_ENVIRONMENT="sod" # oder "online" für Produktion
|
|
```
|
|
|
|
## 4. SuperOffice Konfiguration (Admin)
|
|
|
|
Folgende **Benutzerdefinierte Felder (UDFs)** müssen angelegt sein. Die ProgIds sind umgebungs-spezifisch und müssen pro System (DEV/PROD) geprüft werden.
|
|
|
|
| Objekt | Feldname (Label) | Typ | ProgId (DEV) | ProgId (PROD) |
|
|
| :--- | :--- | :--- | :--- | :--- |
|
|
| Firma | Contact Vertical | Liste | `SuperOffice:5` | *tbd* |
|
|
| Firma | AI Challenge Satz | Text | `SuperOffice:6` | *tbd* |
|
|
| Person | Person Role | Liste | `SuperOffice:3` | *tbd* |
|
|
| Person | Marketing Subject | Text | `SuperOffice:5` | *tbd* |
|
|
| Person | Marketing Intro | Text | `SuperOffice:6` | *tbd* |
|
|
| Person | Marketing Proof | Text | `SuperOffice:7` | *tbd* |
|
|
| Person | AI Copy Hash | Text | `SuperOffice:8` | *tbd* |
|
|
|
|
### B. Listen-Werte & IDs (DEV Environment)
|
|
|
|
Folgende IDs werden in den Skripten als Referenz genutzt. Diese müssen für PROD neu validiert werden.
|
|
|
|
**MA Status (`x_ma_status`):**
|
|
* `Init`
|
|
* `Ready_to_Craft`
|
|
* `Ready_to_Send`
|
|
* `Sent_Week1`
|
|
* `...`
|
|
|
|
**Rollen (`x_person_role` / `SuperOffice:3`):**
|
|
| ID | Name |
|
|
| :--- | :--- |
|
|
| `19` | Operativer Entscheider |
|
|
| `20` | Infrastruktur-Verantwortlicher |
|
|
| `21` | Wirtschaftlicher Entscheider |
|
|
| `22` | Innovations-Treiber |
|
|
|
|
**Verticals (`x_contact_vertical` / `SuperOffice:5`):**
|
|
| ID | Name |
|
|
| :--- | :--- |
|
|
| `23` | Logistics - Warehouse |
|
|
| `24` | Healthcare - Hospital |
|
|
| `25` | Infrastructure - Transport |
|
|
| `26` | Leisure - Indoor Active |
|
|
| `...` | *tbd* |
|
|
|
|
## 5. "Gotchas" & Lessons Learned (Update Feb 16, 2026)
|
|
|
|
* **API-URL:** Der `sod` Tenant `Cust55774` ist nur über `https://app-sod.superoffice.com` erreichbar.
|
|
* **Listen-IDs:** Die API gibt IDs von Listenfeldern im Format `[I:26]` zurück. Der String muss vor der DB-Abfrage auf den Integer `26` geparst werden.
|
|
* **Write-Back (Stammfelder):**
|
|
* **UrlAddress & Phones:** Das einfache Root-Feld `UrlAddress` ist beim `PUT` oft schreibgeschützt. Um die Website oder Telefonnummern zu setzen, muss die entsprechende Liste (`Urls` oder `Phones`) als Array von Objekten gesendet werden (z.B. `{"Value": "...", "Description": "..."}`).
|
|
* **Mandatory Fields:** Beim Update eines `Contact` Objekts müssen Pflichtfelder wie `Name` und `Number2` (oder `Number1`) zwingend im Payload enthalten sein, sonst schlägt die Validierung serverseitig fehl.
|
|
* **Full Object PUT:** SuperOffice REST überschreibt das gesamte Objekt. Felder, die im `PUT`-Payload fehlen, werden im CRM geleert. Es empfiehlt sich, das Objekt erst per `GET` zu laden, die Änderungen vorzunehmen und dann das gesamte Objekt zurückzusenden.
|
|
* **Dev-System Limits:** Die Textfelder im DEV-System sind auf 40 Zeichen limitiert.
|
|
* **Y-Tabellen & Trigger:** Direkter Zugriff auf Zusatz-Tabellen und CRMScript-Trigger sind im SOD-DEV Mandanten blockiert.
|
|
|
|
## 6. Umbau-Plan für Skalierbarkeit (19.02.2026)
|
|
|
|
Die aktuelle Implementierung ist transaktionsbasiert und nicht für die Massenverarbeitung ausgelegt. Der folgende Plan beschreibt den notwendigen Umbau, um die Schnittstelle performant und robust für den Batch-Betrieb zu machen.
|
|
|
|
### 6.1. Kernproblem: Transaktion vs. Batch
|
|
* **IST-Zustand:** Ein geänderter Kontakt löst eine Kette von individuellen API-Calls aus (Lesen, Hashen, Schreiben).
|
|
* **SOLL-Zustand:** Ein periodischer Job sammelt alle Änderungen und verarbeitet sie in einem einzigen, optimierten Durchlauf.
|
|
|
|
### 6.2. Maßnahmen & Technische Umsetzung
|
|
|
|
1. **Batch-fähige Client-Methoden in `superoffice_client.py`:**
|
|
* Die SuperOffice API unterstützt Batch-Operationen via `/$batch`-Endpunkt.
|
|
* **Aufgabe:** Implementierung einer neuen Methode `execute_batch(requests)` im Client. Diese Methode nimmt eine Liste von einzelnen API-Anfragen (z.B. mehrere `PUT`-Operationen) entgegen, bündelt sie in einen einzelnen `POST`-Request an den `/$batch`-Endpunkt und verarbeitet die gesammelte Antwort.
|
|
|
|
2. **Einführung eines "Change-Tracking"-Mechanismus:**
|
|
* **Problem:** Wir müssen effizient erkennen, welche Kontakte seit dem letzten Lauf aktualisiert werden müssen.
|
|
* **Aufgabe:** Wir erweitern unsere lokale Company-Explorer-Datenbank um ein Feld `needs_sync (boolean)` oder `last_so_sync (datetime)`. Wenn eine Änderung im CE stattfindet, wird der Flag gesetzt.
|
|
* Der `polling_daemon` wird umgebaut: Statt einzelne Kontakte zu vergleichen, sammelt er alle Kontakte, bei denen `needs_sync` auf `true` steht.
|
|
|
|
3. **Neuer Workflow im `polling_daemon_final.py`:**
|
|
* **Phase 1: Sammeln:** Der Daemon fragt die lokale DB ab: `SELECT * FROM contacts WHERE needs_sync = true`.
|
|
* **Phase 2: Batch-Request bauen:** Für jeden Kontakt in der Liste wird der notwendige `PUT`-Request für die SuperOffice API vorbereitet (aber noch nicht gesendet).
|
|
* **Phase 3: Ausführen:** Alle vorbereiteten Requests werden an die neue `execute_batch()`-Methode im Client übergeben.
|
|
* **Phase 4: Ergebnisverarbeitung & Fehler-Handling:** Die Batch-Antwort von SuperOffice wird analysiert.
|
|
* Für jeden erfolgreich verarbeiteten Kontakt wird `needs_sync` in der lokalen DB auf `false` gesetzt.
|
|
* Fehlgeschlagene Updates werden detailliert geloggt (inkl. Kontakt-ID und Fehlermeldung), ohne den gesamten Batch abzubrechen.
|
|
|
|
4. **Konfliktlösungs-Strategie definieren:**
|
|
* **Regel:** "SuperOffice hat Priorität."
|
|
* **Umsetzung:** Der Polling-Daemon wird zusätzlich regelmäßig (z.B. alle 6 Stunden) einen Hash-Abgleich für eine größere Menge an Kontakten durchführen. Stellt er eine Diskrepanz fest (weil ein Kontakt manuell in SO geändert wurde), wird die Änderung aus SuperOffice in den Company Explorer übernommen und der `needs_sync`-Flag für diesen Kontakt *nicht* gesetzt. Dies verhindert, dass manuelle Änderungen überschrieben werden.
|
|
|
|
Dieser Umbau ist eine Voraussetzung für den produktiven Einsatz und stellt sicher, dass die Schnittstelle auch bei tausenden von Kontakten stabil und performant bleibt.
|