[2ff88f42] Finalize Verticals Pains/Gains in Notion & Update Docs

Updated all Notion Verticals with sharpened Pains/Gains based on internal strategy (Ops vs Infra focus). Updated SuperOffice Connector README to reflect the 'Static Magic' architecture.
This commit is contained in:
2026-02-20 13:24:13 +00:00
parent 46f650a350
commit 653bd79e1f
7 changed files with 644 additions and 63 deletions

View File

@@ -0,0 +1,117 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81" # ID from the user's link
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found in environment.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def get_vertical_data(vertical_name):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error fetching data for '{vertical_name}': {response.status_code} - {response.text}")
return None
results = response.json().get("results", [])
if not results:
print(f"No entry found for vertical '{vertical_name}'")
return None
# Assuming the first result is the correct one
page = results[0]
props = page["properties"]
# Extract Pains
pains_prop = props.get("Pains", {}).get("rich_text", [])
pains = pains_prop[0]["plain_text"] if pains_prop else "N/A"
# Extract Gains
gains_prop = props.get("Gains", {}).get("rich_text", [])
gains = gains_prop[0]["plain_text"] if gains_prop else "N/A"
# Extract Ops Focus (Checkbox) if available
# The property name might be "Ops. Focus: Secondary" based on user description
# Let's check keys to be sure, but user mentioned "Ops. Focus: Secondary"
# Actually, let's just dump the keys if needed, but for now try to guess
ops_focus = "Unknown"
if "Ops. Focus: Secondary" in props:
ops_focus = props["Ops. Focus: Secondary"].get("checkbox", False)
elif "Ops Focus" in props: # Fallback guess
ops_focus = props["Ops Focus"].get("checkbox", False)
# Extract Product Categories
primary_product = "N/A"
secondary_product = "N/A"
# Assuming these are Select or Multi-select fields, or Relations.
# User mentioned "Primary Product Category" and "Secondary Product Category".
if "Primary Product Category" in props:
pp_data = props["Primary Product Category"].get("select") or props["Primary Product Category"].get("multi_select")
if pp_data:
if isinstance(pp_data, list):
primary_product = ", ".join([item["name"] for item in pp_data])
else:
primary_product = pp_data["name"]
if "Secondary Product Category" in props:
sp_data = props["Secondary Product Category"].get("select") or props["Secondary Product Category"].get("multi_select")
if sp_data:
if isinstance(sp_data, list):
secondary_product = ", ".join([item["name"] for item in sp_data])
else:
secondary_product = sp_data["name"]
return {
"name": vertical_name,
"pains": pains,
"gains": gains,
"ops_focus_secondary": ops_focus,
"primary_product": primary_product,
"secondary_product": secondary_product
}
verticals_to_check = [
"Krankenhaus",
"Pflege", # Might be "Altenheim" or similar
"Hotel",
"Industrie", # Might be "Manufacturing"
"Logistik",
"Einzelhandel",
"Facility Management"
]
print("-" * 60)
for v in verticals_to_check:
data = get_vertical_data(v)
if data:
print(f"VERTICAL: {data['name']}")
print(f" Primary Product: {data['primary_product']}")
print(f" Secondary Product: {data['secondary_product']}")
print(f" Ops. Focus Secondary: {data['ops_focus_secondary']}")
print(f" PAINS: {data['pains']}")
print(f" GAINS: {data['gains']}")
print("-" * 60)

View File

