Files
Brancheneinstufung2/_legacy_gsheets_system/extract_insights.py
Floke 95634d7bb6 feat(company-explorer): Initial Web UI & Backend with Enrichment Flow
This commit introduces the foundational elements for the new "Company Explorer" web application, marking a significant step away from the legacy Google Sheets / CLI system.

Key changes include:
- Project Structure: A new  directory with separate  (FastAPI) and  (React/Vite) components.
- Data Persistence: Migration from Google Sheets to a local SQLite database () using SQLAlchemy.
- Core Utilities: Extraction and cleanup of essential helper functions (LLM wrappers, text utilities) into .
- Backend Services: , ,  for AI-powered analysis, and  logic.
- Frontend UI: Basic React application with company table, import wizard, and dynamic inspector sidebar.
- Docker Integration: Updated  and  for multi-stage builds and sideloading.
- Deployment & Access: Integrated into central Nginx proxy and dashboard, accessible via .

Lessons Learned & Fixed during development:
- Frontend Asset Loading: Addressed issues with Vite's  path and FastAPI's .
- TypeScript Configuration: Added  and .
- Database Schema Evolution: Solved  errors by forcing a new database file and correcting  override.
- Logging: Implemented robust file-based logging ().

This new foundation provides a powerful and maintainable platform for future B2B robotics lead generation.
2026-01-07 17:55:08 +00:00

189 lines
8.2 KiB
Python

