bugfix
This commit is contained in:
@@ -8615,193 +8615,101 @@ class DataProcessor:
|
||||
# Nutzt interne Helfer: _get_cell_value_safe, _load_ml_model (denselben Block).
|
||||
# Nutzt globale Helfer: COLUMN_MAP (Block 1), logger, pandas, numpy, re, clean_text (Block 4), get_numeric_filter_value (Block 5).
|
||||
def _predict_technician_bucket(self, row_data):
|
||||
"""
|
||||
Fuehrt eine Vorhersage des Servicetechniker-Buckets fuer eine einzelne Zeile
|
||||
mit dem trainierten ML-Modell durch. Laedt das Modell und den Imputer bei Bedarf.
|
||||
company_name = self._get_cell_value_safe(row_data, 'CRM Name').strip()
|
||||
self.logger.debug(f"Versuche ML-Schaetzung fuer Zeile ({company_name[:50]}...)")
|
||||
|
||||
Args:
|
||||
row_data (list): Die Rohdaten fuer die Zeile.
|
||||
|
||||
Returns:
|
||||
str: Der vorhergesagte Bucket-Label oder "FEHLER Schaetzung" bei Fehler/kein Ergebnis.
|
||||
"""
|
||||
# Verwenden Sie logger, da das Logging jetzt konfiguriert ist
|
||||
# Logge den Start der ML-Schaetzung fuer diese Zeile
|
||||
company_name = self._get_cell_value_safe(row_data, 'CRM Name').strip() # Block 1 Column Map
|
||||
self.logger.debug(f"Versuche ML-Schaetzung fuer Zeile ({company_name[:50]}...)") # <<< GEÄNDERT
|
||||
|
||||
# Laden Sie das Modell, den Imputer und die erwarteten Feature-Spalten, falls noch nicht geschehen.
|
||||
# Diese werden als Attribute der DataProcessor Instanz gespeichert (_load_ml_model denselben Block).
|
||||
if self.model is None or self.imputer is None or self._expected_features is None:
|
||||
self.logger.info("Lade ML-Modell, Imputer und Feature-Spalten...") # <<< GEÄNDERT
|
||||
self.logger.info("Lade ML-Modell, Imputer und Feature-Spalten...")
|
||||
try:
|
||||
# Der Aufruf von _load_ml_model (denselben Block) ist nicht mit retry_on_failure dekoriert,
|
||||
# da das Laden lokaler Dateien nicht wiederholt werden muss. Fehler deuten auf ein permanentes Problem hin.
|
||||
self._load_ml_model(MODEL_FILE, IMPUTER_FILE) # Nutzt globale Konstanten (Block 1)
|
||||
|
||||
# Pruefe erneut, ob das Laden erfolgreich war.
|
||||
self._load_ml_model(MODEL_FILE, IMPUTER_FILE)
|
||||
if self.model is None or self.imputer is None or self._expected_features is None:
|
||||
self.logger.error("Laden von Modell, Imputer oder Feature-Spalten fehlgeschlagen. Kann ML-Schaetzung nicht durchfuehren.") # <<< GEÄNDERT
|
||||
return "FEHLER Schaetzung" # Gebe Fehlerwert zurueck, wenn Laden fehlschlug
|
||||
|
||||
self.logger.info("ML-Modell, Imputer und Feature-Spalten erfolgreich geladen.") # <<< GEÄNDERT
|
||||
|
||||
except Exception as e:
|
||||
# Fange Fehler beim Laden ab und logge sie.
|
||||
self.logger.error(f"FEHLER beim Laden von ML-Modell/Imputer/Feature-Spalten: {e}") # <<< GEÄNDERT
|
||||
# Logge den Traceback.
|
||||
self.logger.debug(traceback.format_exc()) # <<< GEÄNDERT
|
||||
# Geben Sie einen Fehlerwert zurueck.
|
||||
return f"FEHLER Laden: {str(e)[:100]}..." # Signalisiert Ladefehler (gekuerzt)
|
||||
|
||||
|
||||
# --- Bereiten Sie die Daten fuer DIESE EINE ZEILE fuer die Vorhersage vor ---
|
||||
self.logger.error("Laden von Modell, Imputer oder Feature-Spalten fehlgeschlagen. Kann ML-Schaetzung nicht durchfuehren.")
|
||||
return "FEHLER Schaetzung (Modell-Laden)"
|
||||
self.logger.info("ML-Modell, Imputer und Feature-Spalten erfolgreich geladen.")
|
||||
except Exception as e_load_model:
|
||||
self.logger.error(f"FEHLER beim Laden von ML-Modell/Imputer/Feature-Spalten: {e_load_model}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
return f"FEHLER Laden: {str(e_load_model)[:100]}..."
|
||||
try:
|
||||
# Diese Logik ist aehnlich wie in prepare_data_for_modeling (denselben Block),
|
||||
# aber nur fuer eine einzelne Zeile und muss mit den exakt gleichen
|
||||
# Spaltennamen, Normalisierungs- und Encoding-Schritten arbeiten wie das Training.
|
||||
# Hole die konsolidierten Werte aus der row_data (Spalten BD und BE)
|
||||
# Diese sollten durch vorherige Schritte (z.B. im Chat-Block von _process_single_row
|
||||
# oder durch run_plausibility_checks_batch) bereits befüllt sein.
|
||||
final_umsatz_val_str = self._get_cell_value_safe(row_data, "Finaler Umsatz (Wiki>CRM)")
|
||||
final_ma_val_str = self._get_cell_value_safe(row_data, "Finaler Mitarbeiter (Wiki>CRM)")
|
||||
branche_val_str = self._get_cell_value_safe(row_data, "Chat Vorschlag Branche") # Oder CRM Branche, je nach Trainingsdaten
|
||||
|
||||
# Hole die benoetigten Spaltenwerte fuer diese Zeile (basierend auf COLUMN_MAP keys Block 1)
|
||||
row_values = {
|
||||
# "CRM Name": self._get_cell_value_safe(row_data, "CRM Name"), # Nicht benoetigt fuer Vorhersage
|
||||
"CRM Branche": self._get_cell_value_safe(row_data, "CRM Branche"), # Block 1 Column Map
|
||||
"CRM Umsatz": self._get_cell_value_safe(row_data, "CRM Umsatz"), # Block 1 Column Map
|
||||
"Wiki Umsatz": self._get_cell_value_safe(row_data, "Wiki Umsatz"), # Block 1 Column Map
|
||||
"CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"), # Block 1 Column Map
|
||||
"Wiki Mitarbeiter": self._get_cell_value_safe(row_data, "Wiki Mitarbeiter"), # Block 1 Column Map
|
||||
# Technikerzahl wird fuer die Vorhersage NICHT benoetigt
|
||||
# "CRM Anzahl Techniker": self._get_cell_value_safe(row_data, "CRM Anzahl Techniker"),
|
||||
# Numerische Werte für Vorhersage holen
|
||||
# Wichtig: get_numeric_filter_value gibt 0 zurück, was wir als NaN für Imputation wollen
|
||||
umsatz_for_pred = get_numeric_filter_value(final_umsatz_val_str, is_umsatz=True)
|
||||
ma_for_pred = get_numeric_filter_value(final_ma_val_str, is_umsatz=False)
|
||||
|
||||
umsatz_for_pred = np.nan if umsatz_for_pred == 0 else umsatz_for_pred
|
||||
ma_for_pred = np.nan if ma_for_pred == 0 else ma_for_pred
|
||||
|
||||
# Erstelle das 'is_part_of_group' Feature
|
||||
parent_d_val = self._get_cell_value_safe(row_data, "Parent Account Name").strip().lower()
|
||||
parent_o_val = self._get_cell_value_safe(row_data, "System Vorschlag Parent Account").strip().lower()
|
||||
parent_p_val = self._get_cell_value_safe(row_data, "Parent Vorschlag Status").strip().lower()
|
||||
|
||||
cond1_pred = bool(parent_d_val and parent_d_val != 'k.a.')
|
||||
cond2_o_pred = bool(parent_o_val and parent_o_val != 'k.a.')
|
||||
cond2_p_pred = parent_p_val == 'x'
|
||||
cond2_pred = cond2_o_pred and cond2_p_pred
|
||||
is_group_val = 1 if cond1_pred | cond2_pred else 0
|
||||
|
||||
# Erstelle einen DataFrame für diese eine Zeile mit den Spaltennamen,
|
||||
# die für das One-Hot-Encoding und die Angleichung an _expected_features benötigt werden.
|
||||
# Die Namen hier MÜSSEN mit den internen Namen aus prepare_data_for_modeling übereinstimmen,
|
||||
# BEVOR das One-Hot-Encoding dort stattfand (für Branche) und NACH der Konsolidierung.
|
||||
single_row_dict = {
|
||||
'Finaler_Umsatz_ML': [umsatz_for_pred], # Konsolidierter Name aus prepare_data_for_modeling
|
||||
'Finaler_Mitarbeiter_ML': [ma_for_pred], # Konsolidierter Name aus prepare_data_for_modeling
|
||||
'branche_crm': [str(branche_val_str).strip() if branche_val_str else 'Unbekannt'], # Interner Name vor One-Hot
|
||||
'is_part_of_group': [is_group_val]
|
||||
}
|
||||
|
||||
# Erstellen Sie einen temporaeren DataFrame fuer diese eine Zeile aus den extrahierten Werten
|
||||
df_single_row = pd.DataFrame([row_values])
|
||||
|
||||
|
||||
# --- Konsolidieren Umsatz/Mitarbeiter (Wiki > CRM) ---
|
||||
# Nutzt globale Funktion get_numeric_filter_value (Block 5) - ERSETZT get_valid_numeric
|
||||
# Diese Funktion gibt numerische Werte (Float/Int) oder 0/NaN zurueck.
|
||||
# Stellen Sie sicher, dass die Spalten existieren, bevor apply aufgerufen wird.
|
||||
# Diese Spalten sollten aus row_values extrahiert worden sein, wenn COLUMN_MAP korrekt ist.
|
||||
crm_umsatz_series = df_single_row['CRM Umsatz'].apply(lambda x: get_numeric_filter_value(x, is_umsatz=True)) if 'CRM Umsatz' in df_single_row.columns else pd.Series(np.nan, index=df_single_row.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
wiki_umsatz_series = df_single_row['Wiki Umsatz'].apply(lambda x: get_numeric_filter_value(x, is_umsatz=True)) if 'Wiki Umsatz' in df_single_row.columns else pd.Series(np.nan, index=df_single_row.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
crm_ma_series = df_single_row['CRM Anzahl Mitarbeiter'].apply(lambda x: get_numeric_filter_value(x, is_umsatz=False)) if 'CRM Anzahl Mitarbeiter' in df_single_row.columns else pd.Series(np.nan, index=df_single_row.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
wiki_ma_series = df_single_row['Wiki Mitarbeiter'].apply(lambda x: get_numeric_filter_value(x, is_umsatz=False)).astype(float) if 'Wiki Mitarbeiter' in df_single_row.columns else pd.Series(np.nan, index=df_single_row.index) # KORRIGIERT: Lambda hinzugefügt
|
||||
|
||||
# np.where waehlt den Wiki-Wert, wenn er nicht 0/NaN ist, sonst den CRM-Wert.
|
||||
# WICHTIG: 0 ist hier das Kennzeichen fuer ungueltig/nicht parsebar/k.A. in get_numeric_filter_value
|
||||
df_single_row['Finaler_Umsatz'] = np.where(
|
||||
(wiki_umsatz_series.notna()) & (wiki_umsatz_series > 0), # Wenn Wiki-Wert vorhanden UND > 0
|
||||
wiki_umsatz_series,
|
||||
crm_umsatz_series
|
||||
)
|
||||
|
||||
df_single_row['Finaler_Mitarbeiter'] = np.where(
|
||||
(wiki_ma_series.notna()) & (wiki_ma_series > 0), # Wenn Wiki-Wert vorhanden UND > 0
|
||||
wiki_ma_series,
|
||||
crm_ma_series
|
||||
)
|
||||
|
||||
# Pruefen Sie, ob die konsolidierten numerischen Features NaN sind (nachdem 0 als NaN behandelt wird).
|
||||
# Ersetzen Sie 0 explizit durch NaN für die Imputation, falls get_numeric_filter_value 0 zurückgibt.
|
||||
df_single_row['Finaler_Umsatz'] = df_single_row['Finaler_Umsatz'].replace(0, np.nan)
|
||||
df_single_row['Finaler_Mitarbeiter'] = df_single_row['Finaler_Mitarbeiter'].replace(0, np.nan)
|
||||
|
||||
|
||||
# ML-Vorhersage kann nicht durchgefuehrt werden, wenn diese komplett fehlen (werden vom Imputer erwartet).
|
||||
if pd.isna(df_single_row['Finaler_Umsatz'].iloc[0]) and pd.isna(df_single_row['Finaler_Mitarbeiter'].iloc[0]):
|
||||
self.logger.debug(f" -> ML-Schaetzung uebersprungen: Konsolidierter Umsatz und Mitarbeiter fehlen fuer Zeile.") # <<< GEÄNDERT
|
||||
return "k.A. (Daten fehlen)" # Gebe spezifischen Wert zurueck
|
||||
|
||||
|
||||
# --- Kategoriale Features (Branche) ---
|
||||
branche_col_name = "CRM Branche" # Original Header Name aus COLUMN_MAP (Block 1)
|
||||
# Stellen Sie sicher, dass die Spalte existiert und ein String ist. Fuellen Sie NaNs mit 'Unbekannt'.
|
||||
if branche_col_name not in df_single_row.columns:
|
||||
self.logger.warning(f"Spalte '{branche_col_name}' nicht im DataFrame fuer ML-Vorhersage gefunden. Behandle als 'Unbekannt'.") # <<< GEÄNDERT
|
||||
df_single_row[branche_col_name] = 'Unbekannt' # Setze einen Default-Wert
|
||||
|
||||
df_single_row[branche_col_name] = df_single_row[branche_col_name].astype(str).fillna('Unbekannt').str.strip()
|
||||
|
||||
|
||||
# One-Hot Encoding
|
||||
# WICHTIG: Muss alle BRANCHEN aus dem TRAININGSDATENSATZ (self._expected_features) enthalten,
|
||||
# auch wenn diese in der einzelnen Zeile nicht vorkommen.
|
||||
# pd.get_dummies erstellt Spalten nur fuer die Kategorien in df_single_row.
|
||||
df_encoded = pd.get_dummies(df_single_row, columns=[branche_col_name], prefix='Branche', dummy_na=False) # dummy_na=False, da NaNs gefuellt
|
||||
|
||||
|
||||
# Fugen Sie fehlende Feature-Spalten hinzu (die im Training vorhanden waren, aber in dieser Zeile nicht).
|
||||
# Stellen Sie die Reihenfolge der Spalten sicher, so wie sie im Training waren (self._expected_features).
|
||||
# self._expected_features wird von _load_ml_model (denselben Block) geladen.
|
||||
if self._expected_features is None:
|
||||
self.logger.error("FEHLER: Erwartete Feature-Spalten fuer ML-Vorhersage nicht geladen. Kann nicht vorhersagen.") # <<< GEÄNDERT
|
||||
return "FEHLER Schaetzung" # Gebe Fehlerwert zurueck
|
||||
|
||||
# Erstellen Sie einen neuen DataFrame mit allen erwarteten Features und fuellen Sie fehlende mit 0.
|
||||
# Sicherstellen, dass die Spalten im Ergebnis-DF in der Reihenfolge von self._expected_features sind.
|
||||
df_single_row = pd.DataFrame.from_dict(single_row_dict)
|
||||
|
||||
# One-Hot Encoding (konsistent zum Training)
|
||||
# Wichtig: 'branche_crm' muss der Name sein, der im Training one-hot-encodiert wurde.
|
||||
df_encoded = pd.get_dummies(df_single_row, columns=['branche_crm'], prefix='Branche', dummy_na=False)
|
||||
|
||||
# Angleichung an die im Training verwendeten Features (self._expected_features)
|
||||
df_processed = pd.DataFrame(columns=self._expected_features)
|
||||
# Kopieren Sie die Werte aus df_encoded, wo Spalten uebereinstimmen.
|
||||
for col in self._expected_features:
|
||||
if col in df_encoded.columns:
|
||||
df_processed[col] = df_encoded[col]
|
||||
else:
|
||||
df_processed[col] = 0 # Fuege fehlende Spalten mit 0 hinzu
|
||||
|
||||
# Stellen Sie sicher, dass die numerischen Spalten Float sind (Imputer erwartet das oft)
|
||||
numeric_features_for_imputation = ['Finaler_Umsatz', 'Finaler_Mitarbeiter']
|
||||
for col in numeric_features_for_imputation:
|
||||
if col in df_processed.columns:
|
||||
df_processed[col] = pd.to_numeric(df_processed[col], errors='coerce') # Wandelt NaN in NaN, Fehler in NaN
|
||||
# Wenn eine One-Hot-Spalte aus dem Training hier fehlt, wird sie mit 0 gefüllt
|
||||
# Wenn 'Finaler_Umsatz_ML', 'Finaler_Mitarbeiter_ML' oder 'is_part_of_group' fehlen würde,
|
||||
# wäre das ein Fehler in _expected_features oder der Logik hier.
|
||||
df_processed[col] = 0
|
||||
|
||||
# Numerische Features zu numerischem Typ konvertieren für Imputer
|
||||
numeric_cols_to_impute_pred = ['Finaler_Umsatz_ML', 'Finaler_Mitarbeiter_ML']
|
||||
for col in numeric_cols_to_impute_pred:
|
||||
if col in df_processed.columns:
|
||||
df_processed[col] = pd.to_numeric(df_processed[col], errors='coerce')
|
||||
else: # Sollte nicht passieren, wenn _expected_features korrekt ist
|
||||
self.logger.warning(f"Erwartetes numerisches Feature '{col}' nicht in df_processed für Vorhersage gefunden.")
|
||||
df_processed[col] = np.nan
|
||||
|
||||
|
||||
# --- Imputation der fehlenden Werte ---
|
||||
# Muss konsistent mit dem Imputer aus dem Training sein.
|
||||
# Der Imputer (self.imputer) wird auf die vorbereiteten Features angewendet.
|
||||
if self.imputer is None:
|
||||
self.logger.error("FEHLER: ML-Imputer ist nicht geladen. Kann nicht imputieren/vorhersagen.") # <<< GEÄNDERT
|
||||
return "FEHLER Schaetzung" # Gebe Fehlerwert zurueck
|
||||
|
||||
# Imputer.transform gibt ein Numpy Array zurueck.
|
||||
df_imputed_array = self.imputer.transform(df_processed)
|
||||
# Konvertiere das Ergebnis zurueck zu einem DataFrame mit den erwarteten Spaltennamen.
|
||||
# Imputation
|
||||
df_imputed_array = self.imputer.transform(df_processed[self._expected_features]) # Stelle sicher, dass Spaltenreihenfolge passt
|
||||
df_imputed = pd.DataFrame(df_imputed_array, columns=self._expected_features)
|
||||
|
||||
# Optional: Pruefen Sie, ob nach Imputation NaNs verbleiben (sollte nicht passieren bei SimpleImputer)
|
||||
# if df_imputed.isna().any().any():
|
||||
# self.logger.warning("WARNUNG: NaNs verbleiben nach Imputation.") # <<< GEÄNDERT
|
||||
|
||||
|
||||
# --- Vorhersage ---
|
||||
# Das Decision Tree Modell (self.model) erwartet die vorbereiteten und imputierten Features.
|
||||
if not self.model:
|
||||
self.logger.error("FEHLER: ML-Modell ist nicht geladen. Kann nicht vorhersagen.") # <<< GEÄNDERT
|
||||
return "FEHLER Schaetzung" # Gebe Fehlerwert zurueck
|
||||
|
||||
|
||||
# Fuehren Sie die Vorhersage durch.
|
||||
# predict_proba gibt die Wahrscheinlichkeiten fuer jede Klasse zurueck.
|
||||
# Vorhersage
|
||||
prediction_proba = self.model.predict_proba(df_imputed)
|
||||
# prediction_proba ist ein Array von Wahrscheinlichkeiten pro Klasse fuer jede Eingabezeile (hier nur 1 Zeile).
|
||||
|
||||
# Die Klassen-Labels des Modells (z.B. ['Bucket_1', 'Bucket_2', ...])
|
||||
model_classes = self.model.classes_
|
||||
|
||||
# Finden Sie den Index der Klasse mit der hoechsten Wahrscheinlichkeit fuer die erste (und einzige) Zeile.
|
||||
predicted_class_index = np.argmax(prediction_proba[0])
|
||||
# Holen Sie das entsprechende Label aus den Modell-Klassen.
|
||||
predicted_bucket_label = model_classes[predicted_class_index]
|
||||
|
||||
# Logge die Vorhersage auf Debug-Level
|
||||
self.logger.debug(f" -> ML Vorhersage Ergebnis: '{predicted_bucket_label}' (Wahrscheinlichkeiten: {prediction_proba[0]})") # <<< GEÄNDERT
|
||||
self.logger.debug(f" -> ML Vorhersage Ergebnis: '{predicted_bucket_label}' (Wahrscheinlichkeiten: {prediction_proba[0]})")
|
||||
return predicted_bucket_label
|
||||
|
||||
|
||||
return predicted_bucket_label # Gebe das vorhergesagte Bucket-Label zurueck (String)
|
||||
|
||||
except Exception as e:
|
||||
# Fange alle unerwarteten Fehler ab, die waehrend der Datenvorbereitung oder Vorhersage auftreten.
|
||||
self.logger.exception(f"FEHLER bei der Datenvorbereitung/Vorhersage fuer Zeile (ML): {e}") # <<< GEÄNDERT
|
||||
# Geben Sie einen Fehlerwert zurueck, der im Sheet gespeichert werden kann.
|
||||
return f"FEHLER Schaetzung: {str(e)[:100]}..." # Signalisiert Fehler bei der Schaetzung (gekuerzt)
|
||||
except Exception as e_predict:
|
||||
self.logger.exception(f"FEHLER bei der ML-Vorhersage für Zeile ({company_name[:50]}...): {e_predict}")
|
||||
return f"FEHLER Schaetzung: {str(e_predict)[:100]}..."
|
||||
|
||||
|
||||
|
||||
@@ -9008,6 +8916,44 @@ class DataProcessor:
|
||||
df = pd.DataFrame(data_rows, columns=headers)
|
||||
self.logger.info(f"Initialen DataFrame fuer Modellierung erstellt mit {len(df)} Zeilen und {len(df.columns)} Spalten.") # <<< GEÄNDERT
|
||||
|
||||
|
||||
# DACH-Filter (basierend auf CRM Land - Spalte G)
|
||||
crm_land_col_header = headers[COLUMN_MAP["CRM Land"]] # Holt den tatsächlichen Spaltennamen
|
||||
# Erlaubte Werte für DACH-Länder (Groß- und Kleinschreibung wird durch .str.upper() behandelt)
|
||||
dach_countries = ["DE", "CH", "AT", "DEUTSCHLAND", "ÖSTERREICH", "SCHWEIZ", "OESTERREICH"] # OESTERREICH hinzugefügt
|
||||
|
||||
# Sicherstellen, dass die Spalte existiert, bevor gefiltert wird
|
||||
if crm_land_col_header in df.columns:
|
||||
df = df[df[crm_land_col_header].astype(str).str.upper().isin(dach_countries)].copy() # .copy() um Warnung zu vermeiden
|
||||
self.logger.info(f"Nach DACH-Filter (basierend auf '{crm_land_col_header}'): {len(df)} Zeilen verbleiben.")
|
||||
if df.empty:
|
||||
self.logger.error("Keine DACH-Unternehmen im Datensatz nach Filterung.")
|
||||
return None
|
||||
else:
|
||||
self.logger.error(f"Spalte '{crm_land_col_header}' für DACH-Filter nicht im DataFrame gefunden.")
|
||||
return None
|
||||
|
||||
# Plausibilitätsfilter (basierend auf Spalten BG und BH)
|
||||
plausi_umsatz_col_header = headers[COLUMN_MAP["Plausibilität Umsatz"]]
|
||||
plausi_ma_col_header = headers[COLUMN_MAP["Plausibilität Mitarbeiter"]]
|
||||
|
||||
# Sicherstellen, dass die Spalten existieren
|
||||
if plausi_umsatz_col_header in df.columns and plausi_ma_col_header in df.columns:
|
||||
# Filtere Zeilen, bei denen Plausi-Umsatz oder Plausi-MA einen Fehler anzeigt
|
||||
# Hier gehen wir davon aus, dass Fehler mit "FEHLER_" beginnen.
|
||||
df = df[~df[plausi_umsatz_col_header].astype(str).str.upper().str.startswith('FEHLER')].copy()
|
||||
df = df[~df[plausi_ma_col_header].astype(str).str.upper().str.startswith('FEHLER')].copy()
|
||||
self.logger.info(f"Nach Entfernung von FEHLER-Plausi-Fällen (BG, BH): {len(df)} Zeilen verbleiben.")
|
||||
if df.empty:
|
||||
self.logger.error("Keine Zeilen nach Plausi-Filterung übrig.")
|
||||
return None
|
||||
# Hier könnten Sie noch spezifischere Filter für bestimmte WARNUNG-Typen einbauen, falls gewünscht
|
||||
else:
|
||||
self.logger.error(f"Plausibilitätsspalten '{plausi_umsatz_col_header}' oder '{plausi_ma_col_header}' nicht im DataFrame gefunden.")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# --- Spaltenauswahl und Umbenennung ---
|
||||
# Definiere die notwendigen Spalten anhand ihrer COLUMN_MAP Schluessel (Block 1)
|
||||
# und weisen ihnen interne, einfachere Namen zu, die im DataFrame verwendet werden.
|
||||
|
||||
Reference in New Issue
Block a user