@@ -0,0 +1,90 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81" # Verticals DB
PRODUCT_DB_ID = "2ec88f42854480f0b154f7a07342eb58" # Product Categories DB (from user link)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# 1. Fetch Product Map (ID -> Name)
product_map = {}
def fetch_products():
url = f"https://api.notion.com/v1/databases/{PRODUCT_DB_ID}/query"
response = requests.post(url, headers=headers, json={"page_size": 100})
if response.status_code == 200:
results = response.json().get("results", [])
for p in results:
p_id = p["id"]
# Name property might be "Name" or "Product Category"
props = p["properties"]
name = "Unknown"
if "Name" in props:
name = props["Name"]["title"][0]["plain_text"] if props["Name"]["title"] else "N/A"
elif "Product Category" in props:
name = props["Product Category"]["title"][0]["plain_text"] if props["Product Category"]["title"] else "N/A"
product_map[p_id] = name
# Also map the page ID itself if used in relations
else:
print(f"Error fetching products: {response.status_code}")
# 2. Check Verticals with Relation Resolution
def check_vertical_relations(search_term):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": search_term
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code == 200:
results = resp.json().get("results", [])
if not results:
print(f"❌ No vertical found for '{search_term}'")
return
for page in results:
props = page["properties"]
title = props["Vertical"]["title"][0]["plain_text"]
# Resolve Primary
pp_ids = [r["id"] for r in props.get("Primary Product Category", {}).get("relation", [])]
pp_names = [product_map.get(pid, pid) for pid in pp_ids]
# Resolve Secondary
sp_ids = [r["id"] for r in props.get("Secondary Product", {}).get("relation", [])]
sp_names = [product_map.get(pid, pid) for pid in sp_ids]
print(f"\n🔹 VERTICAL: {title}")
print(f" Primary Product (Rel): {', '.join(pp_names)}")
print(f" Secondary Product (Rel): {', '.join(sp_names)}")
# Pains/Gains short check
pains = props.get("Pains", {}).get("rich_text", [])
print(f" Pains Length: {len(pains[0]['plain_text']) if pains else 0} chars")
else:
print(f"Error fetching vertical: {resp.status_code}")
# Run
print("Fetching Product Map...")
fetch_products()
print(f"Loaded {len(product_map)} products.")
print("\nChecking Verticals...")
targets = ["Hospital", "Hotel", "Logistics", "Manufacturing", "Retail", "Reinigungs", "Dienstleister", "Facility"]
for t in targets:
check_vertical_relations(t)

View File

@@ -0,0 +1,87 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def get_vertical_details(vertical_name_contains):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name_contains
}
}
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error: {response.status_code}")
return
results = response.json().get("results", [])
if not results:
print(f"❌ No entry found containing '{vertical_name_contains}'")
return
for page in results:
props = page["properties"]
# safely extract title
title_list = props.get("Vertical", {}).get("title", [])
title = title_list[0]["plain_text"] if title_list else "Unknown Title"
# Pains
pains_list = props.get("Pains", {}).get("rich_text", [])
pains = pains_list[0]["plain_text"] if pains_list else "N/A"
# Gains
gains_list = props.get("Gains", {}).get("rich_text", [])
gains = gains_list[0]["plain_text"] if gains_list else "N/A"
# Ops Focus
ops_focus = props.get("Ops Focus: Secondary", {}).get("checkbox", False)
# Products
# Primary is select
pp_select = props.get("Primary Product Category", {}).get("select")
pp = pp_select["name"] if pp_select else "N/A"
# Secondary is select
sp_select = props.get("Secondary Product", {}).get("select")
sp = sp_select["name"] if sp_select else "N/A"
print(f"\n🔹 VERTICAL: {title}")
print(f" Primary: {pp}")
print(f" Secondary: {sp}")
print(f" Ops Focus Secondary? {'✅ YES' if ops_focus else '❌ NO'}")
print(f" PAINS:\n {pains}")
print(f" GAINS:\n {gains}")
print("-" * 40)
targets = [
"Hospital",
"Hotel",
"Logistics",
"Manufacturing",
"Retail",
"Facility Management"
]
for t in targets:
get_vertical_details(t)

View File

@@ -0,0 +1,66 @@
import os
import requests
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found in environment.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
def list_pages_and_keys():
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"page_size": 10 # Just list a few to see structure
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"Error fetching data: {response.status_code} - {response.text}")
return
results = response.json().get("results", [])
if not results:
print("No pages found.")
return
print(f"Found {len(results)} pages.")
# Print keys from the first page
first_page = results[0]
props = first_page["properties"]
print("\n--- Property Keys Found ---")
for key in props.keys():
print(f"- {key}")
print("\n--- Page Titles (Verticals) ---")
for page in results:
title_prop = page["properties"].get("Vertical", {}).get("title", []) # Assuming title prop is named "Vertical" based on user input
if not title_prop:
# Try finding the title property dynamically if "Vertical" is wrong
for k, v in page["properties"].items():
if v["id"] == "title":
title_prop = v["title"]
break
if title_prop:
title = title_prop[0]["plain_text"]
print(f"- {title}")
else:
print("- (No Title)")
if __name__ == "__main__":
list_pages_and_keys()

View File

