#!/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 # ============================================================================== # --- 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 ---