v1.6.7: Behebt strukturelle/Syntax-Fehler; passt Filter für Wiki-Suche via SerpAPI an

- Inkrementiere Versionsnummer auf v1.6.7.
- Behebe kritischen AttributeError: Korrigiere die Einrückung für mehrere Verarbeitungsmethoden (_process_single_row, process_reevaluation_rows, process_serp_website_lookup_for_empty, process_website_details_for_marked_rows, prepare_data_for_modeling, process_rows_sequentially, process_find_wiki_with_serp), sodass diese korrekt als Methoden innerhalb der Klasse DataProcessor definiert sind.
- Behebe SyntaxError: Löse das Problem mit komplexen f-Strings in _process_single_row und potenziell anderen Stellen, indem die String-Konstruktion von Ausdrücken innerhalb der f-String-Syntax getrennt wird.
- Passe Filterlogik für Modus 'find_wiki_serp' an: Die SerpAPI-Suche nach fehlenden Wiki-URLs (M=k.A./leer) wird nun ausgelöst, wenn (CRM Umsatz (J) > 200 Mio ODER CRM Anzahl Mitarbeiter (K) > 500). Implementiere robuste numerische Extraktion für J und K innerhalb der Filterlogik.
- Stelle sicher, dass SerpAPI Wiki Search Timestamp (AY) immer nach einem Suchversuch im Modus 'find_wiki_serp' gesetzt wird, unabhängig vom Ergebnis.
- Diverse Logging-Anpassungen für Klarheit und Debugging (z.B. im Wiki-Verarbeitungsschritt).
This commit is contained in:
2025-04-23 05:18:30 +00:00
parent 808a015904
commit bf5db76232

View File

