bugfix
This commit is contained in:
@@ -8618,91 +8618,74 @@ class DataProcessor:
|
||||
|
||||
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...")
|
||||
try:
|
||||
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.")
|
||||
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]}..."
|
||||
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.")
|
||||
return "FEHLER Schaetzung (Modell-Laden)"
|
||||
try:
|
||||
# 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.
|
||||
# === Feature Erstellung (muss exakt zum Training passen!) ===
|
||||
|
||||
# 1. Konsolidierte numerische Werte holen
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 2. 'is_part_of_group' Feature erstellen
|
||||
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
|
||||
cond2_pred = bool(parent_o_val and parent_o_val != 'k.a.' and parent_p_val == 'x')
|
||||
is_group_val = 1 if cond1_pred or cond2_pred else 0
|
||||
|
||||
# 3. Zusätzliche Features (Ratio, Log) erstellen
|
||||
# Log-Transformationen
|
||||
log_umsatz_val = np.log1p(umsatz_for_pred) if pd.notna(umsatz_for_pred) else np.nan
|
||||
log_ma_val = np.log1p(ma_for_pred) if pd.notna(ma_for_pred) else np.nan
|
||||
|
||||
# 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.
|
||||
# Umsatz pro MA
|
||||
umsatz_pro_ma_val = np.nan
|
||||
if pd.notna(umsatz_for_pred) and pd.notna(ma_for_pred) and ma_for_pred > 0:
|
||||
umsatz_pro_ma_val = umsatz_for_pred / ma_for_pred
|
||||
|
||||
# 4. Branchen-Feature holen
|
||||
# Wichtig: Hier die gleiche Branchenspalte wie im Training verwenden!
|
||||
branche_val_str = self._get_cell_value_safe(row_data, "CRM Branche")
|
||||
|
||||
# DataFrame mit einer Zeile und den internen Namen (wie in prepare_data_for_modeling) erstellen
|
||||
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]
|
||||
'Log_Finaler_Umsatz_ML': [log_umsatz_val],
|
||||
'Log_Finaler_Mitarbeiter_ML': [log_ma_val],
|
||||
'Umsatz_pro_MA_ML': [umsatz_pro_ma_val],
|
||||
'is_part_of_group': [is_group_val],
|
||||
'branche_crm': [str(branche_val_str).strip() if branche_val_str else 'Unbekannt']
|
||||
}
|
||||
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.
|
||||
# One-Hot Encoding
|
||||
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)
|
||||
# Angleichung an die im Training verwendeten Features
|
||||
# Erstelle einen DataFrame mit einer Zeile und den erwarteten Spalten
|
||||
data_for_df_processed = {col: [0] for col in self._expected_features}
|
||||
for col in self._expected_features:
|
||||
if col in df_encoded.columns:
|
||||
df_processed[col] = df_encoded[col]
|
||||
else:
|
||||
# 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
|
||||
if col in df_encoded.columns:
|
||||
data_for_df_processed[col] = [df_encoded[col].iloc[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
|
||||
df_processed = pd.DataFrame(data_for_df_processed, columns=self._expected_features)
|
||||
|
||||
# Imputation und Vorhersage
|
||||
df_imputed_array = self.imputer.transform(df_processed)
|
||||
|
||||
prediction_proba = self.model.predict_proba(df_imputed_array)
|
||||
predicted_bucket_label = self.model.classes_[np.argmax(prediction_proba[0])]
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# Vorhersage
|
||||
prediction_proba = self.model.predict_proba(df_imputed)
|
||||
model_classes = self.model.classes_
|
||||
predicted_class_index = np.argmax(prediction_proba[0])
|
||||
predicted_bucket_label = model_classes[predicted_class_index]
|
||||
|
||||
self.logger.debug(f" -> ML Vorhersage Ergebnis: '{predicted_bucket_label}' (Wahrscheinlichkeiten: {prediction_proba[0]})")
|
||||
self.logger.debug(f" -> ML Vorhersage Ergebnis: '{predicted_bucket_label}'")
|
||||
return predicted_bucket_label
|
||||
|
||||
except Exception as e_predict:
|
||||
@@ -8712,6 +8695,7 @@ class DataProcessor:
|
||||
|
||||
|
||||
|
||||
|
||||
# --- 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.
|
||||
@@ -9038,7 +9022,7 @@ class DataProcessor:
|
||||
|
||||
|
||||
|
||||
# --- Features konsolidieren (Umsatz, Mitarbeiter) ---
|
||||
# --- Features konsolidieren (Umsatz, Mitarbeiter) ---
|
||||
self.logger.debug("Konsolidiere Umsatz und Mitarbeiter für ML-Features...")
|
||||
cols_to_process_ml = {
|
||||
'Umsatz': ('umsatz_wiki', 'umsatz_crm', 'Finaler_Umsatz_ML'),
|
||||
@@ -9166,12 +9150,12 @@ class DataProcessor:
|
||||
return df_model_ready
|
||||
|
||||
|
||||
# Methode zum Trainieren des ML Modells
|
||||
# Nutzt interne Methode: prepare_data_for_modeling.
|
||||
# Nutzt globale Helfer: MODEL_FILE, IMPUTER_FILE, PATTERNS_FILE_JSON (Block 1),
|
||||
# logger, pickle, json, os,
|
||||
# train_test_split, SimpleImputer, DecisionTreeClassifier,
|
||||
# accuracy_score, classification_report, confusion_matrix, export_text (sklearn).
|
||||
# Methode zum Trainieren des ML Modells
|
||||
# Nutzt interne Methode: prepare_data_for_modeling.
|
||||
# Nutzt globale Helfer: MODEL_FILE, IMPUTER_FILE, PATTERNS_FILE_JSON (Block 1),
|
||||
# logger, pickle, json, os,
|
||||
# train_test_split, SimpleImputer, DecisionTreeClassifier,
|
||||
# accuracy_score, classification_report, confusion_matrix, export_text (sklearn).
|
||||
def train_technician_model(self, model_out=MODEL_FILE, imputer_out=IMPUTER_FILE, patterns_out=PATTERNS_FILE_JSON):
|
||||
self.logger.info("Starte Training des Servicetechniker Decision Tree Modells...")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user