Spaces:
Sleeping
Sleeping
File size: 6,367 Bytes
ea5ab02 dfcacaf ea5ab02 16dab50 dfcacaf ea5ab02 dfcacaf c02ba89 dfcacaf c02ba89 16dab50 4e481d5 ea5ab02 16dab50 ea5ab02 16dab50 4e481d5 ea5ab02 4e481d5 18008d3 4e481d5 dfcacaf 4e481d5 dfcacaf 4e481d5 dfcacaf ea5ab02 4e481d5 ea5ab02 c02ba89 16dab50 ea5ab02 4e481d5 ea5ab02 4e481d5 ea5ab02 16dab50 ea5ab02 4e481d5 ea5ab02 dfcacaf 4e481d5 dfcacaf ea5ab02 4e481d5 ea5ab02 4e481d5 ea5ab02 4e481d5 16dab50 c02ba89 16dab50 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | from fastapi import FastAPI, Form, Response, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import requests
import datetime
import hashlib
import os
app = FastAPI(title="NEXUS COMMAND CENTER")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "AC3f5f0eb3da2c3f839fb021ac203ad781")
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "88e8a43f9abb88f9abdbf7867c80ab30")
TWILIO_WHATSAPP_NUMBER = "whatsapp:+14155238886"
tickets_db = {}
compteur_ticket = 1
class DispatchOrdre(BaseModel):
service: str
class DemandeWeb(BaseModel):
description: str
def analyser_et_repondre_ia(texte_message: str, numero_expediteur: str, ticket_id: int):
"""Fonction exécutée en arrière-plan : interroge Ollama et met à jour le Dashboard"""
try:
url_ollama = "http://127.0.0.1:11434/api/generate"
payload = {
"model": "gemma4:e4b",
"prompt": f"Tu es le dispatcher d'urgence NEXUS. Analyse ce message et réponds en incluant le service d'urgence le plus approprié (POLICE, POMPIERS, ou SAMU) : {texte_message}",
"stream": False
}
reponse_ollama = requests.post(url_ollama, json=payload, timeout=180)
analyse_ia = reponse_ollama.json().get("response", "Erreur IA")
domaine_ia = "INCONNU"
texte_maj = analyse_ia.upper()
if "POLICE" in texte_maj: domaine_ia = "POLICE"
elif "POMPIERS" in texte_maj: domaine_ia = "POMPIERS"
elif "SAMU" in texte_maj: domaine_ia = "SAMU"
tickets_db[ticket_id]["domaine_ia"] = domaine_ia
tickets_db[ticket_id]["score"] = 8 if domaine_ia != "INCONNU" else 5
tickets_db[ticket_id]["status"] = "EN ATTENTE DISPATCH"
except Exception as e:
analyse_ia = f"⚠️ Échec du traitement local : {str(e)}"
tickets_db[ticket_id]["domaine_ia"] = "ERREUR IA"
url_twilio = f"https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/Messages.json"
donnees_whatsapp = {
"From": TWILIO_WHATSAPP_NUMBER,
"To": numero_expediteur,
"Body": f"🧠 [NEXUS PRIME - ANALYSE IA]\n\n{analyse_ia}"
}
requests.post(url_twilio, data=donnees_whatsapp, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN))
@app.post("/api/twilio")
async def twilio_webhook(background_tasks: BackgroundTasks, Body: str = Form(...), From: str = Form(...)):
"""Route Webhook : Répond immédiatement, sauvegarde le ticket et lance l'IA"""
global compteur_ticket
msg_hash = hashlib.md5(f"{From}:{Body}".encode()).hexdigest()
if any(t.get("hash") == msg_hash for t in tickets_db.values()):
return Response(content="<Response/>", media_type="application/xml")
ticket_id = compteur_ticket
compteur_ticket += 1
tickets_db[ticket_id] = {
"id": ticket_id,
"heure": datetime.datetime.now().strftime("%H:%M:%S"),
"telephone": From,
"message": Body,
"domaine_ia": "Analyse en cours... ⏳",
"score": 0,
"status": "NOUVEAU",
"hash": msg_hash
}
background_tasks.add_task(analyser_et_repondre_ia, Body, From, ticket_id)
reponse_twiml = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Message>NEXUS a bien reçu votre signalement (Dossier #{ticket_id}). Analyse IA en cours... ⏳</Message>
</Response>
"""
return Response(content=reponse_twiml, media_type="application/xml")
@app.get("/api/tickets")
def get_tickets():
"""Route pour que le Dashboard HTML récupère la liste des urgences"""
return sorted(list(tickets_db.values()), key=lambda x: x["id"], reverse=True)
@app.post("/api/tickets/{ticket_id}/dispatch")
def dispatch_ticket(ticket_id: int, ordre: DispatchOrdre):
"""Route appelée par le bouton du Dashboard pour envoyer les secours"""
if ticket_id not in tickets_db:
return {"error": "Ticket introuvable"}
ticket = tickets_db[ticket_id]
service_cible = ordre.service
ticket["status"] = f"TRANSFÉRÉ -> {service_cible}"
message_citoyen = f"✅ NEXUS INFO : Votre urgence a été transmise aux {service_cible}. Les secours sont en route."
url_twilio = f"https://api.twilio.com/2010-04-01/Accounts/{TWILIO_ACCOUNT_SID}/Messages.json"
donnees = {"From": TWILIO_WHATSAPP_NUMBER, "To": ticket["telephone"], "Body": message_citoyen}
requests.post(url_twilio, data=donnees, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN))
return {"status": "success", "ticket": ticket}
@app.post("/api/evaluer")
def evaluer_ticket_web(demande: DemandeWeb):
"""Route réelle : interroge l'IA locale (Ollama) en direct pour le site web"""
try:
url_ollama = "http://127.0.0.1:11434/api/generate"
payload = {
"model": "gemma4:e4b",
"prompt": f"Tu es le dispatcher d'urgence NEXUS. Analyse ce message et réponds en incluant le service d'urgence le plus approprié (POLICE, POMPIERS, ou SAMU) : {demande.description}",
"stream": False
}
reponse_ollama = requests.post(url_ollama, json=payload, timeout=180)
analyse_ia = reponse_ollama.json().get("response", "INCONNU").upper()
domaine = "INCONNU"
score = 5
friction = "COMPLET"
question = ""
if "POLICE" in analyse_ia:
domaine = "POLICE"
score = 8
elif "POMPIERS" in analyse_ia:
domaine = "POMPIERS"
score = 9
elif "SAMU" in analyse_ia:
domaine = "SAMU"
score = 10
else:
friction = "INCOMPLET"
score = 3
question = "L'analyse IA nécessite plus de contexte. Pouvez-vous préciser la nature exacte de l'urgence ?"
return {
"domaine": domaine,
"score": score,
"friction": friction,
"question_ia": question
}
except Exception as e:
return {
"domaine": "ERREUR",
"score": 0,
"friction": "ERREUR SERVEUR",
"question_ia": f"Impossible de joindre le modèle IA local : {str(e)}"
} |