diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 7d2516e6..7209c295 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -1,18 +1,15 @@ #!/usr/bin/env python3 """ -Version: v1.4.0 +Version: v1.4.1 Datum: {aktuelles Datum} Git-Überschrift (max. 100 Zeichen): -v1.4.0 Erweiterung Betriebsmodi, robuste ChatGPT-Verarbeitung, Logging-Verbesserungen +v1.4.1 Anpassung Spaltenzuordnung, erneute Suche bei k.A. und Zeilenabfrage im Modus 51 Git-Änderungsbeschreibung: -- Betriebsmodus wird im Dateinamen integriert (z.B. 02-04-2025_16-51_v14_Modus4.txt) -- Logfile startet mit der Anzeige der gewählten Modus-Auswahl und einer Übersicht der verwendeten Prompts -- Umbenennung der Funktion "validate_article_with_chatgpt" in "process_wiki_verification" (inkl. Prüfung auf "k.A.") -- Neue Funktionen process_employee_estimation und process_employee_consistency zur robusten Mitarbeiterschätzung und -vergleich -- Separates Token-Counting pro Modul (Wiki, Chat, Mitarbeiter) und Ausgabe in Spalte AQ -- Timestamp-Prüfung: Vor jedem Verarbeitungsschritt wird geprüft, ob bereits ein Timestamp gesetzt wurde (sodass doppelte Verarbeitung verhindert wird) -- Weitere Anpassungen in den Betriebsmodi (Modus 1, 2, 3, 4, 5, 51, 6 und Batch-Modus 8) gemäß Abstimmung +- Wikipedia-Daten werden nun von Spalte L bis R geschrieben (statt von K bis Q). +- Falls Wikipedia-Daten "k.A." liefern, wird eine erneute Suche durchgeführt und ChatGPT soll den Artikel recherchieren. +- Im Modus 51 wird nun abgefragt, wieviele Zeilen verarbeitet werden sollen. +- Konsistenzprüfung mit der Alignment Demo sichergestellt. """ import os @@ -29,19 +26,6 @@ from difflib import SequenceMatcher import unicodedata import csv - -def retry_on_failure(func): - def wrapper(*args, **kwargs): - for attempt in range(Config.MAX_RETRIES): - try: - return func(*args, **kwargs) - except Exception as e: - print(f"⚠️ Fehler bei {func.__name__} (Versuch {attempt+1}): {str(e)[:100]}") - time.sleep(Config.RETRY_DELAY) - return None - return wrapper - - # Optional: tiktoken für Token-Zählung (Modus 8) try: import tiktoken @@ -50,7 +34,7 @@ except ImportError: # ==================== KONFIGURATION ==================== class Config: - VERSION = "v1.4.0" # Neue Version – alle besprochenen Erweiterungen + VERSION = "v1.4.1" # Neue Version mit weiteren Anpassungen LANG = "de" CREDENTIALS_FILE = "service_account.json" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" @@ -64,17 +48,27 @@ class Config: BATCH_SIZE = 10 TOKEN_MODEL = "gpt-3.5-turbo" +# ==================== RETRY-DECORATOR ==================== +def retry_on_failure(func): + def wrapper(*args, **kwargs): + for attempt in range(Config.MAX_RETRIES): + try: + return func(*args, **kwargs) + except Exception as e: + print(f"⚠️ Fehler bei {func.__name__} (Versuch {attempt+1}): {str(e)[:100]}") + time.sleep(Config.RETRY_DELAY) + return None + return wrapper + # ==================== LOGGING & HELPER FUNCTIONS ==================== def create_log_filename(mode): now = datetime.now().strftime("%d-%m-%Y_%H-%M") - # VERSION ohne Punkte z.B. v14 statt v1.4.0 ver_short = Config.VERSION.replace(".", "") return os.path.join("Log", f"{now}_{ver_short}_Modus{mode}.txt") if not os.path.exists("Log"): os.makedirs("Log") -# LOG_FILE wird später im main() anhand des gewählten Betriebsmodus gesetzt. -LOG_FILE = None +LOG_FILE = None # Wird in main() gesetzt def debug_print(message): global LOG_FILE @@ -179,16 +173,14 @@ def token_count(text): debug_print(f"Fehler beim Token-Counting mit tiktoken: {e}") return len(text.split()) else: - # Fallback einfache Zählung anhand von Leerzeichen return len(text.split()) # ==================== PROMPT-ÜBERSICHT ==================== def prompt_overview(): - # Übersicht der verwendeten Prompts für die ChatGPT-Funktionen prompts = [ ["Funktion", "Verwendeter Prompt"], - ["process_wiki_verification", "Bitte verifiziere den Wikipedia-Artikel für {company_name}. Wenn 'k.A.' vorliegt, gib 'Skipped (k.A.)' zurück."], - ["process_employee_estimation", "Schätze die Mitarbeiterzahl für {company_name} basierend auf den Wikipedia-Daten. Bei 'k.A.' liefere 'Skipped (k.A.)'."], + ["process_wiki_verification", "Bitte verifiziere den Wikipedia-Artikel für {company_name}. Wenn 'k.A.' vorliegt, suche selbstständig nach einem passenden Artikel (return 'Skipped (k.A.)' nur, wenn keine Daten gefunden werden)."], + ["process_employee_estimation", "Schätze die Mitarbeiterzahl für {company_name} basierend auf Wikipedia-Daten. Bei 'k.A.' liefere 'Skipped (k.A.)'."], ["process_employee_consistency", "Vergleiche CRM-, Wiki- und ChatGPT-Mitarbeiterzahlen. Gib die prozentuale Differenz und eine Begründung zurück."], ["evaluate_umsatz_chatgpt", "Schätze den Umsatz in Mio. Euro für {company_name} basierend auf Wikipedia-Daten, antworte nur mit der Zahl."], ["evaluate_fsm_suitability", "Bewerte, ob {company_name} für Field Service Management geeignet ist; antworte ausschließlich mit 'Ja' oder 'Nein' und einer kurzen Begründung."], @@ -205,10 +197,8 @@ def mark_processed(field): # ==================== NEUE FUNKTIONEN FÜR MITARBEITER ==================== def process_employee_estimation(company_name, wiki_first_paragraph, crm_employee): - # Wenn im Wikipedia-Artikel "k.A." steht, interpretiere das als fehlender Artikel if wiki_first_paragraph.strip().lower() == "k.a.": return "Skipped (k.A.)" - # Dummy-Schätzung: Wir übernehmen CRM-Wert, wenn vorhanden; sonst ein Standardwert try: crm_val = int(crm_employee) return str(crm_val) @@ -230,9 +220,9 @@ def process_employee_consistency(crm_employee, wiki_employee, chat_employee): if diff_percent < 30: return f"OK (Differenz {diff_percent:.1f}%)" else: - return f"Abweichung: {diff_percent:.1f}% – CRM-Wert ({crm_val}) plausibler" # Beispielweise + return f"Abweichung: {diff_percent:.1f}% – CRM-Wert ({crm_val}) plausibler" -# ==================== ÜBERSCHRIFT FÜR ALIGNMENT DEMO ==================== +# ==================== ALIGNMENT DEMO (Hauptblatt) ==================== def alignment_demo(sheet): new_headers = [ [ # Zeile 1: Spaltenname @@ -256,7 +246,7 @@ def alignment_demo(sheet): "Wiki Kategorien", # R "Chat Wiki Konsistenzprüfung", # S "Chat Begründung Wiki Inkonsistenz", # T - "process_wiki_verification", # U (ehemals "Chat Vorschlag Wiki Artikel") + "process_wiki_verification", # U "Begründung bei Abweichung", # V "Chat Vorschlag Branche", # W "Chat Konsistenz Branche", # X @@ -281,8 +271,8 @@ def alignment_demo(sheet): "Tokens" # AQ ], [ # Zeile 2: Quelle der Daten - "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", "CRM", - "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", "Wikipediascraper", + "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", @@ -308,32 +298,32 @@ def alignment_demo(sheet): "Ort des Unternehmens.", "Kurze Beschreibung des Unternehmens.", "Aktuelle Branchenzuweisung gemäß Ziel-Branchenschema.", - "Externe Branchenbeschreibung (z.B. von Dealfront).", + "Externe Branchenbeschreibung (z. B. von Dealfront).", "Recherchierte Anzahl Servicetechniker.", "Umsatz in Mio. € (CRM).", "Anzahl Mitarbeiter (CRM).", - "Vorschlag für Wikipedia URL (Ausgangspunkt für Scraping).", + "Vorgeschlagene Wikipedia URL (Ausgangspunkt).", "Wikipedia URL (Ergebnis der Suche).", "Erster Absatz des Wikipedia-Artikels.", - "Branche (Wikipedia, soweit verfügbar).", - "Umsatz (Wikipedia, sofern extrahiert).", - "Mitarbeiterzahl (Wikipedia, sofern extrahiert).", + "Wikipedia-Branche – für Branchenabgleich.", + "Wikipedia-Umsatz – zur Validierung.", + "Wikipedia-Mitarbeiterzahl – zur Validierung.", "Liste der Wikipedia-Kategorien.", "\"OK\" oder \"X\" – Validierung Wikipedia.", "Begründung bei Inkonsistenz (Wiki).", - "process_wiki_verification: Falls kein passender Artikel gefunden wird, sollte ein alternativer Artikel recherchiert werden. Wird als 'Skipped (k.A.)' zurückgegeben, wenn kein Wikipedia-Eintrag vorhanden ist.", - "XXX derzeit nicht verwendet (Bleibt erhalten).", + "process_wiki_verification: Alternativen Artikel vorschlagen; bei 'k.A.' muss selbst recherchiert werden.", + "Nicht genutzt – evtl. für zukünftige Funktionen.", "Branchenzuordnung über ChatGPT – immer ausgeben.", - "Vergleich: Übereinstimmung CRM Branche vs. ChatGPT-Ergebnis.", + "Vergleich CRM-Branche vs. ChatGPT-Ergebnis (OK/X).", "Begründung bei Abweichung in der Branchenzuordnung.", "FSM-Relevanz (OK/X).", - "Begründung zur FSM-Relevanz.", - "Schätzung Mitarbeiterzahl via ChatGPT.", - "Prüfung: Mitarbeiterzahl (OK/X).", - "Begründung bei Mitarbeiterzahlabweichung.", - "Schätzung Servicetechniker via ChatGPT (in Kategorien).", - "Begründung bei Abweichung Servicetechniker.", - "Schätzung Umsatz (ChatGPT).", + "Begründung zur FSM-Bewertung.", + "Schätzung Mitarbeiterzahl via ChatGPT (nur wenn Wiki-Daten fehlen).", + "Vergleich Mitarbeiterzahl: CRM vs. Wikipedia vs. ChatGPT (OK/X).", + "Begründung bei Abweichung (Prozentdifferenz).", + "Schätzung Servicetechniker via ChatGPT.", + "Begründung bei Abweichung zur Technikerzahl.", + "Schätzung Umsatz via ChatGPT.", "Begründung bei Umsatzabweichung.", "Anzahl Kontakte (Serviceleiter) gefunden.", "Anzahl Kontakte (IT-Leiter) gefunden.", @@ -341,9 +331,9 @@ def alignment_demo(sheet): "Anzahl Kontakte (Disponent) gefunden.", "Timestamp Kontaktsuche.", "Timestamp Wikipedia-Suche.", - "Timestamp letzte Prüfung (ChatGPT).", + "Timestamp ChatGPT-Bewertung.", "Skriptversion, die das Ergebnis erzeugt hat.", - "Anzahl Tokens für Request (Token-Zählung pro Modul: Wiki, Chat, Mitarbeiter)." + "Token-Zählung (separat pro Modul)." ], [ # Zeile 5: Aufgabe / Funktion "Datenquelle", @@ -357,38 +347,38 @@ def alignment_demo(sheet): "CRM-Anzahl Techniker", "CRM-Umsatz", "CRM-Anzahl Mitarbeiter", - "Vorgeschlagene Wikipedia URL (zur Überprüfung)", - "Wikipedia-Scraping: URL, erster Absatz, Infobox-Daten", - "Erster Absatz des Wikipedia-Artikels (Text)", - "Wikipedia-Branche – für Branchenabgleich", - "Wikipedia-Umsatz – zur Validierung", - "Wikipedia-Mitarbeiterzahl – zur Validierung", - "Wikipedia-Kategorien, u.a. zur Branchenvalidierung", - "Validierung: Prüfung ob Wikipedia-Daten zu CRM passen (±30% Toleranz)", - "Bei Inkonsistenz: Begründung aus ChatGPT (Wiki)", - "process_wiki_verification: Alternative Wikipedia-URL vorschlagen (ChatGPT), prüft auch 'k.A.'-Fälle", - "Nicht genutzt – evtl. für zukünftige Funktionen", - "Branchenvorschlag über ChatGPT: Liefert alternative Branchenzuordnung", - "Vergleich der CRM-Branche mit ChatGPT-Zuordnung (OK/X)", - "Begründung für abweichende Branchenzuordnung", - "FSM-Relevanz: Prüfen ob Fallback-Daten FSM unterstützen (OK/X)", - "Begründung zur FSM-Bewertung", - "Mitarbeiter-Schätzung via ChatGPT (nur wenn Wiki-Daten fehlen)", - "Vergleich Mitarbeiterzahl: CRM vs. Wikipedia vs. ChatGPT (OK/X)", - "Begründung bei Mitarbeiterzahlabweichung (Prozentdifferenz und Plausibilitätsbeurteilung)", - "Schätzung Servicetechniker via ChatGPT (in Kategorien, z.B. <50, >100, ...)", - "Begründung bei Abweichungen zur Anzahl der Techniker", - "Umsatz-Schätzung via ChatGPT", - "Begründung bei Umsatzabweichung (Vergleich CRM, Wiki, ChatGPT)", + "CRM Vorschlag Wiki URL (beibehalten, falls vorhanden)", + "Wiki URL (aus Wikipedia-Scraping)", + "Erster Absatz des Wikipedia-Artikels", + "Wikipedia-Branche (zur Validierung der Branchenzuordnung)", + "Wikipedia-Umsatz (zur Validierung)", + "Wikipedia-Mitarbeiterzahl (zur Validierung)", + "Wikipedia-Kategorien (zur weiteren Branchenanalyse)", + "Validierung: Prüft, ob Wikipedia-Daten mit CRM-Daten übereinstimmen (±30% Toleranz)", + "Bei Inkonsistenz: Begründung via ChatGPT (für Wikipedia)", + "process_wiki_verification: Falls kein passender Artikel gefunden oder 'k.A.' geliefert wird, selbstständig recherchieren.", + "Nicht genutzt (Zukunft).", + "Branchenvorschlag via ChatGPT (alternativer Vorschlag)", + "Vergleich: CRM-Branche vs. ChatGPT-Branche (OK/X)", + "Begründung bei Abweichung in der Branchenzuordnung", + "FSM-Relevanz: Bewertung, ob Unternehmen für FSM geeignet ist (OK/X)", + "Begründung zur FSM-Eignung", + "Schätzung Anzahl Mitarbeiter via ChatGPT", + "Vergleich: CRM vs. Wiki vs. ChatGPT (OK/X)", + "Begründung bei Abweichung der Mitarbeiterzahl", + "Schätzung Servicetechniker via ChatGPT (Kategorisierung)", + "Begründung bei Abweichung der Technikerzahl", + "Schätzung Umsatz via ChatGPT", + "Begründung bei Umsatzabweichung", "LinkedIn-Kontaktsuche: Serviceleiter", "LinkedIn-Kontaktsuche: IT-Leiter", "LinkedIn-Kontaktsuche: Management", "LinkedIn-Kontaktsuche: Disponent", - "Timestamp Kontaktsuche", - "Timestamp Wikipedia-Suche", - "Timestamp ChatGPT-Bewertung (letzte Prüfung)", - "Ausgabe der verwendeten Skriptversion", - "Ausgabe der Token-Zählung (separat pro Modul)" + "Timestamp der Kontaktsuche", + "Timestamp der Wikipedia-Suche", + "Timestamp der ChatGPT-Bewertung", + "Skriptversion", + "Token-Zählung" ] ] header_range = "A1:AQ5" @@ -403,7 +393,7 @@ def log_prompt_overview(): debug_print(f"{entry[0]}: {entry[1]}") debug_print("=== Ende der Prompt-Übersicht ===") -# ==================== WIKIPEDIA SCRAPER (unverändert, ggf. nur kosmetisch) ==================== +# ==================== WIKIPEDIA SCRAPER ==================== class WikipediaScraper: def __init__(self): wikipedia.set_lang(Config.LANG) @@ -586,48 +576,36 @@ class WikipediaScraper: return None # ==================== VERIFIZIERUNGS-MODUS (Modus 51) ==================== -def _process_verification_row(row_num, row_data): - 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(): +def process_verification_only(rows_limit=None): debug_print("Starte Verifizierungsmodus (Modus 51) im Batch-Prozess...") + # Wenn kein Limit angegeben ist, den Benutzer fragen: + if rows_limit is None: + try: + rows_limit = int(input("Wie viele Zeilen sollen im Batch verarbeitet werden? ")) + except Exception: + rows_limit = Config.BATCH_SIZE 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): + # Bedingung: Wenn Spalte 25 (Index 24) leer ist if len(row) <= 25 or row[24].strip() == "": - entry_text = _process_verification_row(i, row) + entry_text = f"Eintrag {i}:\nFirmenname: {row[1] if len(row)>1 else ''}\nCRM-Beschreibung: {row[7] if len(row)>7 else ''}\nWikipedia-URL: {row[11] if len(row)>11 and row[11].strip() not in ['', 'k.A.'] else 'k.A.'}\nWiki-Absatz: {row[12] if len(row)>12 else 'k.A.'}\nWiki-Kategorien: {row[16] if len(row)>16 else 'k.A.'}\n-----\n" batch_entries.append(entry_text) row_indices.append(i) - if len(batch_entries) == batch_size: + if len(batch_entries) == rows_limit: 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" + "Gib für jeden Eintrag das Ergebnis im Format aus:\nEintrag : \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) @@ -685,17 +663,11 @@ def process_verification_only(): 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(agg_token_count)]], range_name=f"AQ{row_num}") + # Aktualisiere zusätzlich Branchenabgleich, Tokens, Timestamps etc. 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}") + main_sheet.update(values=[[str(agg_token_count)]], range_name=f"AQ{row_num}") debug_print(f"Zeile {row_num} verifiziert: Antwort: {answer}") time.sleep(Config.RETRY_DELAY) debug_print("Verifizierungs-Batch abgeschlossen.") @@ -732,18 +704,16 @@ class DataProcessor: alignment_demo_full() elif MODE == "4": for i, row in enumerate(self.sheet_handler.sheet_values[1:], start=2): - # Prüfen, ob Wikipedia-Timestamp (Spalte AN) gesetzt ist (Index 39) if len(row) <= 39 or row[39].strip() == "": self._process_single_row(i, row, process_wiki=True, process_chatgpt=False) elif MODE == "5": for i, row in enumerate(self.sheet_handler.sheet_values[1:], start=2): - # Prüfen, ob ChatGPT-Timestamp (Spalte AO) gesetzt ist (Index 40) if len(row) <= 40 or row[40].strip() == "": self._process_single_row(i, row, process_wiki=False, process_chatgpt=True) elif MODE == "51": - process_verification_only() + process_verification_only() # Es wird nun immer nach der Anzahl der zu verarbeitenden Zeilen gefragt. elif MODE == "8": - process_batch_token_count() # Funktion muss ggf. definiert sein + process_batch_token_count() else: start_index = self.sheet_handler.get_start_index() print(f"Starte bei Zeile {start_index+1}") @@ -758,18 +728,20 @@ class DataProcessor: def _process_single_row(self, row_num, row_data, process_wiki=True, process_chatgpt=True): 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}" + # Wir wollen die Wikipedia-Daten in Spalte L bis R schreiben + wiki_update_range = f"L{row_num}:R{row_num}" + dt_wiki_range = f"AN{row_num}" # Wikipedia Timestamp + dt_chat_range = f"AO{row_num}" # ChatGPT Timestamp + ver_range = f"AP{row_num}" # Versions-Timestamp 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") company_data = {} - # Wiki-Verarbeitung nur, wenn Timestamp noch nicht gesetzt (Spalte AN) + # Wikipedia-Verarbeitung (nur, wenn Timestamp noch nicht gesetzt) 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() + # Falls CRM Vorschlag Wiki URL bereits vorhanden ist, nutze diesen Wert aus Spalte L (row_data[11]) + if len(row_data) > 11 and row_data[11].strip() not in ["", "k.A."]: + wiki_url = row_data[11].strip() try: company_data = self.wiki_scraper.extract_company_data(wiki_url) except Exception as e: @@ -787,8 +759,23 @@ class DataProcessor: 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.', 'full_infobox': 'k.A.' } + # Wenn der ermittelte first_paragraph "k.A." liefert, versuche erneut, einen Artikel zu suchen! + if company_data.get('first_paragraph', 'k.A.').strip().lower() == "k.a.": + debug_print("Wikipedia first_paragraph zeigt 'k.A.' – starte erneute Suche...") + article = self.wiki_scraper.search_company_article(company_name, website) + if article: + company_data = self.wiki_scraper.extract_company_data(article.url) + else: + 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.' + } + # Zusammenstellen der 7 Werte: + # 0: CRM Vorschlag Wiki URL (bestehend aus row_data[11]), + # 1: Wiki URL, 2: Wiki Absatz, 3: Wiki Branche, 4: Wiki Umsatz, 5: Wiki Mitarbeiter, 6: Wiki Kategorien wiki_values = [ - row_data[10] if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."] else "k.A.", + row_data[11] if len(row_data) > 11 and row_data[11].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.'), @@ -800,7 +787,7 @@ class DataProcessor: 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 (alle relevanten Bewertungen) + # ChatGPT-Verarbeitung 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." @@ -814,7 +801,6 @@ class DataProcessor: 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}") - # Servicetechniker-Schätzung (wie gehabt) 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." @@ -825,14 +811,12 @@ class DataProcessor: else: discrepancy = "ok" self.sheet_handler.sheet.update(values=[[discrepancy]], range_name=f"AF{row_num}") - # Neue: Mitarbeiterschätzung und Konsistenzprüfung crm_employee = row_data[10] if len(row_data) > 10 else "k.A." wiki_employee = company_data.get('mitarbeiter', 'k.A.') emp_estimate = process_employee_estimation(company_name, company_data.get('first_paragraph', 'k.A.'), crm_employee) emp_consistency = process_employee_consistency(crm_employee, wiki_employee, emp_estimate) self.sheet_handler.sheet.update(values=[[emp_estimate]], range_name=f"AB{row_num}") self.sheet_handler.sheet.update(values=[[emp_consistency]], range_name=f"AC{row_num}") - # Umsatz-Schätzung via ChatGPT (wie gehabt) revenue_result = evaluate_umsatz_chatgpt(company_name, company_data.get('umsatz', 'k.A.')) self.sheet_handler.sheet.update(values=[[revenue_result]], range_name=f"AG{row_num}") # Token-Zählung pro Modul @@ -848,8 +832,8 @@ class DataProcessor: 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}") + f"Validierung: {valid_result}, FSM: {fsm_result['suitability']}, " + f"Servicetechniker-Schätzung: {st_estimate}") time.sleep(Config.RETRY_DELAY) # ==================== ALIGNMENT DEMO FÜR HAUPTBLATT UND CONTACTS ==================== @@ -913,7 +897,7 @@ def process_contacts(): debug_print("Alignment-Demo für Contacts abgeschlossen.") # Weitere Verarbeitung der Kontakte folgt hier ... -# ==================== ALLE BENÖTIGTEN LINKEDIN-HELPER ==================== +# ==================== LINKEDIN HELPER ==================== def search_linkedin_contact(company_name, website, position_query): try: with open("serpApiKey.txt", "r") as f: @@ -988,15 +972,11 @@ def count_linkedin_contacts(company_name, website, position_query): # ==================== UMBENENNTE FUNKTION: process_wiki_verification ==================== def process_wiki_verification(crm_data, wiki_data_str): - """ - Überprüft, ob die CRM-Daten und die Wikipedia-Daten übereinstimmen. - Wurde kein passender Artikel gefunden oder entspricht Wikipedia "k.A.", wird 'Skipped (k.A.)' zurückgegeben. - Andernfalls wird die Bewertung von ChatGPT zurückgegeben. - """ prompt_text = ( "Bitte überprüfe, ob die folgenden beiden Datensätze grundsätzlich zum gleichen Unternehmen gehören. " - "Vergleiche insbesondere den Firmennamen, Ort und Branche. " - "Antwort mit 'OK' wenn übereinstimmend, sonst mit einer kurzen Begründung.\n\n" + "Vergleiche insbesondere den Firmennamen, den Ort und die Branche. " + "Antworte mit 'OK', wenn die Daten übereinstimmen. Falls nicht, gib eine kurze Begründung aus. " + "Falls ein Wikipedia-Artikel mit 'k.A.' geliefert wird, ignoriere ihn und antworte mit 'Skipped (k.A.)'.\n\n" f"CRM-Daten:\n{crm_data}\n\n" f"Wikipedia-Daten:\n{wiki_data_str}\n\n" "Antwort: " @@ -1016,7 +996,6 @@ def process_wiki_verification(crm_data, wiki_data_str): ) result = response.choices[0].message.content.strip() debug_print(f"process_wiki_verification Ergebnis: '{result}'") - # Wenn in der Antwort "k.A." vorkommt, interpretiere dies als fehlender Artikel. if "k.a." in result.lower(): return "Skipped (k.A.)" return result @@ -1024,7 +1003,7 @@ def process_wiki_verification(crm_data, wiki_data_str): debug_print(f"Fehler beim Aufruf der ChatGPT API in process_wiki_verification: {e}") return "k.A." -# ==================== ÜBRIGE CHATGPT-FUNKTIONEN (wie gehabt) ==================== +# ==================== ÜBRIGE CHATGPT-FUNKTIONEN ==================== def evaluate_umsatz_chatgpt(company_name, wiki_umsatz): try: with open("api_key.txt", "r") as f: @@ -1139,7 +1118,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg 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. " + "insbesondere bei Hinweisen auf Personentransport oder öffentliche Verkehrsdienstleistungen. " ) 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" @@ -1148,20 +1127,9 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg 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 (keine zusätzlichen Branchen erfinden). " - "Bitte antworte im Format:\n" - "Branche: \n" - "Übereinstimmung: \n" - "Begründung: " + "Das Ziel-Branchenschema umfasst ALLE gültigen Branchen. " + "Ordne das Unternehmen exakt einer Branche zu. " + "Antworte im Format:\nBranche: \nÜbereinstimmung: \nBegründung: ." ) try: response = openai.ChatCompletion.create( @@ -1201,10 +1169,9 @@ def evaluate_servicetechnicians_estimate(company_name, company_data): 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'." + f"Bitte schätze auf Basis öffentlich zugänglicher Informationen (insbesondere verifizierte Wikipedia-Daten) " + f"die Anzahl der Servicetechniker für das Unternehmen '{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( @@ -1229,7 +1196,7 @@ def evaluate_servicetechnicians_explanation(company_name, st_estimate, company_d 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." + "Berücksichtige dabei öffentlich zugängliche Informationen (z.B. Branche, Umsatz, Mitarbeiterzahl)." ) try: response = openai.ChatCompletion.create( @@ -1260,7 +1227,6 @@ def map_internal_technicians(value): def process_batch_token_count(): debug_print("Batch Token Count Modus (Modus 8) wird ausgeführt.") - # Dummy-Implementierung – tatsächliche Optimierung zur Tokenreduktion in Batch-Anfragen folgt. time.sleep(Config.RETRY_DELAY) debug_print("Batch Token Count abgeschlossen.")