Files
Brancheneinstufung2/_legacy_gsheets_system/generate_marketing_text.py
Floke 95634d7bb6 feat(company-explorer): Initial Web UI & Backend with Enrichment Flow
This commit introduces the foundational elements for the new "Company Explorer" web application, marking a significant step away from the legacy Google Sheets / CLI system.

Key changes include:
- Project Structure: A new  directory with separate  (FastAPI) and  (React/Vite) components.
- Data Persistence: Migration from Google Sheets to a local SQLite database () using SQLAlchemy.
- Core Utilities: Extraction and cleanup of essential helper functions (LLM wrappers, text utilities) into .
- Backend Services: , ,  for AI-powered analysis, and  logic.
- Frontend UI: Basic React application with company table, import wizard, and dynamic inspector sidebar.
- Docker Integration: Updated  and  for multi-stage builds and sideloading.
- Deployment & Access: Integrated into central Nginx proxy and dashboard, accessible via .

Lessons Learned & Fixed during development:
- Frontend Asset Loading: Addressed issues with Vite's  path and FastAPI's .
- TypeScript Configuration: Added  and .
- Database Schema Evolution: Solved  errors by forcing a new database file and correcting  override.
- Logging: Implemented robust file-based logging ().

This new foundation provides a powerful and maintainable platform for future B2B robotics lead generation.
2026-01-07 17:55:08 +00:00

239 lines
12 KiB
Python