@@ -0,0 +1,89 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# COMPLETE LIST OF UPDATES
updates = {
"Infrastructure - Transport": { # Airports, Stations
"Pains": "Sicherheitsbereiche erfordern personalintensives Screening von externen Reinigungskräften. Verschmutzte Böden (Winter/Salz) erhöhen das Rutschrisiko für Passagiere und Klagerisiken.",
"Gains": "Autonome Reinigung innerhalb der Sicherheitszonen ohne externe Personalwechsel. Permanente Trocknung von Nässe (Schneematsch) in Eingangsbereichen."
},
"Leisure - Indoor Active": { # Bowling, Cinema, Gym
"Pains": "Personal ist rar und teuer, Gäste erwarten aber Service am Platz. Reinigung im laufenden Betrieb stört den Erlebnischarakter.",
"Gains": "Service-Roboter als Event-Faktor und Entlastung: Getränke kommen zum Gast, Personal bleibt an der Bar/Theke. Konstante Sauberkeit auch bei hoher Frequenz."
},
"Leisure - Outdoor Park": { # Zoos, Theme Parks
"Pains": "Enorme Flächenleistung (Wege) erfordert viele Arbeitskräfte für die Grobschmutzbeseitigung (Laub, Müll). Sichtbare Reinigungstrupps stören die Immersion der Gäste.",
"Gains": "Autonome Großflächenreinigung (Kehren) in den frühen Morgenstunden vor Parköffnung. Erhalt der 'heilen Welt' (Immersion) für Besucher."
},
"Leisure - Wet & Spa": { # Pools, Thermen
"Pains": "Hohes Unfallrisiko durch Nässe auf Fliesen (Rutschgefahr). Hoher Aufwand für permanente Desinfektion und Trocknung im laufenden Betrieb bindet Aufsichtspersonal.",
"Gains": "Permanente Trocknung und Desinfektion kritischer Barfußbereiche. Reduktion der Rutschgefahr und Haftungsrisiken. Entlastung der Bademeister (Fokus auf Aufsicht)."
},
"Retail - Shopping Center": { # Malls
"Pains": "Food-Court ist der Schmutz-Hotspot: Verschüttete Getränke und Essensreste wirken unhygienisch und binden Personal dauerhaft. Dreckige Böden senken die Verweildauer.",
"Gains": "Sofortige Beseitigung von Malheuren im Food-Court. Steigerung der Aufenthaltsqualität und Verweildauer der Kunden durch sichtbare Sauberkeit."
},
"Retail - Non-Food": { # DIY, Furniture
"Pains": "Riesige Gangflächen verstauben schnell, Personal ist knapp und soll beraten, nicht kehren. Verschmutzte Böden wirken im Premium-Segment (Möbel) wertmindernd.",
"Gains": "Staubfreie Umgebung für angenehmes Einkaufsklima. Roboter reinigen autonom große Flächen, während Mitarbeiter für Kundenberatung verfügbar sind."
},
"Infrastructure - Public": { # Fairs, Schools
"Pains": "Extrem kurze Turnaround-Zeiten zwischen Messetagen oder Events. Hohe Nachtzuschläge für die Endreinigung der Hallengänge oder Klassenzimmer.",
"Gains": "Automatisierte Nachtreinigung der Gänge/Flure stellt die Optik für den nächsten Morgen sicher. Kalkulierbare Kosten ohne Nachtzuschlag."
},
"Hospitality - Gastronomy": { # Restaurants
"Pains": "Servicepersonal verbringt Zeit auf Laufwegen statt am Gast ('Teller-Taxi'). Personalmangel führt zu langen Wartezeiten und Umsatzverlust.",
"Gains": "Servicekräfte werden von Laufwegen befreit und haben Zeit für aktive Beratung und Verkauf (Upselling). Steigerung der Tischumschlagshäufigkeit."
}
}
def update_vertical(vertical_name, new_data):
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200: return
results = resp.json().get("results", [])
if not results:
print(f"Skipping {vertical_name} (Not found)")
return
page_id = results[0]["id"]
update_url = f"https://api.notion.com/v1/pages/{page_id}"
update_payload = {
"properties": {
"Pains": {"rich_text": [{"text": {"content": new_data["Pains"]}}]},
"Gains": {"rich_text": [{"text": {"content": new_data["Gains"]}}]}
}
}
requests.patch(update_url, headers=headers, json=update_payload)
print(f"✅ Updated {vertical_name}")
print("Starting FULL Notion Update...")
for v_name, data in updates.items():
update_vertical(v_name, data)
print("Done.")

