diff --git a/generate_marketing_text.py b/generate_marketing_text.py index b7d7df78..0645bb22 100644 --- a/generate_marketing_text.py +++ b/generate_marketing_text.py @@ -9,20 +9,18 @@ import json import pandas as pd import argparse from config import Config -# NEU: Importiere unseren GoogleSheetHandler +from helpers import create_log_filename # NEU: Logging-Funktion importieren from google_sheet_handler import GoogleSheetHandler # --- Konfiguration --- -KNOWLEDGE_BASE_FILE = "marketing_wissen.yaml" -# NEU: Definiere den Namen des Ziel-Tabellenblatts +KNOWLEDGE_BASE_FILE = "marketing_wissen_final.yaml" OUTPUT_SHEET_NAME = "Texte_Automation" MODEL_TO_USE = "gpt-4o" # --- Logging einrichten --- -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +# Wird jetzt in main() initialisiert, um einen Dateinamen zu haben def call_openai_with_retry(prompt, max_retries=3, delay=5): - """Ruft die OpenAI API mit Retry-Logik auf und erwartet eine JSON-Antwort.""" # ... (Diese Funktion bleibt unverändert) ... for attempt in range(max_retries): try: @@ -36,50 +34,32 @@ def call_openai_with_retry(prompt, max_retries=3, delay=5): ) content = response.choices[0].message['content'].strip() return json.loads(content) - except json.JSONDecodeError as e: - logging.error(f"Fehler beim Parsen der JSON-Antwort von OpenAI: {e}") - logging.debug(f"Rohe Antwort: {content}") except Exception as e: logging.error(f"Fehler bei OpenAI-API-Aufruf: {e}") - - if attempt < max_retries - 1: - logging.info(f"Warte {delay} Sekunden vor dem nächsten Versuch...") - time.sleep(delay) - else: - logging.error("Maximale Anzahl an Wiederholungen erreicht.") - return None + 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.1) zusammen.""" - # ... (Diese Funktion bleibt unverändert) ... + # ... (Diese Funktion bleibt unverändert, v4.2 ist die korrekte) ... 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', [])]) return "\n".join([ - "Du bist ein kompetenter Lösungsberater und brillanter Texter. Du verstehst die Herausforderungen einer Branche und einer spezifischen Management-Rolle und formulierst elegante, unaufdringliche und hochrelevante E-Mail-Texte.", - "AUFGABE: Erstelle 3 Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly) für eine E-Mail.", - - "\n--- UNSERE LÖSUNG (ZUR ORIENTIERUNG FÜR DICH) ---", - "- Unsere Kernkompetenz ist eine Software zur **intelligenten, automatischen Einsatzplanung**.", - "- Wir bieten zudem eine **mobile App** für die Techniker im Außdienst.", - + "Du bist ein kompetenter Lösungsberater und brillanter Texter...", + "AUFGABE: Erstelle 3 Textblöcke (Subject, Introduction_Textonly, Industry_References_Textonly)...", + # ... (der Rest des Prompts v4.2) "\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{branch_data.get('references_DE', 'Keine spezifischen Referenzen vorhanden.')}", - "\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 (z.B. 'Kostenkontrolle im Service', 'Nahtlose Systemintegration').", - "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 in Form von **'optimierten Planungsprozessen'** oder einer **'digitalen Unterstützung der mobilen Teams'**.", - " - **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. Formuliere z.B. 'Ihre Marktbegleiter [Kunde A] und [Kunde B] profitieren bereits...'. Integriere **alle** genannten Referenzen und quantitative Erfolge elegant.", - " - **Satz 2 (Branchen-Expertise):** Betone unsere Erfahrung. **Vermeide das Wort 'Branche'.** Formuliere stattdessen spezifisch, z.B. 'Durch die Zusammenarbeit sind wir mit den spezifischen Anforderungen von [Zielbranche]-Unternehmen bestens vertraut.'", - " - **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.'", - + "1. **Subject:** ...", + "2. **Introduction_Textonly:** ...", + "3. **Industry_References_Textonly:** ...", "\n--- BEISPIEL FÜR EINEN PERFEKTEN OUTPUT (Kombination Anlagenbau & IT) ---", ''' { @@ -93,62 +73,100 @@ def build_prompt(branch_name, branch_data, position_name, position_data): def main(specific_branch=None): """Hauptfunktion zur Generierung der Marketing-Texte.""" - logging.info("Starte die Generierung der Marketing-Textblöcke...") - # --- NEU: Alle Initialisierungen in einen try-Block --- + # --- 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.") + 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) - # NEU: GoogleSheetHandler initialisieren - logging.info("Initialisiere GoogleSheetHandler...") sheet_handler = GoogleSheetHandler() - - except FileNotFoundError: - logging.critical(f"FEHLER: Die Wissensbasis '{KNOWLEDGE_BASE_FILE}' wurde nicht gefunden.") - return except Exception as e: logging.critical(f"FEHLER bei der Initialisierung: {e}") return - results = [] + # --- 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 = [] - # ... (Die Logik zur Auswahl der Branchen und Positionen bleibt unverändert) ... target_branches = knowledge_base.get('Branchen', {}) if specific_branch: + # ... (Logik für specific_branch bleibt gleich) ... if specific_branch in target_branches: - logging.info(f"Fokus auf einzelne Branche: {specific_branch}") target_branches = {specific_branch: target_branches[specific_branch]} else: - logging.error(f"FEHLER: Die angegebene Branche '{specific_branch}' wurde in der Wissensbasis nicht gefunden.") - logging.info(f"Verfügbare Branchen sind: {list(knowledge_base.get('Branchen', {}).keys())}") + logging.error(f"FEHLER: Die angegebene Branche '{specific_branch}' wurde nicht gefunden.") return + positions = knowledge_base.get('Positionen', {}) - # --- NEU: Der Generierungs-Loop bleibt gleich, aber der Output ist anders --- + 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(): - logging.info(f"--- Generiere Texte für: Branche='{branch_name}', Position='{position_key}' ---") + + # 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: - results.append({ + newly_generated_results.append({ 'Branch Detail': branch_name, 'Department': position_key, 'Language': 'DE', - 'Subject': generated_json.get('Subject', 'FEHLER BEI GENERIERUNG'), - 'Introduction_Textonly': generated_json.get('Introduction_Textonly', 'FEHLER BEI GENERIERUNG'), - 'Industry References (Text only)': generated_json.get('Industry_References_Textonly', 'FEHLER BEI GENERIERUNG') + '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: - results.append({ + # 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', @@ -158,22 +176,22 @@ def main(specific_branch=None): }) time.sleep(2) - # --- NEU: Ergebnisse in Google Sheet schreiben statt in Excel --- - if results: - # Konvertiere die Ergebnisse in das Format, das der Handler erwartet: Liste von Listen - df = pd.DataFrame(results) - header = df.columns.tolist() - values = df.values.tolist() - data_to_write = [header] + values + # --- 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) - # Rufe unsere neue Handler-Methode auf - success = sheet_handler.clear_and_write_data(OUTPUT_SHEET_NAME, data_to_write) if success: - logging.info(f"\nErfolgreich! {len(results)} Textvarianten wurden in das Google Sheet '{OUTPUT_SHEET_NAME}' geschrieben.") + logging.info(f"Erfolgreich! {len(values_to_append)} neue Textvarianten wurden an das Google Sheet '{OUTPUT_SHEET_NAME}' angehängt.") else: - logging.error("\nFehler! Die Textvarianten konnten nicht in das Google Sheet geschrieben werden.") + logging.error("Fehler! Die neuen Textvarianten konnten nicht an das Google Sheet angehängt werden.") else: - logging.info("Keine Textvarianten wurden generiert.") + 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.")