[30f88f42] einfügen
einfügen
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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": "...",
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user