v1.2.0 - Bugfix & Robuste Branchen-Regel-Erkennung
- Bugfix: Behebt den Fehler, bei dem keine branchenspezifischen Regeln generiert wurden, weil die Schwellenwerte zu restriktiv waren. - Die Schwellenwerte für die minimale Sample-Anzahl und die prozentuale Branchen-Reinheit wurden gelockert und sind nun am Anfang des Skripts konfigurierbar. - Verbessertes Logging: Das Skript gibt nun detailliert Auskunft, warum ein Department als branchenspezifisch eingestuft oder warum es verworfen wurde.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# knowledge_base_builder.py
|
# knowledge_base_builder.py
|
||||||
|
|
||||||
__version__ = "v1.1.0"
|
__version__ = "v1.2.0"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
@@ -17,13 +17,10 @@ EXACT_MATCH_OUTPUT_FILE = "exact_match_map.json"
|
|||||||
KEYWORD_RULES_OUTPUT_FILE = "keyword_rules.json"
|
KEYWORD_RULES_OUTPUT_FILE = "keyword_rules.json"
|
||||||
|
|
||||||
DEPARTMENT_PRIORITIES = {
|
DEPARTMENT_PRIORITIES = {
|
||||||
# --- Tier 1: Ultra-spezifische Nischen (höchste Priorität) ---
|
|
||||||
"Fuhrparkmanagement": 1,
|
"Fuhrparkmanagement": 1,
|
||||||
"Legal": 1,
|
"Legal": 1,
|
||||||
"Baustofflogistik": 1,
|
"Baustofflogistik": 1,
|
||||||
"Baustoffherstellung": 1,
|
"Baustoffherstellung": 1,
|
||||||
|
|
||||||
# --- Tier 2: Kern-Fachbereiche (sortiert nach Kontakthäufigkeit) ---
|
|
||||||
"Field Service Management / Kundenservice": 2,
|
"Field Service Management / Kundenservice": 2,
|
||||||
"IT": 3,
|
"IT": 3,
|
||||||
"Production Maintenance / Wartung Produktion": 4,
|
"Production Maintenance / Wartung Produktion": 4,
|
||||||
@@ -32,21 +29,14 @@ DEPARTMENT_PRIORITIES = {
|
|||||||
"Supply Chain Management": 7,
|
"Supply Chain Management": 7,
|
||||||
"Finanzen": 8,
|
"Finanzen": 8,
|
||||||
"Technik": 8,
|
"Technik": 8,
|
||||||
|
|
||||||
# --- Tier 3: Übergreifende & Allgemeine Funktionen ---
|
|
||||||
"Management / GF / C-Level": 10,
|
"Management / GF / C-Level": 10,
|
||||||
"Logistik": 11,
|
"Logistik": 11,
|
||||||
"Vertrieb": 12,
|
"Vertrieb": 12,
|
||||||
"Transportwesen": 13,
|
"Transportwesen": 13,
|
||||||
|
|
||||||
# --- Tier 4: Auffang-Kategorien (niedrigste Priorität) ---
|
|
||||||
"Berater": 20,
|
"Berater": 20,
|
||||||
"Undefined": 99
|
"Undefined": 99
|
||||||
}
|
}
|
||||||
|
|
||||||
# NEU: Definition von Branchen-Gruppen für die kontextsensitive Regelerstellung
|
|
||||||
# Key: Ein einfaches, normalisiertes Schlüsselwort für die Gruppe
|
|
||||||
# Value: Eine Liste von d365_branch_detail Werten aus Ihrer config.py
|
|
||||||
BRANCH_GROUP_RULES = {
|
BRANCH_GROUP_RULES = {
|
||||||
"bau": [
|
"bau": [
|
||||||
"Baustoffhandel", "Baustoffindustrie",
|
"Baustoffhandel", "Baustoffindustrie",
|
||||||
@@ -63,8 +53,12 @@ BRANCH_GROUP_RULES = {
|
|||||||
"Braune & Weiße Ware", "Fenster / Glas", "Getränke", "Möbel", "Agrar, Pellets"
|
"Braune & Weiße Ware", "Fenster / Glas", "Getränke", "Möbel", "Agrar, Pellets"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
# Schwellenwert: Wenn >X% der Jobtitel eines Departments in einer Branchengruppe liegen, wird es spezifisch
|
|
||||||
BRANCH_SPECIFICITY_THRESHOLD = 0.8
|
# --- NEU: Angepasste und konfigurierbare Schwellenwerte ---
|
||||||
|
# Ein Department muss mindestens so viele Einträge haben, um eine Branchen-Regel zu bekommen.
|
||||||
|
MIN_SAMPLES_FOR_BRANCH_RULE = 5
|
||||||
|
# Wenn >X% der Jobtitel eines Departments in EINER Branchengruppe liegen, gilt es als spezifisch.
|
||||||
|
BRANCH_SPECIFICITY_THRESHOLD = 0.7
|
||||||
|
|
||||||
STOP_WORDS = {
|
STOP_WORDS = {
|
||||||
'manager', 'leiter', 'head', 'lead', 'senior', 'junior', 'direktor', 'director',
|
'manager', 'leiter', 'head', 'lead', 'senior', 'junior', 'direktor', 'director',
|
||||||
@@ -77,12 +71,7 @@ STOP_WORDS = {
|
|||||||
|
|
||||||
|
|
||||||
def build_knowledge_base():
|
def build_knowledge_base():
|
||||||
"""
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
Hauptfunktion zur Erstellung der Wissensbasis.
|
|
||||||
Liest Rohdaten, analysiert sie und erstellt JSON-Dateien für exakte und Keyword-basierte Übereinstimmungen.
|
|
||||||
Erstellt automatisch Regeln für branchenspezifische Departments.
|
|
||||||
"""
|
|
||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.info(f"Starte Erstellung der Wissensbasis (Version {__version__})...")
|
logger.info(f"Starte Erstellung der Wissensbasis (Version {__version__})...")
|
||||||
|
|
||||||
@@ -90,7 +79,7 @@ def build_knowledge_base():
|
|||||||
df = gsh.get_sheet_as_dataframe(SOURCE_SHEET_NAME)
|
df = gsh.get_sheet_as_dataframe(SOURCE_SHEET_NAME)
|
||||||
|
|
||||||
if df is None or df.empty:
|
if df is None or df.empty:
|
||||||
logger.critical(f"Konnte keine Daten aus '{SOURCE_SHEET_NAME}' laden oder das Tabellenblatt ist leer. Abbruch.")
|
logger.critical(f"Konnte keine Daten aus '{SOURCE_SHEET_NAME}' laden. Abbruch.")
|
||||||
return
|
return
|
||||||
|
|
||||||
df.columns = [col.strip() for col in df.columns]
|
df.columns = [col.strip() for col in df.columns]
|
||||||
@@ -112,7 +101,7 @@ def build_knowledge_base():
|
|||||||
try:
|
try:
|
||||||
with open(EXACT_MATCH_OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
with open(EXACT_MATCH_OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||||
json.dump(exact_match_map, f, indent=4, ensure_ascii=False)
|
json.dump(exact_match_map, f, indent=4, ensure_ascii=False)
|
||||||
logger.info(f"-> '{EXACT_MATCH_OUTPUT_FILE}' mit {len(exact_match_map)} einzigartigen Jobtiteln erfolgreich erstellt.")
|
logger.info(f"-> '{EXACT_MATCH_OUTPUT_FILE}' mit {len(exact_match_map)} Titeln erstellt.")
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(f"Fehler beim Schreiben der Datei '{EXACT_MATCH_OUTPUT_FILE}': {e}")
|
logger.error(f"Fehler beim Schreiben der Datei '{EXACT_MATCH_OUTPUT_FILE}': {e}")
|
||||||
return
|
return
|
||||||
@@ -141,7 +130,8 @@ def build_knowledge_base():
|
|||||||
department_branches = branches_by_department.get(department, [])
|
department_branches = branches_by_department.get(department, [])
|
||||||
total_titles_in_dept = len(department_branches)
|
total_titles_in_dept = len(department_branches)
|
||||||
|
|
||||||
if total_titles_in_dept > 10: # Mindestanzahl an Datenpunkten, um eine Regel zu erstellen
|
# Angepasste Logik mit transparentem Logging
|
||||||
|
if total_titles_in_dept >= MIN_SAMPLES_FOR_BRANCH_RULE:
|
||||||
branch_group_counts = Counter()
|
branch_group_counts = Counter()
|
||||||
for branch_name in department_branches:
|
for branch_name in department_branches:
|
||||||
for group_keyword, d365_names in BRANCH_GROUP_RULES.items():
|
for group_keyword, d365_names in BRANCH_GROUP_RULES.items():
|
||||||
@@ -150,16 +140,24 @@ def build_knowledge_base():
|
|||||||
|
|
||||||
if branch_group_counts:
|
if branch_group_counts:
|
||||||
most_common_group, count = branch_group_counts.most_common(1)[0]
|
most_common_group, count = branch_group_counts.most_common(1)[0]
|
||||||
if (count / total_titles_in_dept) > BRANCH_SPECIFICITY_THRESHOLD:
|
ratio = count / total_titles_in_dept
|
||||||
logger.info(f" -> Department '{department}' ist spezifisch für Branche '{most_common_group}' ({count/total_titles_in_dept:.0%}). Regel wird hinzugefügt.")
|
if ratio > BRANCH_SPECIFICITY_THRESHOLD:
|
||||||
|
logger.info(f" -> Department '{department}' ist spezifisch für Branche '{most_common_group}' ({ratio:.0%}). Regel wird hinzugefügt.")
|
||||||
rule["required_branch_keywords"] = [most_common_group]
|
rule["required_branch_keywords"] = [most_common_group]
|
||||||
|
else:
|
||||||
|
logger.debug(f" -> Department '{department}' nicht spezifisch genug. Dominante Branche '{most_common_group}' nur bei {ratio:.0%}, benötigt >{BRANCH_SPECIFICITY_THRESHOLD:.0%}.")
|
||||||
|
else:
|
||||||
|
logger.debug(f" -> Department '{department}' konnte keiner Branchen-Gruppe zugeordnet werden.")
|
||||||
|
else:
|
||||||
|
logger.debug(f" -> Department '{department}' hat zu wenige Datenpunkte ({total_titles_in_dept} < {MIN_SAMPLES_FOR_BRANCH_RULE}) für eine Branchen-Regel.")
|
||||||
|
|
||||||
|
|
||||||
keyword_rules[department] = rule
|
keyword_rules[department] = rule
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(KEYWORD_RULES_OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
with open(KEYWORD_RULES_OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||||
json.dump(keyword_rules, f, indent=4, ensure_ascii=False)
|
json.dump(keyword_rules, f, indent=4, ensure_ascii=False)
|
||||||
logger.info(f"-> '{KEYWORD_RULES_OUTPUT_FILE}' mit Regeln für {len(keyword_rules)} Departments erfolgreich erstellt.")
|
logger.info(f"-> '{KEYWORD_RULES_OUTPUT_FILE}' mit Regeln für {len(keyword_rules)} Departments erstellt.")
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.error(f"Fehler beim Schreiben der Datei '{KEYWORD_RULES_OUTPUT_FILE}': {e}")
|
logger.error(f"Fehler beim Schreiben der Datei '{KEYWORD_RULES_OUTPUT_FILE}': {e}")
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user