diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 973d09e0..f67f1bf4 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -89,27 +89,9 @@ except ImportError: print("tiktoken nicht gefunden. Token-Zaehlung wird geschaetzt.") # Debugging-Ausgabe - # ============================================================================== - # 2. GLOBALE KONSTANTEN UND KONFIGURATION - # (Logisch 'config.py') - # ============================================================================== - # --- Dateipfade --- -CREDENTIALS_FILE = "service_account.json" -API_KEY_FILE = "api_key.txt" # OpenAI -SERP_API_KEY_FILE = "serpApiKey.txt" -GENDERIZE_API_KEY_FILE = "genderize_API_Key.txt" -BRANCH_MAPPING_FILE = "ziel_Branchenschema.csv" # Enthält Zielschema -LOG_DIR = "Log" -SHEET_ID = "1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" # <<< HINZUFÜGEN -# --- ML Modell Artefakte --- -MODEL_FILE = "technician_decision_tree_model.pkl" -IMPUTER_FILE = "median_imputer.pkl" -PATTERNS_FILE_TXT = "technician_patterns.txt" # Alt (Optional beibehalten) -PATTERNS_FILE_JSON = "technician_patterns.json" # Neu (Empfohlen) - -def load_branch_mapping(file_path='Branchen.csv'): +def load_branch_mapping(file_path=Config.BRANCH_MAPPING_FILE): """ Lädt das Mapping von Detail-Branche zu Branchen-Gruppe aus einer CSV-Datei. Ist extrem robust gegen Kodierungs-, Spaltennamen- und Pfad-Fehler. @@ -164,39 +146,49 @@ def load_branch_mapping(file_path='Branchen.csv'): # --- Globale Konfiguration Klasse --- +# ============================================================================== +# 2. GLOBALE KONSTANTEN UND KONFIGURATION (ZENTRALISIERT) +# ============================================================================== + class Config: """Zentrale Konfigurationseinstellungen.""" - VERSION = "v1.7.8" - LANG = "de" # Sprache fuer Wikipedia etc. - # ACHTUNG: SHEET_URL ist hier ein Platzhalter. Ersetzen Sie ihn durch Ihre tatsaechliche URL. - SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" # <<< ERSETZEN SIE DIES! - MAX_RETRIES = 5 # Anzahl der Versuche (nicht Wiederholungen nach dem ersten Fehler) fuer wiederholbare Fehler - RETRY_DELAY = 10 # Basiswartezeit (Sekunden) fuer Retries (exponentieller Backoff wird im Decorator angewendet) - REQUEST_TIMEOUT = 20 # Timeout (Sekunden) fuer externe HTTP/API Anfragen (Requests) - SIMILARITY_THRESHOLD = 0.65 # Schwelle fuer Namensaaehnlichkeit bei Wikipedia Validierung - DEBUG = True # Detailliertes Logging aktivieren/deaktivieren - WIKIPEDIA_SEARCH_RESULTS = 5 # Anzahl Ergebnisse bei Wikipedia Suche ueber Bibliothek - HTML_PARSER = "html.parser" # Parser fuer BeautifulSoup ('lxml' ist schneller, erfordert aber Installation) - TOKEN_MODEL = "gpt-3.5-turbo" # OpenAI Modell fuer Token-Zaehlung/Chat (Standard fuer die meisten Calls) - USER_AGENT = 'Mozilla/5.0 (compatible; UnternehmenSkript/1.0; +https://www.example.com/bot)' # User-Agent fuer Web Scraping/Requests (Beispiel URL anpassen) - BRANCH_MAPPING_FILE = "Branchen.csv" # << Sicherstellen, dass dies der korrekte Dateiname ist + # --- Grundkonfiguration --- + VERSION = "v1.7.9" # Version auf 1.7.9 erhöht + LANG = "de" + DEBUG = True + HTML_PARSER = "html.parser" + USER_AGENT = 'Mozilla/5.0 (compatible; UnternehmenSkript/1.0; +https://www.example.com/bot)' + + # --- Dateipfade & IDs --- + SHEET_ID = "1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" + SERVICE_ACCOUNT_FILE = "service_account.json" + TOKEN_FILE = "token.json" + API_KEY_FILE = "api_key.txt" + SERP_API_KEY_FILE = "serpApiKey.txt" + GENDERIZE_API_KEY_FILE = "genderize_API_Key.txt" SCHEMA_FILE = "ziel_Branchenschema.csv" - MODEL_FILE = 'technician_decision_tree_model.pkl' - IMPUTER_FILE = 'median_imputer.pkl' - PATTERNS_FILE_JSON = 'technician_patterns.json' + BRANCH_MAPPING_FILE = "Branchen.csv" + LOG_DIR = "Log" + + # --- ML Modell Artefakte --- + MODEL_FILE = "technician_decision_tree_model.pkl" + IMPUTER_FILE = "median_imputer.pkl" + PATTERNS_FILE_JSON = "technician_patterns.json" - # --- Konfiguration fuer Batching & Parallelisierung --- - # Passen Sie diese Werte an die Leistung Ihres Systems und die API-Limits an. - PROCESSING_BATCH_SIZE = 20 # Anzahl Zeilen pro Verarbeitungs-Batch (fuer _process_single_row in Batches) - OPENAI_BATCH_SIZE_LIMIT = 4 # Max. Texte pro OpenAI Call fuer Zusammenfassung (nur fuer summarize_batch_openai) - MAX_SCRAPING_WORKERS = 10 # Threads fuer paralleles Website-Scraping - UPDATE_BATCH_ROW_LIMIT = 50 # Zeilen sammeln fuer gebuendelte Sheet Updates (effizienter) - MAX_BRANCH_WORKERS = 10 # Threads fuer parallele Branchenbewertung - OPENAI_CONCURRENCY_LIMIT = 3 # Max. gleichzeitige OpenAI Calls (Semaphore fuer Branch Evaluation) - PROCESSING_BRANCH_BATCH_SIZE = 20 # Batch-Groesse fuer Branch-Evaluierung - SERPAPI_DELAY = 1.5 # Pause zwischen einzelnen SerpAPI-Aufrufen (Sekunden) + # --- OpenAI & API Konfiguration --- + TOKEN_MODEL = "gpt-3.5-turbo" + MAX_RETRIES = 5 + RETRY_DELAY = 10 + REQUEST_TIMEOUT = 20 + SERPAPI_DELAY = 1.5 + # --- Batching & Parallelisierung --- + MAX_SCRAPING_WORKERS = 10 + MAX_BRANCH_WORKERS = 10 + OPENAI_CONCURRENCY_LIMIT = 3 + UPDATE_BATCH_ROW_LIMIT = 50 + # --- Plausibilitäts-Schwellenwerte --- PLAUSI_UMSATZ_MIN_WARNUNG = 50000 PLAUSI_UMSATZ_MAX_WARNUNG = 200000000000 PLAUSI_MA_MIN_WARNUNG_ABS = 1 @@ -206,54 +198,39 @@ class Config: PLAUSI_RATIO_UMSATZ_PRO_MA_MIN = 25000 PLAUSI_RATIO_UMSATZ_PRO_MA_MAX = 1500000 PLAUSI_ABWEICHUNG_CRM_WIKI_PROZENT = 30 - - - # --- API Schluessel Speicherung (werden in main() geladen) --- + + # --- API Schluessel Speicherung (wird zur Laufzeit befüllt) --- API_KEYS = {} @classmethod def load_api_keys(cls): """Laedt API-Schluessel aus den definierten Dateien.""" - print("Lade API-Schluessel...") - cls.API_KEYS['openai'] = cls._load_key_from_file(API_KEY_FILE) - cls.API_KEYS['serpapi'] = cls._load_key_from_file(SERP_API_KEY_FILE) - cls.API_KEYS['genderize'] = cls._load_key_from_file(GENDERIZE_API_KEY_FILE) - - # import openai # <--- DIESER IMPORT IST NUN ENTFERNT + logger = logging.getLogger(cls.__name__) # Logger innerhalb der Methode holen + logger.info("Lade API-Schluessel...") + cls.API_KEYS['openai'] = cls._load_key_from_file(cls.API_KEY_FILE) + cls.API_KEYS['serpapi'] = cls._load_key_from_file(cls.SERP_API_KEY_FILE) + cls.API_KEYS['genderize'] = cls._load_key_from_file(cls.GENDERIZE_API_KEY_FILE) if cls.API_KEYS.get('openai'): - # Stelle sicher, dass das 'openai' Modul hier im Scope ist, - # indem wir auf den globalen Import zugreifen. - # Da 'openai' schon global importiert wurde (ganz oben im Skript), - # ist es hier direkt verfügbar. openai.api_key = cls.API_KEYS['openai'] - print("OpenAI API Key erfolgreich geladen.") + logger.info("OpenAI API Key erfolgreich geladen.") else: - print("WARNUNG: OpenAI API Key konnte nicht geladen werden (Datei fehlt oder ist leer?). OpenAI-Funktionen sind deaktiviert.") - - if not cls.API_KEYS.get('serpapi'): - print("WARNUNG: SerpAPI Key konnte nicht geladen werden (Datei fehlt oder ist leer?). Bestimmte Suchfunktionen sind deaktiviert.") - if not cls.API_KEYS.get('genderize'): - print("WARNUNG: Genderize API Key konnte nicht geladen werden (Datei fehlt oder ist leer?). Geschlechtserkennung ist eingeschraenkt.") - + logger.warning("WARNUNG: OpenAI API Key konnte nicht geladen werden.") @staticmethod def _load_key_from_file(filepath): """Hilfsfunktion zum Laden eines Schluessels aus einer Datei.""" + logger = logging.getLogger(Config.__name__) try: - # Verwenden Sie "r" fuer Textmodus und geben Sie das Encoding an with open(filepath, "r", encoding="utf-8") as f: key = f.read().strip() - if key: - return key - else: - print(f"WARNUNG: Datei '{filepath}' ist leer.") - return None + if key: return key + return None except FileNotFoundError: - print(f"INFO: API-Schluesseldatei '{filepath}' nicht gefunden.") + logger.debug(f"INFO: API-Schluesseldatei '{filepath}' nicht gefunden.") return None except Exception as e: - print(f"FEHLER beim Lesen der Schluesseldatei '{filepath}': {e}") + logger.error(f"FEHLER beim Lesen der Schluesseldatei '{filepath}': {e}") return None @@ -1212,7 +1189,7 @@ TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar." # String-Repraese ALLOWED_TARGET_BRANCHES = [] # Liste der erlaubten Kurzformen -def load_target_schema(csv_filepath=BRANCH_MAPPING_FILE): +def load_target_schema(csv_filepath=Config.SCHEMA_FILE): logger = logging.getLogger(__name__) global ALLOWED_TARGET_BRANCHES, FOCUS_TARGET_BRANCHES, TARGET_SCHEMA_STRING, FOCUS_BRANCHES_PROMPT_PART @@ -9337,7 +9314,7 @@ class DataProcessor: # logger, pickle, json, os, # train_test_split, SimpleImputer, DecisionTreeClassifier, # accuracy_score, classification_report, confusion_matrix, export_text (sklearn). - def train_technician_model(self, model_out=MODEL_FILE, imputer_out=IMPUTER_FILE, patterns_out=PATTERNS_FILE_JSON): + def train_technician_model(self, model_out=Config.MODEL_FILE, imputer_out=Config.IMPUTER_FILE, patterns_out=Config.PATTERNS_FILE_JSON): self.logger.info("Starte Training des Servicetechniker Decision Tree Modells...") # 1. Daten vorbereiten