# extract_insights.py
import os
import yaml
import logging
import time
import openai
import docx # Die neue Bibliothek zur Verarbeitung von Word-Dokumenten
from config import Config
# --- Konfiguration ---
DOCS_SOURCE_FOLDER = "industry_docs" # Der Ordner, in dem Ihre .docx-Dateien liegen
OUTPUT_FILE = "marketing_wissen_v1.yaml"
MODEL_TO_USE = "gpt-4-turbo" # Empfohlen für komplexe Extraktionsaufgaben
# --- Logging einrichten ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def call_openai_with_retry(prompt, max_retries=3, delay=5):
"""Ruft die OpenAI API mit Retry-Logik auf."""
for attempt in range(max_retries):
try:
logging.info(f"Sende Prompt an OpenAI (Länge: {len(prompt)} Zeichen)...")
response = openai.ChatCompletion.create(
model=MODEL_TO_USE,
messages=[{"role": "user", "content": prompt}],
temperature=0.2, # Niedrige Temperatur für präzise Extraktion
max_tokens=1024
)
content = response.choices[0].message['content'].strip()
return content
except Exception as e:
logging.error(f"Fehler bei OpenAI-API-Aufruf: {e}")
if attempt < max_retries - 1:
logging.info(f"Warte {delay} Sekunden vor dem nächsten Versuch...")
time.sleep(delay)
else:
logging.error("Maximale Anzahl an Wiederholungen erreicht.")
return None
def read_docx_content(filepath):
"""Liest den gesamten Textinhalt aus einer .docx-Datei, inklusive Tabellen."""
try:
doc = docx.Document(filepath)
full_text = []
for para in doc.paragraphs:
full_text.append(para.text)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
full_text.append(cell.text)
return "\n".join(full_text)
except Exception as e:
logging.error(f"Fehler beim Lesen der DOCX-Datei {filepath}: {e}")
return None
def extract_yaml_from_response(response_text):
"""
Extrahiert sauberen YAML-Code aus einer KI-Antwort,
die Markdown-Codeblöcke enthalten kann.
"""
# Sucht nach dem Start des YAML-Codeblocks
if '```yaml' in response_text:
# Extrahiert den Teil nach dem ersten ```yaml
parts = response_text.split('```yaml', 1)
if len(parts) > 1:
response_text = parts[1]
# Sucht nach dem Start eines generischen Codeblocks
elif '```' in response_text:
# Extrahiert den Teil nach dem ersten ```
parts = response_text.split('```', 1)
if len(parts) > 1:
response_text = parts[1]
# Entfernt das Ende des Codeblocks
if '```' in response_text:
response_text = response_text.split('```')[0]
return response_text.strip()
def generate_extraction_prompt(content, data_to_extract):
"""Erstellt einen spezialisierten Prompt, um bestimmte Daten zu extrahieren."""
prompts = {
"pain_points": (
"Du bist ein Branchenanalyst. Lies das folgende Dokument und extrahiere die 5 wichtigsten operativen "
"Herausforderungen (Pain Points) für Unternehmen dieser Branche im Bereich Field Service. "
"Formuliere sie als prägnante Stichpunkte.\n\n"
"Gib das Ergebnis ausschließlich als YAML-Liste unter dem Schlüssel 'pain_points:' aus. KEINE weiteren Kommentare."
),
"key_terms": (
"Du bist ein Fachlexikograf. Lies das folgende Dokument und extrahiere die 10 wichtigsten Fachbegriffe, "
"Abkürzungen oder Normen, die im Kontext von Service, Wartung und Technik verwendet werden.\n\n"
"Gib das Ergebnis ausschließlich als YAML-Liste unter dem Schlüssel 'key_terms:' aus."
),
"summary": (
"Du bist ein Chefredakteur. Lies das folgende Dokument und verfasse eine prägnante Zusammenfassung (max. 3 Sätze) "
"über die allgemeine Geschäftslage, die wichtigsten Trends und die Bedeutung des Field Service in dieser Branche.\n\n"
"Gib das Ergebnis ausschließlich als einfachen Text unter dem YAML-Schlüssel 'summary:' aus."
)
}
if data_to_extract not in prompts:
raise ValueError(f"Unbekannter Extraktionstyp: {data_to_extract}")
return f"{prompts[data_to_extract]}\n\n--- DOKUMENTENINHALT ---\n\n{content}"
def main():
"""Liest .docx-Dateien, extrahiert Wissen per KI und speichert es als YAML."""
logging.info("Starte die KI-gestützte Extraktion von Branchen-Wissen...")
# API-Schlüssel laden
Config.load_api_keys()
openai.api_key = Config.API_KEYS.get('openai')
if not openai.api_key:
logging.critical("OpenAI API Key nicht in config.py gefunden. Skript wird beendet.")
return
if not os.path.exists(DOCS_SOURCE_FOLDER):
logging.critical(f"Der Quellordner '{DOCS_SOURCE_FOLDER}' wurde nicht gefunden. Bitte erstellen und die .docx-Dateien dort ablegen.")
return
knowledge_base = {'Branchen': {}}
doc_files = [f for f in os.listdir(DOCS_SOURCE_FOLDER) if f.endswith('.docx')]
logging.info(f"Gefundene Dokumente zur Verarbeitung: {', '.join(doc_files)}")
for filename in doc_files:
# Extrahiere den Branchennamen aus dem Dateinamen
# z.B. "Focus_insights_HVAC.docx" -> "Gebäudetechnik Heizung, Lüftung, Klima"
# Dies muss manuell oder durch eine Mapping-Tabelle angepasst werden.
# Für den Moment nehmen wir den Namen aus der Datei.
base_name = os.path.splitext(filename)[0].replace("Focus_insights_", "")
# Sie können hier ein Mapping zu den sauberen Namen aus Ihrer `config.py` einfügen.
# Beispiel: branch_name = MAPPING.get(base_name, base_name)
branch_name = base_name.replace("_", " ") # Einfache Normalisierung für den Start
logging.info(f"\n--- Verarbeite Branche: {branch_name} aus Datei {filename} ---")
filepath = os.path.join(DOCS_SOURCE_FOLDER, filename)
content = read_docx_content(filepath)
if not content:
continue
branch_data = {
'references_DE': '[HIER DEUTSCHE REFERENZKUNDEN EINTRAGEN]',
'references_GB': '[HIER ENGLISCHE REFERENZKUNDEN EINTRAGEN]'
}
# Extrahiere Pain Points, Key Terms und Summary
for data_type in ["pain_points", "key_terms", "summary"]:
logging.info(f" -> Extrahiere '{data_type}'...")
prompt = generate_extraction_prompt(content, data_type)
response_text = call_openai_with_retry(prompt)
if response_text:
try:
# NEU: Erst den sauberen YAML-Teil extrahieren
clean_yaml_text = extract_yaml_from_response(response_text)
# Dann den sauberen Text parsen
parsed_yaml = yaml.safe_load(clean_yaml_text)
if parsed_yaml: # Sicherstellen, dass das Ergebnis nicht leer ist
branch_data.update(parsed_yaml)
else:
raise ValueError("Geparsstes YAML ist leer.")
except Exception as e:
logging.error(f" Fehler beim Parsen der YAML-Antwort für '{data_type}': {e}")
# Speichere die *gesamte* ursprüngliche Antwort für Debugging-Zwecke
branch_data[data_type] = f"PARSING-FEHLER: {response_text}"
time.sleep(2) # Pause zwischen API-Aufrufen
knowledge_base['Branchen'][branch_name] = branch_data
# Persona-Daten hinzufügen (diese sind statisch)
# Hier können Sie die Persona-Daten aus der letzten Iteration einfügen.
# ...
# Ergebnis in YAML-Datei speichern
try:
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
yaml.dump(knowledge_base, f, allow_unicode=True, sort_keys=False, width=120)
logging.info(f"\nErfolgreich! Die Wissensbasis wurde in '{OUTPUT_FILE}' gespeichert.")
logging.info("BITTE ÜBERPRÜFEN SIE DIESE DATEI UND PASSEN SIE SIE NACH BEDARF AN.")
except Exception as e:
logging.error(f"Fehler beim Speichern der YAML-Datei: {e}")
if __name__ == "__main__":
main()