v1.3.4 Erweiterung: FSM-Prüfung, Servicetechniker-Schätzung, Wiki-Vorschlag & 3s Pause
Bevorzugter Wikipedia-Artikel aus Spalte K wird genutzt. Nach dem Schreiben der Wiki-Daten erfolgt eine 3-Sekunden-Pause. Neue Funktion zur FSM-Eignungsprüfung (Spalte Y/Z) integriert. Neue Servicetechniker-Schätzung (Spalte AD) und Vergleich mit interner Angabe (Spalte AE) hinzugefügt. Versionsnummer wurde auf v1.3.4 aktualisiert.
This commit is contained in:
@@ -14,7 +14,7 @@ import csv
|
|||||||
|
|
||||||
# ==================== KONFIGURATION ====================
|
# ==================== KONFIGURATION ====================
|
||||||
class Config:
|
class Config:
|
||||||
VERSION = "v1.3.3" # v1.3.3: Branchenabgleich per ChatGPT integriert, Ziel-Branchenschema inkl. aller gültigen Branchen eingebunden.
|
VERSION = "v1.3.4" # v1.3.4: FSM-Eignungsprüfung, Servicetechniker-Schätzung, Wiki-Vorschlag und 3s Pause integriert.
|
||||||
LANG = "de"
|
LANG = "de"
|
||||||
CREDENTIALS_FILE = "service_account.json"
|
CREDENTIALS_FILE = "service_account.json"
|
||||||
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
|
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo"
|
||||||
@@ -193,6 +193,86 @@ def validate_article_with_chatgpt(crm_data, wiki_data):
|
|||||||
debug_print(f"Fehler beim Validierungs-API-Aufruf: {e}")
|
debug_print(f"Fehler beim Validierungs-API-Aufruf: {e}")
|
||||||
return "k.A."
|
return "k.A."
|
||||||
|
|
||||||
|
# ==================== NEUE FUNKTION: FSM-EIGNUNGSPRÜFUNG ====================
|
||||||
|
def evaluate_fsm_suitability(company_name, company_data):
|
||||||
|
try:
|
||||||
|
with open("api_key.txt", "r") as f:
|
||||||
|
api_key = f.read().strip()
|
||||||
|
except Exception as e:
|
||||||
|
debug_print(f"Fehler beim Lesen des API-Tokens: {e}")
|
||||||
|
return {"suitability": "k.A.", "justification": "k.A."}
|
||||||
|
openai.api_key = api_key
|
||||||
|
prompt = (
|
||||||
|
f"Bitte bewerte, ob das Unternehmen '{company_name}' für den Einsatz einer Field Service Management Lösung geeignet ist. "
|
||||||
|
"Berücksichtige, dass ein Unternehmen mit einem technischen Außendienst, idealerweise mit über 50 Technikern und "
|
||||||
|
"Disponenten, die mit der Planung mobiler Ressourcen beschäftigt sind, als geeignet gilt. Nutze dabei vor allem verifizierte "
|
||||||
|
"Wikipedia-Daten und deine eigene Einschätzung. Antworte ausschließlich mit 'Ja' oder 'Nein' und gib eine kurze Begründung."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "system", "content": prompt}],
|
||||||
|
temperature=0.0
|
||||||
|
)
|
||||||
|
result = response.choices[0].message.content.strip()
|
||||||
|
debug_print(f"FSM-Eignungsantwort ChatGPT: '{result}'")
|
||||||
|
# Erwartetes Format: "Eignung: <Ja/Nein>\nBegründung: <...>"
|
||||||
|
suitability = "k.A."
|
||||||
|
justification = ""
|
||||||
|
for line in result.split("\n"):
|
||||||
|
if line.lower().startswith("eignung:"):
|
||||||
|
suitability = line.split(":", 1)[1].strip()
|
||||||
|
elif line.lower().startswith("begründung:"):
|
||||||
|
justification = line.split(":", 1)[1].strip()
|
||||||
|
return {"suitability": suitability, "justification": justification}
|
||||||
|
except Exception as e:
|
||||||
|
debug_print(f"Fehler beim Aufruf der ChatGPT API für FSM-Eignungsprüfung: {e}")
|
||||||
|
return {"suitability": "k.A.", "justification": "k.A."}
|
||||||
|
|
||||||
|
# ==================== NEUE FUNKTION: SCHÄTZUNG DER ANZAHL SERVICETECHNIKER ====================
|
||||||
|
def evaluate_servicetechnicians_estimate(company_name, company_data):
|
||||||
|
try:
|
||||||
|
with open("api_key.txt", "r") as f:
|
||||||
|
api_key = f.read().strip()
|
||||||
|
except Exception as e:
|
||||||
|
debug_print(f"Fehler beim Lesen des API-Tokens: {e}")
|
||||||
|
return "k.A."
|
||||||
|
openai.api_key = api_key
|
||||||
|
prompt = (
|
||||||
|
f"Bitte schätze auf Basis öffentlich zugänglicher Informationen (insbesondere verifizierte Wikipedia-Daten) "
|
||||||
|
f"die Anzahl der Servicetechniker des Unternehmens '{company_name}' ein. "
|
||||||
|
"Berücksichtige dabei Angaben zu Branche, Umsatz und Mitarbeiterzahl. "
|
||||||
|
"Gib die Antwort ausschließlich in einer der folgenden Kategorien aus: "
|
||||||
|
"'<50 Techniker', '>100 Techniker', '>200 Techniker', '>500 Techniker'."
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "system", "content": prompt}],
|
||||||
|
temperature=0.0
|
||||||
|
)
|
||||||
|
result = response.choices[0].message.content.strip()
|
||||||
|
debug_print(f"Schätzung Servicetechniker ChatGPT: '{result}'")
|
||||||
|
# Wir erwarten eine Antwort, die exakt einer der vier Kategorien entspricht.
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
debug_print(f"Fehler beim Aufruf der ChatGPT API für Servicetechniker-Schätzung: {e}")
|
||||||
|
return "k.A."
|
||||||
|
|
||||||
|
def map_internal_technicians(value):
|
||||||
|
try:
|
||||||
|
num = int(value)
|
||||||
|
except Exception:
|
||||||
|
return "k.A."
|
||||||
|
if num < 50:
|
||||||
|
return "<50 Techniker"
|
||||||
|
elif num < 100:
|
||||||
|
return ">100 Techniker"
|
||||||
|
elif num < 200:
|
||||||
|
return ">200 Techniker"
|
||||||
|
else:
|
||||||
|
return ">500 Techniker"
|
||||||
|
|
||||||
# ==================== BRANCHENABGLEICH PER CHATGPT ====================
|
# ==================== BRANCHENABGLEICH PER CHATGPT ====================
|
||||||
def load_target_branches():
|
def load_target_branches():
|
||||||
try:
|
try:
|
||||||
@@ -225,7 +305,6 @@ focus_branches = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien):
|
def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kategorien):
|
||||||
# Lade das Ziel-Branchenschema
|
|
||||||
target_branches = load_target_branches()
|
target_branches = load_target_branches()
|
||||||
target_branches_str = "\n".join(target_branches)
|
target_branches_str = "\n".join(target_branches)
|
||||||
focus_branches_str = "\n".join(focus_branches)
|
focus_branches_str = "\n".join(focus_branches)
|
||||||
@@ -243,7 +322,7 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
|
|||||||
f"Branchenbeschreibung (Spalte G): {beschreibung}\n"
|
f"Branchenbeschreibung (Spalte G): {beschreibung}\n"
|
||||||
f"Wikipedia-Branche (Spalte N): {wiki_branche}\n"
|
f"Wikipedia-Branche (Spalte N): {wiki_branche}\n"
|
||||||
f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n"
|
f"Wikipedia-Kategorien (Spalte Q): {wiki_kategorien}\n\n"
|
||||||
"Das Ziel-Branchenschema umfasst ALLE gültigen Branchen, also sowohl Fokusbranchen als auch weitere Branchen (z. B. 'Housing > Sozialbau Unternehmen').\n"
|
"Das Ziel-Branchenschema umfasst ALLE gültigen Branchen, also sowohl Fokusbranchen als auch weitere, z. B. 'Housing > Sozialbau Unternehmen'.\n"
|
||||||
"Das vollständige Ziel-Branchenschema lautet:\n"
|
"Das vollständige Ziel-Branchenschema lautet:\n"
|
||||||
f"{target_branches_str}\n\n"
|
f"{target_branches_str}\n\n"
|
||||||
"Falls das Unternehmen mehreren Branchen zugeordnet werden könnte, wähle bitte bevorzugt eine Branche aus der folgenden Fokusliste, sofern zutreffend:\n"
|
"Falls das Unternehmen mehreren Branchen zugeordnet werden könnte, wähle bitte bevorzugt eine Branche aus der folgenden Fokusliste, sofern zutreffend:\n"
|
||||||
@@ -266,7 +345,6 @@ def evaluate_branche_chatgpt(crm_branche, beschreibung, wiki_branche, wiki_kateg
|
|||||||
)
|
)
|
||||||
result = response.choices[0].message.content.strip()
|
result = response.choices[0].message.content.strip()
|
||||||
debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'")
|
debug_print(f"Branchenabgleich ChatGPT Antwort: '{result}'")
|
||||||
# Parsing der Antwort
|
|
||||||
branch = "k.A."
|
branch = "k.A."
|
||||||
consistency = "k.A."
|
consistency = "k.A."
|
||||||
justification = ""
|
justification = ""
|
||||||
@@ -504,6 +582,8 @@ class WikipediaScraper:
|
|||||||
}
|
}
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def search_company_article(self, company_name, website):
|
def search_company_article(self, company_name, website):
|
||||||
|
# Zuerst prüfen: Gibt es in Spalte K bereits einen Wikipedia-Vorschlag?
|
||||||
|
# (Dies wird im _process_single_row gehandhabt)
|
||||||
search_terms = self._generate_search_terms(company_name, website)
|
search_terms = self._generate_search_terms(company_name, website)
|
||||||
for term in search_terms:
|
for term in search_terms:
|
||||||
try:
|
try:
|
||||||
@@ -557,11 +637,26 @@ class DataProcessor:
|
|||||||
ver_range = f"AI{row_num}"
|
ver_range = f"AI{row_num}"
|
||||||
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Verarbeite Zeile {row_num}: {company_name}")
|
print(f"\n[{datetime.now().strftime('%H:%M:%S')}] Verarbeite Zeile {row_num}: {company_name}")
|
||||||
|
|
||||||
article = self.wiki_scraper.search_company_article(company_name, website)
|
# Prüfe: Ist in Spalte K (Index 10) bereits ein Wikipedia-Vorschlag hinterlegt?
|
||||||
if article:
|
if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."]:
|
||||||
company_data = self.wiki_scraper.extract_company_data(article.url)
|
wiki_url = row_data[10].strip()
|
||||||
|
try:
|
||||||
|
company_data = self.wiki_scraper.extract_company_data(wiki_url)
|
||||||
|
except Exception as e:
|
||||||
|
debug_print(f"Fehler beim Laden des vorgeschlagenen Wikipedia-Artikels: {e}")
|
||||||
|
article = self.wiki_scraper.search_company_article(company_name, website)
|
||||||
|
company_data = self.wiki_scraper.extract_company_data(article.url) if article else {
|
||||||
|
'url': 'k.A.',
|
||||||
|
'first_paragraph': 'k.A.',
|
||||||
|
'branche': 'k.A.',
|
||||||
|
'umsatz': 'k.A.',
|
||||||
|
'mitarbeiter': 'k.A.',
|
||||||
|
'categories': 'k.A.',
|
||||||
|
'full_infobox': 'k.A.'
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
company_data = {
|
article = self.wiki_scraper.search_company_article(company_name, website)
|
||||||
|
company_data = self.wiki_scraper.extract_company_data(article.url) if article else {
|
||||||
'url': 'k.A.',
|
'url': 'k.A.',
|
||||||
'first_paragraph': 'k.A.',
|
'first_paragraph': 'k.A.',
|
||||||
'branche': 'k.A.',
|
'branche': 'k.A.',
|
||||||
@@ -572,7 +667,7 @@ class DataProcessor:
|
|||||||
}
|
}
|
||||||
|
|
||||||
wiki_values = [
|
wiki_values = [
|
||||||
"k.A.", # Vorschlag Wiki URL
|
row_data[10] if len(row_data) > 10 and row_data[10].strip() not in ["", "k.A."] else "k.A.", # Vorschlag Wiki URL
|
||||||
company_data.get('url', 'k.A.'),
|
company_data.get('url', 'k.A.'),
|
||||||
company_data.get('first_paragraph', 'k.A.'),
|
company_data.get('first_paragraph', 'k.A.'),
|
||||||
company_data.get('branche', 'k.A.'),
|
company_data.get('branche', 'k.A.'),
|
||||||
@@ -581,7 +676,7 @@ class DataProcessor:
|
|||||||
company_data.get('categories', 'k.A.')
|
company_data.get('categories', 'k.A.')
|
||||||
]
|
]
|
||||||
self.sheet_handler.sheet.update(values=[wiki_values], range_name=wiki_update_range)
|
self.sheet_handler.sheet.update(values=[wiki_values], range_name=wiki_update_range)
|
||||||
time.sleep(1)
|
time.sleep(3) # 3 Sekunden warten, bevor Validierung durchgeführt wird.
|
||||||
|
|
||||||
# Umsatz-Schätzung via ChatGPT (wie bisher)
|
# Umsatz-Schätzung via ChatGPT (wie bisher)
|
||||||
wiki_umsatz = company_data.get('umsatz', 'k.A.')
|
wiki_umsatz = company_data.get('umsatz', 'k.A.')
|
||||||
@@ -608,11 +703,6 @@ class DataProcessor:
|
|||||||
wiki_branche = company_data.get('branche', 'k.A.')
|
wiki_branche = company_data.get('branche', 'k.A.')
|
||||||
wiki_kategorien = company_data.get('categories', 'k.A.')
|
wiki_kategorien = company_data.get('categories', 'k.A.')
|
||||||
branche_result = evaluate_branche_chatgpt(crm_branche, beschreibung_branche, wiki_branche, wiki_kategorien)
|
branche_result = evaluate_branche_chatgpt(crm_branche, beschreibung_branche, wiki_branche, wiki_kategorien)
|
||||||
|
|
||||||
# Update der Spalten:
|
|
||||||
# Spalte V: Vorschlag neue Branche
|
|
||||||
# Spalte W: Konsistenzprüfung Branche (ok, wenn Übereinstimmung, sonst X)
|
|
||||||
# Spalte X: Begründung bei Abweichung
|
|
||||||
branche_v_range = f"V{row_num}"
|
branche_v_range = f"V{row_num}"
|
||||||
branche_w_range = f"W{row_num}"
|
branche_w_range = f"W{row_num}"
|
||||||
branche_x_range = f"X{row_num}"
|
branche_x_range = f"X{row_num}"
|
||||||
@@ -620,6 +710,23 @@ class DataProcessor:
|
|||||||
self.sheet_handler.sheet.update(values=[[branche_result["consistency"]]], range_name=branche_w_range)
|
self.sheet_handler.sheet.update(values=[[branche_result["consistency"]]], range_name=branche_w_range)
|
||||||
self.sheet_handler.sheet.update(values=[[branche_result["justification"]]], range_name=branche_x_range)
|
self.sheet_handler.sheet.update(values=[[branche_result["justification"]]], range_name=branche_x_range)
|
||||||
|
|
||||||
|
# Neue FSM-Eignungsprüfung:
|
||||||
|
fsm_result = evaluate_fsm_suitability(company_name, company_data)
|
||||||
|
self.sheet_handler.sheet.update(values=[[fsm_result["suitability"]]], range_name=f"Y{row_num}")
|
||||||
|
self.sheet_handler.sheet.update(values=[[fsm_result["justification"]]], range_name=f"Z{row_num}")
|
||||||
|
|
||||||
|
# Neue Servicetechniker-Schätzung (ohne Berücksichtigung interner Angaben in Spalte H)
|
||||||
|
st_estimate = evaluate_servicetechnicians_estimate(company_name, company_data)
|
||||||
|
self.sheet_handler.sheet.update(values=[[st_estimate]], range_name=f"AD{row_num}")
|
||||||
|
# Vergleich mit interner Angabe (Spalte H)
|
||||||
|
internal_value = row_data[7] if len(row_data) > 7 else "k.A."
|
||||||
|
internal_category = map_internal_technicians(internal_value) if internal_value != "k.A." else "k.A."
|
||||||
|
if internal_category != "k.A." and st_estimate != internal_category:
|
||||||
|
discrepancy = f"Interne Angabe: {internal_category} vs. ChatGPT: {st_estimate}"
|
||||||
|
else:
|
||||||
|
discrepancy = "ok"
|
||||||
|
self.sheet_handler.sheet.update(values=[[discrepancy]], range_name=f"AE{row_num}")
|
||||||
|
|
||||||
# Timestamp und Version schreiben
|
# Timestamp und Version schreiben
|
||||||
current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
current_dt = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_range)
|
self.sheet_handler.sheet.update(values=[[current_dt]], range_name=dt_range)
|
||||||
@@ -628,7 +735,8 @@ class DataProcessor:
|
|||||||
print(f"✅ Aktualisiert: URL: {company_data.get('url', 'k.A.')}, "
|
print(f"✅ Aktualisiert: URL: {company_data.get('url', 'k.A.')}, "
|
||||||
f"Branche: {company_data.get('branche', 'k.A.')}, "
|
f"Branche: {company_data.get('branche', 'k.A.')}, "
|
||||||
f"ChatGPT Umsatz: {chatgpt_umsatz}, Umsatz-Abgleich: {abgleich_result}, "
|
f"ChatGPT Umsatz: {chatgpt_umsatz}, Umsatz-Abgleich: {abgleich_result}, "
|
||||||
f"Validierung: {valid_result}, Branchenvorschlag: {branche_result['branch']}")
|
f"Validierung: {valid_result}, Branchenvorschlag: {branche_result['branch']}, "
|
||||||
|
f"FSM: {fsm_result['suitability']}, Servicetechniker-Schätzung: {st_estimate}")
|
||||||
time.sleep(Config.RETRY_DELAY)
|
time.sleep(Config.RETRY_DELAY)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user