From 50f319e2a2df35eba9a5c0e956dd9210bd98d6df Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 7 Apr 2025 19:06:22 +0000 Subject: [PATCH] 1.4.0 von 4.o --- brancheneinstufung.py | 1220 ++++++++++++++++++++++++++++++----------- 1 file changed, 912 insertions(+), 308 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index dc8c3b75..92745a3c 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -12,6 +12,7 @@ from difflib import SequenceMatcher import unicodedata import csv +# Optional: tiktoken für Token-Zählung (Modus 8) try: import tiktoken except ImportError: @@ -19,7 +20,7 @@ except ImportError: # ==================== KONFIGURATION ==================== class Config: - VERSION = "v1.4.0" # Version 1.4.0 + VERSION = "v1.3.18" # v1.3.18: Neuer Modus 8 (Batch-Token-Zählung) & Modus 51 (nur Verifizierung) LANG = "de" CREDENTIALS_FILE = "service_account.json" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" @@ -48,15 +49,13 @@ def retry_on_failure(func): # ==================== LOGGING & HELPER FUNCTIONS ==================== if not os.path.exists("Log"): os.makedirs("Log") -timestamp = datetime.now().strftime('%d-%m-%Y_%H-%M') -version = Config.VERSION -modi_mapping = {"2": "ReEvaluierung", "3": "AlignmentDemo", "4": "WikiOnly", "5": "ChatGPTOnly", "51": "Verifizierung", "8": "BatchTokenCount"} +LOG_FILE = os.path.join("Log", f"{datetime.now().strftime('%d-%m-%Y_%H-%M')}_{Config.VERSION.replace('.', '')}.txt") def debug_print(message): if Config.DEBUG: print(f"[DEBUG] {message}") try: - with open(os.path.join("Log", f"{timestamp}_{version}.txt"), "a", encoding="utf-8") as f: + with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(f"[DEBUG] {message}\n") except Exception as e: print(f"[DEBUG] Log-Schreibfehler: {e}") @@ -143,6 +142,260 @@ def compare_umsatz_values(crm, wiki): diff_mio = abs(crm_val - wiki_val) return f"Abweichung: {int(round(diff_mio))} Mio €" +def evaluate_umsatz_chatgpt(company_name, wiki_umsatz): + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens: {e}") + return "k.A." + openai.api_key = api_key + prompt = ( + f"Bitte schätze den Umsatz in Mio. Euro für das Unternehmen '{company_name}'. " + f"Die Wikipedia-Daten zeigen: '{wiki_umsatz}'. " + "Antworte nur mit der Zahl." + ) + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": prompt}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"ChatGPT Umsatzschätzung: '{result}'") + try: + value = float(result.replace(',', '.')) + return str(int(round(value))) + except Exception as conv_e: + debug_print(f"Fehler bei der Verarbeitung der Umsatzschätzung '{result}': {conv_e}") + return result + except Exception as e: + debug_print(f"Fehler beim Aufruf der ChatGPT API für Umsatzschätzung: {e}") + return "k.A." + +def validate_article_with_chatgpt(crm_data, wiki_data): + crm_headers = "Firmenname;Website;Ort;Beschreibung;Aktuelle Branche;Beschreibung Branche extern;Anzahl Techniker;Umsatz (CRM);Anzahl Mitarbeiter (CRM)" + wiki_headers = "Wikipedia URL;Wikipedia Absatz;Wikipedia Branche;Wikipedia Umsatz;Wikipedia Mitarbeiter;Wikipedia Kategorien" + prompt_text = ( + "Bitte überprüfe, ob die folgenden beiden Datensätze grundsätzlich zum gleichen Unternehmen gehören. " + "Berücksichtige dabei, dass leichte Abweichungen in Firmennamen (z. B. unterschiedliche Schreibweisen, Mutter-Tochter-Beziehungen) " + "oder im Ort (z. B. 'Oberndorf' vs. 'Oberndorf/Neckar') tolerierbar sind. " + "Vergleiche insbesondere den Firmennamen, den Ort und die Branche. Unterschiede im Umsatz können bis zu 10% abweichen. " + "Wenn die Daten im Wesentlichen übereinstimmen, antworte ausschließlich mit 'OK'. " + "Falls nicht, nenne bitte den wichtigsten Grund und eine kurze Begründung, warum die Abweichung plausibel sein könnte.\n\n" + f"CRM-Daten:\n{crm_headers}\n{crm_data}\n\n" + f"Wikipedia-Daten:\n{wiki_headers}\n{wiki_data}\n\n" + "Antwort: " + ) + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens: {e}") + return "k.A." + openai.api_key = api_key + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": prompt_text}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"Validierungsantwort ChatGPT: '{result}'") + return result + except Exception as e: + debug_print(f"Fehler beim Validierungs-API-Aufruf: {e}") + return "k.A." + +def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien): + # Lade das Ziel-Branchenschema aus der CSV + def load_target_branches(): + try: + with open("ziel_Branchenschema.csv", "r", encoding="utf-8") as csvfile: + reader = csv.reader(csvfile) + branches = [row[0] for row in reader if row] + return branches + except Exception as e: + debug_print(f"Fehler beim Laden des Ziel-Branchenschemas: {e}") + return [] + target_branches = load_target_branches() + target_branches_str = "\n".join(target_branches) + focus_branches = [ + "Gutachter / Versicherungen > Baugutachter", + "Gutachter / Versicherungen > Technische Gutachten", + "Gutachter / Versicherungen > Versicherungsgutachten", + "Gutachter / Versicherungen > Medizinische Gutachten", + "Hersteller / Produzenten > Anlagenbau", + "Hersteller / Produzenten > Automaten (Vending, Slot)", + "Hersteller / Produzenten > Gebäudetechnik Allgemein", + "Hersteller / Produzenten > Gebäudetechnik Heizung, Lüftung, Klima", + "Hersteller / Produzenten > Maschinenbau", + "Hersteller / Produzenten > Medizintechnik", + "Service provider (Dienstleister) > Aufzüge und Rolltreppen", + "Service provider (Dienstleister) > Feuer- und Sicherheitssysteme", + "Service provider (Dienstleister) > Servicedienstleister / Reparatur ohne Produktion", + "Service provider (Dienstleister) > Facility Management", + "Versorger > Telekommunikation" + ] + focus_branches_str = "\n".join(focus_branches) + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (Branche): {e}") + return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} + openai.api_key = api_key + additional_instruction = "" + if wiki_branche.strip() == "k.A.": + additional_instruction = ( + "Da keine Wikipedia-Branche vorliegt, berücksichtige bitte die Wikipedia-Kategorien mit erhöhter Gewichtung, " + "insbesondere wenn Hinweise auf Personentransport oder öffentliche Verkehrsdienstleistungen vorliegen. " + ) + system_prompt = ( + "Du bist ein Experte im Field Service Management. Deine Aufgabe ist es, ein Unternehmen basierend auf folgenden Angaben einer Branche zuzuordnen.\n\n" + f"CRM-Branche (Spalte F): {crm_branche}\n" + f"Branchenbeschreibung (Spalte G): {beschreibung}\n" + f"Wikipedia-Branche (Spalte N): {wiki_branche}\n" + f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n" + + additional_instruction + + "Das Ziel-Branchenschema umfasst ALLE gültigen Branchen, also sowohl Fokusbranchen als auch weitere, z. B. 'Housing > Sozialbau Unternehmen'.\n" + "Das vollständige Ziel-Branchenschema lautet:\n" + f"{target_branches_str}\n\n" + "Falls das Unternehmen mehreren Branchen zugeordnet werden könnte, wähle bitte bevorzugt eine Branche aus der folgenden Fokusliste, sofern zutreffend:\n" + f"{focus_branches_str}\n\n" + "Gewichtung der Angaben:\n" + "1. Wikipedia-Branche (Spalte N) zusammen mit Wikipedia-Kategorien (Spalte Q) (höchste Priorität, wenn verifiziert, ansonsten erhöhte Gewichtung der Kategorien)\n" + "2. Branchenbeschreibung (Spalte G)\n" + "3. CRM-Branche (Spalte F)\n\n" + "Ordne das Unternehmen exakt einer der oben genannten Branchen zu (es dürfen keine zusätzlichen Branchen erfunden werden). " + "Bitte antworte in folgendem Format (ohne zusätzliche Informationen):\n" + "Branche: \n" + "Übereinstimmung: \n" + "Begründung: " + ) + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": system_prompt}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'") + branch = "k.A." + consistency = "k.A." + justification = "" + for line in result.split("\n"): + if line.lower().startswith("branche:"): + branch = line.split(":", 1)[1].strip() + elif line.lower().startswith("übereinstimmung:"): + consistency = line.split(":", 1)[1].strip() + elif line.lower().startswith("begründung:"): + justification = line.split(":", 1)[1].strip() + return {"branch": branch, "consistency": consistency, "justification": justification} + except Exception as e: + debug_print(f"Fehler beim Aufruf der ChatGPT API für Branchenabgleich: {e}") + return {"branch": "k.A.", "consistency": "k.A.", "justification": "k.A."} + +def evaluate_fsm_suitability(company_name, company_data): + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (FSM): {e}") + return {"suitability": "k.A.", "justification": "k.A."} + openai.api_key = api_key + prompt = ( + f"Bitte bewerte, ob das Unternehmen '{company_name}' für den Einsatz einer Field Service Management Lösung geeignet ist. " + "Antworte ausschließlich mit 'Ja' oder 'Nein' und gib eine kurze Begründung." + ) + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": prompt}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"FSM-Eignungsantwort ChatGPT: '{result}'") + suitability = "k.A." + justification = "" + lines = result.split("\n") + if len(lines) == 1: + parts = result.split(" ", 1) + suitability = parts[0].strip() + justification = parts[1].strip() if len(parts) > 1 else "" + else: + for line in lines: + if line.lower().startswith("eignung:"): + suitability = line.split(":", 1)[1].strip() + elif line.lower().startswith("begründung:"): + justification = line.split(":", 1)[1].strip() + if suitability not in ["Ja", "Nein"]: + parts = result.split(" ", 1) + suitability = parts[0].strip() + justification = " ".join(result.split()[1:]).strip() + return {"suitability": suitability, "justification": justification} + except Exception as e: + debug_print(f"Fehler beim Aufruf der ChatGPT API für FSM-Eignungsprüfung: {e}") + return {"suitability": "k.A.", "justification": "k.A."} + +def evaluate_servicetechnicians_estimate(company_name, company_data): + try: + with open("serpApiKey.txt", "r") as f: + serp_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des SerpAPI-Schlüssels (Servicetechniker): {e}") + return "k.A." + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (Servicetechniker): {e}") + return "k.A." + openai.api_key = api_key + prompt = ( + f"Bitte schätze auf Basis öffentlich zugänglicher Informationen (vor allem verifizierte Wikipedia-Daten) " + f"die Anzahl der Servicetechniker des Unternehmens '{company_name}' ein. " + "Gib die Antwort ausschließlich in einer der folgenden Kategorien aus: " + "'<50 Techniker', '>100 Techniker', '>200 Techniker', '>500 Techniker'." + ) + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": prompt}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"Schätzung Servicetechniker ChatGPT: '{result}'") + return result + except Exception as e: + debug_print(f"Fehler beim Aufruf der ChatGPT API für Servicetechniker-Schätzung: {e}") + return "k.A." + +def evaluate_servicetechnicians_explanation(company_name, st_estimate, company_data): + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (ST-Erklärung): {e}") + return "k.A." + openai.api_key = api_key + prompt = ( + f"Bitte erkläre, warum du für das Unternehmen '{company_name}' die Anzahl der Servicetechniker als '{st_estimate}' geschätzt hast. " + "Berücksichtige dabei öffentlich zugängliche Informationen wie Branche, Umsatz, Mitarbeiterzahl und andere relevante Daten." + ) + try: + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "system", "content": prompt}], + temperature=0.0 + ) + result = response.choices[0].message.content.strip() + debug_print(f"Servicetechniker-Erklärung ChatGPT: '{result}'") + return result + except Exception as e: + debug_print(f"Fehler beim Aufruf der ChatGPT API für Servicetechniker-Erklärung: {e}") + return "k.A." + def map_internal_technicians(value): try: num = int(value) @@ -157,61 +410,457 @@ def map_internal_technicians(value): else: return ">500 Techniker" -# ==================== CHATGPT CALL WRAPPERS ==================== -def chatgpt_call(prompt, input_text): +def wait_for_sheet_update(sheet, cell, expected_value, timeout=5): + start_time = time.time() + while time.time() - start_time < timeout: + try: + current_value = sheet.acell(cell).value + if current_value == expected_value: + return True + except Exception as e: + debug_print(f"Fehler beim Lesen von Zelle {cell}: {e}") + time.sleep(0.5) + return False + +# ==================== NEUE FUNKTION: LINKEDIN-KONTAKT-SUCHE (Einzelkontakt) ==================== +def search_linkedin_contact(company_name, website, position_query): + try: + with open("serpApiKey.txt", "r") as f: + serp_key = f.read().strip() + except Exception as e: + debug_print("Fehler beim Lesen des SerpAPI-Schlüssels: " + str(e)) + return None + query = f'site:linkedin.com/in "{position_query}" "{company_name}"' + params = { + "engine": "google", + "q": query, + "api_key": serp_key, + "hl": "de" + } + try: + response = requests.get("https://serpapi.com/search", params=params) + data = response.json() + if "organic_results" in data and len(data["organic_results"]) > 0: + result = data["organic_results"][0] + title = result.get("title", "") + if "–" in title: + parts = title.split("–") + elif "-" in title: + parts = title.split("-") + else: + parts = [title] + if len(parts) >= 2: + name_part = parts[0].strip() + pos = parts[1].split("|")[0].strip() + name_parts = name_part.split(" ", 1) + if len(name_parts) == 2: + firstname, lastname = name_parts + else: + firstname = name_part + lastname = "" + return {"Firmenname": company_name, "Website": website, "Vorname": firstname, "Nachname": lastname, "Position": pos} + else: + return {"Firmenname": company_name, "Website": website, "Vorname": "", "Nachname": "", "Position": title} + else: + return None + except Exception as e: + debug_print(f"Fehler bei der SerpAPI-Suche: {e}") + return None + +def count_linkedin_contacts(company_name, website, position_query): + try: + with open("serpApiKey.txt", "r") as f: + serp_key = f.read().strip() + except Exception as e: + debug_print("Fehler beim Lesen des SerpAPI-Schlüssels: " + str(e)) + return 0 + query = f'site:linkedin.com/in "{position_query}" "{company_name}"' + params = { + "engine": "google", + "q": query, + "api_key": serp_key, + "hl": "de" + } + try: + response = requests.get("https://serpapi.com/search", params=params) + data = response.json() + if "organic_results" in data: + count = len(data["organic_results"]) + debug_print(f"Anzahl Kontakte für Query '{query}': {count}") + return count + else: + debug_print(f"Keine Ergebnisse für Query: {query}") + return 0 + except Exception as e: + debug_print(f"Fehler bei der SerpAPI-Suche (Count): {e}") + return 0 + +# ==================== VERIFIZIERUNGS-MODUS (Modus 51) ==================== +def _process_verification_row(self, row_num, row_data): + # Verarbeitung: Extrahiere relevante Daten für die Verifizierung + company_name = row_data[1] if len(row_data) > 1 else "" + website = row_data[3] if len(row_data) > 3 else "" + crm_description = row_data[7] if len(row_data) > 7 else "" + wiki_url = row_data[11] if len(row_data) > 11 and row_data[11].strip() not in ["", "k.A."] else "k.A." + wiki_absatz = row_data[12] if len(row_data) > 12 else "k.A." + wiki_categories = row_data[16] if len(row_data) > 16 else "k.A." + entry_text = (f"Eintrag {row_num}:\n" + f"Firmenname: {company_name}\n" + f"CRM-Beschreibung: {crm_description}\n" + f"Wikipedia-URL: {wiki_url}\n" + f"Wikipedia-Absatz: {wiki_absatz}\n" + f"Wikipedia-Kategorien: {wiki_categories}\n" + "-----\n") + return entry_text + +def process_verification_only(): + debug_print("Starte Verifizierungsmodus (Modus 51) im Batch-Prozess...") + gc = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name( + Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])) + sh = gc.open_by_url(Config.SHEET_URL) + main_sheet = sh.sheet1 + data = main_sheet.get_all_values() + batch_size = Config.BATCH_SIZE + batch_entries = [] + row_indices = [] + for i, row in enumerate(data[1:], start=2): + if len(row) <= 19 or row[18].strip() == "": + entry_text = _process_verification_row(None, i, row) + batch_entries.append(entry_text) + row_indices.append(i) + if len(batch_entries) == batch_size: + break + if not batch_entries: + debug_print("Keine Einträge für die Verifizierung gefunden.") + return + aggregated_prompt = ("Du bist ein Experte in der Verifizierung von Wikipedia-Artikeln für Unternehmen. " + "Für jeden der folgenden Einträge prüfe, ob der vorhandene Wikipedia-Artikel (URL, Absatz, Kategorien) plausibel passt. " + "Gib für jeden Eintrag das Ergebnis im Format aus:\n" + "Eintrag : \n" + "Dabei gilt:\n" + "- Wenn der Artikel passt, antworte mit 'OK'.\n" + "- Wenn der Artikel unpassend ist, antworte mit 'Alternativer Wikipedia-Artikel vorgeschlagen: | X | '.\n" + "- Wenn kein Artikel gefunden wurde, antworte mit 'Kein Wikipedia-Eintrag vorhanden.'\n\n") + aggregated_prompt += "\n".join(batch_entries) + debug_print("Aggregierter Prompt für Verifizierungs-Batch erstellt.") + token_count = "n.v." + if tiktoken: + try: + enc = tiktoken.encoding_for_model(Config.TOKEN_MODEL) + token_count = len(enc.encode(aggregated_prompt)) + debug_print(f"Token-Zahl für Batch: {token_count}") + except Exception as e: + debug_print(f"Fehler beim Token-Counting: {e}") + try: + with open("api_key.txt", "r") as f: + api_key = f.read().strip() + except Exception as e: + debug_print(f"Fehler beim Lesen des API-Tokens (Verifizierung): {e}") + return + openai.api_key = api_key try: response = openai.ChatCompletion.create( model=Config.TOKEN_MODEL, - messages=[{"role": "user", "content": prompt}], + messages=[{"role": "system", "content": aggregated_prompt}], temperature=0.0 ) result = response.choices[0].message.content.strip() - tokens = 0 - if tiktoken: - try: - enc = tiktoken.encoding_for_model(Config.TOKEN_MODEL) - tokens = len(enc.encode(prompt)) - except Exception as e: - debug_print(f"Tokenzählung fehlgeschlagen: {e}") - return result, tokens + debug_print(f"Antwort ChatGPT Verifizierung Batch: {result}") except Exception as e: - debug_print(f"Fehler im chatgpt_call: {e}") - return "k.A.", 0 + debug_print(f"Fehler bei der ChatGPT Anfrage für Verifizierung: {e}") + return + answers = result.split("\n") + for idx, row_num in enumerate(row_indices): + answer = "k.A." + for line in answers: + if line.strip().startswith(f"Eintrag {row_num}:"): + answer = line.split(":", 1)[1].strip() + break + if answer.upper() == "OK": + wiki_confirm = "OK" + alt_article = "" + wiki_explanation = "" + elif answer.upper() == "KEIN WIKIPEDIA-EINTRAG VORHANDEN.": + wiki_confirm = "" + alt_article = "Kein Wikipedia-Eintrag vorhanden." + wiki_explanation = "" + elif answer.startswith("Alternativer Wikipedia-Artikel vorgeschlagen:"): + parts = answer.split(":", 1)[1].split("|") + alt_article = parts[0].strip() if len(parts) > 0 else "k.A." + wiki_explanation = parts[2].strip() if len(parts) > 2 else "" + wiki_confirm = "X" + else: + wiki_confirm = "" + alt_article = answer + wiki_explanation = answer + main_sheet.update(values=[[wiki_confirm]], range_name=f"S{row_num}") + main_sheet.update(values=[[alt_article]], range_name=f"U{row_num}") + main_sheet.update(values=[[wiki_explanation]], range_name=f"V{row_num}") + crm_branch = data[row_num-1][6] if len(data[row_num-1]) > 6 else "k.A." + ext_branch = data[row_num-1][7] if len(data[row_num-1]) > 7 else "k.A." + wiki_branch = data[row_num-1][14] if len(data[row_num-1]) > 14 else "k.A." + wiki_cats = data[row_num-1][17] if len(data[row_num-1]) > 17 else "k.A." + branch_result = evaluate_branche_chatgpt(crm_branch, ext_branch, wiki_branch, wiki_cats) + main_sheet.update(values=[[branch_result["branch"]]], range_name=f"W{row_num}") + main_sheet.update(values=[[branch_result["consistency"]]], range_name=f"Y{row_num}") + main_sheet.update(values=[[str(token_count)]], range_name=f"AQ{row_num}") + current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + main_sheet.update(values=[[current_dt]], range_name=f"AO{row_num}") + main_sheet.update(values=[[Config.VERSION]], range_name=f"AP{row_num}") + debug_print(f"Zeile {row_num} verifiziert: Antwort: {answer}") + time.sleep(Config.RETRY_DELAY) + debug_print("Verifizierungs-Batch abgeschlossen.") -def safe_chatgpt_call(prompt, input_text, wiki_value): - if wiki_value == "k.A.": - return "Skipped (k.A.)", 0 - return chatgpt_call(prompt, input_text) +# ==================== GOOGLE SHEET HANDLER ==================== +class GoogleSheetHandler: + def __init__(self): + self.sheet = None + self.sheet_values = [] + self._connect() + def _connect(self): + scope = ["https://www.googleapis.com/auth/spreadsheets"] + creds = ServiceAccountCredentials.from_json_keyfile_name(Config.CREDENTIALS_FILE, scope) + self.sheet = gspread.authorize(creds).open_by_url(Config.SHEET_URL).sheet1 + self.sheet_values = self.sheet.get_all_values() + def get_start_index(self): + filled_n = [row[13] if len(row) > 13 else '' for row in self.sheet_values[1:]] + return next((i + 1 for i, v in enumerate(filled_n, start=1) if not str(v).strip()), len(filled_n) + 1) -# ==================== PROMPT ÜBERSICHT ==================== -def prompt_overview(): - data = [ - ["Funktion", "Prompt"], - ["Wiki Artikel Vorschlag", "Bitte schlage einen passenden Wikipedia-Artikel für das folgende Unternehmen vor: {company_name}"], - ["Begründung Wiki Inkonsistenz", "Bitte begründe, warum die Angaben aus Wikipedia von den folgenden CRM-Daten abweichen: {crm_data}"], - ["Mitarbeiter Schätzung", "Wie viele Mitarbeiter hat das folgende Unternehmen? Kontext: {wiki_text}"], - ["Mitarbeiter Konsistenz", "Vergleiche die Mitarbeiterzahlen aus CRM ({crm_value}) und Wikipedia ({wiki_value}). Gibt es Abweichungen? Welche Zahl erscheint plausibler?"], - ["Umsatz Schätzung", "Wie hoch ist der geschätzte Jahresumsatz des Unternehmens basierend auf den folgenden Angaben? Kontext: {crm_data}, Wikipedia: {wiki_text}"], - ["Branchenvorschlag", "Welcher Branche gehört das folgende Unternehmen an? Kontext: {wiki_text}, {crm_data}"] +# ==================== ALIGNMENT DEMO (Hauptblatt) ==================== +def alignment_demo(sheet): + new_headers = [ + [ # Spaltenname + "ReEval Flag", # A + "CRM Name", # B + "CRM Kurzform", # C + "CRM Website", # D + "CRM Ort", # E + "CRM Beschreibung", # F + "CRM Branche", # G + "CRM Beschreibung Branche extern", # H + "CRM Anzahl Techniker", # I + "CRM Umsatz", # J + "CRM Anzahl Mitarbeiter", # K + "CRM Vorschlag Wiki URL", # L + "Wiki URL", # M + "Wiki Absatz", # N + "Wiki Branche", # O + "Wiki Umsatz", # P + "Wiki Mitarbeiter", # Q + "Wiki Kategorien", # R + "Chat Wiki Konsistenzprüfung", # S + "Chat Begründung Wiki Inkonsistenz", # T + "Chat Vorschlag Wiki Artikel", # U + "Begründung bei Abweichung", # V + "Chat Vorschlag Branche", # W + "Chat Konsistenz Branche", # X + "Chat Begründung Abweichung Branche", # Y + "Chat Prüfung FSM Relevanz", # Z + "Chat Begründung für FSM Relevanz", # AA + "Chat Schätzung Anzahl Mitarbeiter", # AB + "Chat Konsistenzprüfung Mitarbeiterzahl", # AC + "Chat Begründung Abweichung Mitarbeiterzahl", # AD + "Chat Einschätzung Anzahl Servicetechniker", # AE + "Chat Begründung Abweichung Anzahl Servicetechniker", # AF + "Chat Schätzung Umsatz", # AG + "Chat Begründung Abweichung Umsatz", # AH + "Linked Serviceleiter gefunden", # AI + "Linked It-Leiter gefunden", # AJ + "Linked Management gefunden", # AK + "Linked Disponent gefunden", # AL + "Contact Search Timestamp", # AM + "Wikipedia Timestamp", # AN + "Timestamp letzte Prüfung", # AO + "Version", # AP + "Tokens" # AQ + ], + [ # Quelle der Daten + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "CRM", + "Wikipediascraper", + "Wikipediascraper", + "Wikipediascraper", + "Wikipediascraper", + "Wikipediascraper", + "Wikipediascraper", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "Chat GPT API", + "LinkedIn (via SerpApi)", + "LinkedIn (via SerpApi)", + "LinkedIn (via SerpApi)", + "LinkedIn (via SerpApi)", + "System", + "System", + "System", + "System", + "System" + ], + [ # Feldkategorie z.B. alle unterschiedlichen Umsatz-bezogenen Felder haben hier "Umsatz" vermerkt + "Prozess", + "Firmenname", + "Firmenname", + "Website", + "Ort", + "Beschreibung (Text)", + "Branche", + "Branche", + "Anzahl Servicetechniker", + "Umsatz", + "Anzahl Mitarbeiter", + "Wikipedia Artikel URL", + "Wikipedia Artikel", + "Beschreibung (Text)", + "Branche", + "Umsatz", + "Anzahl Mitarbeiter", + "Kategorien (Text)", + "Verifizierung", + "Begründung bei Abweichung", + "Wikipedia Artikel", + "Wikipedia Artikel", + "Branche", + "Branche", + "Branche", + "FSM Relevanz", + "FSM Relevanz", + "Anzahl Mitarbeiter", + "Anzahl Mitarbeiter", + "Anzahl Mitarbeiter", + "Anzahl Servicetechniker", + "Anzahl Servicetechniker", + "Umsatz", + "Umsatz", + "Kontakte zur Firma", + "Kontakte zur Firma", + "Kontakte zur Firma", + "Kontakte zur Firma", + "Timestamp", + "Timestamp", + "Timestamp", + "Version des Skripts die verwendet wurde", + "ChatGPT Tokens" + ], + [ # Kurze Beschreibung, in welchem Format die Daten vorliegen + "Systemspalte, irrelevant für den Prompt. Wird genutzt um die manuelle Neuprüfung dieses Accounts durchzuführen.", + "Enthält den Firmennamen nach bestem Gewissen. Firmennamen sind manchmal herausfordernd, insbesondere was unterschiedliche Schreibweisen, Firmierung, Tochter/Mutterfirmen etc. anbelangt. Zur besseren Trefferquote in der Wikipedia-Suche normalisieren wir den Firmennamen und entfernen sämtliche Firmenformen, wie z.B. AG, GmbH, SE etc.", + "Enthält eine manuell gepflegte (normalisierte) Kurzform des Firmennamens, wie auch ein Mensch die Firma nennen würde. Dies bedeutet insbesondere, dass die Firmenform wie z.B. GmbH oder AG aus dem Namen entfernt wird. Meist entspricht die Kurzform den ersten beiden Worten des Firmennamens. Manchmal sind auch Worte nötig, wenn die ersten beiden worte zu wenig Aussagekraft haben. Beispiele dafür sind beispielsweise Firmen wie 'Schmidt & Söhne', bei denen 'Schmidt &' wenig Sinn machen würde, oder 'Philip Morris Tabakwaren' - weil in diesem Fall 'Philip Morris' zu generisch wäre bzw. wenig eindeutig.", + "Von uns ermittelte Website des Unternehmens, sofern verfügbar.", + "von uns ermittelter Ort des Unternehmens", + "Kurze Beschreibung des Unternehmens", + "Aktuelle Branchenzuweisung entsprechend unserem Ziel-Branchenschema", + "Von Dealfront gelieferte externe Beschreibung der Branche. Diese Branchenbeschreibung sollte in den allermeisten Fällen sehr zutreffend sein und ist vermutlich verlässlicher als die aktuelle Branche aus Spalte F", + "Von uns Recherchierte Anzahl der Servicetechniker. Diese ist in den meisten Fällen korrekt. Dieser Wert gilt als guter Lernwert um zu verstehen, wie ein Unternehmen aussieht, das viele bzw. wenige Techniker hat. Dies ist je nach Branche und Art des Unternehmens unterschiedlich. Es gibt Unternehmen, die hauptsächlich auf die Produktion fokussiert sind, bei denen Service nur einen kleinen Anteil am Geschäft ausmacht und wiederum andere Unternehmen, die fast nur von Kundenservice leben (z.B. Dienstleister). Es gibt allerdings keine 100% verlässliche Faustformel. Das System soll später seine eigene Schätzung mit den von uns recherchierten Werten vergleichen, um dadurch selbst zu lernen Unternehmen besser einzuschätzen.", + "Von uns recherchierter Umsatz in Mio. €", + "Von uns recherchierte Anzahl der Mitarbeiter", + "Enthält aus einer alten Recherche Vorschläge für die Wikipedia URL zum Unternehmen. Dieser muss aber nicht stimmen. Sollte als Ausgangs- und Vergleichspunkt für die nachgelagerte Wikipedia-Suche dienen. Der Wert soll mit den üblichen Methoden geprüft werden z.B. kommt die normalisierte Website vor, Ähnlichkeitsprüfung des Firmennamens mit dem Artikelnamen von Wikipedia etc.", + "Wikipedia URL aus der Recherche im laufenden Prozess", + "Erster Absatz des Wikipedia-Artikels", + "Branche aus Wikipedia-Artikel soweit verfügbar", + "Umsatz aus Wikipediaartikel soweit verfügbar.", + "Anzahl Mitarbeiter laut Wikipedia sofern verfügbar.", + "\"Komma-separierte Liste der Kategorien, denen der Artikel in Wikipedia zugewiesen wurde. Hier ist auch häufig eine Branche enthalten, häufig auch noch weitere Informationen etwa zur Gründung, ob sie etwa im DAX gelistet ist etc. Guter Anhaltspunkt zur Differenzierung von Unternehmenseinträgen und Wikipedia-Seiten, die kein Unternehmen beschreiben und fälschlicherweise zugewiesen wurden. \nBei jeder Unternehmensseite MUSS das Wort unternehmen in irgendeiner Art und Weise vorkommen.\nNEGATIVSIGNAL: EHEMALIGES UNTERNEHMEN -> Weist darauf hin, dass das Unternehmen nicht mehr besteht.\"", + "\"OK\" wird bei Firmen eingetragen, wo Firma und Wikipedia-Eintrag zusammenpassen. \"X\" wird bei Firmen eingetragen, wo Firma und Wikipedia-Eintrag nicht zusammenpassen.", + "Begründung welche Inkonsistenz aus den Daten hervorgeht.", + "URL des durch ChatGPT recherchierten Wikipedia-Artikels", + "XXX derzeit nicht verwendet, wird vermutlich gelöscht xxx", + "Durch ChatGPT ermittelte Branche des Unternehmens", + "\"OK\" wird bei Firmen eingetragen, wo die Einschätzung zur Branche mit der CRM Branche übereinstimmt. \"X\" wird ausgegeben, wenn die Einschätzungen nicht zusammenpassen.", + "Begründung für Abweichung der Branche von CRM Branche", + "\"OK\" wird bei Firmen eingetragen, für die FSM relevant ist, \"X\" für Firmen, für die FSM irrelevant ist.", + "Begründung für die Beurteilung in Spalte Chat Begründung für FSM Relevanz", + "Anzahl der Mitarbeiter durch ChatGPT geschätzt.", + "\"OK\" wird bei Firmen eingetragen, für die Anzahl der Mitarbeiter grob mit der aus Spalte CRM Anzahl Mitarbeiter bzw. der Spalte Wiki Mitarbeiter übereinstimmt. \"X\" für Firmen, bei denen dies nicht zutrifft.", + "Begründung für Abweichende Mitarbeiterzahl", + "Anzahl der Servicetechniker geschätzt durch Chat GPT", + "Begründung für Abweichungen zur Anzahl der Techniker", + "Umsatz durch ChatGTP geschätzt", + "Begründung für Abweichungen zum Umsatz", + "Anzahl der Kontakte die zur Suche Serviceleiter gefunden wurden", + "Anzahl der Kontakte die zur Suche IT-Leiter gefunden wurden", + "Anzahl der Kontakte die zur Suche Management gefunden wurden", + "Anzahl der Kontakte die zur Suche Disponent gefunden wurden", + "Timestamp des Zeitpunkts zu dem die Kontaktsuche fertiggestellt wurde", + "Timestamp des Zeitpunkts zu dem der Wikipedia-Artikel eingelesen wurde", + "Timestamp des Zeitpunkts zu dem die Validierung durch ChatGPT durchgeführt wurde", + "Systemspalte zur Ausgabe der Skriptversion die das Ergebnis generiert hat", + "Zeigt an, wie viele Tokens für den Request benötigt wurden" + ], + [ # Aufgabe / Funktion. Hier wird beschrieben, wie die Daten in der Spalte verarbeitet werden. Diese Spalte ist von zentraler Bedeutung für das Verständnis des Projekts. + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Datenquelle", + "Wird durch Wikipedia Scraper bereitgestellt", + "Wird zunächst nicht verwendet, kann möglicherweise in einem späteren Schritt z.B. zum Vergleich mit der CRM Beschreibung genutzt werden, um auf Textähnlichkeit / Übereinstimmende Worte geprüft zu werden und damit eine Validierung des Artikels zum Account sicherzustellen.", + "Wird u.a. zur finalen Ermittlung der Branche im Ziel-Branchenschema genutzt und mit der CRM Branche bzw. CRM Beschreibung Branche Extern verglichen. Stimmen alle drei Einstufungen grob überein, bestärkt dies die urpsrüngliche Einstufung. Laufen diese Branchen weit auseinander, soll, sofern der Wikipedia-Artikel verifiziert ist, die Branche von Wikipedia als zuverlässigste Quelle bewertet werden, danach folgen die CRM Beschreibung Branche Extern und die CRM Branche an dritter Stelle.", + "Wird u.a. mit CRM Umsatz zur Validierung des Unternehmens verglichen bzw. zur Bewertung der größe / Einschätzung Anzahl der Techniker bzw. Bewertung der Relevanz für FSM genutzt.", + "Wird u.a. mit CRM Anzahl Mitarbeiter zur Validierung des Unternehmens verglichen bzw. zur Bewertung der größe / Einschätzung Anzahl der Techniker bzw. Bewertung der Relevanz für FSM genutzt.", + "Wenn Wiki Branche nicht gepflegt ist, wird dieses Feld zur finalen Ermittlung der Branche im Ziel-Branchenschema genutzt und mit der CRM Branche bzw. CRM Beschreibung Branche Extern verglichen. Dabei muss aus dem Feld natürlich die Branche ermittelt werden, die auch hier nicht zwingend eingetragen ist. Stimmen alle drei Einstufungen grob überein, bestärkt dies die urpsrüngliche Einstufung. Laufen diese Branchen weit auseinander, soll, sofern der Wikipedia-Artikel verifiziert ist, die Branche von Wikipedia als zuverlässigste Quelle bewertet werden, danach folgen die CRM Beschreibung Branche Extern und die CRM Branche an dritter Stelle.", + "\"Es soll durch ChatGPT geprüft werden, ob anhand der vorliegenden Daten bestätigt werden kann, dass der Wikipedia-Eintrag sicher das Unternehmen beschreibt. Hierzu können sämtliche Daten miteinander verglichen werden. u.a. stimmt die Website überein, ist der Umsatz in einer ähnlichen größenordnung, passt die mitarbeiterzahl etc. Bei allen daten darf eine gewisse Unschärfe zum Vergleich (+-30%) gelten.\n Es muss teilweise etwas großzügig bewertet werden, insbesondere bei Konzernstrukturen, wo oft Töchter keinen eigenen Wikipedia-Artikel haben, aber quasi am Umsatz der Mutter hängen und damit prinzipiell die gleichen Daten zur Bewertung herangezogen werden können wie für die Mutter.\nAbweichungen sollen in der Spalte Chat Begründung Wiki Inkonsistenz begründet werden.\"", + "\"Liegt eine Inkonsistenz zwischen gefundenem Wikipedia-Artikel und dem Unternehmen vor, soll dies hier kurz begründet werden.\nWurde der Artikel als unpassend identifiziert, soll Chat GPT selbst einen passenden wikipedia-Artikel zum Unternehmen finden und diesen in Spalte Chat Vorschlag Wiki Artikel ausgeben.\"", + "\"Sollte durch die Wikipedia-Suche kein Artikel gefunden werden, oder der Artikel von Chat GPT als nicht zum Unternehmen passend identifiziert werden, soll Chat GPT eigenständig nach einem Wikipedia-Artikel recherchieren. Auch dieser soll die gleichen Stufen zur Qualitätsprüfung durchlaufen wie bei der Wikipedia-Suche üblich.\nDer von ChatGPT gefundene Artikel muss vom als falsch bewerteten Artikel abweichen. Wurde kein passender Artikel gefunden, soll 'kein Artikel verfügbar' ausgegeben werden\"", + "XXX derzeit nicht verwendet, wird vermutlich gelöscht xxx", + "\"Chat GPT soll anhand der vorliegenden Informationen prüfen, welcher Branche des Ziel-Branchenschemas das Unternehmen am ehesten zugeordnet werden kann. Das Ziel-Branchenschema darf nicht verändert oder erweitert werden, sondern die Vorschläge müssen genau dem Ziel-Branchenschema entsprechen.\nDie Bewertung soll möglichst ohne Abgleich mit der CRM Branche bewertet werden, da diese falsch sein könnte. ChatGPT soll auch die Firmenwebsite und ähnliche Quellen zur Bewertung des Unernehmens heranziehen.\"", + "Die durch uns festgelegte Branche in Spalte CRM Branche soll mit der von ChatGPT ermittelten Branche in Spalte Chat Vorschlag Branche verglichen werden.", + "Weicht die Branche von unserer Eisntufung in Spalte CRM Branche ab, soll ChatGPT die Abweichung kurz begründen.", + "Chat GPT soll anhand der vorliegenden Informationen sowie eigener Recherche prüfen, ob für das Unternehmen der Einsatz einer Fieldservice Management Lösung vorteilhaft ist. Sprich hat das Unternehmen mutmaßlich einen technischen Außendienst bzw. Disponenten die mit der Planung mobiler Resourcen beschäftigt sind.", + "Die in Spalte Chat Begründung für FSM Relevanz soll begründet werden.", + "Nur wenn kein Wikipedia-Eintrag vorliegt (Wiki URL = \"\") soll ChatGPT auf Basis öffentlich verfügbarer Informationen z.B. durch Auswertung der Firmen-Website herausfinden oder schätzen, wieviele Mitarbeiter das Unternehmen hat. Wenn keine Schätzung möglich ist, soll \"keine Schätzung möglich\" ausgegeben werden.", + "Entspricht die durch ChatGPT ermittelte Mitarbeiterzahl der von uns ermittelten (Spalte CRM Anzahl Mitarbeiter) bzw. der durch Wikipedia ermittelten Mitarbeiterzahl (Spalte Wiki Mitarbeiter). Begründung bei Abweichung über +-30% in Spalte Chat Begründung Abweichung Mitarbeiter", + "Weicht die durch Chat GPT ermittelte Mitarbeiterzahl erheblich von der Anzahl der Mitarbeiter aus dem CRM (Spalte CRM Anzahl Mitarbeiter) bzw. der von Wikipedia ermittelten Anzahl (Spalte Wiki Mitarbeiter) ab, soll dies kurz begründet werden.", + "Chat GPT soll auf basis öffentlich zugänglicher Information eine Schätzung abgeben, wieviele Servicetechniker das Unternehmen hat. Hierzu können auch Querverbindungen zwischen Anzahl der Mitarbeiter, Umsatz, Branche hergestellt werden, um eine möglichst solide Schätzung abgeben zu können. Die Schätzung soll in den Abstufungen 0, <50 , >100, >200, >500 Techniker abgegeben werden. In Entwicklung ist eine Aggegierung von branchenspezifischen Merkmalen (z.B. Umsatz, Mitarbeiterzahl) die für jede Gruppe typisch ist. In weiterer Zukunft kann hierzu möglicherweise auf ein RAG-System (Retrieval-Augmented Generation) zurückgegriffen werden. Abweichungen der Einschätzung von der durch uns ermittelten Anzahl Servicetechniker (die relativ zuverlässig ist) sollen in Spalte Chat Begründung Abweichung Anzahl Servicetechniker ausgegeben werden. Wenn die Einschätzung zu einem ähnlichen Ergebnis kommt, soll \"OK\" ausgegeben werden.", + "Weicht die Einschätzung in Spalte Chat Einschätzung Anzahl Servicetechniker von den durch uns recherchierten Ergebnissen in Spalte CRM Anzahl Techniker ab, soll dies begründet werden.", + "Nur wenn kein wikipedia-Eintrag vorliegt (Wiki URL = \"\") soll ChatGPT den Umsatz auf Basis seiner Daten oder der Unternehmenswebsite ermitteln. Wenn keine Schätzung möglich ist, soll \"keine Schätzung möglich\" ausgegeben werden.", + "ChatGPT soll erhebliche Abweichungen beim Umsatz zwischen Chat Schätzung Umsatz, Wiki Umsatz und CRM Umsatz begründen. Sind alle Umsätze einigermaßen deckungsgleich (+-30%) soll \"OK\" ausgegeben werden.", + "über SerpAPI wird gemeinsam mit der Kurzform des Unternehmensnamens (Spalte CRM Kurzform) und der folgenden Liste per ODER verknüpfung gesucht.\n- 'Serviceleiter'\n- 'Leiter Service'\n- 'technischer Leiter'\n- 'Service Manager'\n- 'Leiter Kundendienst'", + "über SerpAPI wird gemeinsam mit der Kurzform des Unternehmensnamens (Spalte CRM Kurzform) und der folgenden Liste per ODER verknüpfung gesucht.\n- 'Leiter IT'\n- 'IT Leiter'\n- 'Head of IT'\n- 'IT-Leiter'\n- 'CIO'", + "über SerpAPI wird gemeinsam mit der Kurzform des Unternehmensnamens (Spalte CRM Kurzform) und der folgenden Liste per ODER verknüpfung gesucht.\n- 'Geschäftsführer'\n- 'Geschäftsführung'\n- 'GF'\n- 'CEO'\n- 'Geschäftsführerin'\n- 'Managing Director'\n- 'Geschäftsführender Gesellschafter'", + "über SerpAPI wird gemeinsam mit der Kurzform des Unternehmensnamens (Spalte CRM Kurzform) und der folgenden Liste per ODER verknüpfung gesucht.\n- 'Serviceleiter'\n- 'Leiter Service'\n- 'technischer Leiter'\n- 'Service Manager'\n- 'Leiter Kundendienst'", + "Wenn die Kontaktsuche gestartet wird, wird der erste Eintrag ohne Zeitstempel in dieser Spalte gesucht und die Bearbeitung ab hier forgesetzt. Zeilen die bereits einen Zeitstempel haben werden bei der Bearbeitung übersprungen", + "Wenn die Wikipediasuche gestartet wird, wird der erste Eintrag ohne Zeitstempel in dieser Spalte gesucht und die Bearbeitung ab hier forgesetzt. Zeilen die bereits einen Zeitstempel haben werden bei der Bearbeitung übersprungen", + "Wenn die ChatGPT Bewertung gestartet wird, wird der erste Eintrag ohne Zeitstempel in dieser Spalte gesucht und die Bearbeitung ab hier forgesetzt. Zeilen die bereits einen Zeitstempel haben werden bei der Bearbeitung übersprungen", + "Wird durch das System befüllt", + "Wird durch Ticktokens berechnet" + ] ] - return data + header_range = "A1:AQ5" + sheet.update(values=new_headers, range_name=header_range) + + print("Alignment-Demo abgeschlossen: Neues Spaltenschema in Zeilen A1 bis AQ5 geschrieben.") -def print_prompt_overview(): - print("----- Prompt Übersicht -----") - prompts = prompt_overview() - for row in prompts: - print(f"{row[0]}: {row[1]}") - print("-----------------------------\n") -# ==================== INITIALISIERUNG DES LOGS ==================== -def initialize_log(modus): - filename = f"{timestamp}_{version}_{modi_mapping.get(modus, f'Modus{modus}')}.txt" - logfile = open(filename, "w", encoding="utf-8") - logfile.write(f"Modus: {modus} - {modi_mapping.get(modus, f'Modus{modus}')}\n") - logfile.write(f"Version: {version}\n") - logfile.write(f"Timestamp: {timestamp}\n\n") - print_prompt_overview() # Ausgabe der Prompt Übersicht - return logfile # ==================== WIKIPEDIA SCRAPER ==================== class WikipediaScraper: @@ -395,158 +1044,12 @@ class WikipediaScraper: continue return None -# ==================== GOOGLE SHEET HANDLER ==================== -class GoogleSheetHandler: - def __init__(self): - self.sheet = None - self.sheet_values = [] - self._connect() - def _connect(self): - scope = ["https://www.googleapis.com/auth/spreadsheets"] - creds = ServiceAccountCredentials.from_json_keyfile_name(Config.CREDENTIALS_FILE, scope) - self.sheet = gspread.authorize(creds).open_by_url(Config.SHEET_URL).sheet1 - self.sheet_values = self.sheet.get_all_values() - def get_start_index(self): - filled_n = [row[13] if len(row) > 13 else '' for row in self.sheet_values[1:]] - return next((i + 1 for i, v in enumerate(filled_n, start=1) if not str(v).strip()), len(filled_n) + 1) - -# ==================== ALIGNMENT DEMO ==================== -def alignment_demo(sheet): - new_headers = [ - [ # Spaltenname (Zeile 1) - "ReEval Flag", # A - "CRM Name", # B - "CRM Kurzform", # C - "CRM Website", # D - "CRM Ort", # E - "CRM Beschreibung", # F - "CRM Branche", # G - "CRM Beschreibung Branche extern", # H - "CRM Anzahl Techniker", # I - "CRM Umsatz", # J - "CRM Anzahl Mitarbeiter", # K - "CRM Vorschlag Wiki URL", # L - "Wiki URL", # M - "Wiki Absatz", # N - "Wiki Branche", # O - "Wiki Umsatz", # P - "Wiki Mitarbeiter", # Q - "Wiki Kategorien", # R - "Chat Wiki Konsistenzprüfung", # S - "Chat Begründung Wiki Inkonsistenz", # T - "Chat Vorschlag Wiki Artikel", # U - "Begründung bei Abweichung", # V - "Chat Vorschlag Branche", # W - "Chat Konsistenz Branche", # X - "Chat Begründung Abweichung Branche", # Y - "Chat Prüfung FSM Relevanz", # Z - "Chat Begründung für FSM Relevanz", # AA - "Chat Schätzung Anzahl Mitarbeiter", # AB - "Chat Konsistenzprüfung Mitarbeiterzahl", # AC - "Chat Begründung Abweichung Mitarbeiterzahl", # AD - "Chat Einschätzung Anzahl Servicetechniker", # AE - "Chat Begründung Abweichung Servicetechniker", # AF - "Chat Schätzung Umsatz", # AG - "Chat Begründung Abweichung Umsatz", # AH - "Linked Serviceleiter gefunden", # AI - "Linked It-Leiter gefunden", # AJ - "Linked Management gefunden", # AK - "Linked Disponent gefunden", # AL - "Contact Search Timestamp", # AM - "Wikipedia Timestamp", # AN - "Timestamp letzte Prüfung", # AO - "Version", # AP - "Tokens" # AQ - ], - [ # Quelle der Daten - "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", - "CRM", "CRM", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", - "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Chat GPT API", - "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", - "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", - "Chat GPT API", "Chat GPT API", "Chat GPT API", "Chat GPT API", "LinkedIn (via SerpApi)", - "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "System", - "System", "System", "System", "System" - ], - [ # Feldkategorie - "Prozess", "Firmenname", "Firmenname", "Website", "Ort", "Beschreibung (Text)", - "Branche", "Branche", "Anzahl Servicetechniker", "Umsatz", "Anzahl Mitarbeiter", - "Wikipedia Artikel URL", "Wikipedia Artikel", "Beschreibung (Text)", "Branche", - "Umsatz", "Anzahl Mitarbeiter", "Kategorien (Text)", "Verifizierung", - "Begründung bei Abweichung", "Wikipedia Artikel", "Wikipedia Artikel", "Branche", - "Branche", "Branche", "FSM Relevanz", "FSM Relevanz", "Anzahl Mitarbeiter", - "Anzahl Mitarbeiter", "Anzahl Mitarbeiter", "Anzahl Servicetechniker", - "Anzahl Servicetechniker", "Umsatz", "Umsatz", "Kontakte zur Firma", - "Kontakte zur Firma", "Kontakte zur Firma", "Kontakte zur Firma", "Timestamp", - "Timestamp", "Timestamp", "Version des Skripts die verwendet wurde", "ChatGPT Tokens" - ], - [ # Kurzbeschreibung - "Systemspalte, irrelevant für den Prompt. Wird zur manuellen Neuprüfung genutzt.", - "Enthält den Firmennamen (CRM).", "Enthält die manuell gepflegte Kurzform.", - "Ermittelte Website des Unternehmens.", "Ermittelter Ort des Unternehmens.", - "Kurze Unternehmensbeschreibung.", "Aktuelle Branchenzuweisung (CRM).", - "Externe Branchenbeschreibung (z.B. von Dealfront).", "Recherchierte Anzahl Servicetechniker.", - "Recherchierter Umsatz in Mio. €.", "Recherchierte Anzahl Mitarbeiter.", - "Vorschlag für Wikipedia URL.", "Wikipedia URL aus laufender Suche.", - "Erster Absatz des Wikipedia-Artikels.", "Branche aus Wikipedia.", - "Umsatz laut Wikipedia.", "Mitarbeiterzahl laut Wikipedia.", - "Wikipedia Kategorien.", "\"OK\" oder \"X\" bei Wiki-Validierung.", - "Begründung bei Wiki Inkonsistenz.", "Neu recherchierter Wikipedia Artikel.", - "Begründung bei Abweichung.", "ChatGPT basierte Branchenzuordnung.", - "Vergleich CRM vs. ChatGPT Branche.", "Begründung bei Branchenabweichung.", - "Prüfung FSM-Relevanz (ChatGPT).", "Begründung zur FSM-Eignung.", - "Schätzung Mitarbeiterzahl (ChatGPT).", "Konsistenzprüfung Mitarbeiterzahl (ChatGPT).", - "Begründung bei Mitarbeiterabweichung.", "Schätzung Servicetechniker (ChatGPT).", - "Begründung bei Technikerabweichung.", "Schätzung Umsatz (ChatGPT).", - "Begründung bei Umsatzabweichung.", "LinkedIn Serviceleiter Kontakte.", - "LinkedIn IT-Leiter Kontakte.", "LinkedIn Management Kontakte.", - "LinkedIn Disponent Kontakte.", "Timestamp Kontaktsuche.", - "Timestamp Wikipedia-Abruf.", "Timestamp ChatGPT-Prüfung.", - "Skriptversion.", "Gesamte GPT-Tokens." - ], - [ # Aufgabe / Funktion - "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", - "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", "Datenquelle", - "Wird durch Wikipedia Scraper bereitgestellt.", "Wird als Vergleich genutzt (Validierung).", - "Für finale Branchenermittlung.", "Zur Umsatzvalidierung.", "Zur Validierung der Mitarbeiterzahl.", - "Zur Kategorisierung des Wikipedia-Artikels.", "Prüfung, ob Wikipedia-Artikel passt.", - "Begründung bei Wiki Inkonsistenz.", "Neues Recherchieren, falls unpassend.", "Begründung bei Abweichung.", - "ChatGPT Branchenzuordnung.", "Vergleich CRM vs. ChatGPT Branche.", "Begründung bei Branchenabweichung.", - "Prüfung FSM-Relevanz (ChatGPT).", "Begründung zur FSM-Eignung.", "Schätzung Mitarbeiterzahl (ChatGPT).", - "Konsistenzprüfung Mitarbeiterzahl.", "Begründung bei Mitarbeiterabweichung.", - "Schätzung Servicetechniker (ChatGPT).", "Begründung bei Technikerabweichung.", - "Schätzung Umsatz (ChatGPT).", "Begründung bei Umsatzabweichung.", - "LinkedIn Suche Serviceleiter.", "LinkedIn Suche IT-Leiter.", "LinkedIn Suche Management.", - "LinkedIn Suche Disponent.", "Timestamp Kontaktsuche.", "Timestamp Wikipedia-Abruf.", - "Timestamp ChatGPT-Prüfung.", "Skriptversion.", "Gesamte GPT-Tokens." - ] - ] - header_range = "A1:AQ5" - sheet.update(values=new_headers, range_name=header_range) - print("Alignment-Demo abgeschlossen: Neues Spaltenschema in Zeilen A1 bis AQ5 geschrieben.") - -def alignment_demo_full(): - alignment_demo(GoogleSheetHandler().sheet) - gc = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name( - Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])) - sh = gc.open_by_url(Config.SHEET_URL) - try: - contacts_sheet = sh.worksheet("Contacts") - except gspread.exceptions.WorksheetNotFound: - contacts_sheet = sh.add_worksheet(title="Contacts", rows="1000", cols="10") - header = ["Firmenname", "Website", "Kurzform", "Vorname", "Nachname", "Position", "Anrede", "E-Mail"] - contacts_sheet.update(values=[header], range_name="A1:H1") - debug_print("Neues Blatt 'Contacts' erstellt und Header eingetragen.") - alignment_demo(contacts_sheet) - debug_print("Alignment-Demo für Hauptblatt und Contacts abgeschlossen.") - # ==================== DATA PROCESSOR ==================== class DataProcessor: def __init__(self): self.sheet_handler = GoogleSheetHandler() self.wiki_scraper = WikipediaScraper() def process_rows(self, num_rows=None): - MODE = "default" # Ersetze mit tatsächlicher Moduswahl if MODE == "2": print("Re-Evaluierungsmodus: Verarbeitung aller Zeilen mit 'x' in Spalte A.") for i, row in enumerate(self.sheet_handler.sheet_values[1:], start=2): @@ -580,125 +1083,113 @@ class DataProcessor: break self._process_single_row(i, row) rows_processed += 1 + def _process_single_row(self, row_num, row_data, process_wiki=True, process_chatgpt=True): - total_tokens = 0 - company_name = row_data[1] if len(row_data) > 1 else "" - website = row_data[3] if len(row_data) > 3 else "" - # Default-Initialisierung für company_data, falls Wiki-Auswertung übersprungen wird - company_data = { - 'url': 'k.A.', - 'first_paragraph': 'k.A.', - 'branche': 'k.A.', - 'umsatz': 'k.A.', - 'mitarbeiter': 'k.A.', - 'categories': 'k.A.', - 'full_infobox': 'k.A.' - } - # Wiki-Daten werden in Spalten L bis R abgelegt - wiki_update_range = f"L{row_num}:R{row_num}" - dt_wiki_range = f"AN{row_num}" - dt_chat_range = f"AO{row_num}" - ver_range = f"AP{row_num}" - print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Verarbeite Zeile {row_num}: {company_name}") - current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # Wiki-Verarbeitung - if process_wiki: - if len(row_data) <= 39 or row_data[39].strip() == "": - if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."]: - wiki_url = row_data[10].strip() - try: - company_data = self.wiki_scraper.extract_company_data(wiki_url) - except Exception as e: - debug_print(f"Fehler beim Laden des vorgeschlagenen Wikipedia-Artikels: {e}") + company_name = row_data[1] if len(row_data) > 1 else "" + website = row_data[2] if len(row_data) > 2 else "" + wiki_update_range = f"K{row_num}:Q{row_num}" + dt_wiki_range = f"AN{row_num}" + dt_chat_range = f"AO{row_num}" + ver_range = f"AP{row_num}" + print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Verarbeite Zeile {row_num}: {company_name}") + current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if process_wiki: + if len(row_data) <= 39 or row_data[39].strip() == "": + if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."]: + wiki_url = row_data[10].strip() + try: + company_data = self.wiki_scraper.extract_company_data(wiki_url) + except Exception as e: + debug_print(f"Fehler beim Laden des vorgeschlagenen Wikipedia-Artikels: {e}") + article = self.wiki_scraper.search_company_article(company_name, website) + company_data = self.wiki_scraper.extract_company_data(article.url) if article else { + 'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', + 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', + 'full_infobox': 'k.A.' + } + else: article = self.wiki_scraper.search_company_article(company_name, website) company_data = self.wiki_scraper.extract_company_data(article.url) if article else { 'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', 'full_infobox': 'k.A.' } + wiki_values = [ + row_data[10] if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."] else "k.A.", + company_data.get('url', 'k.A.'), + company_data.get('first_paragraph', 'k.A.'), + company_data.get('branche', 'k.A.'), + company_data.get('umsatz', 'k.A.'), + company_data.get('mitarbeiter', 'k.A.'), + company_data.get('categories', 'k.A.') + ] + self.sheet_handler.sheet.update(values=[wiki_values], range_name=wiki_update_range) + self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_wiki_range) else: - wiki_url = "k.A." - article = self.wiki_scraper.search_company_article(company_name, website) - company_data = self.wiki_scraper.extract_company_data(article.url) if article else { - 'url': 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', - 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', - 'full_infobox': 'k.A.' - } - wiki_values = [ - row_data[10] if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."] else "k.A.", - company_data.get('url', 'k.A.'), - company_data.get('first_paragraph', 'k.A.'), - company_data.get('branche', 'k.A.'), - company_data.get('umsatz', 'k.A.'), - company_data.get('mitarbeiter', 'k.A.'), - company_data.get('categories', 'k.A.') - ] - self.sheet_handler.sheet.update(values=[wiki_values], range_name=wiki_update_range) - self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_wiki_range) - else: - debug_print(f"Zeile {row_num}: Wikipedia-Timestamp bereits gesetzt – überspringe Wiki-Auswertung.") - # ChatGPT-Verarbeitung - if process_chatgpt: - crm_umsatz = row_data[9] if len(row_data) > 9 else "k.A." - consistency_result = compare_umsatz_values(crm_umsatz, company_data.get('umsatz', 'k.A.')) - self.sheet_handler.sheet.update(values=[[consistency_result]], range_name=f"S{row_num}") - crm_data = ";".join(row_data[1:10]) - wiki_data_str = ";".join(row_data[11:18]) - prompt = ("Bitte überprüfe, ob die folgenden beiden Datensätze grundsätzlich zum gleichen Unternehmen gehören. " - f"CRM-Daten: {crm_data} | Wikipedia-Daten: {wiki_data_str}") - valid_result, tokens = safe_chatgpt_call(prompt, crm_data + " " + wiki_data_str, row_data[10] if len(row_data) > 10 else "k.A.") - total_tokens += tokens - if valid_result.strip().upper() == "OK": - wiki_consistency = "OK" - wiki_article_suggestion = "" - else: - wiki_consistency = "X" - wiki_article_suggestion = valid_result - self.sheet_handler.sheet.update(values=[[wiki_consistency]], range_name=f"T{row_num}") - self.sheet_handler.sheet.update(values=[[wiki_article_suggestion]], range_name=f"U{row_num}") - prompt_fsm = f"Bitte bewerte, ob das Unternehmen '{company_name}' für den Einsatz einer Field Service Management Lösung geeignet ist. Antworte mit 'Ja' oder 'Nein' und begründe kurz." - fsm_result, tokens = safe_chatgpt_call(prompt_fsm, company_name, row_data[10] if len(row_data) > 10 else "k.A.") - total_tokens += tokens - parts = fsm_result.split("-", 1) - fsm_suitability = parts[0].strip() if parts else fsm_result - fsm_justification = parts[1].strip() if len(parts) > 1 else "" - self.sheet_handler.sheet.update(values=[[fsm_suitability]], range_name=f"Z{row_num}") - self.sheet_handler.sheet.update(values=[[fsm_justification]], range_name=f"AA{row_num}") - prompt_st = f"Bitte schätze die Anzahl der Servicetechniker für das Unternehmen '{company_name}' ein. Antwortoptionen: '<50 Techniker', '>100 Techniker', '>200 Techniker', '>500 Techniker'." - st_estimate, tokens = safe_chatgpt_call(prompt_st, company_name, row_data[10] if len(row_data) > 10 else "k.A.") - total_tokens += tokens - self.sheet_handler.sheet.update(values=[[st_estimate]], range_name=f"AE{row_num}") - internal_value = row_data[7] if len(row_data) > 7 else "k.A." - internal_category = map_internal_technicians(internal_value) if internal_value != "k.A." else "k.A." - if internal_category != "k.A." and st_estimate != internal_category: - prompt_st_expl = f"Bitte erkläre, warum du für das Unternehmen '{company_name}' die Anzahl der Servicetechniker als '{st_estimate}' geschätzt hast." - st_explanation, tokens = safe_chatgpt_call(prompt_st_expl, company_name, row_data[10] if len(row_data) > 10 else "k.A.") - total_tokens += tokens - technician_explanation = st_explanation - else: - technician_explanation = "ok" - self.sheet_handler.sheet.update(values=[[technician_explanation]], range_name=f"AF{row_num}") - crm_mitarbeiter = row_data[10] if len(row_data) > 10 else "k.A." - wiki_mitarbeiter = company_data.get('mitarbeiter', "k.A.") - try: - crm_emp = float(crm_mitarbeiter) - wiki_emp = float(wiki_mitarbeiter) - diff = abs(crm_emp - wiki_emp) / ((crm_emp + wiki_emp) / 2) * 100 - reason = "Beide Werte ähnlich" if diff < 30 else "Signifikante Abweichung" - mitarbeiter_result = f"CRM: {crm_mitarbeiter}, Wikipedia: {wiki_mitarbeiter}, Differenz: {diff:.2f}%, Einschätzung: {reason}" - except Exception as e: - mitarbeiter_result = "k.A." - self.sheet_handler.sheet.update(values=[[mitarbeiter_result]], range_name=f"AB{row_num}") - prompt_umsatz = f"Bitte schätze den Jahresumsatz (in Mio. €) für das Unternehmen '{company_name}' ein basierend auf den Daten: CRM: {crm_umsatz}, Wikipedia: {company_data.get('umsatz', 'k.A.')}. Antworte nur mit der Zahl." - umsatz_estimate, tokens = safe_chatgpt_call(prompt_umsatz, company_name, row_data[10] if len(row_data) > 10 else "k.A.") - total_tokens += tokens - self.sheet_handler.sheet.update(values=[[umsatz_estimate]], range_name=f"AG{row_num}") - self.sheet_handler.sheet.update(values=[[str(total_tokens)]], range_name=f"AQ{row_num}") - self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_chat_range) - self.sheet_handler.sheet.update(values=[[current_dt]], range_name=ver_range) - self.sheet_handler.sheet.update(values=[[Config.VERSION]], range_name=ver_range) - debug_print(f"Zeile {row_num} verifiziert: URL: {company_data.get('url', 'k.A.')}, Branche: {company_data.get('branche', 'k.A.')}") - time.sleep(Config.RETRY_DELAY) + debug_print(f"Zeile {row_num}: Wikipedia-Timestamp bereits gesetzt – überspringe Wiki-Auswertung.") + if process_chatgpt: + if len(row_data) <= 40 or row_data[40].strip() == "": + crm_umsatz = row_data[8] if len(row_data) > 8 else "k.A." + abgleich_result = compare_umsatz_values(crm_umsatz, company_data.get('umsatz', 'k.A.')) + self.sheet_handler.sheet.update(values=[[abgleich_result]], range_name=f"AG{row_num}") + crm_data = ";".join(row_data[1:10]) + wiki_data_str = ";".join(row_data[11:18]) + valid_result = validate_article_with_chatgpt(crm_data, wiki_data_str) + self.sheet_handler.sheet.update(values=[[valid_result]], range_name=f"R{row_num}") + fsm_result = evaluate_fsm_suitability(company_name, company_data) + self.sheet_handler.sheet.update(values=[[fsm_result["suitability"]]], range_name=f"Y{row_num}") + self.sheet_handler.sheet.update(values=[[fsm_result["justification"]]], range_name=f"Z{row_num}") + st_estimate = evaluate_servicetechnicians_estimate(company_name, company_data) + self.sheet_handler.sheet.update(values=[[st_estimate]], range_name=f"AD{row_num}") + internal_value = row_data[7] if len(row_data) > 7 else "k.A." + internal_category = map_internal_technicians(internal_value) if internal_value != "k.A." else "k.A." + if internal_category != "k.A." and st_estimate != internal_category: + explanation = evaluate_servicetechnicians_explanation(company_name, st_estimate, company_data) + discrepancy = explanation + else: + discrepancy = "ok" + self.sheet_handler.sheet.update(values=[[discrepancy]], range_name=f"AF{row_num}") + self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_chat_range) + else: + debug_print(f"Zeile {row_num}: ChatGPT-Timestamp bereits gesetzt – überspringe ChatGPT-Auswertung.") + self.sheet_handler.sheet.update(values=[[current_dt]], range_name=ver_range) + self.sheet_handler.sheet.update(values=[[Config.VERSION]], range_name=ver_range) + debug_print(f"✅ Aktualisiert: URL: {company_data.get('url', 'k.A.')}, " + f"Branche: {company_data.get('branche', 'k.A.')}, Umsatz-Abgleich: {abgleich_result}, " + f"Validierung: {valid_result}, " + f"FSM: {fsm_result['suitability']}, Servicetechniker-Schätzung: {st_estimate}") + time.sleep(Config.RETRY_DELAY) + +# ==================== GOOGLE SHEET HANDLER (für Hauptdaten) ==================== +class GoogleSheetHandler: + def __init__(self): + self.sheet = None + self.sheet_values = [] + self._connect() + def _connect(self): + scope = ["https://www.googleapis.com/auth/spreadsheets"] + creds = ServiceAccountCredentials.from_json_keyfile_name(Config.CREDENTIALS_FILE, scope) + self.sheet = gspread.authorize(creds).open_by_url(Config.SHEET_URL).sheet1 + self.sheet_values = self.sheet.get_all_values() + def get_start_index(self): + filled_n = [row[13] if len(row) > 13 else '' for row in self.sheet_values[1:]] + return next((i + 1 for i, v in enumerate(filled_n, start=1) if not str(v).strip()), len(filled_n) + 1) + +# ==================== ALIGNMENT DEMO (Hauptblatt und Contacts) ==================== +def alignment_demo_full(): + alignment_demo(GoogleSheetHandler().sheet) + gc = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name( + Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])) + sh = gc.open_by_url(Config.SHEET_URL) + try: + contacts_sheet = sh.worksheet("Contacts") + except gspread.exceptions.WorksheetNotFound: + contacts_sheet = sh.add_worksheet(title="Contacts", rows="1000", cols="10") + header = ["Firmenname", "Website", "Kurzform", "Vorname", "Nachname", "Position", "Anrede", "E-Mail"] + contacts_sheet.update(values=[header], range_name="A1:H1") + debug_print("Neues Blatt 'Contacts' erstellt und Header eingetragen.") + alignment_demo(contacts_sheet) + debug_print("Alignment-Demo für Hauptblatt und Contacts abgeschlossen.") # ==================== NEUER MODUS: CONTACT RESEARCH (via SerpAPI) ==================== def process_contact_research(): @@ -720,7 +1211,7 @@ def process_contact_research(): count_disponent = count_linkedin_contacts(search_name, website, "Disponent") current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S") main_sheet.update(values=[[str(count_service)]], range_name=f"AI{i}") - main_sheet.update(values=[[str(count_it)]], range_name=f"AJ{i)") + main_sheet.update(values=[[str(count_it)]], range_name=f"AJ{i}") main_sheet.update(values=[[str(count_management)]], range_name=f"AK{i}") main_sheet.update(values=[[str(count_disponent)]], range_name=f"AL{i}") main_sheet.update(values=[[current_dt]], range_name=f"AM{i}") @@ -744,5 +1235,118 @@ def process_contacts(): main_sheet = sh.sheet1 data = main_sheet.get_all_values() positions = ["Serviceleiter", "IT-Leiter", "Leiter After Sales", "Leiter Einsatzplanung"] - # Fortsetzung der Kontaktsuche... - debug_print("LinkedIn-Kontaktsuche abgeschlossen.") + new_rows = [] + for idx, row in enumerate(data[1:], start=2): + company_name = row[1] if len(row) > 1 else "" + search_name = row[2].strip() if len(row) > 2 and row[2].strip() not in ["", "k.A."] else company_name + website = row[3] if len(row) > 3 else "" + if not company_name or not website: + continue + for pos in positions: + debug_print(f"Suche nach Position: '{pos}' bei '{search_name}'") + contact = search_linkedin_contact(search_name, website, pos) + if contact: + new_rows.append([contact["Firmenname"], website, search_name, contact["Vorname"], contact["Nachname"], contact["Position"], "", ""]) + else: + debug_print(f"Kein Kontakt für Position '{pos}' bei '{search_name}' gefunden.") + if new_rows: + last_row = len(contacts_sheet.get_all_values()) + 1 + range_str = f"A{last_row}:H{last_row + len(new_rows) - 1}" + contacts_sheet.update(values=new_rows, range_name=range_str) + debug_print(f"{len(new_rows)} Kontakte in 'Contacts' hinzugefügt.") + else: + debug_print("Keine Kontakte gefunden.") + +# ==================== NEUER MODUS: BATCH-PROZESSING MIT TOKEN-ZÄHLUNG (Modus 8) ==================== +def process_batch_token_count(batch_size=10): + import tiktoken + def count_tokens(text, model="gpt-3.5-turbo"): + encoding = tiktoken.encoding_for_model(model) + tokens = encoding.encode(text) + return len(tokens) + debug_print("Starte Batch-Token-Zählung (Modus 8)...") + gc = gspread.authorize(ServiceAccountCredentials.from_json_keyfile_name( + Config.CREDENTIALS_FILE, ["https://www.googleapis.com/auth/spreadsheets"])) + sh = gc.open_by_url(Config.SHEET_URL) + main_sheet = sh.sheet1 + data = main_sheet.get_all_values() + for i in range(2, len(data)+1, batch_size): + batch_rows = data[i-1:i-1+batch_size] + aggregated_prompt = "" + for row in batch_rows: + info = [] + if len(row) > 1: + info.append(row[1]) # Firmenname + if len(row) > 2: + info.append(row[2]) # Kurzform + if len(row) > 3: + info.append(row[3]) # Website + if len(row) > 4: + info.append(row[4]) # Ort + if len(row) > 5: + info.append(row[5]) # Beschreibung + if len(row) > 6: + info.append(row[6]) # Aktuelle Branche + aggregated_prompt += "; ".join(info) + "\n" + token_count = count_tokens(aggregated_prompt) + debug_print(f"Batch beginnend in Zeile {i}: {token_count} Tokens") + for j in range(i, min(i+batch_size, len(data)+1)): + main_sheet.update(values=[[str(token_count)]], range_name=f"AQ{j}") + time.sleep(Config.RETRY_DELAY) + debug_print("Batch-Token-Zählung abgeschlossen.") + +# ==================== MAIN PROGRAMM ==================== +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--mode", type=str, help="Modus: 1,2,3,4,5,6,7,51 oder 8") + parser.add_argument("--num_rows", type=int, default=0, help="Anzahl der zu bearbeitenden Zeilen (nur für Modus 1)") + args = parser.parse_args() + + if not args.mode: + print("Modi:") + print("1 = Regulärer Modus") + print("2 = Re-Evaluierungsmodus (nur Zeilen mit 'x' in Spalte A)") + print("3 = Alignment-Demo (Header in Hauptblatt und Contacts)") + print("4 = Nur Wikipedia-Suche (Zeilen ohne Wikipedia-Timestamp)") + print("5 = Nur ChatGPT-Bewertung (Zeilen ohne ChatGPT-Timestamp)") + print("6 = Contact Research (via SerpAPI)") + print("7 = Contacts (LinkedIn)") + print("8 = Batch-Token-Zählung") + print("51 = Nur Verifizierung (Wikipedia + Brancheneinordnung)") + args.mode = input("Wählen Sie den Modus: ").strip() + + MODE = args.mode + if MODE == "1": + try: + num_rows = args.num_rows if args.num_rows > 0 else int(input("Wieviele Zeilen sollen überprüft werden? ")) + except Exception as e: + print("Ungültige Eingabe. Bitte eine Zahl eingeben.") + exit(1) + processor = DataProcessor() + processor.process_rows(num_rows) + elif MODE in ["2", "3"]: + processor = DataProcessor() + processor.process_rows() + elif MODE == "4": + processor = DataProcessor() + for i, row in enumerate(processor.sheet_handler.sheet_values[1:], start=2): + if len(row) <= 39 or row[39].strip() == "": + processor._process_single_row(i, row, process_wiki=True, process_chatgpt=False) + elif MODE == "5": + processor = DataProcessor() + for i, row in enumerate(processor.sheet_handler.sheet_values[1:], start=2): + if len(row) <= 40 or row[40].strip() == "": + processor._process_single_row(i, row, process_wiki=False, process_chatgpt=True) + elif MODE == "51": + processor = DataProcessor() + for i, row in enumerate(processor.sheet_handler.sheet_values[1:], start=2): + if len(row) <= 25 or row[24].strip() == "": + processor._process_verification_row(i, row) + elif MODE == "6": + process_contact_research() + elif MODE == "7": + process_contacts() + elif MODE == "8": + process_batch_token_count() + print(f"\n✅ Auswertung abgeschlossen ({Config.VERSION})")