from fastapi import FastAPI, Request import json import os import firebase_admin from firebase_admin import credentials, firestore from datetime import datetime app = FastAPI() # --- SETUP --- COLLECTION_KNOWLEDGE = "knowledge_base" COLLECTION_RULES = "availability_rules" COLLECTION_INBOX = "inbox" # NEU: Hier landen ungelöste Fragen KNOWLEDGE_CACHE = [] # --- FIREBASE VERBINDUNG --- db = None try: key = os.environ.get("FIREBASE_KEY") if key: cred = credentials.Certificate(json.loads(key)) if not firebase_admin._apps: firebase_admin.initialize_app(cred) db = firestore.client() print("✅ DB VERBUNDEN") else: print("❌ FEHLER: FIREBASE_KEY fehlt!") except Exception as e: print(f"❌ DB CRASH: {e}") # --- CACHE LADEN --- def reload_knowledge(): global KNOWLEDGE_CACHE if not db: return try: docs = db.collection(COLLECTION_KNOWLEDGE).stream() KNOWLEDGE_CACHE = [d.to_dict() for d in docs] print(f"📚 {len(KNOWLEDGE_CACHE)} Einträge geladen.") except Exception as e: print(f"❌ Cache Fehler: {e}") @app.on_event("startup") async def startup(): reload_knowledge() def get_stem(word): w = word.lower().strip() for end in ["ern", "en", "er", "es", "st", "te", "e", "s", "t"]: if w.endswith(end) and len(w) > len(end)+2: return w[:-len(end)] return w # --- HELPER: VAPI REQUEST PARSER --- def parse_vapi_request(data): tool_call_id = "unknown" args = {} try: msg = data.get("message", {}) if "toolCallList" in msg: call = msg["toolCallList"][0] tool_call_id = call["id"] if "function" in call and "arguments" in call["function"]: args = call["function"]["arguments"] elif "toolCalls" in msg: call = msg["toolCalls"][0] tool_call_id = call["id"] if "function" in call and "arguments" in call["function"]: args = call["function"]["arguments"] if isinstance(args, str): args = json.loads(args) except Exception as e: print(f"⚠️ Parsing Info: {e}") return tool_call_id, args # ========================================== # TOOL 1: VERFÜGBARKEIT # ========================================== @app.post("/check_availability") async def check_availability(request: Request): data = await request.json() tool_call_id, _ = parse_vapi_request(data) today = datetime.now().strftime("%Y-%m-%d") status = "available" instruction = "Normal arbeiten" try: if db: rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream() for r in rules: rd = r.to_dict() if rd.get('start_date') <= today <= rd.get('end_date'): print(f"🛑 REGEL AKTIV: {rd.get('name')}") # Einfache Logik: Wenn "Ferien" im Namen -> Limited, sonst Unavailable if "ferien" in rd.get('name', '').lower(): status = "limited" else: status = "unavailable" instruction = rd.get('instruction_text') break except Exception as e: print(f"❌ ERROR CHECK: {e}") return { "results": [{"toolCallId": tool_call_id, "result": {"status": status, "instruction": instruction}}] } # ========================================== # TOOL 2: SUCHE (Mit Inbox-Speicherung!) # ========================================== @app.post("/search") async def search(request: Request): data = await request.json() tool_call_id, args = parse_vapi_request(data) query = args.get("search_query") or args.get("query") or data.get("search_query") print(f"🔎 FRAGE (ID: {tool_call_id}): '{query}'") answer_text = "Dazu habe ich leider keine Informationen in meiner Datenbank." if query: STOP_WORDS = ["hallo", "guten", "tag", "moin", "bitte", "danke", "frage"] q_words = [get_stem(w) for w in query.lower().split() if len(w)>2] relevant_words = [w for w in q_words if w not in STOP_WORDS] found = False if relevant_words: best_doc = None best_score = 0 for doc in KNOWLEDGE_CACHE: score = 0 title = doc.get("question", "").lower() content = doc.get("answer", "").lower() keywords = [k.lower() for k in doc.get("keywords", [])] for word in relevant_words: if word in title: score += 50 for k in keywords: if get_stem(k) == get_stem(word): score += 30 if word in content: score += 5 if score > best_score: best_score = score best_doc = doc # SCHWELLE: 20 PUNKTE if best_doc and best_score >= 20: print(f"🏆 TREFFER ({best_score}): {best_doc.get('question')}") answer_text = best_doc.get("answer") found = True else: print(f"⚠️ Zu wenig Relevanz (Max: {best_score})") # --- NEU: SPEICHERN WENN NICHT GEFUNDEN --- if not found and db: print("📥 Speichere in Inbox...") db.collection(COLLECTION_INBOX).add({ "query": query, "timestamp": datetime.now(), "status": "open" }) return { "results": [{"toolCallId": tool_call_id, "result": answer_text}] } @app.post("/vapi-incoming") async def dummy_incoming(request: Request): return {"status": "ok"} @app.get("/") def home(): return {"status": "Online", "docs": len(KNOWLEDGE_CACHE)}