# generate_marketing_text.py
import os
import yaml
import logging
import time
import openai
import json
import pandas as pd
import argparse
from config import Config
from helpers import create_log_filename # NEU: Logging-Funktion importieren
from google_sheet_handler import GoogleSheetHandler
# --- Konfiguration ---
KNOWLEDGE_BASE_FILE = "marketing_wissen_final.yaml"
OUTPUT_SHEET_NAME = "Texte_Automation"
MODEL_TO_USE = "gpt-4o"
# --- Logging einrichten ---
# Wird jetzt in main() initialisiert, um einen Dateinamen zu haben
def call_openai_with_retry(prompt, max_retries=3, delay=5):
# ... (Diese Funktion bleibt unverändert) ...
for attempt in range(max_retries):
try:
logging.info(f"Sende Prompt an OpenAI (Versuch {attempt + 1}/{max_retries})...")
response = openai.ChatCompletion.create(
model=MODEL_TO_USE,
response_format={"type": "json_object"},
messages=[{"role": "user", "content": prompt}],
temperature=0.6,
max_tokens=1024
)
content = response.choices[0].message['content'].strip()
return json.loads(content)
except Exception as e:
logging.error(f"Fehler bei OpenAI-API-Aufruf: {e}")
if attempt < max_retries - 1:
time.sleep(delay)
else:
return None
def build_prompt(branch_name, branch_data, position_name, position_data):
"""
Baut den finalen Master-Prompt (v4.3) dynamisch zusammen.
Nutzt eine Fallback-Logik, wenn keine branchenspezifischen Referenzen vorhanden sind.
"""
branch_pain_points = "\n".join([f"- {p}" for p in branch_data.get('pain_points', [])])
position_pain_points = "\n".join([f"- {p}" for p in position_data.get('pains_DE', [])])
# --- Dynamischer Teil: Referenzen und Expertise-Formulierung ---
specific_references = branch_data.get('references_DE')
# Prüfen, ob echte Referenzen vorhanden sind (nicht leer und nicht der Platzhalter)
if specific_references and '[HIER' not in specific_references:
references_for_prompt = specific_references
expertise_instruction = (
"- **Satz 2 (Branchen-Expertise):** Betone unsere Erfahrung in der Branche. **Vermeide das Wort 'Branche'.** "
f"Formuliere stattdessen spezifisch, z.B. 'Durch die Zusammenarbeit sind wir mit den spezifischen Anforderungen von {branch_name}-Unternehmen bestens vertraut.'"
)
else:
# Fallback-Logik
references_for_prompt = ", ".join(Config.FALLBACK_REFERENCES)
expertise_instruction = (
"- **Satz 2 (Branchen-Expertise):** Formuliere allgemeiner. Betone unsere branchenübergreifende Expertise in der Optimierung komplexer Serviceprozesse. "
"Formuliere z.B. 'Unsere Erfahrung zeigt, dass die grundlegenden Herausforderungen in der Einsatzplanung oft branchenübergreifend ähnlich sind.'"
)
# --- Zusammensetzen des finalen Prompts ---
return "\n".join([
"Du bist ein kompetenter Lösungsberater und brillanter Texter...", # Gekürzt zur Übersicht
"AUFGABE: Erstelle 3 Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly) für eine E-Mail.",
"\n--- KONTEXT ---",
f"ZIELBRANCHE: {branch_name}",
f"BRANCHEN-HERAUSFORDERUNGEN (PAIN POINTS):\n{branch_pain_points}",
f"\nANSPRECHPARTNER: {position_name}",
f"PERSÖNLICHE HERAUSFORDERUNGEN DES ANSPRECHPARTNERS (PAIN POINTS):\n{position_pain_points}",
f"\nREFERENZKUNDEN (Rohdaten):\n{references_for_prompt}",
"\n--- DEINE AUFGABE ---",
"1. **Subject:** Formuliere eine kurze Betreffzeile (max. 5 Wörter). Richte sie **direkt an einem der persönlichen Pain Points** des Ansprechpartners.",
"2. **Introduction_Textonly:** Formuliere einen Einleitungstext (2 Sätze).",
" - **Satz 1 (Die Brücke):** Knüpfe an die (uns unbekannte) operative Herausforderung an. Beschreibe subtil den Nutzen einer Lösung...",
" - **Satz 2 (Die Relevanz):** Schaffe die Relevanz für die Zielperson, indem du das Thema mit einem ihrer persönlichen Pain Points verknüpfst.",
"3. **Industry_References_Textonly:** Formuliere einen **strategischen Referenz-Block (ca. 2-3 Sätze)** nach folgendem Muster:",
" - **Satz 1 (Social Proof):** Beginne direkt mit den Referenzkunden. Integriere **alle** genannten Referenzen und quantitative Erfolge elegant.",
expertise_instruction, # HIER WIRD DIE DYNAMISCHE ANWEISUNG EINGEFÜGT
" - **Satz 3 (Rollen-Relevanz):** Schaffe den direkten Nutzen für die Zielperson. Formuliere z.B. 'Dieser Wissensvorsprung hilft uns, Ihre [persönlicher Pain Point der Rolle] besonders effizient zu lösen.'",
"\n--- BEISPIEL FÜR EINEN PERFEKTEN OUTPUT (MIT SPEZIFISCHEN REFERENZEN) ---",
'''
{
"Subject": "Nahtlose Systemintegration",
"Introduction_Textonly": "Genau hier setzt die digitale Unterstützung Ihrer Techniker an... Für Sie als IT-Leiter ist dabei die nahtlose und sichere Integration... von entscheidender Bedeutung.",
"Industry_References_Textonly": "Ihre Marktbegleiter wie Jungheinrich mit weltweit über 4.000 Technikern und Christ Wash Systems... profitieren bereits... Durch die langjährige Zusammenarbeit sind wir mit den spezifischen Anforderungen von Anlagenbau-Unternehmen... bestens vertraut. Dieser Wissensvorsprung hilft uns, Ihre Integrations-Herausforderungen... zu lösen."
}
''',
"\n--- BEISPIEL FÜR EINEN PERFEKTEN OUTPUT (MIT FALLBACK-REFERENZEN) ---",
'''
{
"Subject": "Kostenkontrolle im Service",
"Introduction_Textonly": "Genau bei der Optimierung dieser Serviceprozesse können erhebliche Effizienzgewinne erzielt werden. Für Sie als Finanzleiter ist dabei die Sicherstellung der Profitabilität bei gleichzeitiger Kostentransparenz von zentraler Bedeutung.",
"Industry_References_Textonly": "Namhafte Unternehmen wie Jungheinrich, Vivawest und TK Elevators profitieren bereits von unseren Lösungen. Unsere Erfahrung zeigt, dass die grundlegenden Herausforderungen in der Einsatzplanung oft branchenübergreifend ähnlich sind. Dieser Wissensvorsprung hilft uns, Ihre Ziele bei der Kostenkontrolle und Profitabilitätssteigerung besonders effizient zu unterstützen."
}
''',
"\nErstelle jetzt das JSON-Objekt für die oben genannte Kombination aus Branche und Ansprechpartner."
])
def main(specific_branch=None):
"""Hauptfunktion zur Generierung der Marketing-Texte."""
# --- NEUES, ROBUSTES LOGGING SETUP ---
log_file_path = create_log_filename("generate_texts")
log_level = logging.INFO
log_format = '%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s'
# Root-Logger konfigurieren
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
# Bestehende Handler entfernen, um Dopplung zu vermeiden
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# Neue Handler hinzufügen
root_logger.addHandler(logging.StreamHandler()) # Immer auf der Konsole loggen
if log_file_path:
file_handler = logging.FileHandler(log_file_path, mode='a', encoding='utf-8')
file_handler.setFormatter(logging.Formatter(log_format))
root_logger.addHandler(file_handler)
logging.info(f"===== Skript gestartet: Modus 'generate_texts' =====")
logging.info(f"Logdatei: {log_file_path}")
# --- Initialisierung ---
try:
Config.load_api_keys()
openai.api_key = Config.API_KEYS.get('openai')
if not openai.api_key: raise ValueError("OpenAI API Key nicht gefunden.")
with open(KNOWLEDGE_BASE_FILE, 'r', encoding='utf-8') as f:
knowledge_base = yaml.safe_load(f)
sheet_handler = GoogleSheetHandler()
except Exception as e:
logging.critical(f"FEHLER bei der Initialisierung: {e}")
return
# --- NEU: Bestehende Texte aus dem Sheet laden ---
try:
logging.info(f"Lese bestehende Texte aus dem Tabellenblatt '{OUTPUT_SHEET_NAME}'...")
existing_texts_df = sheet_handler.get_sheet_as_dataframe(OUTPUT_SHEET_NAME)
if existing_texts_df is not None and not existing_texts_df.empty:
existing_combinations = set(zip(existing_texts_df['Branch Detail'], existing_texts_df['Department']))
logging.info(f"{len(existing_combinations)} bereits existierende Kombinationen gefunden.")
else:
existing_combinations = set()
logging.info("Keine bestehenden Texte gefunden. Alle Kombinationen werden neu erstellt.")
except Exception as e:
logging.error(f"Fehler beim Lesen des '{OUTPUT_SHEET_NAME}'-Sheets. Nehme an, es ist leer. Fehler: {e}")
existing_combinations = set()
# --- Generierungs-Loop ---
newly_generated_results = []
target_branches = knowledge_base.get('Branchen', {})
if specific_branch:
# ... (Logik für specific_branch bleibt gleich) ...
if specific_branch in target_branches:
target_branches = {specific_branch: target_branches[specific_branch]}
else:
logging.error(f"FEHLER: Die angegebene Branche '{specific_branch}' wurde nicht gefunden.")
return
positions = knowledge_base.get('Positionen', {})
total_combinations = len(target_branches) * len(positions)
logging.info(f"Prüfe {total_combinations} mögliche Kombinationen...")
for branch_name, branch_data in target_branches.items():
for position_key, position_data in positions.items():
# NEU: Überspringe, wenn die Kombination bereits existiert
if (branch_name, position_key) in existing_combinations:
logging.debug(f"Überspringe bereits existierende Kombination: Branche='{branch_name}', Position='{position_key}'")
continue
logging.info(f"--- Generiere Texte für NEUE Kombination: Branche='{branch_name}', Position='{position_key}' ---")
prompt = build_prompt(branch_name, branch_data, position_data.get('name_DE', position_key), position_data)
generated_json = call_openai_with_retry(prompt)
if generated_json:
newly_generated_results.append({
'Branch Detail': branch_name,
'Department': position_key,
'Language': 'DE',
'Subject': generated_json.get('Subject', 'FEHLER'),
'Introduction_Textonly': generated_json.get('Introduction_Textonly', 'FEHLER'),
'Industry References (Text only)': generated_json.get('Industry_References_Textonly', 'FEHLER')
})
else:
# Füge einen Fehler-Eintrag hinzu, um zu sehen, was fehlgeschlagen ist
newly_generated_results.append({
'Branch Detail': branch_name,
'Department': position_key,
'Language': 'DE',
'Subject': 'FEHLER: KI-Antwort war ungültig',
'Introduction_Textonly': 'FEHLER: KI-Antwort war ungültig',
'Industry References (Text only)': 'FEHLER: KI-Antwort war ungültig'
})
time.sleep(2)
# --- NEU: Hänge neue Ergebnisse an das Sheet an ---
if newly_generated_results:
logging.info(f"{len(newly_generated_results)} neue Textvarianten wurden generiert.")
df_new = pd.DataFrame(newly_generated_results)
# Konvertiere in die Liste-von-Listen-Struktur
values_to_append = df_new.values.tolist()
success = sheet_handler.append_rows(OUTPUT_SHEET_NAME, values_to_append)
if success:
logging.info(f"Erfolgreich! {len(values_to_append)} neue Textvarianten wurden an das Google Sheet '{OUTPUT_SHEET_NAME}' angehängt.")
else:
logging.error("Fehler! Die neuen Textvarianten konnten nicht an das Google Sheet angehängt werden.")
else:
logging.info("Keine neuen Textvarianten zu generieren. Das Sheet ist auf dem neuesten Stand.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generiert Marketing-Textblöcke basierend auf der Wissensbasis.")
parser.add_argument("--branch", type=str, help="Generiert Texte nur für diese eine Branche.")
args = parser.parse_args()
main(specific_branch=args.branch)