[30f88f42] einfügen

einfügen
This commit is contained in:
2026-02-22 19:22:39 +00:00
parent 0d294cd0cc
commit acd2062d0b
7 changed files with 227 additions and 51 deletions

View File

@@ -1 +1 @@
{"task_id": "30e88f42-8544-804e-ac61-ed061d57563a", "token": "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", "session_start_time": "2026-02-22T14:30:47.658775"}
{"task_id": "30f88f42-8544-8062-a877-c3ddb55598ef", "token": "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", "session_start_time": "2026-02-22T19:22:21.427017"}

View File

@@ -218,24 +218,65 @@ Der Prozess ist streng sequenziell und baut aufeinander auf.
* **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
### 17.3 "Atomic Opener" Generierung im Detail (Zweistufiger Prozess, Feb 22, 2026)
**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.
**Ziel:** Zwei hoch-personalisierte, schlagkräftige Einleitungssätze (2-3 Sätze) zu generieren, die eine operative Herausforderung als wertschätzende Beobachtung formulieren.
* **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).
Um die Qualität der Ergebnisse zu maximieren und "Rauschen" aus den Website-Texten zu eliminieren, wurde ein zweistufiger Generierungsprozess implementiert.
* **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`.
#### Schritt 1: Das Website-Dossier (Extraktion & Komprimierung)
Zunächst analysiert die KI den Rohtext der Website und erstellt ein strukturiertes Dossier. Dieser Zwischenschritt dient dazu, die wesentlichen Informationen über das Geschäftsmodell und das Reinigungspotenzial (Hygieneanforderungen, Flächengröße) zu isolieren.
* **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.
**Prompt (Website-Dossier - Fokus Reinigung):**
```text
**Rolle:** Du bist ein erfahrener B2B-Marktanalyst mit Fokus auf Facility Management und Gebäudereinigung.
**Aufgabe:** Analysiere den Website-Text des Unternehmens '{company_name}' und erstelle ein prägnantes Dossier.
**Deine Analyse besteht aus ZWEI TEILEN:**
**TEIL 1: Geschäftsmodell-Analyse**
1. Identifiziere die Kernprodukte und/oder Dienstleistungen des Unternehmens.
2. Fasse in 2-3 prägnanten Sätzen zusammen, was das Unternehmen macht und für welche Kunden.
**TEIL 2: Reinigungspotenzial & Hygiene-Analyse**
1. Scanne den Text gezielt nach Hinweisen auf große Bodenflächen, Publikumsverkehr oder hohe Hygieneanforderungen (Schlüsselwörter: Reinigung, Sauberkeit, Hygiene, Bodenpflege, Verkaufsfläche, Logistikhalle, Patientenversorgung, Gästeerlebnis).
2. Bewerte das Potenzial für automatisierte Reinigungslösungen auf einer Skala (Hoch / Mittel / Niedrig).
3. Extrahiere die 1-2 wichtigsten Sätze, die diese Anforderungen oder die Größe der Einrichtung belegen.
**Antworte AUSSCHLIESSLICH im folgenden exakten Format:**
GESCHÄFTSMODELL: <Deine 2-3 Sätze über das Kerngeschäft des Unternehmens.>
REINIGUNGSPOTENZIAL: <Hoch / Mittel / Niedrig / Kein Hinweis>
HYGIENE-BEWEISE: <Die 1-2 aussagekräftigsten Sätze als Bullet Points (* Satz 1...)>
```
#### Schritt 2: Formulierung des Openers (Scharfsinnige Beobachtung)
Basierend auf dem Dossier aus Schritt 1 und den branchenspezifischen Pains/Gains wird der finale Opener formuliert.
**Prompt (Finaler Opener - Optimiert Feb 22, 2026):**
```text
Du bist ein scharfsinniger Marktbeobachter und Branchenexperte. Formuliere eine wertschätzende Einleitung (genau 2-3 Sätze) für ein Anschreiben an das Unternehmen {company.name}.
DEINE PERSONA:
Ein anerkennender Branchenkenner, der eine scharfsinnige Beobachtung teilt. Dein Ton ist wertschätzend, professionell und absolut NICHT verkäuferisch.
STRATEGISCHER HINTERGRUND (Nicht nennen!):
Dieses Unternehmen wird kontaktiert, weil sein Geschäftsmodell perfekt zu folgendem Bereich passt: "{product_name}" ({product_desc}).
Ziel des Schreibens ist es, die Branchen-Herausforderungen "{relevant_pains}" zu adressieren und die Mehrwerte "{relevant_gains}" zu ermöglichen.
DEINE AUFGABE:
1. Firmenname kürzen: Kürze "{company.name}" sinnvoll (meist erste zwei Worte). Entferne UNBEDINGT Rechtsformen wie GmbH, AG, gGmbH, e.V. etc.
2. Struktur: Genau 2 bis 3 flüssige Sätze.
3. Inhalt:
- Satz 1: Eine wertschätzende Beobachtung zum Geschäftsmodell oder einem aktuellen Fokus des Unternehmens (siehe Analyse-Dossier).
- Satz 2-3: Leite elegant zu einer spezifischen operativen Herausforderung über, die für das Unternehmen aufgrund seiner Größe oder Branche relevant ist (orientiere dich an "{relevant_pains}").
4. STRENGES VERBOT: Nenne KEIN Produkt ("{product_name}") and biete KEINE "Lösungen", "Hilfe" oder "Zusammenarbeit" an. Der Text soll eine reine Beobachtung bleiben.
5. KEINE Anrede (kein "Sehr geehrte Damen und Herren", kein "Hallo").
KONTEXT (Analyse-Dossier):
{context_text}
```
* **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)
@@ -268,10 +309,41 @@ Die Synchronisation von Stammdaten (Adresse, VAT) erforderte ein tiefes Eintauch
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.
### 17.6 Lessons Learned: Full E-Mail Simulation & Persona Hardening (Feb 22, 2026)
Der End-to-End-Test der E-Mail-Simulation im SuperOffice-Kalender deckte subtile UI-Herausforderungen auf.
1. **Die "42-Zeichen-Falle" (Appointment Title):**
* **Problem:** Das `MainHeader`-Feld in der SuperOffice-Listenansicht ist auf ca. 42 Zeichen begrenzt. Längere Betreffzeilen werden abgeschnitten, und der Überhang wird oft als erste Zeile in den Textkörper verschoben.
* **Lösung:** Strikte serverseitige Kürzung des Titels auf 40 Zeichen. Zusätzlich wird der **vollständige Betreff** als allererste Zeile in die Description geschrieben, gefolgt von zwei Newlines. Dies stellt sicher, dass SuperOffice den Betreff "stiehlt" und die Anrede ("Hallo...") sicher im Body bleibt.
2. **Rollen-Dynamik & "Sticking":**
* **Problem:** Wenn ein Nutzer den Jobtitel ändert, blieb oft die alte Persona (z.B. "Infrastruktur") aktiv, wenn für den neuen Titel kein exakter Match vorlag.
* **Lösung:** Implementierung eines **Rollen-Resets**. Bei jeder Namens- oder Funktionsänderung wird die Persona im Company Explorer gelöscht und basierend auf den neuesten Mappings (z.B. neue Regeln für "Geschäftsleitung" -> "Wirtschaftlicher Entscheider") neu berechnet.
3. **Kaskadierung & Loop-Schutz:**
* **Problem:** Änderungen am Unternehmen (Contact) müssen alle Personen aktualisieren. Dies triggerte jedoch Endlosschleifen, da SuperOffice nach jedem Update einen eigenen Webhook sendete.
* **Lösung:** Implementierung einer **Deep-Filter-Logik**. Der Worker ignoriert nun Felder wie `updated`, `updated_associate_id` und `registered`. Nur inhaltliche Änderungen (Name, Branche, Funktion) lösen die Verarbeitungslogik aus.
### 17.7 Marketing Matrix Schärfung (v3.2 - Feb 22, 2026)
Die Qualität der Marketing-Matrix (Subject, Intro, Social Proof) ist entscheidend für den Erfolg des Outreachs. Daher wurde die Generierungslogik in `generate_matrix.py` massiv geschärft.
**Kern-Konzept: Der Strategische Brückenschlag**
Die KI agiert nicht mehr als reiner Copywriter, sondern als **scharfsinniger B2B-Strategieberater**. Der Prompt erzwingt die Verknüpfung von drei Ebenen:
1. **Persönlicher Druck (Persona):** Was hält den Entscheider nachts wach? (Pains der Rolle)
2. **Strategische Notwendigkeit (Branche):** Welchen externen Anforderungen muss das Unternehmen gerecht werden? (Pains/Gains der Branche)
3. **Technologischer Enabler (Produkt):** Wie genau löst die spezifische Robotik-Kategorie (Cleaning, Service, etc.) diesen Konflikt?
**Prompt-Anforderungen (Matrix):**
* **Betreff:** "Finger in die Wunde" (Max. 6 Wörter).
* **Einleitung:** Sofortiges Gefühl von "Verstanden-Werden" durch Verbindung von Rollen-Verantwortung und Branchen-Herausforderung.
* **Social Proof:** Quantifizierbare Effekte statt leerer Versprechungen, abgestimmt auf die Branche.
* **Produkt-Kontext:** Nutzung der offiziellen Kategorie-Beschreibungen aus der Datenbank zur Vermeidung von generischen Floskeln.
---
## 18. Next Steps & Todos (Post-Migration)
Nach Abschluss der Kern-Migration stehen folgende Optimierungen an:
### Task 1: Monitoring & Alerting

View File

@@ -165,6 +165,7 @@ class CompanyDetailsResponse(BaseModel):
# Openers
ai_opener: Optional[str] = None
ai_opener_secondary: Optional[str] = None
research_dossier: Optional[str] = None
# Relations
industry_details: Optional[IndustryDetails] = None

View File

@@ -74,6 +74,7 @@ class Company(Base):
# NEW: AI-generated Marketing Openers
ai_opener = Column(Text, nullable=True)
ai_opener_secondary = Column(Text, nullable=True)
research_dossier = Column(Text, nullable=True)
# Relationships
signals = relationship("Signal", back_populates="company", cascade="all, delete-orphan")

View File

@@ -17,45 +17,66 @@ MODEL_NAME = "gemini-2.0-flash" # High quality copy
def generate_prompt(industry: Industry, persona: Persona) -> str:
"""
Builds the prompt for the AI to generate the marketing texts.
Combines Industry context with Persona specific pains/gains.
Combines Industry context with Persona specific pains/gains and Product Category.
"""
# Safely load JSON lists
# 1. Determine Product Context
# We focus on the primary category for the general matrix,
# but we inform the AI about the secondary option if applicable.
primary_cat = industry.primary_category
product_context = f"{primary_cat.name}: {primary_cat.description}" if primary_cat else "Intelligente Robotik-Lösungen"
# 2. Extract specific segments from industry pains/gains
def extract_segment(text, marker):
if not text: return ""
import re
segments = re.split(r'\[(.*?)\]', text)
for i in range(1, len(segments), 2):
if marker.lower() in segments[i].lower():
return segments[i+1].strip()
return text
industry_pains = extract_segment(industry.pains, "Primary Product")
industry_gains = extract_segment(industry.gains, "Primary Product")
# 3. Handle Persona Data
try:
persona_pains = json.loads(persona.pains) if persona.pains else []
persona_gains = json.loads(persona.gains) if persona.gains else []
except:
persona_pains = [persona.pains] if persona.pains else []
persona_gains = [persona.gains] if persona.gains else []
industry_pains = industry.pains if industry.pains else "Allgemeine Effizienzprobleme"
prompt = f"""
Du bist ein erfahrener B2B-Copywriter für Robotik-Lösungen (Reinigung, Transport, Service).
Ziel: Erstelle personalisierte E-Mail-Textbausteine für einen Outreach.
Du bist ein scharfsinniger B2B-Strategieberater und exzellenter Copywriter.
Deine Aufgabe: Erstelle hochpräzise, "scharfe" Marketing-Textbausteine für einen Outreach an Entscheider.
--- KONTEXT ---
ZIELBRANCHE: {industry.name}
--- STRATEGISCHER RAHMEN ---
ZIELUNTERNEHMEN (Branche): {industry.name}
BRANCHEN-KONTEXT: {industry.description or 'Keine spezifische Beschreibung'}
BRANCHEN-PAINS: {industry_pains}
BRANCEHN-HERAUSFORDERUNGEN: {industry_pains}
ANGESTREBTE MEHRWERTE: {industry_gains}
ZIELPERSON (ARCHETYP): {persona.name}
PERSÖNLICHE PAINS (Herausforderungen):
ZIELPERSON (Rolle): {persona.name}
PERSÖNLICHER DRUCK (Pains der Rolle):
{chr(10).join(['- ' + p for p in persona_pains])}
GEWÜNSCHTE GAINS (Ziele):
GEWÜNSCHTE ERFOLGE (Gains der Rolle):
{chr(10).join(['- ' + g for g in persona_gains])}
--- AUFGABE ---
Erstelle ein JSON-Objekt mit genau 3 Textbausteinen.
Tonalität: Professionell, lösungsorientiert, auf den Punkt. Keine Marketing-Floskeln ("Game Changer").
ANGEBOTENE LÖSUNG (Produkt-Fokus):
{product_context}
1. "subject": Betreffzeile (Max 6 Wörter). Muss neugierig machen und einen Pain adressieren.
2. "intro": Einleitungssatz (1-2 Sätze). Verbinde die Branchen-Herausforderung mit der persönlichen Rolle des Empfängers. Zeige Verständnis für seine Situation.
3. "social_proof": Ein Satz, der Vertrauen aufbaut. Nenne generische Erfolge (z.B. "Unternehmen in der {industry.name} senken so ihre Kosten um 15%"), da wir noch keine spezifischen Logos nennen dürfen.
--- DEIN AUFTRAG ---
Erstelle ein JSON-Objekt mit 3 Textbausteinen, die den persönlichen Druck des Empfängers mit den strategischen Notwendigkeiten seiner Branche und der technologischen Lösung verknüpfen.
Tonalität: Wertschätzend, auf Augenhöhe, scharfsinnig, absolut NICHT marktschreierisch.
1. "subject": Eine Betreffzeile (Max 6 Wörter), die den Finger direkt in eine Wunde (Pain) legt oder ein hohes Ziel (Gain) verspricht.
2. "intro": Einleitung (2-3 Sätze). Verbinde die spezifische Branchen-Herausforderung mit der persönlichen Verantwortung des Empfängers. Er muss sich sofort verstanden fühlen.
3. "social_proof": Ein Beweissatz, der zeigt, dass diese Lösung in der Branche {industry.name} bereits reale Probleme (z.B. Personalmangel, Dokumentationsdruck) gelöst hat. Nenne keine konkreten Firmennamen, aber quantifizierbare Effekte.
--- FORMAT ---
Respond ONLY with a valid JSON object. Do not add markdown formatting like ```json ... ```.
Antworte NUR mit einem validen JSON-Objekt.
Format:
{{
"subject": "...",

View File

@@ -181,12 +181,54 @@ JSON ONLY.
return area_metrics
return None
def _generate_marketing_opener(self, company: Company, industry: Industry, website_text: str, focus_mode: str = "primary") -> Optional[str]:
def _summarize_website_for_opener(self, company_name: str, website_text: str) -> str:
"""
Creates a high-quality summary of the website content to provide
better context for the opener generation.
"""
prompt = f"""
**Rolle:** Du bist ein erfahrener B2B-Marktanalyst mit Fokus auf Facility Management und Gebäudereinigung.
**Aufgabe:** Analysiere den Website-Text des Unternehmens '{company_name}' und erstelle ein prägnantes Dossier.
**Deine Analyse besteht aus ZWEI TEILEN:**
**TEIL 1: Geschäftsmodell-Analyse**
1. Identifiziere die Kernprodukte und/oder Dienstleistungen des Unternehmens.
2. Fasse in 2-3 prägnanten Sätzen zusammen, was das Unternehmen macht und für welche Kunden.
**TEIL 2: Reinigungspotenzial & Hygiene-Analyse**
1. Scanne den Text gezielt nach Hinweisen auf große Bodenflächen, Publikumsverkehr oder hohe Hygieneanforderungen (Schlüsselwörter: Reinigung, Sauberkeit, Hygiene, Bodenpflege, Verkaufsfläche, Logistikhalle, Patientenversorgung, Gästeerlebnis).
2. Bewerte das Potenzial für automatisierte Reinigungslösungen auf einer Skala (Hoch / Mittel / Niedrig).
3. Extrahiere die 1-2 wichtigsten Sätze, die diese Anforderungen oder die Größe der Einrichtung belegen.
**Antworte AUSSCHLIESSLICH im folgenden exakten Format:**
GESCHÄFTSMODELL: <Deine 2-3 Sätze über das Kerngeschäft des Unternehmens.>
REINIGUNGSPOTENZIAL: <Hoch / Mittel / Niedrig / Kein Hinweis>
HYGIENE-BEWEISE: <Die 1-2 aussagekräftigsten Sätze als Bullet Points (* Satz 1...)>
**Hier ist der Website-Text:**
{website_text[:5000]}
"""
try:
response = call_gemini_flash(prompt)
return response.strip() if response else "Keine Zusammenfassung möglich."
except Exception as e:
logger.error(f"Summary Error: {e}")
return "Fehler bei der Zusammenfassung."
def _generate_marketing_opener(self, company: Company, industry: Industry, context_text: str, focus_mode: str = "primary") -> Optional[str]:
if not industry: return None
# 1. Determine Context & Pains/Gains
product_context = industry.primary_category.name if industry.primary_category else "Robotik-Lösungen"
# 1. Determine Product Category & Context
category = industry.primary_category
raw_pains = industry.pains or ""
raw_gains = industry.gains or ""
if focus_mode == "secondary" and industry.ops_focus_secondary and industry.secondary_category:
category = industry.secondary_category
product_name = category.name if category else "Robotik-Lösungen"
product_desc = category.description if category and category.description else "Automatisierung von operativen Prozessen"
# Split pains/gains based on markers
def extract_segment(text, marker):
@@ -195,23 +237,41 @@ JSON ONLY.
for i in range(1, len(segments), 2):
if marker.lower() in segments[i].lower():
return segments[i+1].strip()
return text # Fallback to full text if no markers found
return text
relevant_pains = extract_segment(raw_pains, "Primary Product")
relevant_gains = extract_segment(raw_gains, "Primary Product")
if focus_mode == "secondary" and industry.ops_focus_secondary and industry.secondary_category:
product_context = industry.secondary_category.name
relevant_pains = extract_segment(raw_pains, "Secondary Product")
relevant_gains = extract_segment(raw_gains, "Secondary Product")
prompt = f"""
Du bist ein exzellenter B2B-Stratege und Texter. Formuliere einen hochpersonalisierten Einleitungssatz (1-2 Sätze).
Unternehmen: {company.name}
Branche: {industry.name}
Fokus: {focus_mode.upper()}
Herausforderungen: {relevant_pains}
Kontext: {website_text[:2500]}
Du bist ein scharfsinniger Marktbeobachter und Branchenexperte. Formuliere eine wertschätzende Einleitung (genau 2-3 Sätze) für ein Anschreiben an das Unternehmen {company.name}.
REGEL: Nenne NICHT das Produkt "{product_context}". Fokussiere dich NUR auf die Herausforderung.
AUSGABE: NUR den fertigen Satz.
DEINE PERSONA:
Ein anerkennender Branchenkenner, der eine scharfsinnige Beobachtung teilt. Dein Ton ist wertschätzend, professionell und absolut NICHT verkäuferisch.
STRATEGISCHER HINTERGRUND (Nicht nennen!):
Dieses Unternehmen wird kontaktiert, weil sein Geschäftsmodell perfekt zu folgendem Bereich passt: "{product_name}" ({product_desc}).
Ziel des Schreibens ist es, die Branchen-Herausforderungen "{relevant_pains}" zu adressieren und die Mehrwerte "{relevant_gains}" zu ermöglichen.
DEINE AUFGABE:
1. Firmenname kürzen: Kürze "{company.name}" sinnvoll (meist erste zwei Worte). Entferne UNBEDINGT Rechtsformen wie GmbH, AG, gGmbH, e.V. etc.
2. Struktur: Genau 2 bis 3 flüssige Sätze.
3. Inhalt:
- Satz 1: Eine wertschätzende Beobachtung zum Geschäftsmodell oder einem aktuellen Fokus des Unternehmens (siehe Analyse-Dossier).
- Satz 2-3: Leite elegant zu einer spezifischen operativen Herausforderung über, die für das Unternehmen aufgrund seiner Größe oder Branche relevant ist (orientiere dich an "{relevant_pains}").
4. STRENGES VERBOT: Nenne KEIN Produkt ("{product_name}") und biete KEINE "Lösungen", "Hilfe" oder "Zusammenarbeit" an. Der Text soll eine reine Beobachtung bleiben.
5. KEINE Anrede (kein "Sehr geehrte Damen und Herren", kein "Hallo").
KONTEXT (Analyse-Dossier):
{context_text}
BEISPIEL-STIL:
"Das Kreiskrankenhaus Weilburg leistet einen beeindruckenden Beitrag zur regionalen Patientenversorgung. Bei der lückenlosen Dokumentation und den strengen Hygienevorgaben im Klinikalltag stelle ich mir jedoch die Aufrechterhaltung höchster Standards bei gleichzeitigem Kostendruck als enorme operative Herausforderung vor."
AUSGABE: Nur der fertige Text.
"""
try:
response = call_gemini_flash(prompt)
@@ -300,8 +360,12 @@ AUSGABE: NUR den fertigen Satz.
company.metric_confidence = 0.8
company.metric_confidence_reason = "Metric processed."
company.ai_opener = self._generate_marketing_opener(company, matched_industry, website_content, "primary")
company.ai_opener_secondary = self._generate_marketing_opener(company, matched_industry, website_content, "secondary")
# NEW: Two-Step approach with summarization
website_summary = self._summarize_website_for_opener(company.name, website_content)
company.research_dossier = website_summary
company.ai_opener = self._generate_marketing_opener(company, matched_industry, website_summary, "primary")
company.ai_opener_secondary = self._generate_marketing_opener(company, matched_industry, website_summary, "secondary")
company.last_classification_at = datetime.utcnow()
company.status = "ENRICHED"
db.commit()

View File

@@ -146,8 +146,25 @@ Die Synchronisation von Adressdaten stellte sich als unerwartet komplex heraus.
* **Problem:** Wenn Adress-Daten (`PUT Contact`) und UDF-Daten (`PUT Contact/Udef`) in separaten API-Aufrufen kurz hintereinander gesendet werden, gewinnt der letzte Call. Da dieser oft auf einem *veralteten* `GET`-Stand basiert (bevor das erste Update durch war), wurde die Adresse wieder mit "Leer" überschrieben.
* **Lösung:** **Atomic Update Strategy**. Der Worker sammelt *alle* Änderungen (Adresse, VAT, Vertical, Openers) in einem einzigen Dictionary und sendet genau **einen** `PUT`-Request an den Kontakt-Endpunkt. Dies garantiert Konsistenz.
## Appendix: The "First Sentence" Prompt
---
### 9. Lessons Learned: Appointment Simulation & Persona Matching
Die Simulation von E-Mails via Terminen (Appointments) erforderte Workarounds für das UI-Verhalten von SuperOffice.
1. **Header vs. Description (Die 42-Zeichen-Grenze):**
* SuperOffice nutzt im Kalender und in Listen den `MainHeader` als Titel. Dieser ist auf ca. 42 Zeichen begrenzt.
* Ist der Titel länger, schneidet SuperOffice ihn ab. Erscheint der Titel inkonsistent, "stiehlt" das UI oft die erste Zeile der `Description` als Titel-Ersatz.
* **Strategie:** Wir kürzen den `MainHeader` auf 40 Zeichen und stellen sicher, dass der **vollständige Betreff** als allererste Zeile in der `Description` steht. Danach folgen zwei Newlines. Damit landet der Betreff im UI-Header und die Anrede ("Hallo...") bleibt sicher im Textkörper.
2. **Mapping-Resilienz (Funktion vs. Titel):**
* Jobtitel (Funktion) landen in SuperOffice inkonsistent in den Feldern `JobTitle` oder `Title`.
* **Lösung:** Der Worker fragt nun beide Felder ab (`person.get("JobTitle") or person.get("Title")`), um die Rolle korrekt zuzuweisen.
3. **Rollen-Dynamik:**
* Um zu verhindern, dass alte Rollen (z.B. "Infrastruktur") nach einer Beförderung/Änderung in SuperOffice "kleben" bleiben, führt das System nun bei jeder Namens- oder Funktionsänderung einen **Rollen-Reset** durch.
## Appendix: The "First Sentence" Prompt
This is the core logic used to generate the company-specific opener.
**Goal:** Prove understanding of the business model + imply the pain (positive observation).