v1.7.8: Feature 'is_part_of_group' für ML & erweiterte Konzernlogik Plausi
- Feature Engineering: Dynamische Erstellung des Features 'is_part_of_group' in `prepare_data_for_modeling` und `_predict_technician_bucket` basierend auf Spalten D (Parent Account Name) sowie O (System Vorschlag Parent Account) und P (Parent Vorschlag Status). Dieses Feature wird nun für das ML-Training und die Vorhersage verwendet. - Plausibilitäts-Logik erweitert: `_check_financial_plausibility` berücksichtigt jetzt nicht nur Spalte D, sondern auch einen bestätigten Parent-Vorschlag aus Spalte O (mit P='x'), um die `INFO_KONZERN_LOGIK` für die Abweichungsflags (BJ, BK) anzuwenden. Die aufrufenden Stellen in `_process_single_row` und `run_plausibility_checks_batch` wurden angepasst, um die notwendigen Daten (O, P) an die Plausi-Funktion zu übergeben. - Bugfix: `UnboundLocalError` für die Variable `bonus` in `serp_wikipedia_lookup` durch korrekte Initialisierung behoben. - Bugfix: `KeyError` für "Timestamp letzte Pruefung" in `_process_single_row` durch korrekte Schreibweise des Spaltennamens-Schlüssels (mit "ue") behoben. - Bugfix: `NameError` für `source_of_wiki_data_origin` im Konsolidierungs-Log-String in `_process_single_row` durch Verwendung eines Fallback-Wertes behoben. - Code-Struktur: Debug-Logausgabe für den Inhalt von `current_wiki_url_r` in `_process_single_row` vor der Parent-Prüfung hinzugefügt. - Code-Struktur: Korrektur der Limit-Anwendung und Entfernung eines fehlerhaften Code-Blocks in `run_plausibility_checks_batch`.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Automatisiertes Unternehmensbewertungs-Skript - Refactoring v1.7.0
|
||||
Automatisiertes Unternehmensbewertungs-Skript - Refactoring v1.7.8
|
||||
Basierend auf v1.6.x - Umstrukturierung in modulare Klassen und flexibles UI.
|
||||
|
||||
Dieses Skript dient der automatisierten Anreicherung, Validierung und Standardisierung
|
||||
@@ -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.7
|
||||
Version: v1.7.8
|
||||
|
||||
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.7"
|
||||
VERSION = "v1.7.8"
|
||||
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!
|
||||
@@ -4826,15 +4826,19 @@ class DataProcessor:
|
||||
self.logger.debug(f" Zeile {row_num_in_sheet}: Führe Plausibilitäts-Checks durch (Parent D: '{parent_account_name_d}')...")
|
||||
try:
|
||||
plausi_input_data = {
|
||||
"Finaler Umsatz (Wiki>CRM)": final_umsatz_str_konsolidiert, # Aus Schritt 3e
|
||||
"Finaler Mitarbeiter (Wiki>CRM)": final_ma_str_konsolidiert, # Aus Schritt 3e
|
||||
"CRM Umsatz": self._get_cell_value_safe(row_data, "CRM Umsatz"), # Spalte L
|
||||
"Wiki Umsatz": final_wiki_data.get('umsatz', 'k.A.'), # Spalte W (kann von Parent sein)
|
||||
"CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"), # Spalte M
|
||||
"Wiki Mitarbeiter": final_wiki_data.get('mitarbeiter', 'k.A.'), # Spalte X (kann von Parent sein)
|
||||
"Parent Account Name": parent_account_name_d # WICHTIG: Spalte D übergeben
|
||||
"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.'), # oder self._get_cell_value_safe(row_data, "Wiki Umsatz")
|
||||
"CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"),
|
||||
"Wiki Mitarbeiter": final_wiki_data.get('mitarbeiter', 'k.A.'), # oder self._get_cell_value_safe(row_data, "Wiki Mitarbeiter")
|
||||
"Parent Account Name": parent_account_name_d,
|
||||
# NEU HINZUGEFÜGT:
|
||||
"System Vorschlag Parent Account": self._get_cell_value_safe(row_data, "System Vorschlag Parent Account"),
|
||||
"Parent Vorschlag Status": self._get_cell_value_safe(row_data, "Parent Vorschlag Status")
|
||||
}
|
||||
plausi_results = self._check_financial_plausibility(plausi_input_data) # Diese Methode muss D berücksichtigen
|
||||
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", "ERR_FLAG")]]})
|
||||
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", "ERR_FLAG")]]})
|
||||
@@ -7960,9 +7964,34 @@ class DataProcessor:
|
||||
|
||||
parent_account_name_d_val = row_data_dict.get("Parent Account Name", "").strip()
|
||||
is_konzern_tochter_laut_d = bool(parent_account_name_d_val and parent_account_name_d_val.lower() != 'k.a.')
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ NEU/ERWEITERT: Info aus Spalte O und P für Konzernlogik heranziehen +++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# Diese Keys müssen im row_data_dict vorhanden sein, wenn diese Funktion aufgerufen wird!
|
||||
parent_o_val = row_data_dict.get("System Vorschlag Parent Account", "").strip().lower()
|
||||
parent_p_val = row_data_dict.get("Parent Vorschlag Status", "").strip().lower()
|
||||
is_konzern_tochter_laut_o_und_p = bool(parent_o_val and parent_o_val != 'k.a.' and parent_p_val == 'x')
|
||||
|
||||
# (Das `wiki_stammt_von_parent_explizit` Flag ist optional, wenn Sie den anderen Ansatz verfolgen)
|
||||
# wiki_stammt_von_parent_explizit = row_data_dict.get("Wiki Daten von Parent", False)
|
||||
|
||||
is_part_of_a_group_for_plausi = is_konzern_tochter_laut_d or is_konzern_tochter_laut_o_und_p # or wiki_stammt_von_parent_explizit
|
||||
|
||||
log_msg_group_parts = []
|
||||
if is_konzern_tochter_laut_d:
|
||||
self.logger.debug(f" PlausiCheck: Tochter von Konzern '{parent_account_name_d_val}' (aus Spalte D). Abweichungs-Checks CRM/Wiki werden als INFO_KONZERN_LOGIK behandelt.")
|
||||
log_msg_group_parts.append(f"D='{parent_account_name_d_val}'")
|
||||
if is_konzern_tochter_laut_o_und_p:
|
||||
log_msg_group_parts.append(f"O/P='{parent_o_val}/{parent_p_val}'")
|
||||
# if wiki_stammt_von_parent_explizit:
|
||||
# log_msg_group_parts.append("WikiParentFlag=True")
|
||||
|
||||
if is_part_of_a_group_for_plausi:
|
||||
self.logger.debug(f" PlausiCheck: Unternehmen ist Teil einer Gruppe ({'; '.join(log_msg_group_parts)}). Abweichungs-Checks CRM/Wiki werden als INFO_KONZERN_LOGIK behandelt.")
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ ENDE NEU/ERWEITERT ++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
# --- 1. Plausibilität Finaler Umsatz (BG) ---
|
||||
final_umsatz_str = row_data_dict.get("Finaler Umsatz (Wiki>CRM)", "k.A.")
|
||||
@@ -8041,14 +8070,8 @@ class DataProcessor:
|
||||
|
||||
# Umsatz Abweichung (BJ)
|
||||
if pd.notna(crm_u_abs) and pd.notna(wiki_u_abs) and crm_u_abs > 0 and wiki_u_abs > 0 :
|
||||
# --- START ANPASSUNG FÜR KONZERN ---
|
||||
if is_konzern_tochter_laut_d:
|
||||
if is_part_of_a_group_for_plausi: # ERWEITERTE BEDINGUNG HIER VERWENDEN
|
||||
results["abweichung_umsatz_flag"] = "INFO_KONZERN_LOGIK"
|
||||
# Begründung wird hier NICHT mehr automatisch hinzugefügt,
|
||||
# da sie sonst bei jeder Konzernfirma erscheint, auch wenn alles OK ist.
|
||||
# Man könnte es hinzufügen, wenn man möchte, dass es immer da steht.
|
||||
# temp_begruendungen.append(f"INFO: Konzern - Umsatzabweichung CRM/Wiki nicht als Warnung.")
|
||||
# --- ENDE ANPASSUNG FÜR KONZERN ---
|
||||
else:
|
||||
diff_umsatz = abs(crm_u_abs - wiki_u_abs)
|
||||
bezugswert_umsatz = max(crm_u_abs, wiki_u_abs) if max(crm_u_abs, wiki_u_abs) > 0 else 1
|
||||
@@ -8063,11 +8086,8 @@ class DataProcessor:
|
||||
|
||||
# Mitarbeiter Abweichung (BK)
|
||||
if pd.notna(crm_m_abs_comp) and pd.notna(wiki_m_abs_comp) and crm_m_abs_comp > 0 and wiki_m_abs_comp > 0:
|
||||
# --- START ANPASSUNG FÜR KONZERN ---
|
||||
if is_konzern_tochter_laut_d:
|
||||
if is_part_of_a_group_for_plausi: # ERWEITERTE BEDINGUNG HIER VERWENDEN
|
||||
results["abweichung_ma_flag"] = "INFO_KONZERN_LOGIK"
|
||||
# temp_begruendungen.append(f"INFO: Konzern - MA-Abweichung CRM/Wiki nicht als Warnung.")
|
||||
# --- ENDE ANPASSUNG FÜR KONZERN ---
|
||||
else:
|
||||
diff_ma = abs(crm_m_abs_comp - wiki_m_abs_comp)
|
||||
bezugswert_ma = max(crm_m_abs_comp, wiki_m_abs_comp) if max(crm_m_abs_comp, wiki_m_abs_comp) > 0 else 1
|
||||
@@ -8183,13 +8203,19 @@ class DataProcessor:
|
||||
"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": self._get_cell_value_safe(row_data, "Wiki Umsatz"),
|
||||
"Wiki Umsatz": final_wiki_data.get('umsatz', 'k.A.'), # oder self._get_cell_value_safe(row_data, "Wiki Umsatz")
|
||||
"CRM Anzahl Mitarbeiter": self._get_cell_value_safe(row_data, "CRM Anzahl Mitarbeiter"),
|
||||
"Wiki Mitarbeiter": self._get_cell_value_safe(row_data, "Wiki Mitarbeiter"),
|
||||
"Parent Account Name": parent_account_name_d_val
|
||||
"Wiki Mitarbeiter": final_wiki_data.get('mitarbeiter', 'k.A.'), # oder self._get_cell_value_safe(row_data, "Wiki Mitarbeiter")
|
||||
"Parent Account Name": parent_account_name_d,
|
||||
# NEU HINZUGEFÜGT:
|
||||
"System Vorschlag Parent Account": self._get_cell_value_safe(row_data, "System Vorschlag Parent Account"),
|
||||
"Parent Vorschlag Status": self._get_cell_value_safe(row_data, "Parent Vorschlag Status")
|
||||
}
|
||||
plausi_results = self._check_financial_plausibility(plausi_input_data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
current_row_updates.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", "ERR_FLAG")]]})
|
||||
current_row_updates.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", "ERR_FLAG")]]})
|
||||
current_row_updates.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", "ERR_FLAG")]]})
|
||||
@@ -8930,6 +8956,9 @@ class DataProcessor:
|
||||
"ma_crm": "CRM Anzahl Mitarbeiter", # Fuer Konsolidierung
|
||||
"ma_wiki": "Wiki Mitarbeiter", # Fuer Konsolidierung
|
||||
"techniker": "CRM Anzahl Techniker" # DIE ZIELVARIABLE (Bekannte Technikerzahl)
|
||||
"parent_d_raw": "Parent Account Name", # Spalte D
|
||||
"parent_o_raw": "System Vorschlag Parent Account", # Spalte O
|
||||
"parent_p_raw": "Parent Vorschlag Status" # Spalte P
|
||||
}
|
||||
|
||||
# Ueberpruefe, ob alle benoetigten Spalten-Schluessel in der COLUMN_MAP (Block 1) vorhanden sind
|
||||
@@ -8979,6 +9008,35 @@ class DataProcessor:
|
||||
|
||||
self.logger.info(f"Benötigte Spalten fuer Modellierung ausgewaehlt und umbenannt: {list(df_subset.columns)}") # <<< GEÄNDERT
|
||||
|
||||
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ NEUER BLOCK: Erstellung des 'is_part_of_group' Features +++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
self.logger.info("Erstelle Feature 'is_part_of_group'...")
|
||||
# Sicherstellen, dass die Spalten als String behandelt werden und NaNs/Leerstrings korrekt ausgewertet werden
|
||||
parent_d_series = df_subset['parent_d_raw'].astype(str).str.strip().str.lower()
|
||||
parent_o_series = df_subset['parent_o_raw'].astype(str).str.strip().str.lower()
|
||||
parent_p_series = df_subset['parent_p_raw'].astype(str).str.strip().str.lower()
|
||||
|
||||
# Bedingung 1: Parent D ist gefüllt und nicht 'k.a.'
|
||||
cond1 = parent_d_series.notna() & (parent_d_series != 'k.a.') & (parent_d_series != '')
|
||||
|
||||
# Bedingung 2: Parent O ist gefüllt und nicht 'k.a.' UND Parent P Status ist 'x'
|
||||
cond2_o = parent_o_series.notna() & (parent_o_series != 'k.a.') & (parent_o_series != '')
|
||||
cond2_p = parent_p_series == 'x' # 'x' für akzeptiert
|
||||
cond2 = cond2_o & cond2_p
|
||||
|
||||
df_subset['is_part_of_group'] = np.where(cond1 | cond2, 1, 0)
|
||||
|
||||
self.logger.info(f"Feature 'is_part_of_group' erstellt. {df_subset['is_part_of_group'].sum()} Unternehmen als Teil einer Gruppe markiert.")
|
||||
# Überprüfen Sie die Verteilung, um sicherzustellen, dass es nicht nur Nullen oder Einsen sind
|
||||
self.logger.debug(f"Verteilung von 'is_part_of_group':\n{df_subset['is_part_of_group'].value_counts(normalize=True)}")
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++ ENDE NEUER BLOCK ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
|
||||
# --- Features konsolidieren (Umsatz, Mitarbeiter) ---
|
||||
# Nutzt die globale Hilfsfunktion get_numeric_filter_value (Block 5) - ERSETZT get_valid_numeric
|
||||
cols_to_process = {
|
||||
@@ -9096,6 +9154,19 @@ class DataProcessor:
|
||||
# .str.strip() entfernt führende/endende Leerzeichen.
|
||||
df_filtered[branche_col_internal] = df_filtered[branche_col_internal].astype(str).fillna('Unbekannt').str.strip()
|
||||
|
||||
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
|
||||
|
||||
# df_single_row ist hier der DataFrame mit einer Zeile
|
||||
df_single_row['is_part_of_group'] = 1 if cond1_pred | cond2_pred else 0
|
||||
self.logger.debug(f" ML Pred: 'is_part_of_group' gesetzt auf {df_single_row['is_part_of_group'].iloc[0]}")
|
||||
|
||||
|
||||
# One-Hot Encoding (pd.get_dummies)
|
||||
# dummy_na=False, da wir NaNs bereits mit 'Unbekannt' gefuellt haben.
|
||||
|
||||
Reference in New Issue
Block a user