#!/usr/bin/env python3 """ config.py Zentrale Konfiguration für das Projekt "Automatisierte Unternehmensbewertung". Enthält Dateipfade, API-Schlüssel-Pfade, die globale Config-Klasse und das Spalten-Mapping für das Google Sheet. """ import os import re import openai import logging # ============================================================================== # 1. GLOBALE KONSTANTEN UND DATEIPFADE # ============================================================================== # --- Dateipfade (NEU: Absolute Pfade) --- # Basisverzeichnis des Projekts, in dem sich diese config.py befindet. BASE_DIR = os.path.dirname(os.path.abspath(__file__)) CREDENTIALS_FILE = os.path.join(BASE_DIR, "service_account.json") API_KEY_FILE = os.path.join(BASE_DIR, "api_key.txt") # OpenAI SERP_API_KEY_FILE = os.path.join(BASE_DIR, "serpApiKey.txt") GENDERIZE_API_KEY_FILE = os.path.join(BASE_DIR, "genderize_API_Key.txt") BRANCH_MAPPING_FILE = None LOG_DIR = os.path.join(BASE_DIR, "Log") # --- ML Modell Artefakte --- MODEL_FILE = os.path.join(BASE_DIR, "technician_decision_tree_model.pkl") IMPUTER_FILE = os.path.join(BASE_DIR, "median_imputer.pkl") PATTERNS_FILE_TXT = os.path.join(BASE_DIR, "technician_patterns.txt") # Alt (Optional beibehalten) PATTERNS_FILE_JSON = os.path.join(BASE_DIR, "technician_patterns.json") # Neu (Empfohlen) # Marker für URLs, die erneut per SERP gesucht werden sollen URL_CHECK_MARKER = "URL_CHECK_NEEDED" # --- User Agents für Rotation --- USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0', 'Mozilla/5.0 (X11; Linux i686; rv:108.0) Gecko/20100101 Firefox/108.0', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:108.0) Gecko/20100101 Firefox/108.0', ] # ============================================================================== # 2. VORAB-HELPER FUNKTION (wird von Config-Klasse benötigt) # ============================================================================== def normalize_for_mapping(text): """ Normalisiert einen String aggressiv für Mapping-Zwecke. Muss VOR der Config-Klasse definiert werden, da sie dort verwendet wird. """ if not isinstance(text, str): return "" text = text.lower() text = text.strip() text = re.sub(r'[^a-z0-9]', '', text) return text # ============================================================================== # 3. ZENTRALE KONFIGURATIONS-KLASSE # ============================================================================== class Config: """Zentrale Konfigurationseinstellungen.""" VERSION = "v2.0.0" # Version hochgezählt nach Refactoring 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 RETRY_DELAY = 10 REQUEST_TIMEOUT = 20 SIMILARITY_THRESHOLD = 0.65 DEBUG = True WIKIPEDIA_SEARCH_RESULTS = 5 HTML_PARSER = "html.parser" TOKEN_MODEL = "gpt-3.5-turbo" USER_AGENT = 'Mozilla/5.0 (compatible; UnternehmenSkript/1.0; +https://www.example.com/bot)' # --- Konfiguration fuer Batching & Parallelisierung --- PROCESSING_BATCH_SIZE = 20 OPENAI_BATCH_SIZE_LIMIT = 4 MAX_SCRAPING_WORKERS = 10 UPDATE_BATCH_ROW_LIMIT = 50 MAX_BRANCH_WORKERS = 10 OPENAI_CONCURRENCY_LIMIT = 3 PROCESSING_BRANCH_BATCH_SIZE = 20 SERPAPI_DELAY = 1.5 # --- Plausibilitäts-Schwellenwerte --- PLAUSI_UMSATZ_MIN_WARNUNG = 50000 PLAUSI_UMSATZ_MAX_WARNUNG = 200000000000 PLAUSI_MA_MIN_WARNUNG_ABS = 1 PLAUSI_MA_MIN_WARNUNG_BEI_UMSATZ = 3 PLAUSI_UMSATZ_MIN_SCHWELLE_FUER_MA_CHECK = 1000000 PLAUSI_MA_MAX_WARNUNG = 1000000 PLAUSI_RATIO_UMSATZ_PRO_MA_MIN = 25000 PLAUSI_RATIO_UMSATZ_PRO_MA_MAX = 1500000 PLAUSI_ABWEICHUNG_CRM_WIKI_PROZENT = 30 # --- Branchen-Gruppen Mapping (NEUE STRUKTUR) --- # Single Source of Truth für alle Branchen. # Key: Lesbare Branche (wie sie im Sheet steht und von ChatGPT erwartet wird) # Value: Obergruppe für ML-Modell BRANCH_GROUP_MAPPING = { "Baustoffhandel": "Baubranche", "Bauunternehmen": "Baubranche", "Versicherungsgutachten": "Gutachter / Versicherungen", "Technische Gutachten": "Gutachter / Versicherungen", "Baugutachter": "Gutachter / Versicherungen", "Medizinische Gutachten": "Gutachter / Versicherungen", "Energie (Brennstoffe)": "Handel", "Großhandel": "Handel", "Einzelhandel": "Handel", "Automaten (Vending / Slot)": "Hersteller / Produzenten", "Anlagenbau": "Hersteller / Produzenten", "IT / Telekommunikation": "Hersteller / Produzenten", "Maschinenbau": "Hersteller / Produzenten", "Chemie & Pharma": "Hersteller / Produzenten", "Medizintechnik": "Hersteller / Produzenten", "Agrar / Pellets": "Hersteller / Produzenten", "Elektrotechnik": "Hersteller / Produzenten", "Gebäudetechnik Allgemein": "Hersteller / Produzenten", "Fenster / Glas": "Hersteller / Produzenten", "Lebensmittelproduktion": "Hersteller / Produzenten", "Automobil": "Hersteller / Produzenten", "Gebäudetechnik Heizung / Lüftung / Klima": "Hersteller / Produzenten", "Braune & Weiße Ware": "Hersteller / Produzenten", "Bürotechnik": "Hersteller / Produzenten", "Möbel": "Hersteller / Produzenten", "Getränke": "Hersteller / Produzenten", "Sozialbau Unternehmen": "Housing", "Renovierungsunternehmen": "Housing", "Anbieter für Soziales Wohnen": "Housing", "Logistik / Sonstige": "Logistik", "Auslieferdienste": "Logistik", "Logistik": "Logistik", "Facility Management": "Service provider (Dienstleister)", "Servicedienstleister / Reparatur ohne Produktion": "Service provider (Dienstleister)", "Feuer- und Sicherheitssysteme": "Service provider (Dienstleister)", "Healthcare/Pflegedienste": "Service provider (Dienstleister)", "Schädlingsbekämpfung": "Service provider (Dienstleister)", "Entsorgung": "Service provider (Dienstleister)", "Personentransport": "Service provider (Dienstleister)", "Messdienstleister": "Service provider (Dienstleister)", "Aufzüge und Rolltreppen": "Service provider (Dienstleister)", "Catering Services": "Service provider (Dienstleister)", "Sonstige": "Sonstige", "IT Beratung": "Sonstige", "Unternehmensberatung": "Sonstige", "Sonstiger Service": "Sonstige", "Öffentliche Verwaltung": "Sonstige", "Engineering": "Sonstige", "Telekommunikation": "Versorger", "Verteilnetzbetreiber": "Versorger", "Stadtwerke": "Versorger", "Gase & Mineralöl": "Versorger", } # --- API Schlüssel Speicherung (werden in main() geladen) --- API_KEYS = {} @classmethod def load_api_keys(cls): """Laedt API-Schluessel aus den definierten Dateien.""" logger = logging.getLogger(__name__) logger.info("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) if cls.API_KEYS.get('openai'): openai.api_key = cls.API_KEYS['openai'] logger.info("OpenAI API Key erfolgreich geladen.") else: logger.warning("OpenAI API Key konnte nicht geladen werden. OpenAI-Funktionen sind deaktiviert.") if not cls.API_KEYS.get('serpapi'): logger.warning("SerpAPI Key konnte nicht geladen werden. Suchfunktionen sind deaktiviert.") if not cls.API_KEYS.get('genderize'): logger.warning("Genderize API Key konnte nicht geladen werden. Geschlechtserkennung ist eingeschraenkt.") @staticmethod def _load_key_from_file(filepath): """Hilfsfunktion zum Laden eines Schluessels aus einer Datei.""" logger = logging.getLogger(__name__) try: with open(filepath, "r", encoding="utf-8") as f: key = f.read().strip() if key: return key else: logger.warning(f"Datei '{filepath}' ist leer.") return None except FileNotFoundError: logger.info(f"API-Schluesseldatei '{filepath}' nicht gefunden.") return None except Exception as e: logger.error(f"FEHLER beim Lesen der Schluesseldatei '{filepath}': {e}") return None # ============================================================================== # 4. GLOBALE DATENSTRUKTUR-VARIABLEN # ============================================================================== # NEU: Definiert die exakte und garantierte Reihenfolge der Spalten. # Dies ist die neue "Single Source of Truth" für alle Index-Berechnungen. COLUMN_ORDER = [ "ReEval Flag", "CRM Name", "CRM Kurzform", "Parent Account Name", "CRM Website", "CRM Ort", "CRM Land", "CRM Beschreibung", "CRM Branche", "CRM Beschreibung Branche extern", "CRM Anzahl Techniker", "CRM Umsatz", "CRM Anzahl Mitarbeiter", "CRM Vorschlag Wiki URL", "System Vorschlag Parent Account", "Parent Vorschlag Status", "Parent Vorschlag Timestamp", "Wiki URL", "Wiki Sitz Stadt", "Wiki Sitz Land", "Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien", "Wikipedia Timestamp", "Wiki Verif. Timestamp", "SerpAPI Wiki Search Timestamp", "Chat Wiki Konsistenzpruefung", "Chat Begründung Wiki Inkonsistenz", "Chat Vorschlag Wiki Artikel", "Begründung bei Abweichung", "Website Rohtext", "Website Zusammenfassung", "Website Meta-Details", "Website Scrape Timestamp", "URL Prüfstatus", "Chat Vorschlag Branche", "Chat Branche Konfidenz", "Chat Konsistenz Branche", "Chat Begruendung Abweichung Branche", "Chat Prüfung FSM Relevanz", "Chat Begründung für FSM Relevanz", "Chat Schätzung Anzahl Mitarbeiter", "Chat Konsistenzprüfung Mitarbeiterzahl", "Chat Begruendung Abweichung Mitarbeiterzahl", "Chat Einschätzung Anzahl Servicetechniker", "Chat Begründung Abweichung Anzahl Servicetechniker", "Chat Schätzung Umsatz", "Chat Begründung Abweichung Umsatz", "FSM Pitch", "FSM Pitch Timestamp", "Linked Serviceleiter gefunden", "Linked It-Leiter gefunden", "Linked Management gefunden", "Linked Disponent gefunden", "Contact Search Timestamp", "Finaler Umsatz (Wiki>CRM)", "Finaler Mitarbeiter (Wiki>CRM)", "Geschaetzter Techniker Bucket", "Plausibilität Umsatz", "Plausibilität Mitarbeiter", "Plausibilität Umsatz/MA Ratio", "Abweichung Umsatz CRM/Wiki", "Abweichung MA CRM/Wiki", "Plausibilität Begründung", "Plausibilität Prüfdatum", "Timestamp letzte Pruefung", "Version", "Tokens", "CRM ID" ] # --- Spalten-Mapping (Single Source of Truth) --- # Version 1.8.0 - 68 Spalten (A-BP) COLUMN_MAP = { # A-E: Stammdaten & Prozesssteuerung "ReEval Flag": {"Titel": "A", "index": 0}, "CRM Name": {"Titel": "B", "index": 1}, "CRM Kurzform": {"Titel": "C", "index": 2}, "Parent Account Name": {"Titel": "D", "index": 3}, "CRM Website": {"Titel": "E", "index": 4}, # F-M: CRM-Daten "CRM Ort": {"Titel": "F", "index": 5}, "CRM Land": {"Titel": "G", "index": 6}, "CRM Beschreibung": {"Titel": "H", "index": 7}, "CRM Branche": {"Titel": "I", "index": 8}, "CRM Beschreibung Branche extern": {"Titel": "J", "index": 9}, "CRM Anzahl Techniker": {"Titel": "K", "index": 10}, "CRM Umsatz": {"Titel": "L", "index": 11}, "CRM Anzahl Mitarbeiter": {"Titel": "M", "index": 12}, # N-Q: System & Parent Vorschläge "CRM Vorschlag Wiki URL": {"Titel": "N", "index": 13}, "System Vorschlag Parent Account": {"Titel": "O", "index": 14}, "Parent Vorschlag Status": {"Titel": "P", "index": 15}, "Parent Vorschlag Timestamp": {"Titel": "Q", "index": 16}, # R-AB: Wikipedia Extraktion "Wiki URL": {"Titel": "R", "index": 17}, "Wiki Sitz Stadt": {"Titel": "S", "index": 18}, "Wiki Sitz Land": {"Titel": "T", "index": 19}, "Wiki Absatz": {"Titel": "U", "index": 20}, "Wiki Branche": {"Titel": "V", "index": 21}, "Wiki Umsatz": {"Titel": "W", "index": 22}, "Wiki Mitarbeiter": {"Titel": "X", "index": 23}, "Wiki Kategorien": {"Titel": "Y", "index": 24}, "Wikipedia Timestamp": {"Titel": "Z", "index": 25}, "Wiki Verif. Timestamp": {"Titel": "AA", "index": 26}, "SerpAPI Wiki Search Timestamp": {"Titel": "AB", "index": 27}, # AC-AF: ChatGPT Wiki Verifizierung "Chat Wiki Konsistenzpruefung": {"Titel": "AC", "index": 28}, "Chat Begründung Wiki Inkonsistenz": {"Titel": "AD", "index": 29}, "Chat Vorschlag Wiki Artikel": {"Titel": "AE", "index": 30}, "Begründung bei Abweichung": {"Titel": "AF", "index": 31}, # AG-AK: Website Scraping "Website Rohtext": {"Titel": "AG", "index": 32}, "Website Zusammenfassung": {"Titel": "AH", "index": 33}, "Website Meta-Details": {"Titel": "AI", "index": 34}, "Website Scrape Timestamp": {"Titel": "AJ", "index": 35}, "URL Prüfstatus": {"Titel": "AK", "index": 36}, # AL-AU: ChatGPT Branchen & FSM Analyse "Chat Vorschlag Branche": {"Titel": "AL", "index": 37}, "Chat Branche Konfidenz": {"Titel": "AM", "index": 38}, "Chat Konsistenz Branche": {"Titel": "AN", "index": 39}, "Chat Begruendung Abweichung Branche": {"Titel": "AO", "index": 40}, "Chat Prüfung FSM Relevanz": {"Titel": "AP", "index": 41}, "Chat Begründung für FSM Relevanz": {"Titel": "AQ", "index": 42}, "Chat Schätzung Anzahl Mitarbeiter": {"Titel": "AR", "index": 43}, "Chat Konsistenzprüfung Mitarbeiterzahl": {"Titel": "AS", "index": 44}, "Chat Begruendung Abweichung Mitarbeiterzahl": {"Titel": "AT", "index": 45}, "Chat Einschätzung Anzahl Servicetechniker": {"Titel": "AU", "index": 46}, # AV-AZ: ChatGPT Fortsetzung & FSM Pitch "Chat Begründung Abweichung Anzahl Servicetechniker": {"Titel": "AV", "index": 47}, "Chat Schätzung Umsatz": {"Titel": "AW", "index": 48}, "Chat Begründung Abweichung Umsatz": {"Titel": "AX", "index": 49}, "FSM Pitch": {"Titel": "AY", "index": 50}, "FSM Pitch Timestamp": {"Titel": "AZ", "index": 51}, # BA-BE: LinkedIn Kontaktsuche "Linked Serviceleiter gefunden": {"Titel": "BA", "index": 52}, "Linked It-Leiter gefunden": {"Titel": "BB", "index": 53}, "Linked Management gefunden": {"Titel": "BC", "index": 54}, "Linked Disponent gefunden": {"Titel": "BD", "index": 55}, "Contact Search Timestamp": {"Titel": "BE", "index": 56}, # BF-BH: Konsolidierte Daten & ML "Finaler Umsatz (Wiki>CRM)": {"Titel": "BF", "index": 57}, "Finaler Mitarbeiter (Wiki>CRM)": {"Titel": "BG", "index": 58}, "Geschaetzter Techniker Bucket": {"Titel": "BH", "index": 59}, # BI-BO: Plausibilitäts-Checks "Plausibilität Umsatz": {"Titel": "BI", "index": 60}, "Plausibilität Mitarbeiter": {"Titel": "BJ", "index": 61}, "Plausibilität Umsatz/MA Ratio": {"Titel": "BK", "index": 62}, "Abweichung Umsatz CRM/Wiki": {"Titel": "BL", "index": 63}, "Abweichung MA CRM/Wiki": {"Titel": "BM", "index": 64}, "Plausibilität Begründung": {"Titel": "BN", "index": 65}, "Plausibilität Prüfdatum": {"Titel": "BO", "index": 66}, # BP-BS: Metadaten "Timestamp letzte Pruefung": {"Titel": "BP", "index": 67}, "Version": {"Titel": "BQ", "index": 68}, "Tokens": {"Titel": "BR", "index": 69}, "CRM ID": {"Titel": "BS", "index": 70} } # ============================================================================== # 5. DEALFRONT AUTOMATION CONFIGURATION # ============================================================================== DEALFRONT_CREDENTIALS_FILE = os.path.join(BASE_DIR, "dealfront_credentials.json") DEALFRONT_LOGIN_URL = "https://app.dealfront.com/login" # Die direkte URL zum 'Target'-Bereich. Dies hat sich als der robusteste Weg erwiesen. DEALFRONT_TARGET_URL = "https://app.dealfront.com/t/prospector/companies" # WICHTIG: Der exakte Name der vordefinierten Suche, die nach der Navigation geladen werden soll. TARGET_SEARCH_NAME = "Facility Management" # <-- PASSEN SIE DIESEN NAMEN AN IHRE ZIEL-LISTE AN # --- END OF FILE config.py ---