Files
Brancheneinstufung2/lead-engine/generate_reply.py
Floke 895e8b5c19 [31e88f42] Keine neuen Commits in dieser Session.
Keine neuen Commits in dieser Session.
2026-03-09 08:46:33 +00:00

233 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import json
import requests
import sqlite3
import re
import datetime
# --- Helper: Get Gemini Key ---
def get_gemini_key():
candidates = [
"gemini_api_key.txt", # Current dir
"/app/gemini_api_key.txt", # Docker default
os.path.join(os.path.dirname(__file__), "gemini_api_key.txt"), # Script dir
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'gemini_api_key.txt') # Parent dir
]
for path in candidates:
if os.path.exists(path):
try:
with open(path, 'r') as f:
return f.read().strip()
except:
pass
return os.getenv("GEMINI_API_KEY")
def get_matrix_context(industry_name, persona_name):
"""Fetches Pains, Gains and Arguments from CE Database."""
context = {
"industry_pains": "",
"industry_gains": "",
"persona_description": "",
"persona_arguments": ""
}
db_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'companies_v3_fixed_2.db')
if not os.path.exists(db_path):
return context
try:
conn = sqlite3.connect(db_path)
c = conn.cursor()
# Get Industry Data
c.execute('SELECT pains, gains FROM industries WHERE name = ?', (industry_name,))
ind_res = c.fetchone()
if ind_res:
context["industry_pains"], context["industry_gains"] = ind_res
# Get Persona Data
c.execute('SELECT description, convincing_arguments FROM personas WHERE name = ?', (persona_name,))
per_res = c.fetchone()
if per_res:
context["persona_description"], context["persona_arguments"] = per_res
conn.close()
except Exception as e:
print(f"DB Error in matrix lookup: {e}")
return context
def get_suggested_date():
"""Calculates a suggested meeting date (3-4 days in future, avoiding weekends)."""
now = datetime.datetime.now()
# Jump 3 days ahead
suggested = now + datetime.timedelta(days=3)
# If weekend, move to Monday
if suggested.weekday() == 5: # Saturday
suggested += datetime.timedelta(days=2)
elif suggested.weekday() == 6: # Sunday
suggested += datetime.timedelta(days=1)
days_de = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
return f"{days_de[suggested.weekday()]}, den {suggested.strftime('%d.%m.')} um 10:00 Uhr"
def clean_company_name(name):
"""Removes legal suffixes like GmbH, AG, etc. for a more personal touch."""
if not name: return ""
# Remove common German legal forms
cleaned = re.sub(r'\s+(GmbH|AG|GmbH\s+&\s+Co\.\s+KG|KG|e\.V\.|e\.K\.|Limited|Ltd|Inc)\.?(?:\s|$)', '', name, flags=re.IGNORECASE)
return cleaned.strip()
def get_qualitative_area_description(area_str):
"""Converts a string with area information into a qualitative description."""
nums = re.findall(r'\d+', area_str.replace('.', '').replace(',', ''))
area_val = int(nums[0]) if nums else 0
if area_val >= 10000:
return "sehr große Flächen"
if area_val >= 5000:
return "große Flächen"
if area_val >= 1000:
return "mittlere Flächen"
if area_val > 0:
return "kleine bis mittlere Flächen"
return "Ihre Flächen" # Fallback
def get_multi_solution_recommendation(area_str, purpose_str):
"""
Selects a range of robots based on surface area AND requested purposes.
"""
recommendations = []
purpose_lower = purpose_str.lower()
# 1. Cleaning Logic (Area based)
nums = re.findall(r'\d+', area_str.replace('.', '').replace(',', ''))
area_val = int(nums[0]) if nums else 0
if "reinigung" in purpose_lower:
if area_val >= 5000 or "über 10.000" in area_str:
recommendations.append("den Scrubber 75 als industrielles Kraftpaket für Ihre Großflächen")
elif area_val >= 1000:
recommendations.append("den Scrubber 50 oder Phantas für eine wendige und gründliche Bodenreinigung")
else:
recommendations.append("den Phantas oder Pudu CC1 für eine effiziente Reinigung Ihrer Räumlichkeiten")
# 2. Service/Transport Logic
if any(word in purpose_lower for word in ["servieren", "abräumen", "speisen", "getränke"]):
recommendations.append("den BellaBot zur Entlastung Ihres Teams beim Transport von Speisen und Getränken")
# 3. Marketing/Interaction Logic
if any(word in purpose_lower for word in ["marketing", "gästebetreuung", "kundenansprache"]):
recommendations.append("den KettyBot als interaktiven Begleiter für Marketing und Patienteninformation")
if not recommendations:
recommendations.append("unsere wendigen Allrounder wie den Phantas")
return {
"solution_text": " und ".join(recommendations),
"has_multi": len(recommendations) > 1
}
def generate_email_draft(lead_data, company_data, booking_link="[IHR BUCHUNGSLINK]"):
"""
Generates a high-end, personalized sales email using Gemini API and Matrix knowledge.
"""
api_key = get_gemini_key()
if not api_key:
return "Error: Gemini API Key not found."
# Extract Data from Lead Engine
company_raw = lead_data.get('company_name', 'Interessent')
company_name = clean_company_name(company_raw)
contact_name = lead_data.get('contact_name', 'Damen und Herren')
# Metadata from Lead
meta = {}
if lead_data.get('lead_metadata'):
try: meta = json.loads(lead_data['lead_metadata'])
except: pass
area = meta.get('area', 'Unbekannte Fläche')
purpose = meta.get('purpose', 'Reinigung')
role = meta.get('role', 'Wirtschaftlicher Entscheider')
salutation = meta.get('salutation', 'Damen und Herren')
cleaning_functions = meta.get('cleaning_functions', '')
# Data from Company Explorer
ce_summary = company_data.get('research_dossier') or company_data.get('summary', '')
ce_vertical = company_data.get('industry_ai') or company_data.get('vertical', 'Healthcare')
ce_opener = company_data.get('ai_opener', '')
# Multi-Solution Logic
solution = get_multi_solution_recommendation(area, purpose)
qualitative_area = get_qualitative_area_description(area)
suggested_date = get_suggested_date()
# Fetch "Golden Records" from Matrix
matrix = get_matrix_context(ce_vertical, role)
# Prompt Engineering for "Unwiderstehliche E-Mail"
prompt = f"""
Du bist ein Senior Sales Executive bei Robo-Planet. Antworte auf eine Anfrage von Tradingtwins.
Schreibe eine E-Mail auf "Human Expert Level".
WICHTIGE IDENTITÄT:
- Anrede-Form: {salutation} (z.B. Herr, Frau)
- Name: {contact_name}
- Firma: {company_name}
STRATEGIE:
- STARTE DIREKT mit dem strategischen Aufhänger aus dem Company Explorer ({ce_opener}). Baue daraus den ersten Absatz.
- KEIN "mit großem Interesse verfolge ich..." oder ähnliche Phrasen. Das wirkt unnatürlich.
- Deine Mail reagiert auf die Anfrage zu: {purpose} für {qualitative_area}.
- Fasse die vorgeschlagene Lösung ({solution['solution_text']}) KOMPAKT zusammen. Wir bieten ein ganzheitliches Entlastungskonzept an, keine Detail-Auflistung von Datenblättern.
KONTEXT:
- Branche: {ce_vertical}
- Pains aus Matrix: {matrix['industry_pains']}
- Dossier/Wissen: {ce_summary}
- Strategischer Aufhänger (CE-Opener): {ce_opener}
AUFGABE:
1. ANREDE: Erzeuge KEINE Anrede (wie "Sehr geehrter..."). Starte direkt mit dem ersten Satz.
2. EINSTIEG: Nutze den inhaltlichen Kern von: "{ce_opener}".
3. DER ÜBERGANG: Verknüpfe dies mit der Anfrage zu {purpose}. Erkläre, dass manuelle Prozesse bei {qualitative_area} angesichts der Dokumentationspflichten und des Fachkräftemangels zum Risiko werden.
4. DIE LÖSUNG: Schlage die Kombination aus {solution['solution_text']} als integriertes Konzept vor, um das Team in Reinigung, Service und Patientenansprache spürbar zu entlasten.
5. ROI: Sprich kurz die Amortisation (18-24 Monate) an als Argument für den wirtschaftlichen Entscheider.
6. CTA: Schließe die E-Mail ab und leite zu den nächsten Schritten über, ohne direkt Termine vorzuschlagen oder nach Links zu fragen. Erzeuge KEINE Schlussformel (wie "Mit freundlichen Grüßen").
STIL: Senior, lösungsorientiert, direkt. Keine unnötigen Füllwörter.
FORMAT:
[E-Mail Text]
"""
# Call Gemini API
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}"
headers = {'Content-Type': 'application/json'}
payload = {"contents": [{"parts": [{"text": prompt}]}]}
try:
response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
# Remove the placeholder from the LLM-generated text
cleaned_text = result['candidates'][0]['content']['parts'][0]['text'].replace(booking_link, '').strip()
return cleaned_text
except Exception as e:
return f"Error generating draft: {str(e)}"
if __name__ == "__main__":
# Test Mock
mock_lead = {
"company_name": "Klinikum Test",
"contact_name": "Dr. Müller",
"lead_metadata": json.dumps({"area": "5000 qm", "purpose": "Desinfektion und Boden", "city": "Berlin"})
}
mock_company = {
"vertical": "Healthcare / Krankenhaus",
"summary": "Ein großes Klinikum der Maximalversorgung mit Fokus auf Kardiologie."
}
print(generate_email_draft(mock_lead, mock_company))