View File

@@ -0,0 +1,94 @@
import os
import requests
import json
from dotenv import load_dotenv
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_DB_ID = "2ec88f4285448014ab38ea664b4c2b81"
if not NOTION_API_KEY:
print("Error: NOTION_API_KEY not found.")
exit(1)
headers = {
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}
# Define the updates with "Sharp" Pains/Gains
updates = {
"Healthcare - Hospital": {
"Pains": "Fachpflegekräfte sind bis zu 30% der Schichtzeit mit logistischen Routinetätigkeiten (Wäsche, Essen, Laborproben) gebunden ('Hände weg vom Bett'). Steigende Hygienerisiken bei gleichzeitigem Personalmangel im Reinigungsteam führen zu lückenhafter Dokumentation und Gefährdung der RKI-Konformität.",
"Gains": "Rückgewinnung von ca. 2,5h Fachkraft-Kapazität pro Schicht durch automatisierte Stationslogistik. Validierbare, RKI-konforme Reinigungsqualität rund um die Uhr, unabhängig vom Krankenstand des Reinigungsteams."
},
"Hospitality - Hotel": {
"Pains": "Enorme Fluktuation im Housekeeping gefährdet die pünktliche Zimmer-Freigabe (Check-in 15:00 Uhr). Hohe Nachtzuschläge oder fehlendes Personal verhindern, dass die Lobby und Konferenzbereiche morgens um 06:00 Uhr perfekt glänzen.",
"Gains": "Lautlose Nachtreinigung der Lobby und Flure ohne Personalzuschläge. Servicekräfte im Restaurant werden von Laufwegen ('Teller-Taxi') befreit und haben Zeit für aktives Upselling am Gast."
},
"Logistics - Warehouse": {
"Pains": "Verschmutzte Fahrwege durch Palettenabrieb und Staub gefährden die Sensorik von FTS (Fahrerlosen Transportsystemen) und erhöhen das Unfallrisiko für Flurförderzeuge. Manuelle Reinigung stört den 24/7-Betrieb und bindet Fachpersonal.",
"Gains": "Permanente Staubreduktion im laufenden Betrieb schützt empfindliche Anlagentechnik (Lichtschranken). Saubere Hallen als Visitenkarte und Sicherheitsfaktor (Rutschgefahr), ohne operative Unterbrechungen."
},
"Industry - Manufacturing": {
"Pains": "Hochbezahlte Facharbeiter unterbrechen die Wertschöpfung für unproduktive Such- und Holzeiten von Material (C-Teile). Intransparente Materialflüsse an der Linie führen zu Mikrostillständen und gefährden die Taktzeit.",
"Gains": "Just-in-Time Materialversorgung direkt an die Linie. Fachkräfte bleiben an der Maschine. Stabilisierung der Taktzeiten und OEE durch automatisierten Nachschub."
},
"Reinigungsdienstleister": { # Facility Management
"Pains": "Margendruck durch steigende Tariflöhne bei gleichzeitigem Preisdiktat der Auftraggeber. Hohe Fluktuation (>30%) führt zu ständiger Rekrutierung ('No-Show'-Quote), was Objektleiter bindet und die Qualitätskontrolle vernachlässigt.",
"Gains": "Kalkulationssicherheit durch Fixkosten statt variabler Personalkosten. Garantierte Reinigungsleistung in Objekten unabhängig vom Personalstand. Innovationsträger für Ausschreibungen."
},
"Retail - Food": { # Supermarkets
"Pains": "Reinigungskosten steigen linear zur Fläche, während Kundenfrequenz schwankt. Sichtbare Reinigungsmaschinen blockieren tagsüber Kundenwege ('Störfaktor'). Abends/Nachts schwer Personal zu finden.",
"Gains": "Unsichtbare Reinigung: Roboter fahren in Randzeiten oder weichen Kunden dynamisch aus. Konstantes Sauberkeits-Level ('Lobby-Effekt') steigert Verweildauer."
}
}
def update_vertical(vertical_name, new_data):
# 1. Find Page ID
url = f"https://api.notion.com/v1/databases/{NOTION_DB_ID}/query"
payload = {
"filter": {
"property": "Vertical",
"title": {
"contains": vertical_name
}
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 200:
print(f"Error searching {vertical_name}: {resp.status_code}")
return
results = resp.json().get("results", [])
if not results:
print(f"Skipping {vertical_name} (Not found)")
return
page_id = results[0]["id"]
# 2. Update Page
update_url = f"https://api.notion.com/v1/pages/{page_id}"
update_payload = {
"properties": {
"Pains": {
"rich_text": [{"text": {"content": new_data["Pains"]}}]
},
"Gains": {
"rich_text": [{"text": {"content": new_data["Gains"]}}]
}
}
}
upd_resp = requests.patch(update_url, headers=headers, json=update_payload)
if upd_resp.status_code == 200:
print(f"✅ Updated {vertical_name}")
else:
print(f"❌ Failed to update {vertical_name}: {upd_resp.text}")
print("Starting Notion Update...")
for v_name, data in updates.items():
update_vertical(v_name, data)
print("Done.")

View File

@@ -1,75 +1,113 @@
# SuperOffice Connector ("The Muscle") - GTM Engine v2.0
# SuperOffice Connector & GTM Engine ("The Muscle & The Brain")
Dies ist der Microservice zur bidirektionalen Anbindung von **SuperOffice CRM** an die **Company Explorer Intelligence**.
Der Connector agiert als intelligenter Bote ("Muscle"): Er nimmt Webhook-Events entgegen, filtert Rauschen heraus, fragt das "Gehirn" (Company Explorer) nach Instruktionen und schreibt Ergebnisse (Marketing-Texte, Branchen-Verticals, Rollen) ins CRM zurück.
Dieses Dokument beschreibt die Architektur der **Go-to-Market (GTM) Engine**, die SuperOffice CRM mit der Company Explorer Intelligence verbindet.
## 1. Architektur: "Noise-Reduced Event Pipeline"
Ziel des Systems ist der vollautomatisierte Versand von **hyper-personalisierten E-Mails**, die so wirken, als wären sie manuell von einem Branchenexperten geschrieben worden.
Wir nutzen eine **Event-gesteuerte Architektur** mit integrierter Rauschunterdrückung, um die CRM-Last zu minimieren und Endlosschleifen zu verhindern.
---
**Der Datenfluss:**
1. **Auslöser:** Ein User ändert Stammdaten in SuperOffice.
2. **Filterung (Noise Reduction):** Der Webhook-Receiver ignoriert sofort:
* Irrelevante Entitäten (Sales, Projects, Appointments, Documents).
* Irrelevante Felder (Telefon, E-Mail, Fax, interne Systemfelder).
* *Nur strategische Änderungen (Name, Website, Job-Titel, Position) triggern die Pipeline.*
3. **Queueing:** Valide Events landen in der lokalen `SQLite`-Queue (`connector_queue.db`).
4. **Provisioning:** Der Worker fragt den **Company Explorer** (:8000): "Was ist die KI-Wahrheit für diesen Kontakt?".
5. **Write-Back:** Der Connector schreibt die Ergebnisse (Vertical-ID, Persona-ID, E-Mail-Snippets) via REST API zurück in die SuperOffice UDF-Felder.
## 1. Das Konzept: "Static Magic"
## 2. 🚀 Go-Live Checkliste (User Tasks)
Anders als bei üblichen KI-Tools, die E-Mails "on the fly" generieren, setzt dieses System auf **vorberechnete, statische Textbausteine**.
Um das System auf der Produktivumgebung ("Live") in Betrieb zu nehmen, müssen folgende Schritte durchgeführt werden:
**Warum?**
1. **Qualitätssicherung:** Jeder Baustein kann vor dem Versand geprüft werden.
2. **Performance:** SuperOffice muss beim Versand keine KI anfragen, sondern nur Felder zusammenfügen.
3. **Konsistenz:** Ein "Finanzleiter im Maschinenbau" bekommt immer dieselbe, perfekte Argumentation egal bei welchem Unternehmen.
### Schritt A: SuperOffice Registrierung (IT / Admin)
Da wir eine **Private App** nutzen, ist keine Zertifizierung nötig.
1. Loggen Sie sich ins [SuperOffice Developer Portal](https://dev.superoffice.com/) ein.
2. Registrieren Sie eine neue App ("Custom Application").
* **Redirect URI:** `http://localhost`
* **Scopes:** `Contact:Read/Write`, `Person:Read/Write`, `List:Read`, `Appointment:Write`.
3. Notieren Sie sich **Client ID**, **Client Secret** und den **Token** (falls System User genutzt wird).
### Die E-Mail-Formel
### Schritt B: Konfiguration & Mapping
1. **Credentials:** Tragen Sie die Daten aus Schritt A in die `.env` Datei auf dem Server ein (`SO_CLIENT_ID`, etc.).
2. **Discovery:** Starten Sie den Container und führen Sie einmalig das Discovery-Tool aus, um die IDs der Felder in der Live-Umgebung zu finden:
```bash
python3 connector-superoffice/discover_fields.py
```
3. **Mapping Update:** Tragen Sie die ermittelten IDs in die `.env` ein:
* `VERTICAL_MAP_JSON`: Mappen Sie die CE-Branchen auf die SuperOffice "Business"-IDs.
* `PERSONA_MAP_JSON`: Mappen Sie die Rollen (z.B. "Influencer", "Wirtschaftlicher Entscheider") auf die SuperOffice "Position"-IDs.
Eine E-Mail setzt sich aus **drei statischen Komponenten** zusammen, die im CRM (SuperOffice) gespeichert sind:
### Schritt C: Webhook Einrichtung (SuperOffice Admin)
Gehen Sie in SuperOffice zu **Einstellungen & Verwaltung -> Webhooks** und legen Sie einen neuen Hook an:
* **Target URL:** `http://<IHRE-SERVER-IP>:8003/webhook?token=<SECRET_AUS_ENV>`
* **Events:** `contact.created`, `contact.changed`, `person.created`, `person.changed`.
### Schritt D: Feiertags-Import
Damit der Versand an Feiertagen pausiert:
1. Kopieren Sie den Inhalt von `connector-superoffice/import_holidays_CRMSCRIPT.txt`.
2. Führen Sie ihn in SuperOffice unter **CRMScript -> Execute** aus.
## 3. Business Logik & Features
### 4.1. Persona Mapping ("Golden Record")
Das Feld `Position` (Rolle) in SuperOffice wird als Ziel-Feld für die CE-Archetypen genutzt.
* **Logik:** Der CE analysiert den Jobtitel (z.B. "Einkaufsleiter") -> Mappt auf "Influencer".
* **Sync:** Der Connector setzt das Feld `Position` in SuperOffice auf den entsprechenden Wert (sofern in der Config gemappt).
### 4.2. Vertical Mapping
KI-Verticals (z.B. "Healthcare - Hospital") werden auf die SuperOffice-Branchenliste gemappt. Manuelle Änderungen durch User im CRM werden aktuell beim nächsten Update überschrieben (Master: CE).
## 4. Testing & Simulation
Verwenden Sie `test_full_roundtrip.py`, um die Kette zu testen, ohne E-Mails zu versenden. Das Skript erstellt stattdessen **Termine** in SuperOffice als Beweis.
```bash
# Startet Simulation für Person ID 2
python3 connector-superoffice/tests/test_full_roundtrip.py
```text
[1. Opener (Unternehmens-Spezifisch)] + [2. Bridge (Persona x Vertical)] + [3. Social Proof (Vertical)]
```
## 5. Roadmap (v2.1)
* **1. Opener (Der Haken):** Bezieht sich zu 100% auf das spezifische Unternehmen und dessen Geschäftsmodell.
* *Quelle:* `Company`-Objekt (Feld: `ai_opener`).
* *Beispiel:* "Die präzise Just-in-Time-Fertigung von **Müller CNC** erfordert einen reibungslosen Materialfluss ohne Mikrostillstände."
* **2. Bridge (Die Relevanz):** Holt die Person in ihrer Rolle ab und verknüpft sie mit dem Branchen-Pain.
* *Quelle:* `Matrix`-Tabelle (Feld: `intro`).
* *Beispiel:* "Für Sie als **Produktionsleiter** bedeutet das, trotz Fachkräftemangel die Taktzeiten an der Linie stabil zu halten."
* **3. Social Proof (Die Lösung):** Zeigt Referenzen und den konkreten Nutzen (Gains).
* *Quelle:* `Matrix`-Tabelle (Feld: `social_proof`).
* *Beispiel:* "Unternehmen wie **Jungheinrich** nutzen unsere Transportroboter, um Fachkräfte an der Maschine zu halten und Suchzeiten um 30% zu senken."
* [ ] **Manual Override Protection:** Schutz manueller Änderungen (Vertical/Rolle) durch den User vor Überschreiben durch die KI.
* [ ] **Notion Dashboard:** KPI-Reporting.
* [ ] **Lead-Attribution:** Automatisches Setzen der `Sale.Source` auf "Marketing Automation".
---
## 2. Die Datenbasis (Foundation)
Die Qualität der Texte steht und fällt mit der Datenbasis. Diese wird zentral in **Notion** gepflegt und in den Company Explorer synchronisiert.
### A. Verticals (Branchen)
Definiert die **Makro-Pains** und **Gains** einer Branche sowie das **passende Produkt**.
* *Beispiel:* Healthcare -> Pain: "Pflegekräfte machen Logistik" -> Gain: "Hände fürs Bett" -> Produkt: Service-Roboter.
* *Wichtig:* Unterscheidung nach **Ops-Focus** (Operativ vs. Infrastruktur) steuert das Produkt (Reinigung vs. Service).
### B. Personas (Rollen)
Definiert die **persönlichen Pains** einer Rolle.
* *Beispiel:* Produktionsleiter -> Pain: "OEE / Taktzeit".
* *Beispiel:* Geschäftsführer -> Pain: "ROI / Amortisation".
---
## 3. Die Matrix-Engine (Multiplikation)
Das Skript `generate_matrix.py` (im Backend) ist das Herzstück. Es berechnet **alle möglichen Kombinationen** aus Verticals und Personas voraus.
**Logik:**
1. Lade alle Verticals (`V`) und Personas (`P`).
2. Für jede Kombination `V x P`:
* Lade `V.Pains` und `P.Pains`.
* Generiere via Gemini einen **perfekten Satz 2 (Bridge)** und **Satz 3 (Proof)**.
* Generiere ein **Subject**, das den Persona-Pain trifft.
3. Speichere das Ergebnis in der Tabelle `marketing_matrix`.
*Ergebnis:* Eine Lookup-Tabelle, aus der für jeden Kontakt sofort der passende Text gezogen werden kann.
---
## 4. Der "Opener" (First Sentence)
Dieser Baustein ist der einzige, der **pro Unternehmen** generiert wird (bei der Analyse/Discovery).
**Logik:**
1. Scrape Website-Content.
2. Identifiziere das **Vertical** (z.B. Maschinenbau).
3. Lade den **Core-Pain** des Verticals (z.B. "Materialfluss").
4. **Prompt:** "Analysiere das Geschäftsmodell von [Firma]. Formuliere einen Satz, der erklärt, warum [Core-Pain] für genau dieses Geschäftsmodell kritisch ist."
*Ergebnis:* Ein Satz, der beweist: "Ich habe verstanden, was ihr tut."
---
## 5. SuperOffice Connector ("The Muscle")
Der Connector ist der Bote, der diese Daten in das CRM bringt.
**Workflow:**
1. **Trigger:** Kontakt-Änderung in SuperOffice (Webhook).
2. **Enrichment:** Connector fragt Company Explorer: "Gib mir Daten für Firma X, Person Y".
3. **Lookup:** Company Explorer...
* Holt den `Opener` aus der Company-Tabelle.
* Bestimmt `Vertical` und `Persona`.
* Sucht den passenden Eintrag in der `MarketingMatrix`.
4. **Write-Back:** Connector schreibt die Texte in die UDF-Felder (User Defined Fields) des Kontakts in SuperOffice.
* `UDF_Opener`
* `UDF_Bridge`
* `UDF_Proof`
* `UDF_Subject`
---
## 6. Setup & Wartung
### Neue Branche hinzufügen
1. In **Notion** anlegen (Pains/Gains/Produkte definieren).
2. Sync-Skript laufen lassen: `python3 backend/scripts/sync_notion_industries.py`.
3. Matrix neu berechnen: `python3 backend/scripts/generate_matrix.py --live`.
### Prompt-Tuning
Die Prompts für Matrix und Opener liegen in:
* Matrix: `backend/scripts/generate_matrix.py`
* Opener: `backend/services/classification.py` (oder `enrichment.py`)