@@ -1,22 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
v1.6.6: Füge SerpAPI-Suche für fehlende Wiki-URLs großer Firmen hinzu v1.6.7: Behebt strukturelle/Syntax-Fehler; passt Filter für Wiki-Suche via SerpAPI an
Git-Änderungsbeschreibung: Git-Änderungsbeschreibung:
- Füge neuen Betriebsmodus `--mode find_wiki_serp` hinzu. - Inkrementiere Versionsnummer auf v1.6.7.
- Implementiere neue Funktion `serp_wikipedia_lookup`, die SerpAPI nutzt, um gezielt nach Wikipedia-Artikeln für einen Firmennamen zu suchen. - Behebe kritischen AttributeError: Korrigiere die Einrückung für mehrere Verarbeitungsmethoden (_process_single_row, process_reevaluation_rows, process_serp_website_lookup_for_empty, process_website_details_for_marked_rows, prepare_data_for_modeling, process_rows_sequentially, process_find_wiki_with_serp), sodass diese korrekt als Methoden innerhalb der Klasse DataProcessor definiert sind.
- Implementiere neue Funktion `process_find_wiki_with_serp`: - Behebe SyntaxError: Löse das Problem mit komplexen f-Strings in _process_single_row und potenziell anderen Stellen, indem die String-Konstruktion von Ausdrücken innerhalb der f-String-Syntax getrennt wird.
- Lädt aktuelle Sheet-Daten. - Passe Filterlogik für Modus 'find_wiki_serp' an: Die SerpAPI-Suche nach fehlenden Wiki-URLs (M=k.A./leer) wird nun ausgelöst, wenn (CRM Umsatz (J) > 200 Mio ODER CRM Anzahl Mitarbeiter (K) > 500). Implementiere robuste numerische Extraktion für J und K innerhalb der Filterlogik.
- Filtert Zeilen, bei denen Spalte M (Wiki URL) leer/'k.A.' ist UND Spalte K (CRM Mitarbeiter) einen Schwellenwert (Standard: 500) überschreitet. - Stelle sicher, dass SerpAPI Wiki Search Timestamp (AY) immer nach einem Suchversuch im Modus 'find_wiki_serp' gesetzt wird, unabhängig vom Ergebnis.
- Ruft `serp_wikipedia_lookup` für gefilterte Zeilen auf. - Diverse Logging-Anpassungen für Klarheit und Debugging (z.B. im Wiki-Verarbeitungsschritt).
- Bei erfolgreicher URL-Findung:
- Schreibt die gefundene URL in Spalte M.
- Setzt Flag 'x' in Spalte A (ReEval Flag).
- Löscht Timestamps in Spalten AN (Wikipedia Timestamp) und AO (Timestamp letzte Prüfung).
- Führt gebündelte Sheet-Updates am Ende durch.
- Integriere den neuen Modus `find_wiki_serp` in die Argumentenverarbeitung und Ausführungslogik der `main`-Funktion.
- Füge notwendige Imports hinzu und stelle sicher, dass die neuen Funktionen Logging verwenden.
- Aktualisiere Versionsnummer in `Config.VERSION` auf v1.6.6.
""" """
import os import os
@@ -73,7 +65,7 @@ PATTERNS_FILE_JSON = "technician_patterns.json" # Optional
# ==================== KONFIGURATION ==================== # ==================== KONFIGURATION ====================
class Config: class Config:
# ... (Alle deine bisherigen Config-Einstellungen) ... # ... (Alle deine bisherigen Config-Einstellungen) ...
VERSION = "v1.6.6" # Versionsnummer erhöhen VERSION = "v1.6.7" # Versionsnummer erhöhen
LANG = "de" LANG = "de"
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
MAX_RETRIES = 3 MAX_RETRIES = 3
@@ -368,9 +360,17 @@ def serp_wikipedia_lookup(company_name, website=None, min_score=0.4):
return None # Bei unerwarteten Fehlern None zurückgeben return None # Bei unerwarteten Fehlern None zurückgeben
# Kann als eigenständige Funktion oder Methode in DataProcessor implementiert werden # Kann als eigenständige Funktion oder Methode in DataProcessor implementiert werden
def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500): # Annahme: COLUMN_MAP ist global definiert und enthält mindestens:
# "CRM Name" (B), "CRM Anzahl Mitarbeiter" (K), "CRM Umsatz" (J),
# "Wiki URL" (M), "ReEval Flag" (A), "Wiki Absatz" (N), ..., "Wiki Verif. Timestamp" (AX), "SerpAPI Wiki Search Timestamp" (AY)
# Annahme: serp_wikipedia_lookup und simple_normalize_url sind definiert und nutzen logging/retry
# Annahme: clean_text und normalize_company_name sind definiert
# Annahme: get_valid_numeric (oder ähnliche Logik zur numerischen Extraktion) ist verfügbar/implementiert.
def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500, min_umsatz=200):
""" """
Sucht fehlende Wikipedia-URLs (Spalte M = k.A.) für Unternehmen mit > min_employees Sucht fehlende Wikipedia-URLs (Spalte M = k.A.) für Unternehmen mit
(Umsatz CRM > min_umsatz ODER Mitarbeiter CRM > min_employees)
über SerpAPI und trägt gefundene URLs in Spalte M ein. Setzt ReEval-Flag (A) über SerpAPI und trägt gefundene URLs in Spalte M ein. Setzt ReEval-Flag (A)
und löscht abhängige Wiki-Spalten (N-V, AN, AO, AP, AX). und löscht abhängige Wiki-Spalten (N-V, AN, AO, AP, AX).
Merkt sich in Spalte AY, wann die Suche durchgeführt wurde. Merkt sich in Spalte AY, wann die Suche durchgeführt wurde.
@@ -378,149 +378,218 @@ def process_find_wiki_with_serp(sheet_handler, row_limit=None, min_employees=500
Args: Args:
sheet_handler (GoogleSheetHandler): Initialisierte Instanz. sheet_handler (GoogleSheetHandler): Initialisierte Instanz.
row_limit (int, optional): Maximale Anzahl zu prüfender Zeilen. Defaults to None. row_limit (int, optional): Maximale Anzahl zu prüfender Zeilen. Defaults to None.
min_employees (int, optional): Mindestanzahl Mitarbeiter (Spalte K) als Filter. Defaults to 500. min_employees (int, optional): Mindestanzahl Mitarbeiter (Spalte K) als Teilfilter. Defaults to 500.
min_umsatz (int, optional): Mindestumsatz (Spalte J) als Teilfilter. Defaults to 200.
""" """
logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen > {min_employees} MA...") logging.info(f"Starte Modus 'find_wiki_serp': Suche fehlende Wiki-URLs für Firmen mit (Umsatz CRM > {min_umsatz} ODER Mitarbeiter CRM > {min_employees})...")
if not sheet_handler.load_data(): return if not sheet_handler.load_data(): return
all_data = sheet_handler.get_all_data_with_headers() all_data = sheet_handler.get_all_data_with_headers()
if not all_data or len(all_data) <= 5: # Annahme: 5 Header-Zeilen
header_rows = 5
if not all_data or len(all_data) <= header_rows:
logging.warning("Keine oder zu wenige Daten im Sheet für 'find_wiki_serp' gefunden.") logging.warning("Keine oder zu wenige Daten im Sheet für 'find_wiki_serp' gefunden.")
return return
header_rows = 5 data_rows = all_data[header_rows:] # Daten ab Zeile 6
data_rows = all_data[header_rows:]
# Benötigte Spaltenindizes holen (inkl. aller zu löschenden Spalten) # Benötigte Spaltenindizes holen (inkl. aller zu löschenden Spalten)
try: # Verwenden Sie hier das COLUMN_MAP robust
col_indices = { col_indices = {}
"A": COLUMN_MAP["ReEval Flag"], required_keys = [
"K": COLUMN_MAP["CRM Anzahl Mitarbeiter"], "ReEval Flag", "CRM Anzahl Mitarbeiter", "CRM Umsatz", "Wiki URL", "CRM Name",
"M": COLUMN_MAP["Wiki URL"], "Wiki Absatz", "Wiki Branche", "Wiki Umsatz", "Wiki Mitarbeiter", "Wiki Kategorien",
"B": COLUMN_MAP["CRM Name"], "Chat Wiki Konsistenzprüfung", "Chat Begründung Wiki Inkonsistenz", "Chat Vorschlag Wiki Artikel",
"N": COLUMN_MAP["Wiki Absatz"], # NEU zum Löschen "Begründung bei Abweichung", "Wikipedia Timestamp", "Timestamp letzte Prüfung",
"O": COLUMN_MAP["Wiki Branche"], # NEU zum Löschen "Version", "Wiki Verif. Timestamp", "SerpAPI Wiki Search Timestamp"
"P": COLUMN_MAP["Wiki Umsatz"], # NEU zum Löschen ]
"Q": COLUMN_MAP["Wiki Mitarbeiter"], # NEU zum Löschen all_keys_found = True
"R": COLUMN_MAP["Wiki Kategorien"], # NEU zum Löschen for key in required_keys:
"S": COLUMN_MAP["Chat Wiki Konsistenzprüfung"], # NEU zum Löschen idx = COLUMN_MAP.get(key)
"T": COLUMN_MAP["Chat Begründung Wiki Inkonsistenz"], # NEU zum Löschen col_indices[key] = idx
"U": COLUMN_MAP["Chat Vorschlag Wiki Artikel"], # NEU zum Löschen if idx is None:
"V": COLUMN_MAP["Begründung bei Abweichung"], # NEU zum Löschen logging.critical(f"FEHLER: Benötigter Spaltenschlüssel '{key}' nicht in COLUMN_MAP gefunden! Modus abgebrochen.")
"AN": COLUMN_MAP["Wikipedia Timestamp"], all_keys_found = False
"AO": COLUMN_MAP["Timestamp letzte Prüfung"],
"AP": COLUMN_MAP["Version"], # NEU zum Löschen if not all_keys_found:
"AX": COLUMN_MAP["Wiki Verif. Timestamp"], # NEU zum Löschen return # Abbruch, da Spalten fehlen
"AY": COLUMN_MAP["SerpAPI Wiki Search Timestamp"]
} # Hilfsfunktion zur Konvertierung Spaltenindex -> Buchstabe
col_letters = {key: sheet_handler._get_col_letter(idx + 1) for key, idx in col_indices.items()} col_letters = {key: sheet_handler._get_col_letter(idx + 1) for key, idx in col_indices.items()}
except KeyError as e:
logging.critical(f"FEHLER: Benötigter Spaltenschlüssel '{e}' nicht in COLUMN_MAP gefunden! Modus abgebrochen.")
return
except Exception as e:
logging.critical(f"FEHLER beim Holen der Spaltenbuchstaben: {e}")
return
all_sheet_updates = [] all_sheet_updates = []
processed_rows = 0 processed_rows_count = 0 # Zählt Zeilen, für die SerpAPI versucht wurde
found_urls = 0 found_urls_count = 0 # Zählt Zeilen, wo eine URL gefunden wurde
skipped_timestamp_ay = 0 skipped_timestamp_ay_count = 0
skipped_employee_count = 0 skipped_size_count = 0
skipped_m_filled_count = 0 skipped_m_filled_count = 0
now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
for idx, row in enumerate(data_rows): # --- Hilfsfunktion zur sicheren numerischen Extraktion (adaptiert von prepare_data_for_modeling) ---
row_num_in_sheet = idx + header_rows + 1 def safe_numeric_extract(value_str):
if value_str is None or pd.isna(value_str) or str(value_str).strip() == '': return 0 # Return 0, nicht NaN, für die Vergleichslogik
try:
# Annahme: clean_text existiert
processed_value = clean_text(str(value_str))
if processed_value == "k.A.": return 0
if row_limit is not None and processed_rows >= row_limit: processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|über|unter|mehr als|weniger als|bis zu)\s+', '', processed_value)
processed_value = re.sub(r'[€$£¥]', '', processed_value).strip()
processed_value = re.split(r'\s*(-||bis)\s*', processed_value, 1)[0].strip()
# Entferne Punkte UND Apostrophe als Tausendertrenner, ersetze Komma durch Punkt
processed_value = processed_value.replace('.', '').replace("'", "").replace(',', '.')
match = re.search(r'([\d.]+)', processed_value)
if not match: return 0 # Keine numerischen Zeichen gefunden
num_str = match.group(1)
if not num_str or num_str == '.': return 0
num = float(num_str)
# Einheiten-Multiplikatoren (Mrd, Mio, Tsd) - Wichtig für Umsatz
multiplier = 1.0
original_lower = str(value_str).lower() # Nutze den Originalstring für Einheiten
if "mrd" in original_lower or "milliarden" in original_lower or "billion" in original_lower: multiplier = 1000000000.0
elif "mio" in original_lower or "millionen" in original_lower or "mill." in original_lower: multiplier = 1000000.0
elif "tsd" in original_lower or "tausend" in original_lower: multiplier = 1000.0
num = num * multiplier
# Für den Vergleich (Umsatz in Mio, Mitarbeiter int)
# Umsatz soll > 200 MIO sein, also num direkt mit 200,000,000 vergleichen
# Mitarbeiter > 500, num direkt mit 500 vergleichen
# Geben Sie den Rohwert nach Multiplikator zurück
return num if num > 0 else 0 # Nur positive Werte zählen
except Exception as e:
logging.debug(f"Fehler in safe_numeric_extract für Wert '{str(value_str)[:50]}...': {e}")
return 0
# --- Ende Hilfsfunktion ---
# Iteriere durch die Datenzeilen
for idx, row in enumerate(data_rows):
row_num_in_sheet = idx + header_rows + 1 # 1-basierte Sheet-Zeilennummer
# Limit-Prüfung
if row_limit is not None and processed_rows_count >= row_limit:
logging.info(f"Zeilenlimit ({row_limit}) für durchgeführte Suchen erreicht.") logging.info(f"Zeilenlimit ({row_limit}) für durchgeführte Suchen erreicht.")
break break
# Prüfe AY Timestamp # Sicherstellen, dass die Zeile lang genug für alle benötigten Spalten ist
ts_ay_val = row[col_indices["AY"]] if len(row) > col_indices["AY"] else "" max_needed_idx = max(col_indices.values())
if len(row) <= max_needed_idx:
logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Zeile zu kurz für benötigte Spalten, erwartet > {max_needed_idx}, hat {len(row)}).")
continue
# Prüfe AY Timestamp: Überspringe, wenn SerpAPI Suche für diese Zeile schon versucht wurde
ts_ay_val = row[col_indices["SerpAPI Wiki Search Timestamp"]]
if ts_ay_val and ts_ay_val.strip(): if ts_ay_val and ts_ay_val.strip():
skipped_timestamp_ay += 1 skipped_timestamp_ay_count += 1
continue continue
try: # Prüfe Wiki URL (M): Überspringe, wenn bereits gefüllt (nicht k.A. oder leer)
# Mitarbeiterzahl prüfen m_value = row[col_indices["Wiki URL"]]
ma_val_str = row[col_indices["K"]] if len(row) > col_indices["K"] else "0" if m_value and str(m_value).strip().lower() != "k.a.": # Jetzt auch 'leer' abfangen? Nein, '' ist schon abgedeckt
try: skipped_m_filled_count += 1
ma_val_str_cleaned = re.sub(r"[^\d]", "", ma_val_str)
ma_val = int(ma_val_str_cleaned) if ma_val_str_cleaned else 0
except ValueError: ma_val = 0
if ma_val <= min_employees:
skipped_employee_count += 1
continue
# Wiki URL (M) prüfen
m_value = row[col_indices["M"]] if len(row) > col_indices["M"] else ""
if m_value and m_value.strip().lower() != "k.a.":
skipped_m_filled_count += 1
continue
# Kandidat gefunden
company_name = row[col_indices["B"]] if len(row) > col_indices["B"] else ""
if not company_name:
logging.warning(f"Zeile {row_num_in_sheet}: Übersprungen, kein Firmenname für Suche vorhanden.")
continue
# SerpAPI Suche
logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (MA: {ma_val})...")
wiki_url_found = serp_wikipedia_lookup(company_name)
processed_rows += 1
time.sleep(1.5)
# Updates vorbereiten
# Timestamp AY IMMER setzen
row_updates = [{'range': f'{col_letters["AY"]}{row_num_in_sheet}', 'values': [[now_timestamp_str]]}]
if wiki_url_found:
logging.info(f" -> URL gefunden: {wiki_url_found}. Bereite Update vor (Setze M, A; Lösche N-V, AN, AO, AP, AX).")
found_urls += 1
# Zusätzliche Updates für gefundene URL
row_updates.extend([
{'range': f'{col_letters["M"]}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, # URL setzen
{'range': f'{col_letters["A"]}{row_num_in_sheet}', 'values': [['x']]}, # ReEval Flag
# --- Spalten leeren ---
{'range': f'{col_letters["N"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["O"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["P"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["Q"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["R"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["S"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["T"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["U"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["V"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["AN"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["AO"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["AP"]}{row_num_in_sheet}', 'values': [['']]},
{'range': f'{col_letters["AX"]}{row_num_in_sheet}', 'values': [['']]}
])
else:
logging.info(f" -> Keine Wiki-URL für '{company_name}' via SerpAPI gefunden.")
# Nur AY Timestamp wird geschrieben
all_sheet_updates.extend(row_updates)
except Exception as e:
logging.exception(f"Unerwarteter Fehler bei Verarbeitung von Zeile {row_num_in_sheet}: {e}")
continue continue
# --- Prüfe Unternehmensgröße (J Umsatz ODER K Mitarbeiter) ---
umsatz_val_str = row[col_indices["CRM Umsatz"]]
ma_val_str = row[col_indices["CRM Anzahl Mitarbeiter"]]
umsatz_val_num = safe_numeric_extract(umsatz_val_str)
ma_val_num = safe_numeric_extract(ma_val_str)
# Filterlogik: Umsatz > min_umsatz ODER Mitarbeiter > min_employees
# Wenn *nicht* (Umsatz > min_umsatz ODER Mitarbeiter > min_employees), dann überspringe
if not (umsatz_val_num > min_umsatz * 1000000 or ma_val_num > min_employees):
logging.debug(f"Zeile {row_num_in_sheet}: Übersprungen (Größe nicht ausreichend. Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num}).")
skipped_size_count += 1
continue
# --- Ende Größenprüfung ---
# Kandidat gefunden: M leer/k.A., AY leer, und Größe passt
company_name = row[col_indices["CRM Name"]] if len(row) > col_indices["CRM Name"] else ""
if not company_name or str(company_name).strip() == "":
logging.warning(f"Zeile {row_num_in_sheet}: Übersprungen, kein Firmenname für Suche vorhanden.")
# Setze AY Timestamp, damit wir nicht immer wieder versuchen
ay_col_letter = col_letters["SerpAPI Wiki Search Timestamp"]
all_sheet_updates.append({'range': f'{ay_col_letter}{row_num_in_sheet}', 'values': [[now_timestamp_str]]})
continue
# SerpAPI Suche
logging.info(f"Zeile {row_num_in_sheet}: Suche Wiki-URL für '{company_name}' (Umsatz: {umsatz_val_num:.0f}, MA: {ma_val_num})...")
processed_rows_count += 1 # Zähle VOR dem Call, dass ein Versuch gestartet wird
# Annahme: serp_wikipedia_lookup existiert und nutzt logging/retry
# website_url wird nicht direkt im SerpAPI Lookup verwendet, kann aber als Kontext hilfreich sein
website_url = row[COLUMN_MAP.get("CRM Website", -1)] if COLUMN_MAP.get("CRM Website", -1) != -1 and len(row) > COLUMN_MAP.get("CRM Website", -1) else None
wiki_url_found = serp_wikipedia_lookup(company_name, website=website_url)
# Updates vorbereiten
# Timestamp AY IMMER setzen, nachdem der Versuch gemacht wurde
ay_col_letter = col_letters["SerpAPI Wiki Search Timestamp"]
all_sheet_updates.append({'range': f'{ay_col_letter}{row_num_in_sheet}', 'values': [[now_timestamp_str]]})
if wiki_url_found and wiki_url_found.strip() and wiki_url_found != "k.A.":
logging.info(f" -> URL gefunden: {wiki_url_found}. Bereite Update vor (Setze M, A; Lösche N-V, AN, AO, AP, AX).")
found_urls_count += 1
# Zusätzliche Updates für gefundene URL
m_l = col_letters["Wiki URL"]
a_l = col_letters["ReEval Flag"]
# Spalten N-V leeren
n_l = col_letters["Wiki Absatz"]
v_l = col_letters["Begründung bei Abweichung"]
# Timestamps AN, AO, AX, Version AP leeren
an_l = col_letters["Wikipedia Timestamp"]
ao_l = col_letters["Timestamp letzte Prüfung"]
ap_l = col_letters["Version"]
ax_l = col_letters["Wiki Verif. Timestamp"]
all_sheet_updates.extend([
{'range': f'{m_l}{row_num_in_sheet}', 'values': [[wiki_url_found]]}, # URL setzen in M
{'range': f'{a_l}{row_num_in_sheet}', 'values': [['x']]}, # ReEval Flag setzen in A
# --- Spalten leeren, damit sie neu befüllt werden ---
# Range N:V leeren
{'range': f'{n_l}{row_num_in_sheet}:{v_l}{row_num_in_sheet}', 'values': [[''] * (col_indices["Begründung bei Abweichung"] - col_indices["Wiki Absatz"] + 1)]},
{'range': f'{an_l}{row_num_in_sheet}', 'values': [['']]}, # AN leeren
{'range': f'{ao_l}{row_num_in_sheet}', 'values': [['']]}, # AO leeren
{'range': f'{ap_l}{row_num_in_sheet}', 'values': [['']]}, # AP leeren
{'range': f'{ax_l}{row_num_in_sheet}', 'values': [['']]} # AX leeren
])
else:
logging.info(f" -> Keine Wiki-URL für '{company_name}' via SerpAPI gefunden.")
# Nur AY Timestamp wird gesetzt, was bereits oben passiert ist.
# Kleiner Sleep nach jeder SerpAPI-Suche, auch wenn es ein Retry gibt
# Der Decorator kümmert sich um Retries mit Backoff, dies ist nur eine globale Rate-Limit-Vorsorge.
time.sleep(getattr(Config, 'RETRY_DELAY', 5) * 0.3)
# --- Batch Update am Ende --- # --- Batch Update am Ende ---
if all_sheet_updates: if all_sheet_updates:
logging.info(f"Sende Batch-Update für {processed_rows} geprüfte Zeilen ({found_urls} URLs gefunden, {len(all_sheet_updates)} Zellen)...") logging.info(f"Sende Batch-Update für {processed_rows_count} geprüfte Zeilen ({found_urls_count} URLs gefunden, {len(all_sheet_updates)} Zellen)...")
# Annahme: sheet_handler.batch_update_cells existiert und nutzt logging/retry
success = sheet_handler.batch_update_cells(all_sheet_updates) success = sheet_handler.batch_update_cells(all_sheet_updates)
if success: if success:
logging.info(f"Sheet-Update für 'find_wiki_serp' erfolgreich.") logging.info(f"Sheet-Update für 'find_wiki_serp' erfolgreich.")
# Der else-Fall wird von batch_update_cells geloggt
else: else:
logging.info("Keine Zeilen gefunden, für die eine SerpAPI Wiki-Suche durchgeführt werden musste/konnte.") logging.info("Keine Zeilen gefunden, für die eine SerpAPI Wiki-Suche durchgeführt werden musste/konnte.")
logging.info(f"Modus 'find_wiki_serp' abgeschlossen.") logging.info(f"Modus 'find_wiki_serp' abgeschlossen.")
logging.info(f" Durchgeführte Suchen in diesem Lauf: {processed_rows}") logging.info(f" Durchgeführte Suchen in diesem Lauf: {processed_rows_count}")
logging.info(f" Gefundene & eingetragene URLs: {found_urls}") logging.info(f" Gefundene & eingetragene URLs: {found_urls_count}")
logging.info(f" Übersprungen (AY bereits gesetzt): {skipped_timestamp_ay}") logging.info(f" Übersprungen (AY bereits gesetzt): {skipped_timestamp_ay_count}")
logging.info(f" Übersprungen (MA <= {min_employees}): {skipped_employee_count}") logging.info(f" Übersprungen (Größe nicht ausreichend): {skipped_size_count}")
logging.info(f" Übersprungen (M bereits gefüllt): {skipped_m_filled_count}") logging.info(f" Übersprungen (M bereits gefüllt): {skipped_m_filled_count}")
def prepare_data_for_modeling(sheet_handler): def prepare_data_for_modeling(sheet_handler):