# lead-engine/trading_twins/manager.py import requests import json import os import time from datetime import datetime, timedelta from threading import Thread, Lock import uvicorn from fastapi import FastAPI, Response # --- Konfiguration --- # In einer echten Anwendung würden diese Werte aus .env-Dateien oder einer Config-Map geladen TEAMS_WEBHOOK_URL = "https://wacklergroup.webhook.office.com/webhookb2/fe728cde-790c-4190-b1d3-be393ca0f9bd@6d85a9ef-3878-420b-8f43-38d6cb12b665/IncomingWebhook/e9a8ee6157594a6cab96048cf2ea2232/V2WFmjcbkMzSU4f6lDSdUOM9VNm7F7n1Th4YDiu3fLZ_Y1" FEEDBACK_SERVER_BASE_URL = "http://localhost:8004" # TODO: Muss durch die öffentliche IP/Domain ersetzt werden DEFAULT_WAIT_MINUTES = 5 # --- In-Memory-Speicher für den Status der Anfragen --- # In einem Produktionsszenario wäre hier eine robustere Lösung wie Redis oder eine DB nötig. request_status_storage = {} _lock = Lock() # --- Modul zur Erstellung von Adaptive Cards --- def create_adaptive_card_payload(customer_name: str, send_time: datetime, request_id: str) -> dict: """ Erstellt die JSON-Payload für die Adaptive Card in Teams. """ send_time_str = send_time.strftime("%H:%M Uhr") stop_url = f"{FEEDBACK_SERVER_BASE_URL}/stop/{request_id}" send_now_url = f"{FEEDBACK_SERVER_BASE_URL}/send_now/{request_id}" card = { "type": "message", "attachments": [ { "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.4", "body": [ { "type": "TextBlock", "text": f"🤖 Automatisierte E-Mail an {customer_name} (via Trading Twins) wird um {send_time_str} ausgesendet.", "wrap": True, "size": "Medium", "weight": "Bolder" }, { "type": "TextBlock", "text": f"Wenn Du bis {send_time_str} NICHT reagierst, wird die generierte E-Mail automatisch ausgesendet.", "wrap": True, "isSubtle": True } ], "actions": [ { "type": "Action.OpenUrl", "title": "❌ STOP Aussendung", "url": stop_url, "style": "destructive" }, { "type": "Action.OpenUrl", "title": "✅ JETZT Aussenden", "url": send_now_url, "style": "positive" } ] } } ] } return card # --- Haupt-Workflow-Logik --- def send_teams_notification(payload: dict): """Sendet die vorbereitete Payload an den Teams Webhook.""" try: response = requests.post(TEAMS_WEBHOOK_URL, json=payload, timeout=10) if response.status_code == 200 or response.status_code == 202: print(f"INFO: Adaptive Card sent to Teams. Response: {response.text}") return True else: print(f"ERROR: Failed to send card. Status: {response.status_code}, Text: {response.text}") return False except requests.RequestException as e: print(f"ERROR: Request to Teams failed: {e}") return False def process_email_request(request_id: str, customer_name: str): """ Der Hauptprozess, der die Benachrichtigung auslöst und auf das Ergebnis wartet. """ send_time = datetime.now() + timedelta(minutes=DEFAULT_WAIT_MINUTES) with _lock: request_status_storage[request_id] = { "status": "pending", # pending, cancelled, send_now, sent, timeout "customer": customer_name, "send_time": send_time.isoformat() } # 1. Adaptive Card erstellen und an Teams senden adaptive_card = create_adaptive_card_payload(customer_name, send_time, request_id) if not send_teams_notification(adaptive_card): print(f"CRITICAL: Could not send Teams notification for request {request_id}. Aborting.") return # 2. Warten auf menschliches Feedback oder Timeout print(f"INFO: Waiting for feedback for request {request_id} until {send_time.strftime('%H:%M:%S')}...") while datetime.now() < send_time: with _lock: current_status = request_status_storage[request_id]["status"] if current_status == "cancelled": print(f"INFO: Request {request_id} was cancelled by the user.") return if current_status == "send_now": print(f"INFO: Request {request_id} was triggered to send immediately by the user.") break # Schleife verlassen und sofort senden time.sleep(5) # 3. Finale Entscheidung und Ausführung with _lock: final_status = request_status_storage[request_id]["status"] # Update status to avoid race conditions if final_status == "pending": request_status_storage[request_id]["status"] = "timeout" final_status = "timeout" if final_status in ["send_now", "timeout"]: print(f"SUCCESS: Proceeding to send email for request {request_id} (Status: {final_status})") # --- HIER KOMMT DIE ECHTE E-MAIL LOGIK (MS GRAPH API) --- # send_email_via_graph_api(customer_name, signature_path, banner_path) print("MOCK: Email would be sent now.") # --------------------------------------------------------- with _lock: request_status_storage[request_id]["status"] = "sent" else: # Dieser Fall sollte eigentlich nicht eintreten, aber zur Sicherheit print(f"WARN: Email for request {request_id} was not sent due to final status: {final_status}") # --- Feedback-Server (FastAPI) --- app = FastAPI() @app.get("/stop/{request_id}") async def stop_sending(request_id: str): with _lock: if request_id in request_status_storage: if request_status_storage[request_id]["status"] == "pending": request_status_storage[request_id]["status"] = "cancelled" customer = request_status_storage[request_id]['customer'] print(f"INFO: Received STOP for request {request_id}") return Response(content=f"
Der Versand wurde erfolgreich abgebrochen.
", media_type="text/html") else: status = request_status_storage[request_id]['status'] return Response(content=f"Der Status für diese Anfrage ist bereits '{status}'. Es kann nicht mehr gestoppt werden.
", media_type="text/html", status_code=409) return Response(content="Anfrage-ID nicht gefunden.
", media_type="text/html", status_code=404) @app.get("/send_now/{request_id}") async def send_now(request_id: str): with _lock: if request_id in request_status_storage: if request_status_storage[request_id]["status"] == "pending": request_status_storage[request_id]["status"] = "send_now" customer = request_status_storage[request_id]['customer'] print(f"INFO: Received SEND_NOW for request {request_id}") return Response(content=f"Der Versand wird sofort ausgelöst.
", media_type="text/html") else: status = request_status_storage[request_id]['status'] return Response(content=f"Der Status für diese Anfrage ist bereits '{status}'.
", media_type="text/html", status_code=409) return Response(content="Anfrage-ID nicht gefunden.
", media_type="text/html", status_code=404) def run_server(): """Startet den FastAPI-Server.""" uvicorn.run(app, host="0.0.0.0", port=8004) if __name__ == "__main__": # Starte den Feedback-Server in einem separaten Thread server_thread = Thread(target=run_server) server_thread.daemon = True server_thread.start() print("INFO: Feedback-Server started on port 8004 in background.") time.sleep(2) # Kurz warten, bis der Server gestartet ist # Simuliere eine neue Anfrage test_request_id = f"req_{int(time.time())}" test_customer = "Klinikum Erding" print(f"\n--- Starting new email request for '{test_customer}' with ID: {test_request_id} ---") process_email_request(test_request_id, test_customer) print(f"--- Process for {test_request_id} finished. ---") # Halte das Hauptprogramm am Leben, damit der Server weiterlaufen kann # In einer echten Anwendung wäre dies Teil eines größeren Dienstes. print("\nManager is running. Press Ctrl+C to stop.") try: while True: time.sleep(1) except KeyboardInterrupt: print("\nShutting down manager.")