This commit is contained in:
2025-05-26 11:52:57 +00:00
parent d3ebcf93b4
commit 034f31ec1d

View File

@@ -8395,24 +8395,27 @@ class DataProcessor:
"""
Batch-Prozess zur Generierung von Parent-Account-Vorschlägen mittels ChatGPT.
Schreibt Ergebnisse in Spalten O, P, Q.
Bearbeitet nur Zeilen, bei denen Spalte Q (Parent Vorschlag Timestamp) leer ist,
es sei denn re_evaluate_question_mark ist True und Spalte P ist '?'.
Args:
start_sheet_row (int, optional): Die 1-basierte Startzeile.
end_sheet_row (int, optional): Die 1-basierte Endzeile.
limit (int, optional): Maximale Anzahl zu verarbeitender Zeilen.
re_evaluate_question_mark (bool, optional): Wenn True, werden auch Zeilen mit '?'
in Spalte P (Parent Vorschlag Status) erneut bewertet.
re_evaluate_question_mark (bool, optional): Wenn True, werden auch Zeilen mit '?'
in Spalte P (Parent Vorschlag Status) erneut bewertet,
AUCH WENN Spalte Q bereits einen Timestamp hat.
"""
self.logger.info(f"Starte Parent Account Suggestion Batch. Bereich: {start_sheet_row if start_sheet_row else 'Start'}-{end_sheet_row if end_sheet_row else 'Ende'}, Limit: {limit if limit else 'Unbegrenzt'}, Re-Eval ?: {re_evaluate_question_mark}")
# --- Daten laden und Startzeile ermitteln ---
col_o_key = "System Vorschlag Parent Account"
col_p_key = "Parent Vorschlag Status"
col_q_key = "Parent Vorschlag Timestamp"
col_q_key = "Parent Vorschlag Timestamp" # Timestamp-Spalte für die Auswahl
if start_sheet_row is None:
self.logger.info(f"Automatische Ermittlung der Startzeile basierend auf leerem '{col_o_key}'...")
start_data_index_no_header = self.sheet_handler.get_start_row_index(check_column_key=col_o_key, min_sheet_row=7)
self.logger.info(f"Automatische Ermittlung der Startzeile basierend auf leerem '{col_q_key}'...") # Geändert: Start basierend auf leerem Q
start_data_index_no_header = self.sheet_handler.get_start_row_index(check_column_key=col_q_key, min_sheet_row=7)
if start_data_index_no_header == -1:
self.logger.error("FEHLER bei autom. Startzeilenermittlung. Breche ab.")
return
@@ -8446,24 +8449,21 @@ class DataProcessor:
col_o_letter = self.sheet_handler._get_col_letter(COLUMN_MAP[col_o_key] + 1)
col_p_letter = self.sheet_handler._get_col_letter(COLUMN_MAP[col_p_key] + 1)
col_q_letter = self.sheet_handler._get_col_letter(COLUMN_MAP[col_q_key] + 1)
# Begründung kann optional in eine neue Spalte oder hier ins Log
# Fürs Erste nur Logging der Begründung.
# --- Konfiguration für Parallelverarbeitung ---
openai_sem = threading.Semaphore(getattr(Config, 'OPENAI_CONCURRENCY_LIMIT', 3))
max_workers = getattr(Config, 'MAX_BRANCH_WORKERS', 10) # Wiederverwendung der Branch Worker Config
processing_batch_size = getattr(Config, 'PROCESSING_BRANCH_BATCH_SIZE', 10) # Batch-Größe für Tasks
max_workers = getattr(Config, 'MAX_BRANCH_WORKERS', 10)
processing_batch_size = getattr(Config, 'PROCESSING_BRANCH_BATCH_SIZE', 10)
update_batch_row_limit = getattr(Config, 'UPDATE_BATCH_ROW_LIMIT', 50)
tasks_for_current_openai_batch = []
all_sheet_updates = []
processed_count = 0
skipped_count = 0
# Funktion zum Verarbeiten und Schreiben eines Batches
# Funktion zum Verarbeiten und Schreiben eines Batches (bleibt intern gleich)
def _execute_and_write_openai_batch(current_tasks):
nonlocal processed_count # Um processed_count im äußeren Scope zu modifizieren
# ... (Code der inneren Funktion bleibt identisch wie im vorherigen Vorschlag) ...
nonlocal processed_count
if not current_tasks:
return
@@ -8482,17 +8482,16 @@ class DataProcessor:
except Exception as e_future:
self.logger.error(f"Exception im Future für Parent Suggestion Zeile {task_info_orig['row_num']}: {e_future}")
batch_results_list.append({
"row_num": task_info_orig['row_num'],
"suggested_parent": "FEHLER_TASK",
"row_num": task_info_orig['row_num'],
"suggested_parent": "FEHLER_TASK",
"justification": str(e_future)[:150],
"error": str(e_future)
})
self.logger.debug(f" OpenAI Batch ({batch_start_log_row}-{batch_end_log_row}) abgeschlossen. {len(batch_results_list)} Ergebnisse erhalten.")
if batch_results_list:
now_ts_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
current_version_str = getattr(Config, 'VERSION', 'unknown')
updates_for_this_batch = []
for res_item in batch_results_list:
@@ -8500,33 +8499,29 @@ class DataProcessor:
parent_val = res_item.get('suggested_parent', 'k.A. (Fehler)')
updates_for_this_batch.append({'range': f'{col_o_letter}{rn}', 'values': [[parent_val]]})
# Nur auf "?" setzen, wenn ein Vorschlag gemacht wurde (nicht "k.A." oder Fehler)
status_val = "?" if parent_val and parent_val.lower() != "k.a." and not parent_val.startswith("FEHLER") else ""
updates_for_this_batch.append({'range': f'{col_p_letter}{rn}', 'values': [[status_val]]})
updates_for_this_batch.append({'range': f'{col_q_letter}{rn}', 'values': [[now_ts_str]]})
# Optional: Begründung in eine neue Spalte schreiben
# updates_for_this_batch.append({'range': f'{BEGRUENDUNG_SPALTE_LETTER}{rn}', 'values': [[res_item.get('justification', '')]]})
# Logge die Begründung, auch wenn sie nicht ins Sheet geschrieben wird
if res_item.get('justification'):
self.logger.debug(f"Zeile {rn} - Parent Begründung: {res_item.get('justification')[:200]}...")
all_sheet_updates.extend(updates_for_this_batch)
processed_count += len(current_tasks) # Erhöhe processed_count hier
processed_count += len(current_tasks) # Zähle hier, wenn Tasks tatsächlich an OpenAI gingen
# Sheet Updates in Batches senden
if len(all_sheet_updates) >= update_batch_row_limit * 3: # 3 Spalten pro Update (O, P, Q)
self.logger.info(f"Sende Batch-Updates für Parent Suggestions ({len(all_sheet_updates)//3} Zeilen)...")
self.sheet_handler.batch_update_cells(all_sheet_updates)
all_sheet_updates.clear()
if len(all_sheet_updates) >= update_batch_row_limit * 3:
self.logger.info(f"Sende Batch-Updates für Parent Suggestions ({len(all_sheet_updates)//3} Zeilen)...")
self.sheet_handler.batch_update_cells(all_sheet_updates)
all_sheet_updates.clear()
# Pause nach dem Batch
time.sleep(getattr(Config, 'RETRY_DELAY', 5) * 0.5)
# Ende der Hilfsfunktion _execute_and_write_openai_batch
# Hauptschleife über die Zeilen
for i in range(start_sheet_row, end_sheet_row + 1):
# Limit-Prüfung erfolgt jetzt innerhalb der _execute_and_write_openai_batch
# oder besser hier vor dem Sammeln von Tasks, um nicht unnötig zu iterieren.
if limit is not None and processed_count >= limit:
self.logger.info(f"Verarbeitungslimit ({limit}) für Parent Suggestions erreicht.")
break
@@ -8540,15 +8535,15 @@ class DataProcessor:
continue
# Kriterien für Verarbeitung
val_o = self._get_cell_value_safe(row, col_o_key).strip()
val_p = self._get_cell_value_safe(row, col_p_key).strip()
# val_q = self._get_cell_value_safe(row, col_q_key).strip() # Timestamp nicht direkt für Auswahl relevant
val_q_timestamp = self._get_cell_value_safe(row, col_q_key).strip() # Timestamp aus Spalte Q
val_p_status = self._get_cell_value_safe(row, col_p_key).strip() # Status aus Spalte P
needs_processing = False
if not val_o or val_o.lower() == "k.a.": # Spalte O ist leer oder k.A.
if not val_q_timestamp: # Spalte Q (Timestamp) ist leer
needs_processing = True
elif re_evaluate_question_mark and val_p == "?": # Neubewertung für Status "?"
elif re_evaluate_question_mark and val_p_status == "?": # Neubewertung für Status "?" auch wenn Timestamp Q gesetzt ist
needs_processing = True
self.logger.debug(f"Zeile {i}: Wird trotz vorhandenem Timestamp in Q ('{val_q_timestamp}') verarbeitet, da P='?' und re_evaluate_question_mark=True.")
if not needs_processing:
skipped_count += 1
@@ -8571,11 +8566,9 @@ class DataProcessor:
_execute_and_write_openai_batch(tasks_for_current_openai_batch)
tasks_for_current_openai_batch.clear()
# Letzten Batch verarbeiten
if tasks_for_current_openai_batch:
_execute_and_write_openai_batch(tasks_for_current_openai_batch)
# Letzte Sheet Updates senden
if all_sheet_updates:
self.logger.info(f"Sende finale Batch-Updates für Parent Suggestions ({len(all_sheet_updates)//3} Zeilen)...")
self.sheet_handler.batch_update_cells(all_sheet_updates)