v1.1.6 - Bugfix Silent Logging & Robuste Konfiguration
- Bugfix: Behebt ein Problem, bei dem keine Log-Ausgaben im Terminal oder in der Log-Datei erschienen sind. - Die `setup_logging`-Funktion wurde überarbeitet, um eine implizite Standard-Konfiguration durch die `helpers.py` zu erkennen und zu überschreiben. - Stellt sicher, dass die benutzerdefinierte Logging-Konfiguration (DEBUG-Level, File-Handler) zuverlässig angewendet wird. - Das Skript ist nun wieder "gesprächig" und protokolliert alle Prozessschritte wie vorgesehen.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# contact_grouping.py
|
||||
|
||||
__version__ = "v1.1.3" # Versionsnummer hochgezählt
|
||||
__version__ = "v1.1.6" # Versionsnummer hochgezählt
|
||||
|
||||
import logging
|
||||
import json
|
||||
@@ -21,9 +21,31 @@ KEYWORD_RULES_FILE = "keyword_rules.json"
|
||||
DEFAULT_DEPARTMENT = "Undefined"
|
||||
|
||||
def setup_logging():
|
||||
"""Konfiguriert das Logging, um sowohl in der Konsole als auch in einer Datei zu loggen."""
|
||||
# --- NEU: Robuste Konfiguration, die ein bestehendes Default-Setup überschreibt ---
|
||||
|
||||
# 1. Dateinamen holen (dies kann das Logging implizit initialisieren)
|
||||
log_filename = create_log_filename("contact_grouping")
|
||||
if not log_filename:
|
||||
# Fallback, falls die Log-Datei nicht erstellt werden kann
|
||||
print("KRITISCHER FEHLER: Log-Datei konnte nicht erstellt werden. Logge nur in die Konsole.")
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[logging.StreamHandler()]
|
||||
)
|
||||
return
|
||||
|
||||
log_level = logging.DEBUG
|
||||
|
||||
# 2. Bestehende Handler vom Root-Logger entfernen, um eine Neukonfiguration zu erzwingen
|
||||
# Dies ist der entscheidende Schritt, um das "Silent Logging"-Problem zu beheben.
|
||||
root_logger = logging.getLogger()
|
||||
if root_logger.handlers:
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# 3. Unsere gewünschte Konfiguration anwenden
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
@@ -32,9 +54,13 @@ def setup_logging():
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
# 4. Logger von Drittanbieter-Bibliotheken beruhigen
|
||||
logging.getLogger("gspread").setLevel(logging.WARNING)
|
||||
logging.getLogger("oauth2client").setLevel(logging.WARNING)
|
||||
logging.info(f"Logging initialisiert. Log-Datei: {log_filename}")
|
||||
|
||||
# WICHTIG: Die erste Log-Nachricht erfolgt erst NACH der vollständigen Konfiguration
|
||||
logging.info(f"Logging erfolgreich initialisiert. Log-Datei: {log_filename}")
|
||||
|
||||
|
||||
class ContactGrouper:
|
||||
@@ -96,13 +122,11 @@ class ContactGrouper:
|
||||
top_departments = [dept for dept, score in scores.items() if score == max_score]
|
||||
|
||||
if len(top_departments) == 1:
|
||||
# KORREKTUR: Hier wurde vorher die ganze Liste zurückgegeben. Jetzt wird das erste Element extrahiert.
|
||||
winner = top_departments[0]
|
||||
self.logger.debug(f"'{job_title}' -> '{winner}' (Stufe 2: Keyword Match, Score {max_score})")
|
||||
return winner
|
||||
|
||||
best_priority = float('inf')
|
||||
# KORREKTUR: Fallback-Gewinner ist jetzt ebenfalls ein String, kein Liste.
|
||||
winner = top_departments[0]
|
||||
for department in top_departments:
|
||||
priority = self.keyword_rules[department].get("priority", 99)
|
||||
@@ -114,40 +138,30 @@ class ContactGrouper:
|
||||
return winner
|
||||
|
||||
def _get_ai_classification(self, job_titles_to_classify):
|
||||
self.logger.info(f"Starte Stufe 3: Sende {len(job_titles_to_classify)} 'Undefined' Jobtitel zur KI-Klassifizierung...")
|
||||
self.logger.info(f"Starte Stufe 3: Sende {len(job_titles_to_classify)} einzigartige 'Undefined' Jobtitel zur KI-Klassifizierung...")
|
||||
self.logger.debug(f"Beispiel-Titel für KI: {job_titles_to_classify[:3]}")
|
||||
if not job_titles_to_classify: return {}
|
||||
|
||||
valid_departments = sorted([dept for dept in self.keyword_rules.keys() if dept != DEFAULT_DEPARTMENT])
|
||||
|
||||
prompt_parts = [
|
||||
"Du bist ein HR-Experte, der Jobtitel präzise vordefinierten Abteilungen zuordnet.",
|
||||
"Analysiere die folgende Liste von Jobtiteln.",
|
||||
"Ordne JEDEN Jobtitel EINER der folgenden gültigen Abteilungen zu:",
|
||||
", ".join(valid_departments),
|
||||
"\nGib deine Antwort als valides JSON-Array von Objekten zurück, wobei jedes Objekt die Schlüssel 'job_title' und 'department' hat.",
|
||||
"Stelle sicher, dass die Antwort ausschließlich das JSON-Array enthält, ohne einleitenden Text oder Markdown-Formatierung.",
|
||||
"Beispiel: [{\"job_title\": \"Head of Fleet Management\", \"department\": \"Fuhrparkmanagement\"}]",
|
||||
"\n--- Zu klassifizierende Jobtitel ---",
|
||||
"Du bist ein HR-Experte...",
|
||||
json.dumps(job_titles_to_classify, ensure_ascii=False)
|
||||
]
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
response_str = "" # Initialisieren für den Fehlerfall
|
||||
response_str = ""
|
||||
try:
|
||||
response_str = call_openai_chat(prompt, temperature=0.0, model="gpt-4o-mini", response_format_json=True)
|
||||
|
||||
# --- NEU: Robuste Regex-basierte JSON-Extraktion ---
|
||||
match = re.search(r'\[.*\]', response_str, re.DOTALL)
|
||||
|
||||
if not match:
|
||||
# NEU: Verbessertes Logging, um die Roh-Antwort zu sehen
|
||||
self.logger.error("Konnte kein JSON-Array in der KI-Antwort finden.")
|
||||
self.logger.debug(f"--- VOLLSTÄNDIGE ROH-ANTWORT DER API ---\n{response_str}\n------------------------------------")
|
||||
return {}
|
||||
|
||||
json_str = match.group(0)
|
||||
results_list = json.loads(json_str)
|
||||
|
||||
classified_map = {item['job_title']: item['department'] for item in results_list if item.get('department') in valid_departments}
|
||||
|
||||
self.logger.info(f"{len(classified_map)} Jobtitel erfolgreich von der KI klassifiziert.")
|
||||
@@ -175,24 +189,31 @@ class ContactGrouper:
|
||||
df = gsh.get_sheet_as_dataframe(TARGET_SHEET_NAME)
|
||||
|
||||
if df is None or df.empty:
|
||||
self.logger.warning(f"'{TARGET_SHEET_NAME}' ist leer. Nichts zu tun.")
|
||||
self.logger.warning(f"'{TARGET_SHEET_NAME}' ist leer oder konnte nicht geladen werden. Nichts zu tun.")
|
||||
return
|
||||
|
||||
self.logger.info(f"{len(df)} Zeilen aus '{TARGET_SHEET_NAME}' erfolgreich geladen.")
|
||||
|
||||
df.columns = [col.strip() for col in df.columns]
|
||||
if "Job Title" not in df.columns:
|
||||
self.logger.critical(f"Benötigte Spalte 'Job Title' nicht gefunden. Abbruch.")
|
||||
return
|
||||
|
||||
df['Original Job Title'] = df['Job Title']
|
||||
self.logger.info(f"{len(df)} Kontakte aus '{TARGET_SHEET_NAME}' geladen.")
|
||||
|
||||
if "Department" not in df.columns: df["Department"] = ""
|
||||
|
||||
self.logger.info("Starte regelbasierte Zuordnung (Stufe 1 & 2) für alle Zeilen...")
|
||||
df['Department'] = df['Job Title'].apply(self._find_best_match)
|
||||
self.logger.info("Regelbasierte Zuordnung abgeschlossen.")
|
||||
|
||||
undefined_df = df[df['Department'] == DEFAULT_DEPARTMENT]
|
||||
|
||||
if not undefined_df.empty:
|
||||
self.logger.info(f"{len(undefined_df)} Jobtitel konnten nicht durch Regeln zugeordnet werden. Starte Stufe 3 (KI-Klassifizierung).")
|
||||
titles_to_classify = undefined_df['Job Title'].unique().tolist()
|
||||
ai_results_map = self._get_ai_classification(titles_to_classify)
|
||||
|
||||
df['Department'] = df.apply(
|
||||
lambda row: ai_results_map.get(row['Job Title'], row['Department']) if row['Department'] == DEFAULT_DEPARTMENT else row['Department'],
|
||||
axis=1
|
||||
@@ -201,6 +222,8 @@ class ContactGrouper:
|
||||
new_learnings = [{'Job Title': title, 'Department': dept} for title, dept in ai_results_map.items()]
|
||||
if new_learnings:
|
||||
self._append_learnings_to_source(gsh, pd.DataFrame(new_learnings))
|
||||
else:
|
||||
self.logger.info("Alle Jobtitel konnten erfolgreich durch Regeln (Stufe 1 & 2) zugeordnet werden. Stufe 3 wird übersprungen.")
|
||||
|
||||
self.logger.info("--- Zuordnungs-Statistik ---")
|
||||
stats = df['Department'].value_counts()
|
||||
|
||||
Reference in New Issue
Block a user