# 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)