341 lines
17 KiB
Python
341 lines
17 KiB
Python
#!/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 --- |