v1.7.5: Plausibilitäts-Check Spalten für Finanzdaten hinzugefügt
- 6 neue Spalten für Plausibilitätsprüfungen (Umsatz, MA, Ratio, Abweichungen CRM/Wiki, Begründung) in COLUMN_MAP und alignment_demo eingefügt. - Indizes aller nachfolgenden Spalten angepasst. - Ziel: Detaillierte Kennzeichnung von Datenqualitätsproblemen.
This commit is contained in:
@@ -8,7 +8,7 @@ von Unternehmensdaten, primär aus einem Google Sheet, ergänzt durch Web Scrapi
|
||||
Wikipedia, OpenAI (ChatGPT) und SerpAPI (Google Search, LinkedIn).
|
||||
|
||||
Autor: Christian Godelmann
|
||||
Version: v1.7.4
|
||||
Version: v1.7.5
|
||||
|
||||
Hinweis zur Struktur:
|
||||
Dieser Code wird in logischen Bloecken uebermittelt. Fuegen Sie die Bloecke
|
||||
@@ -107,7 +107,7 @@ PATTERNS_FILE_JSON = "technician_patterns.json" # Neu (Empfohlen)
|
||||
# --- Globale Konfiguration Klasse ---
|
||||
class Config:
|
||||
"""Zentrale Konfigurationseinstellungen."""
|
||||
VERSION = "v1.7.4"
|
||||
VERSION = "v1.7.5"
|
||||
LANG = "de" # Sprache fuer Wikipedia etc.
|
||||
# ACHTUNG: SHEET_URL ist hier ein Platzhalter. Ersetzen Sie ihn durch Ihre tatsaechliche URL.
|
||||
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" # <<< ERSETZEN SIE DIES!
|
||||
@@ -253,11 +253,19 @@ COLUMN_MAP = {
|
||||
"Finaler Mitarbeiter (Wiki>CRM)": 51, # AZ (vorher AY)
|
||||
"Geschaetzter Techniker Bucket": 52, # BA (vorher AZ)
|
||||
|
||||
# Timestamps (Rest) & System (BB-BE) - VORHER BA-BD
|
||||
"Contact Search Timestamp": 53, # BB (vorher BA)
|
||||
"Timestamp letzte Pruefung": 54, # BC (vorher BB)
|
||||
"Version": 55, # BD (vorher BC)
|
||||
"Tokens": 56, # BE (vorher BD)
|
||||
# NEUE PLAUSIBILITÄTS-SPALTEN (BB-BG)
|
||||
"Plausibilität Umsatz": 53, # BB (NEU)
|
||||
"Plausibilität Mitarbeiter": 54, # BC (NEU)
|
||||
"Plausibilität Umsatz/MA Ratio": 55, # BD (NEU)
|
||||
"Abweichung Umsatz CRM/Wiki": 56, # BE (NEU)
|
||||
"Abweichung MA CRM/Wiki": 57, # BF (NEU)
|
||||
"Plausibilität Begründung": 58, # BG (NEU)
|
||||
|
||||
# Timestamps (Rest) & System (BH-BJ) - Indizes angepasst
|
||||
"Contact Search Timestamp": 59, # BH (vorher 53 -> BA)
|
||||
"Timestamp letzte Pruefung": 60, # BI (vorher 54 -> BB)
|
||||
"Version": 61, # BJ (vorher 55 -> BC)
|
||||
"Tokens": 62, # BK (vorher 56 -> BD) # Ups, hier war ein Fehler in meiner Zählung. 58+3=61, also BK
|
||||
}
|
||||
|
||||
# --- Globale Variablen fuer Branch Mapping (werden von load_target_schema() befuellt) ---
|
||||
@@ -2503,6 +2511,7 @@ def alignment_demo(sheet):
|
||||
"Chat Vorschlag Branche", "Chat Branche Konfidenz", "Chat Konsistenz Branche", "Chat Begruendung Abweichung Branche", "Chat Prüfung FSM Relevanz", "Chat Begründung für FSM Relevanz", "Chat Schätzung Anzahl Mitarbeiter", "Chat Konsistenzprüfung Mitarbeiterzahl", "Chat Begründung Abweichung Mitarbeiterzahl", "Chat Einschätzung Anzahl Servicetechniker", "Chat Begründung Abweichung Anzahl Servicetechniker", "Chat Schätzung Umsatz", "Chat Begründung Abweichung Umsatz",
|
||||
"Linked Serviceleiter gefunden", "Linked It-Leiter gefunden", "Linked Management gefunden", "Linked Disponent gefunden",
|
||||
"Finaler Umsatz (Wiki>CRM)", "Finaler Mitarbeiter (Wiki>CRM)", "Geschaetzter Techniker Bucket",
|
||||
"Plausibilität Umsatz", "Plausibilität Mitarbeiter", "Plausibilität Umsatz/MA Ratio", "Abweichung Umsatz CRM/Wiki", "Abweichung MA CRM/Wiki", "Plausibilität Begründung", # NEU
|
||||
"Contact Search Timestamp", "Timestamp letzte Prüfung", "Version", "Tokens"
|
||||
],
|
||||
[ # Zeile 2: Quelle der Daten
|
||||
@@ -2511,8 +2520,9 @@ def alignment_demo(sheet):
|
||||
"Web Scraper", "ChatGPT API", "Web Scraper", "System", "System/Web Scraper",
|
||||
"ChatGPT API", "ChatGPT API", "System/ChatGPT API", "ChatGPT API", "ChatGPT API", "ChatGPT API", "ChatGPT API", "System/ChatGPT API", "ChatGPT API", "ChatGPT API", "ChatGPT API", "ChatGPT API", "ChatGPT API",
|
||||
"LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)", "LinkedIn (via SerpApi)",
|
||||
"Skript (Wiki/CRM Logik)", "Skript (Wiki/CRM Logik)", "ML Modell / Skript",
|
||||
"System", "System", "System", "System"
|
||||
"Skript (Wiki/CRM Logik)", "Skript (Wiki/CRM Logik)", "ML Modell / Skript", "Skript (Wiki/CRM Logik)", "ML Modell / Skript",
|
||||
"Skript (Plausi-Check)", "Skript (Plausi-Check)", "Skript (Plausi-Check)", "Skript (Plausi-Check)", "Skript (Plausi-Check)", "Skript (Plausi-Check)", # NEU
|
||||
"System", "System", "System", "System", "System", "System", "System", "System"
|
||||
],
|
||||
[ # Zeile 3: Feldkategorie
|
||||
"Prozess", "Firmenname", "Firmenname", "Website", "Ort", "Land", "Beschreibung (Text)", "Branche", "Branche", "Anzahl Servicetechniker", "Umsatz", "Anzahl Mitarbeiter", "Wikipedia Artikel URL",
|
||||
@@ -2520,8 +2530,8 @@ def alignment_demo(sheet):
|
||||
"Website-Content", "Website-Content (Zusammenfassung)", "Website-Content (Meta)", "Timestamp", "Prozess-Status",
|
||||
"Branche (Vorschlag KI)", "Branche (Konfidenz KI)", "Branche (Konsistenz)", "Branche (Begründung KI)", "FSM Relevanz (KI)", "FSM Relevanz (Begründung KI)", "Anzahl Mitarbeiter (KI)", "Anzahl Mitarbeiter (Konsistenz KI)", "Anzahl Mitarbeiter (Begründung KI)", "Anzahl Servicetechniker (KI)", "Anzahl Servicetechniker (Begründung KI)", "Umsatz (KI)", "Umsatz (Begründung KI)",
|
||||
"Kontakte (Anzahl)", "Kontakte (Anzahl)", "Kontakte (Anzahl)", "Kontakte (Anzahl)",
|
||||
"Umsatz (Konsolidiert)", "Anzahl Mitarbeiter (Konsolidiert)", "Anzahl Servicetechniker (Bucket ML)",
|
||||
"Timestamp", "Timestamp", "Skript Version", "API Tokens"
|
||||
"Umsatz (Konsolidiert)", "Anzahl Mitarbeiter (Konsolidiert)", "Anzahl Servicetechniker (Bucket ML)", "Plausibilität", "Plausibilität", "Plausibilität", "Datenqualitäts-Indikator", "Datenqualitäts-Indikator", "Plausibilität (Text)", # NEU
|
||||
"Timestamp", "Timestamp", "Skript Version", "API Tokens", "Timestamp", "Timestamp", "Skript Version", "API Tokens"
|
||||
],
|
||||
[ # Zeile 4: Kurze Beschreibung
|
||||
"Systemspalte, irrelevant für den Prompt. 'x' markiert Zeile für Re-Evaluation.", #A
|
||||
@@ -2577,10 +2587,16 @@ def alignment_demo(sheet):
|
||||
"Konsolidierter Umsatzwert in Mio. €. Priorisiert Wiki (S) > CRM (K).", #AY
|
||||
"Konsolidierte Mitarbeiterzahl. Priorisiert Wiki (T) > CRM (L).", #AZ
|
||||
"Ergebnis der Schätzung durch das trainierte Machine-Learning-Modell.", #BA
|
||||
"Zeitstempel der letzten Kontaktsuche via SerpAPI für diese Zeile.", #BB
|
||||
"Zeitstempel der letzten übergreifenden Prüfung/Bewertung durch ChatGPT (z.B. Branchenevaluation AH-AT).", #BC
|
||||
"Version des Skripts, die diese Zeile zuletzt signifikant bearbeitet hat.", #BD
|
||||
"Verbrauchte Tokens für OpenAI API-Aufrufe für diese Zeile." #BE
|
||||
"Plausibilitätsstatus für den finalen Umsatzwert (z.B. OK, WARNUNG_HOCH).", #BB (NEU)
|
||||
"Plausibilitätsstatus für die finale Mitarbeiterzahl (z.B. OK, WARNUNG_NIEDRIG).", #BC (NEU)
|
||||
"Plausibilitätsstatus für die Umsatz-pro-Mitarbeiter-Ratio.", #BD (NEU)
|
||||
"Indikator für Abweichung zwischen CRM- und Wiki-Umsatz.", #BE (NEU)
|
||||
"Indikator für Abweichung zwischen CRM- und Wiki-Mitarbeiterzahl.", #BF (NEU)
|
||||
"Gesammelte Begründungen für Plausibilitätswarnungen oder -fehler.", #BG (NEU)
|
||||
"Zeitstempel der letzten Kontaktsuche via SerpAPI für diese Zeile.", #BH
|
||||
"Zeitstempel der letzten übergreifenden Prüfung/Bewertung durch ChatGPT (z.B. Branchenevaluation AH-AT).", #BI
|
||||
"Version des Skripts, die diese Zeile zuletzt signifikant bearbeitet hat.", #BJ
|
||||
"Verbrauchte Tokens für OpenAI API-Aufrufe für diese Zeile." #BK
|
||||
],
|
||||
[ # Zeile 5: Aufgabe / Funktion
|
||||
"Datenquelle/Prozesssteuerung: 'x' markiert Zeile für Re-Evaluation in Modus 'reeval'.", #A
|
||||
@@ -2636,10 +2652,16 @@ def alignment_demo(sheet):
|
||||
"Ziel: Konsolidierter Umsatz (Wiki-Wert (S) > CRM-Wert (K)). Input für ML-Modell.", #AY
|
||||
"Ziel: Konsolidierte Mitarbeiterzahl (Wiki-Wert (T) > CRM-Wert (L)). Input für ML-Modell.", #AZ
|
||||
"Ziel: Vom ML-Modell vorhergesagter Bucket für die Anzahl der Servicetechniker.", #BA
|
||||
"System: Timestamp der letzten Kontaktsuche (Modus 'contacts'). Steuert Wiederholung.", #BB
|
||||
"System: Timestamp der letzten übergreifenden ChatGPT-Evaluation (Branchen, FSM etc.). Steuert Wiederholung.", #BC
|
||||
"System: Skriptversion, die die Zeile zuletzt signifikant bearbeitet hat.", #BD
|
||||
"System: Verbrauchte Tokens für OpenAI API-Aufrufe für diese Zeile." #BE
|
||||
"Ziel: Kennzeichnung der Plausibilität des finalen Umsatzwertes.", #BB (NEU)
|
||||
"Ziel: Kennzeichnung der Plausibilität der finalen Mitarbeiterzahl.", #BC (NEU)
|
||||
"Ziel: Kennzeichnung der Plausibilität der Umsatz-pro-Mitarbeiter-Verhältniszahl.", #BD (NEU)
|
||||
"Ziel: Indikator zur Datenqualität bezüglich Umsatz CRM vs. Wiki.", #BE (NEU)
|
||||
"Ziel: Indikator zur Datenqualität bezüglich Mitarbeiter CRM vs. Wiki.", #BF (NEU)
|
||||
"Ziel: Zusammenfassung der Gründe für erkannte Plausibilitätsprobleme.", #BG (NEU)
|
||||
"System: Timestamp der letzten Kontaktsuche (Modus 'contacts'). Steuert Wiederholung.", #BH
|
||||
"System: Timestamp der letzten übergreifenden ChatGPT-Evaluation (Branchen, FSM etc.). Steuert Wiederholung.", #BI
|
||||
"System: Skriptversion, die die Zeile zuletzt signifikant bearbeitet hat.", #BJ
|
||||
"System: Verbrauchte Tokens für OpenAI API-Aufrufe für diese Zeile." #BK
|
||||
]
|
||||
]
|
||||
|
||||
@@ -4623,6 +4645,7 @@ class DataProcessor:
|
||||
if force_reeval: grund_message_parts.append('Re-Eval')
|
||||
if not self._get_cell_value_safe(row_data, "Timestamp letzte Pruefung").strip(): grund_message_parts.append('BC (Timestamp letzte Pruefung) leer') # Neuer Schlüssel BC
|
||||
if wiki_data_updated_in_this_run: grund_message_parts.append('Wiki Daten gerade aktualisiert')
|
||||
|
||||
grund_message = ", ".join(filter(None, grund_message_parts)) or "Unbekannter Grund"
|
||||
|
||||
self.logger.info(f"Zeile {row_num_in_sheet}: Fuehre CHATGPT Evaluationen aus (Grund: {grund_message})...")
|
||||
@@ -4700,36 +4723,68 @@ class DataProcessor:
|
||||
pass
|
||||
|
||||
# --- 3e. Konsolidierung Umsatz/Mitarbeiter (AY, AZ) ---
|
||||
# (Dieser Block bleibt wie im vorherigen Vorschlag, da er gut aussah)
|
||||
self.logger.debug(" -> Konsolidiere Umsatz (AY) und Mitarbeiter (AZ) (Wiki > CRM Logik)...")
|
||||
final_umsatz_str_konsolidiert = "k.A." # Default
|
||||
final_ma_str_konsolidiert = "k.A." # Default
|
||||
try:
|
||||
# ... (Logik für Konsolidierung wie gehabt) ...
|
||||
# ... (Ihre Konsolidierungslogik, die final_umsatz_str_konsolidiert und final_ma_str_konsolidiert setzt)
|
||||
crm_umsatz_val = self._get_cell_value_safe(row_data, "CRM Umsatz")
|
||||
wiki_umsatz_val = final_wiki_data.get('umsatz', 'k.A.')
|
||||
crm_ma_val = self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter")
|
||||
wiki_ma_val = final_wiki_data.get('mitarbeiter', 'k.A.')
|
||||
num_crm_umsatz = get_numeric_filter_value(crm_umsatz_val, is_umsatz=True)
|
||||
num_wiki_umsatz = get_numeric_filter_value(wiki_umsatz_val, is_umsatz=True)
|
||||
num_crm_ma = get_numeric_filter_value(crm_ma_val, is_umsatz=False)
|
||||
num_wiki_ma = get_numeric_filter_value(wiki_ma_val, is_umsatz=False)
|
||||
num_crm_umsatz = get_numeric_filter_value(crm_umsatz_val, is_umsatz=True) # Globale Funktion
|
||||
num_wiki_umsatz = get_numeric_filter_value(wiki_umsatz_val, is_umsatz=True) # Globale Funktion
|
||||
num_crm_ma = get_numeric_filter_value(crm_ma_val, is_umsatz=False) # Globale Funktion
|
||||
num_wiki_ma = get_numeric_filter_value(wiki_ma_val, is_umsatz=False) # Globale Funktion
|
||||
final_num_umsatz = num_wiki_umsatz if num_wiki_umsatz > 0 else num_crm_umsatz
|
||||
final_num_ma = num_wiki_ma if num_wiki_ma > 0 else num_crm_ma
|
||||
final_umsatz_str = str(int(round(final_num_umsatz))) if final_num_umsatz > 0 else 'k.A.'
|
||||
final_ma_str = str(int(round(final_num_ma))) if final_num_ma > 0 else 'k.A.'
|
||||
final_umsatz_str_konsolidiert = str(int(round(final_num_umsatz))) if final_num_umsatz > 0 else 'k.A.'
|
||||
final_ma_str_konsolidiert = str(int(round(final_num_ma))) if final_num_ma > 0 else 'k.A.'
|
||||
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_umsatz_str]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_ma_str]]})
|
||||
self.logger.debug(f" -> Konsolidiert: Umsatz={final_umsatz_str}, MA={final_ma_str}")
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_umsatz_str_konsolidiert]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [[final_ma_str_konsolidiert]]})
|
||||
self.logger.debug(f" -> Konsolidiert: Umsatz={final_umsatz_str_konsolidiert}, MA={final_ma_str_konsolidiert}")
|
||||
except Exception as e_consolidate:
|
||||
self.logger.error(f"FEHLER bei Konsolidierung Umsatz/Mitarbeiter fuer Zeile {row_num_in_sheet}: {e_consolidate}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER KONSO']]}) # Deutlicherer Fehler
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Umsatz (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER KONSO']]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Finaler Mitarbeiter (Wiki>CRM)"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER KONSO']]})
|
||||
pass
|
||||
final_umsatz_str_konsolidiert = "FEHLER KONSO" # Wichtig für Plausi-Check
|
||||
final_ma_str_konsolidiert = "FEHLER KONSO" # Wichtig für Plausi-Check
|
||||
|
||||
# Setze den Timestamp letzte Pruefung (BC), da die ChatGPT-Evaluationen liefen
|
||||
# --- NEU: 3f. Plausibilitäts-Checks durchführen (BB-BG) ---
|
||||
self.logger.debug(f" -> Führe Plausibilitäts-Checks für Zeile {row_num_in_sheet} durch...")
|
||||
try:
|
||||
# Erstelle ein Dictionary mit den benötigten Werten für den Plausi-Check
|
||||
plausi_input_data = {
|
||||
"Finaler Umsatz (Wiki>CRM)": final_umsatz_str_konsolidiert,
|
||||
"Finaler Mitarbeiter (Wiki>CRM)": final_ma_str_konsolidiert,
|
||||
"CRM Umsatz": self._get_cell_value_safe(row_data, "CRM Umsatz"),
|
||||
"Wiki Umsatz": final_wiki_data.get('umsatz', 'k.A.'), # Nutze den Wiki-Wert, der für Konsolidierung verwendet wurde
|
||||
"CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"),
|
||||
"Wiki Mitarbeiter": final_wiki_data.get('mitarbeiter', 'k.A.') # Nutze den Wiki-Wert, der für Konsolidierung verwendet wurde
|
||||
}
|
||||
plausi_results = self._check_financial_plausibility(plausi_input_data)
|
||||
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_umsatz_flag", "FEHLER")]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Mitarbeiter"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_ma_flag", "FEHLER")]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz/MA Ratio"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plaus_ratio_flag", "FEHLER")]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung Umsatz CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_umsatz_flag", "FEHLER")]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung MA CRM/Wiki"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("abweichung_ma_flag", "FEHLER")]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Begründung"] + 1)}{row_num_in_sheet}', 'values': [[plausi_results.get("plausi_begruendung_final", "Fehler bei Begründungserstellung")]]})
|
||||
self.logger.debug(f" -> Plausi-Ergebnisse: U:{plausi_results.get('plaus_umsatz_flag')} MA:{plausi_results.get('plaus_ma_flag')} Ratio:{plausi_results.get('plaus_ratio_flag')} AbwU:{plausi_results.get('abweichung_umsatz_flag')} AbwMA:{plausi_results.get('abweichung_ma_flag')}")
|
||||
except Exception as e_plausi:
|
||||
self.logger.error(f"FEHLER bei Plausibilitäts-Checks für Zeile {row_num_in_sheet}: {e_plausi}")
|
||||
# Setze Fehler-Flags, falls noch nicht geschehen
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz"] + 1)}{row_num_in_sheet}', 'values': [['FEHLER_CHECK']]})
|
||||
# ... (ähnlich für andere Plausi-Spalten) ...
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Begründung"] + 1)}{row_num_in_sheet}', 'values': [[f"Systemfehler Plausi-Check: {str(e_plausi)[:50]}"]]})
|
||||
|
||||
|
||||
# Setze den Timestamp letzte Pruefung (BI - vorher BB), da die ChatGPT-Evaluationen (und jetzt Plausi) liefen
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Timestamp letzte Pruefung"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]})
|
||||
|
||||
|
||||
# else if run_chat_step (aber nicht processing_needed):
|
||||
# self.logger.debug(f"Zeile {row_num_in_sheet}: Ueberspringe CHATGPT Evaluationen und Plausi-Checks (Timestamp BI gesetzt, Wiki nicht aktualisiert und kein Re-Eval).")
|
||||
|
||||
# else if run_chat_step:
|
||||
# Die Chat-Schritte waren angefordert, aber nicht noetig basierend auf Status/Re-Eval/Wiki-Update.
|
||||
@@ -7815,6 +7870,268 @@ class DataProcessor:
|
||||
self.logger.info(f"Sitz-Daten Reparatur abgeschlossen. {processed_rows_count} Zeilen geprüft, {updated_rows_count} Zeilen aktualisiert.")
|
||||
|
||||
|
||||
|
||||
def _get_numeric_value_for_plausi(self, value_str, is_umsatz=False):
|
||||
"""
|
||||
Hilfsfunktion, um einen String in einen numerischen Wert (float) für Plausi-Checks umzuwandeln.
|
||||
Gibt np.nan zurück, wenn der Wert nicht numerisch oder 'k.A.' ist.
|
||||
Unterscheidet sich von get_numeric_filter_value, da es float/nan für interne Berechnungen liefert.
|
||||
"""
|
||||
if value_str is None or pd.isna(value_str) or str(value_str).strip().lower() in ['', 'k.a.', 'n/a', '-']:
|
||||
return np.nan
|
||||
|
||||
# Nutze die bestehende Logik von extract_numeric_value, aber sorge für float/nan Output
|
||||
# extract_numeric_value gibt Strings oder "k.A." zurück. Wir brauchen hier Zahlen.
|
||||
|
||||
# Temporäre (vereinfachte) Konvertierung, die verbessert werden könnte,
|
||||
# um die volle Logik von extract_numeric_value (Einheiten etc.) zu nutzen,
|
||||
# aber sicherstellt, dass ein float oder np.nan zurückkommt.
|
||||
|
||||
# Erster Versuch, die Logik von extract_numeric_value zu adaptieren:
|
||||
raw_value_str_clean = str(value_str).strip()
|
||||
if not raw_value_str_clean: return np.nan
|
||||
|
||||
processed_value = clean_text(raw_value_str_clean) # Globale Funktion
|
||||
if processed_value.lower() in ['k.a.', 'n/a', '-']: return np.nan
|
||||
|
||||
processed_value = re.sub(r'(?i)^\s*(ca\.?|circa|rund|etwa|ueber|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()
|
||||
|
||||
processed_value_no_thousands = processed_value.replace('.', '').replace("'", "")
|
||||
processed_value_final = processed_value_no_thousands.replace(',', '.')
|
||||
|
||||
match = re.search(r'([\d.]+)', processed_value_final)
|
||||
if not match: return np.nan
|
||||
|
||||
num_str = match.group(1)
|
||||
try:
|
||||
if not num_str or num_str == '.' or num_str.count('.') > 1: raise ValueError
|
||||
num = float(num_str)
|
||||
|
||||
original_lower = raw_value_str_clean.lower()
|
||||
multiplier = 1.0
|
||||
if re.search(r'\bmrd\s*\b|\bmilliarden\s*\b|\bbillion\s*\b', original_lower): multiplier = 1000000000.0
|
||||
elif re.search(r'\bmio\s*\b|\bmillionen\s*\b|\bmill[.]?\s*\b', original_lower): multiplier = 1000000.0
|
||||
elif re.search(r'\btsd\s*\b|\btausend\s*\b', original_lower): multiplier = 1000.0
|
||||
|
||||
num = num * multiplier
|
||||
return num if num > 0 else np.nan # Nur positive Werte, sonst NaN
|
||||
|
||||
except ValueError:
|
||||
return np.nan
|
||||
|
||||
|
||||
def _check_financial_plausibility(self, row_data_dict):
|
||||
"""
|
||||
Prüft die Plausibilität der finalen Umsatz- und Mitarbeiterzahlen
|
||||
sowie deren Ratio und den Abgleich mit CRM-Daten.
|
||||
|
||||
Args:
|
||||
row_data_dict (dict): Ein Dictionary mit den relevanten Werten der Zeile,
|
||||
z.B. {'Finaler Umsatz (Wiki>CRM)': '123',
|
||||
'Finaler Mitarbeiter (Wiki>CRM)': '10',
|
||||
'CRM Umsatz': '120', 'Wiki Umsatz': '123', ...}
|
||||
Returns:
|
||||
dict: Ein Dictionary mit Plausibilitäts-Flags und einer Begründung.
|
||||
"""
|
||||
results = {
|
||||
"plaus_umsatz_flag": "OK",
|
||||
"plaus_ma_flag": "OK",
|
||||
"plaus_ratio_flag": "OK",
|
||||
"abweichung_umsatz_flag": "N/A", # N/A wenn nicht prüfbar
|
||||
"abweichung_ma_flag": "N/A", # N/A wenn nicht prüfbar
|
||||
"begruendungen": [] # Liste für mehrere Begründungen
|
||||
}
|
||||
|
||||
final_umsatz_str = row_data_dict.get("Finaler Umsatz (Wiki>CRM)", "k.A.")
|
||||
final_ma_str = row_data_dict.get("Finaler Mitarbeiter (Wiki>CRM)", "k.A.")
|
||||
|
||||
umsatz_num = self._get_numeric_value_for_plausi(final_umsatz_str, is_umsatz=True)
|
||||
ma_num = self._get_numeric_value_for_plausi(final_ma_str, is_umsatz=False)
|
||||
|
||||
# 1. Format-Plausibilität (wird durch _get_numeric_value_for_plausi schon behandelt)
|
||||
if pd.isna(umsatz_num) and final_umsatz_str.lower() not in ['k.a.', '', None]:
|
||||
results["plaus_umsatz_flag"] = "FEHLER_FORMAT"
|
||||
results["begruendungen"].append(f"Finaler Umsatz ('{final_umsatz_str}') nicht numerisch.")
|
||||
if pd.isna(ma_num) and final_ma_str.lower() not in ['k.a.', '', None]:
|
||||
results["plaus_ma_flag"] = "FEHLER_FORMAT"
|
||||
results["begruendungen"].append(f"Finale MA-Zahl ('{final_ma_str}') nicht numerisch.")
|
||||
|
||||
# Nur weiter prüfen, wenn Werte numerisch sind
|
||||
if not pd.isna(umsatz_num):
|
||||
if umsatz_num < getattr(Config, 'PLAUSI_UMSATZ_MIN_WARNUNG', 50000):
|
||||
results["plaus_umsatz_flag"] = "WARNUNG_NIEDRIG"
|
||||
results["begruendungen"].append(f"Finaler Umsatz ({umsatz_num:,.0f} €) sehr niedrig (< {getattr(Config, 'PLAUSI_UMSATZ_MIN_WARNUNG', 50000):,.0f} €).")
|
||||
if umsatz_num > getattr(Config, 'PLAUSI_UMSATZ_MAX_WARNUNG', 200000000000):
|
||||
results["plaus_umsatz_flag"] = "WARNUNG_HOCH"
|
||||
results["begruendungen"].append(f"Finaler Umsatz ({umsatz_num:,.0f} €) sehr hoch (> {getattr(Config, 'PLAUSI_UMSATZ_MAX_WARNUNG', 200000000000):,.0f} €).")
|
||||
|
||||
if not pd.isna(ma_num):
|
||||
if ma_num < getattr(Config, 'PLAUSI_MA_MIN_WARNUNG_ABS', 1):
|
||||
results["plaus_ma_flag"] = "WARNUNG_NIEDRIG"
|
||||
results["begruendungen"].append(f"Finale MA-Zahl ({ma_num:.0f}) < {getattr(Config, 'PLAUSI_MA_MIN_WARNUNG_ABS', 1)}.")
|
||||
if ma_num > getattr(Config, 'PLAUSI_MA_MAX_WARNUNG', 1000000):
|
||||
results["plaus_ma_flag"] = "WARNUNG_HOCH"
|
||||
results["begruendungen"].append(f"Finale MA-Zahl ({ma_num:.0f}) sehr hoch (> {getattr(Config, 'PLAUSI_MA_MAX_WARNUNG', 1000000)}).")
|
||||
|
||||
# Spezifischer Check: Wenig MA bei signifikantem Umsatz
|
||||
if not pd.isna(umsatz_num) and umsatz_num > getattr(Config, 'PLAUSI_UMSATZ_MIN_SCHWELLE_FUER_MA_CHECK', 1000000) and \
|
||||
ma_num < getattr(Config, 'PLAUSI_MA_MIN_WARNUNG_BEI_UMSATZ', 3):
|
||||
# Flag könnte auch 'WARNUNG_NIEDRIG' sein, aber Begründung ist spezifischer
|
||||
if results["plaus_ma_flag"] == "OK": results["plaus_ma_flag"] = "WARNUNG_RATIO_IMPL" # Implizite Ratio Warnung
|
||||
results["begruendungen"].append(f"Wenige MA ({ma_num:.0f}) bei signifikantem Umsatz ({umsatz_num:,.0f} €).")
|
||||
|
||||
# 3. Umsatz/MA Ratio
|
||||
if not pd.isna(umsatz_num) and not pd.isna(ma_num) and ma_num > 0: # Division durch Null vermeiden
|
||||
ratio = umsatz_num / ma_num
|
||||
if ratio < getattr(Config, 'PLAUSI_RATIO_UMSATZ_PRO_MA_MIN', 25000):
|
||||
results["plaus_ratio_flag"] = "WARNUNG_RATIO_NIEDRIG"
|
||||
results["begruendungen"].append(f"Umsatz/MA Ratio ({ratio:,.0f} €/MA) sehr niedrig (< {getattr(Config, 'PLAUSI_RATIO_UMSATZ_PRO_MA_MIN', 25000):,.0f}).")
|
||||
if ratio > getattr(Config, 'PLAUSI_RATIO_UMSATZ_PRO_MA_MAX', 1500000):
|
||||
results["plaus_ratio_flag"] = "WARNUNG_RATIO_HOCH"
|
||||
results["begruendungen"].append(f"Umsatz/MA Ratio ({ratio:,.0f} €/MA) sehr hoch (> {getattr(Config, 'PLAUSI_RATIO_UMSATZ_PRO_MA_MAX', 1500000):,.0f}).")
|
||||
elif not pd.isna(umsatz_num) and (pd.isna(ma_num) or ma_num == 0) and umsatz_num > 0 : # Umsatz vorhanden, aber keine MA
|
||||
results["plaus_ratio_flag"] = "FEHLER_RATIO_MA_NULL"
|
||||
results["begruendungen"].append("Ratio nicht berechenbar: Umsatz vorhanden, aber MA=0 oder nicht numerisch.")
|
||||
if results["plaus_ma_flag"] == "OK": results["plaus_ma_flag"] = "WARNUNG_NULL_BEI_UMSATZ"
|
||||
|
||||
|
||||
# 4. Abgleich CRM vs. Wiki
|
||||
crm_umsatz_str = row_data_dict.get("CRM Umsatz", "k.A.")
|
||||
wiki_umsatz_str = row_data_dict.get("Wiki Umsatz", "k.A.")
|
||||
crm_ma_str = row_data_dict.get("CRM Anzahl Mitarbeiter", "k.A.")
|
||||
wiki_ma_str = row_data_dict.get("Wiki Mitarbeiter", "k.A.")
|
||||
|
||||
crm_u = self._get_numeric_value_for_plausi(crm_umsatz_str, True)
|
||||
wiki_u = self._get_numeric_value_for_plausi(wiki_umsatz_str, True)
|
||||
crm_m = self._get_numeric_value_for_plausi(crm_ma_str, False)
|
||||
wiki_m = self._get_numeric_value_for_plausi(wiki_ma_str, False)
|
||||
|
||||
abweichung_prozent = getattr(Config, 'PLAUSI_ABWEICHUNG_CRM_WIKI_PROZENT', 30) / 100.0
|
||||
|
||||
# Umsatz Abweichung
|
||||
if not pd.isna(crm_u) and not pd.isna(wiki_u):
|
||||
if crm_u > 0 and wiki_u > 0 : # Nur prüfen, wenn beide positiv sind
|
||||
diff = abs(crm_u - wiki_u)
|
||||
max_val = max(crm_u, wiki_u)
|
||||
if max_val > 0 and (diff / max_val) > abweichung_prozent:
|
||||
results["abweichung_umsatz_flag"] = "WARNUNG_SIGNIFIKANT"
|
||||
results["begruendungen"].append(f"Umsatz CRM ({crm_u:,.0f}) vs. Wiki ({wiki_u:,.0f}) weicht >{abweichung_prozent*100:.0f}% ab.")
|
||||
else:
|
||||
results["abweichung_umsatz_flag"] = "OK"
|
||||
else: # Einer oder beide sind 0 oder negativ, was schon auffällig ist
|
||||
results["abweichung_umsatz_flag"] = "INFO_NULL_WERT"
|
||||
|
||||
elif pd.isna(crm_u) and not pd.isna(wiki_u): results["abweichung_umsatz_flag"] = "CRM_FEHLT"
|
||||
elif not pd.isna(crm_u) and pd.isna(wiki_u): results["abweichung_umsatz_flag"] = "WIKI_FEHLT"
|
||||
|
||||
# Mitarbeiter Abweichung
|
||||
if not pd.isna(crm_m) and not pd.isna(wiki_m):
|
||||
if crm_m > 0 and wiki_m > 0:
|
||||
diff = abs(crm_m - wiki_m)
|
||||
max_val = max(crm_m, wiki_m)
|
||||
if max_val > 0 and (diff / max_val) > abweichung_prozent:
|
||||
results["abweichung_ma_flag"] = "WARNUNG_SIGNIFIKANT"
|
||||
results["begruendungen"].append(f"MA CRM ({crm_m:.0f}) vs. Wiki ({wiki_m:.0f}) weicht >{abweichung_prozent*100:.0f}% ab.")
|
||||
else:
|
||||
results["abweichung_ma_flag"] = "OK"
|
||||
else:
|
||||
results["abweichung_ma_flag"] = "INFO_NULL_WERT"
|
||||
elif pd.isna(crm_m) and not pd.isna(wiki_m): results["abweichung_ma_flag"] = "CRM_FEHLT"
|
||||
elif not pd.isna(crm_m) and pd.isna(wiki_m): results["abweichung_ma_flag"] = "WIKI_FEHLT"
|
||||
|
||||
# Begründungen zusammenfassen
|
||||
if not results["begruendungen"]:
|
||||
results["plausi_begruendung_final"] = "Plausibilität OK"
|
||||
else:
|
||||
results["plausi_begruendung_final"] = "; ".join(results["begruendungen"])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def run_plausibility_checks_batch(self, start_sheet_row=None, end_sheet_row=None, limit=None):
|
||||
self.logger.info(f"Starte Modus 'Plausibilitäts-Checks'. Bereich: {start_sheet_row if start_sheet_row is not None else 'Datenstart'} bis {end_sheet_row if end_sheet_row else 'Sheet-Ende'}, Limit: {limit if limit is not None else 'Unbegrenzt'}")
|
||||
|
||||
if not self.sheet_handler.load_data():
|
||||
self.logger.error("Konnte Sheet-Daten nicht laden für Plausi-Checks. Abbruch.")
|
||||
return
|
||||
|
||||
all_data = self.sheet_handler.get_all_data_with_headers()
|
||||
header_offset = self.sheet_handler._header_rows
|
||||
|
||||
# Benötigte Spalten für den Input des Plausi-Checks
|
||||
required_input_keys = [
|
||||
"Finaler Umsatz (Wiki>CRM)", "Finaler Mitarbeiter (Wiki>CRM)",
|
||||
"CRM Umsatz", "Wiki Umsatz", "CRM Anzahl Mitarbeiter", "Wiki Mitarbeiter"
|
||||
]
|
||||
# Spalten für den Output
|
||||
output_keys = [
|
||||
"Plausibilität Umsatz", "Plausibilität Mitarbeiter", "Plausibilität Umsatz/MA Ratio",
|
||||
"Abweichung Umsatz CRM/Wiki", "Abweichung MA CRM/Wiki", "Plausibilität Begründung"
|
||||
]
|
||||
|
||||
# Prüfen, ob alle Keys da sind (vereinfacht)
|
||||
if not all(key in COLUMN_MAP for key in required_input_keys + output_keys):
|
||||
self.logger.error("Nicht alle benötigten Spalten für Plausi-Checks in COLUMN_MAP. Abbruch.")
|
||||
return
|
||||
|
||||
updates_fuer_sheet = []
|
||||
processed_rows_count = 0
|
||||
|
||||
effective_start_row = start_sheet_row if start_sheet_row is not None else header_offset + 1
|
||||
effective_end_row = end_sheet_row if end_sheet_row is not None else len(all_data)
|
||||
|
||||
for row_num_sheet in range(effective_start_row, effective_end_row + 1):
|
||||
if limit is not None and processed_rows_count >= limit:
|
||||
self.logger.info(f"Limit von {limit} Zeilen für Plausi-Checks erreicht.")
|
||||
break
|
||||
|
||||
row_list_idx = row_num_sheet - 1
|
||||
if row_list_idx >= len(all_data): break
|
||||
row_data = all_data[row_list_idx]
|
||||
|
||||
# Hole die Input-Daten für den Plausi-Check
|
||||
plausi_input_data = {}
|
||||
valid_input_for_check = True
|
||||
for key in required_input_keys:
|
||||
val = self._get_cell_value_safe(row_data, key)
|
||||
plausi_input_data[key] = val
|
||||
# Einfache Prüfung: Wenn die Haupt-Inputwerte (Finaler U/MA) fehlen, nicht prüfen
|
||||
if key in ["Finaler Umsatz (Wiki>CRM)", "Finaler Mitarbeiter (Wiki>CRM)"] and (not val or str(val).lower() == 'k.a.'):
|
||||
valid_input_for_check = False
|
||||
|
||||
if not valid_input_for_check:
|
||||
# self.logger.debug(f"Zeile {row_num_sheet}: Übersprungen, finale U/MA fehlen für Plausi-Check.")
|
||||
continue
|
||||
|
||||
processed_rows_count +=1
|
||||
|
||||
try:
|
||||
plausi_results = self._check_financial_plausibility(plausi_input_data)
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("plaus_umsatz_flag", "FEHLER")]]})
|
||||
# ... (alle anderen Plausi-Spalten hinzufügen) ...
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Mitarbeiter"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("plaus_ma_flag", "FEHLER")]]})
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Umsatz/MA Ratio"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("plaus_ratio_flag", "FEHLER")]]})
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung Umsatz CRM/Wiki"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("abweichung_umsatz_flag", "FEHLER")]]})
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Abweichung MA CRM/Wiki"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("abweichung_ma_flag", "FEHLER")]]})
|
||||
updates_fuer_sheet.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Plausibilität Begründung"] + 1)}{row_num_sheet}', 'values': [[plausi_results.get("plausi_begruendung_final", "Fehler Begr.")]]})
|
||||
|
||||
except Exception as e_plausi_run:
|
||||
self.logger.error(f"Fehler im Plausi-Check Lauf für Zeile {row_num_sheet}: {e_plausi_run}")
|
||||
# ... (Fehlerwerte in Plausi-Spalten schreiben) ...
|
||||
|
||||
if len(updates_fuer_sheet) >= getattr(Config, 'UPDATE_BATCH_ROW_LIMIT', 50) * 6: # 6 Spalten
|
||||
self.logger.info(f"Sende Plausi-Check Batch-Update ({len(updates_fuer_sheet)//6} Zeilen)...")
|
||||
self.sheet_handler.batch_update_cells(updates_fuer_sheet)
|
||||
updates_fuer_sheet = []
|
||||
|
||||
if updates_fuer_sheet:
|
||||
self.logger.info(f"Sende finalen Plausi-Check Batch-Update ({len(updates_fuer_sheet)//6} Zeilen)...")
|
||||
self.sheet_handler.batch_update_cells(updates_fuer_sheet)
|
||||
|
||||
self.logger.info(f"Plausibilitäts-Check-Lauf beendet. {processed_rows_count} Zeilen geprüft.")
|
||||
|
||||
# ==========================================================================
|
||||
# === Utility Methods (ML Data Prep & Training) ============================
|
||||
# ==========================================================================
|
||||
@@ -8015,6 +8332,8 @@ class DataProcessor:
|
||||
return f"FEHLER Schaetzung: {str(e)[:100]}..." # Signalisiert Fehler bei der Schaetzung (gekuerzt)
|
||||
|
||||
|
||||
|
||||
|
||||
# --- Methode zum Laden des ML Modells und Imputers ---
|
||||
# Diese Methode wird von _predict_technician_bucket (denselben Block) und train_technician_model (denselben Block) aufgerufen.
|
||||
# Sie laedt die serialisierten Modelle von der Festplatte.
|
||||
|
||||
Reference in New Issue
Block a user