Spaces:
Sleeping
Sleeping
| # executives/main.py | |
| import asyncio | |
| import sqlite3 | |
| import datetime | |
| import warnings | |
| import sys | |
| import re | |
| import textwrap | |
| import os | |
| import difflib | |
| from ollama import AsyncClient | |
| from groq import AsyncGroq | |
| import nexus_config | |
| from nexus_qualification import QualificationEngine | |
| from nexus_matrix import DOMAINES | |
| warnings.filterwarnings("ignore") | |
| # ===================================================================== | |
| # MODULE 1 : BASE DE DONNÉES & LOGGING (MUTLI-THREAD) | |
| # ===================================================================== | |
| class ShadowLogger: | |
| def __init__(self): | |
| self.conn = sqlite3.connect(nexus_config.DB_PATH, check_same_thread=False) | |
| self.cursor = self.conn.cursor() | |
| self.cursor.execute('''CREATE TABLE IF NOT EXISTS interactions_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, date_log TEXT, ticket_complet TEXT, | |
| domaine_principal TEXT, domaines_secondaires TEXT, score REAL, statut TEXT)''') | |
| self.conn.commit() | |
| def log(self, ticket_final, domaine_principal, sec_list, score, statut="CLOS"): | |
| date_now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| sec_str = ", ".join(sec_list) if sec_list else "AUCUN" | |
| ticket_assaini = str(ticket_final).encode('utf-8', errors='ignore').decode('utf-8') | |
| try: | |
| self.cursor.execute('''INSERT INTO interactions_log (date_log, ticket_complet, domaine_principal, domaines_secondaires, score, statut) | |
| VALUES (?, ?, ?, ?, ?, ?)''', (date_now, ticket_assaini, domaine_principal, sec_str, score, statut)) | |
| self.conn.commit() | |
| except sqlite3.OperationalError: | |
| self.cursor.execute('DROP TABLE IF EXISTS interactions_log') | |
| self.cursor.execute('''CREATE TABLE IF NOT EXISTS interactions_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, date_log TEXT, ticket_complet TEXT, | |
| domaine_principal TEXT, domaines_secondaires TEXT, score REAL, statut TEXT)''') | |
| self.cursor.execute('''INSERT INTO interactions_log (date_log, ticket_complet, domaine_principal, domaines_secondaires, score, statut) | |
| VALUES (?, ?, ?, ?, ?, ?)''', (date_now, ticket_assaini, domaine_principal, sec_str, score, statut)) | |
| self.conn.commit() | |
| print("\n" + "📊 " + "=" * 25 + " DATABASE COMMIT OVERSIGHT " + "=" * 25) | |
| print(f" 📅 Horodatage : {date_now}") | |
| print(f" 📂 Statut Log : {statut}") | |
| print(f" 🎯 Domaine Maître : {domaine_principal} | Urgence Score : {score}/10") | |
| print(f" 🚨 Renforts Unit : {sec_str}") | |
| print(f" 📝 Transcript Final Reconstitué :\n {ticket_assaini}") | |
| print("=" * 78 + "\n") | |
| # ===================================================================== | |
| # MODULE 2 : EXÉCUTION TACTIQUE | |
| # ===================================================================== | |
| class TaskExecutor: | |
| def declencher_protocoles(domaines_impliques, niveau_urgence, resume_ticket, motif_cloture="STANDARD"): | |
| print("\n ⚡ DÉCLENCHEMENT DES TÂCHES OPÉRATIONNELLES (MULTI-SERVICES) :") | |
| if motif_cloture == "SILENCE_CRITIQUE": | |
| print(" [ALERTE] 🚨 RUPTURE DE LIAISON DÉTECTÉE - DÉPLOIEMENT SUR DERNIÈRE POSITION CONNUE") | |
| elif motif_cloture == "CRI_DETECTE": | |
| print(" [ALERTE] 🚨 PANIQUE ACOUSTIQUE DÉTECTÉE - INTERVENTION MAXIMUM IMMÉDIATE") | |
| elif motif_cloture == "FRUSTRATION_CRITIQUE": | |
| print(" [ALERTE] ⚠️ RUPTURE DE COOPÉRATION - ARBITRAGE D'URGENCE : ENVOI IMMÉDIAT DES SECOURS") | |
| if niveau_urgence == "⚡ PROTOCOLE JUPITER": | |
| print(" [CONSEIL DE DÉFENSE] 🎖️ Activation immédiate du protocole souverain -> ALERTÉ") | |
| print(" [GIGN / RAID] 🚁 Déploiement des forces d'intervention spéciales -> EN COURS") | |
| elif niveau_urgence == "🟣 CONTINUITÉ DE L'ÉTAT": | |
| print(" [CIC REGLÉ] 📡 Activation de la Cellule Interministérielle de Crise -> ALERTÉ") | |
| elif "PLAN NOVI" in niveau_urgence: | |
| print(" [PLAN NOVI] ⛺ Déploiement Poste Médical Avancé (PMA) et Plan Blanc -> EN COURS") | |
| print(" [COD PRÉFET] 📡 Activation du Centre Opérationnel Départemental -> ALERTÉ") | |
| elif "URGENCE VITALE" in niveau_urgence: | |
| print(" [SAMU 15] 🚑 Envoi d'une Unité Mobile Hospitalière (SMUR) -> EN COURS") | |
| for domaine in domaines_impliques: | |
| print(f"\n --- Unité En Alerte : {domaine} ---") | |
| # VARIÉTÉ DES DOMAINES : Le dispatcher ignore la basse priorité | |
| if motif_cloture == "ABANDON" or domaine in ["EN_ATTENTE", "NON_URGENT", "DIGITAL SUPPORT", "ANIMALIER"]: | |
| print(f" [DISPATCH] 🗑️ Traitement basse priorité / Administratif ({domaine}) -> ARCHIVÉ SANS SIRÈNE") | |
| elif domaine == "POMPIER": | |
| print(" [WEBHOOK] 🚒 Transmission au SDIS local (Code Rouge) -> OK") | |
| print(" [TASK] 🗺️ Extraction des coordonnées GPS pour les engins -> EN COURS") | |
| elif domaine == "POLICE": | |
| print(" [WEBHOOK] 🚓 Alerte patrouille secteur en cours (Sécurisation) -> OK") | |
| elif domaine == "ÉNERGIE & INFRASTRUCTURES" or domaine == "CYBERSÉCURITÉ": | |
| print(" [WEBHOOK] ⚡ Alerte Cellule de Crise Infrastructure -> OK") | |
| elif domaine == "MÉDICAL" and "URGENCE VITALE" not in niveau_urgence and "PLAN NOVI" not in niveau_urgence: | |
| print(" [WEBHOOK] 🚑 Transmission du bilan au SAMU (Régulation 15) -> OK") | |
| else: | |
| if domaine != "MÉDICAL": | |
| print(f" [DISPATCH] 📨 Transmission standard au centre {domaine} -> OK") | |
| # ===================================================================== | |
| # MODULE 3 : MOTEUR COGNITIF HYBRIDE (LLM) | |
| # ===================================================================== | |
| class NexusAgenticSystem: | |
| def __init__(self): | |
| print(f"🧠 Initialisation NEXUS V_ULTIME (Cloud & Local {nexus_config.MODEL_LOCAL})...") | |
| self.logger = ShadowLogger() | |
| self.evaluator = QualificationEngine() | |
| self.executor = TaskExecutor() | |
| self.client_llm = AsyncClient() | |
| self.local_model = nexus_config.MODEL_LOCAL | |
| self.llm_en_ligne = False | |
| self.groq_key = os.getenv("GROQ_API_KEY") | |
| self.groq_disponible = bool(self.groq_key) | |
| if self.groq_disponible: | |
| self.client_groq = AsyncGroq(api_key=self.groq_key) | |
| print("🌐 [CLOUD] Client Groq initialisé.") | |
| else: | |
| print("⚠️ [WARN] Clé GROQ_API_KEY introuvable. Souveraineté locale exclusive.") | |
| async def prechauffer_cerveau(self): | |
| print(f"🔥 Pré-chauffage du bouclier local {self.local_model} en cours...") | |
| try: | |
| await self.client_llm.chat(model=self.local_model, messages=[{'role': 'user', 'content': 'ping'}], | |
| options={'num_predict': 1}) | |
| self.llm_en_ligne = True | |
| print(f"✅ Moteur local {self.local_model} paré et monté en RAM.") | |
| except Exception as e: | |
| self.llm_en_ligne = False | |
| print(f"⚠️ ERREUR CRITIQUE LOCAL : Impossible de joindre Ollama. {e}") | |
| if self.groq_disponible: | |
| print(f"📡 Vérification du pipeline Groq Cloud ({nexus_config.MODEL_CLOUD})...") | |
| try: | |
| await asyncio.wait_for( | |
| self.client_groq.chat.completions.create( | |
| model=nexus_config.MODEL_CLOUD, messages=[{'role': 'user', 'content': 'ping'}], | |
| max_tokens=1 | |
| ), | |
| timeout=5.0 | |
| ) | |
| print(f"✅ [STATUS] Inférence Cloud active ({nexus_config.MODEL_CLOUD}).") | |
| except Exception as e: | |
| self.groq_disponible = False | |
| def est_salutation_basique(self, texte): | |
| t_clean = texte.strip().lower() | |
| t_clean = re.sub(r'[^\w\s]', '', t_clean) | |
| return t_clean in ["bonjour", "salut", "allo", "allô", "bonsoir", "oui", "non", "ok"] | |
| def detecter_choc_acoustique(self, texte): | |
| t = texte.upper() | |
| if re.search(r'\b(CASSÉ MON TÉLÉPHONE|CASSE MON TELEPHONE|PÉTÉ MON ORDI|PASSEPORT|CLAVIER)\b', t): | |
| return False | |
| if re.search(r'\b(AU SECOURS|AIDEZ-MOI|ÇA EXPLOSE|AU FEU|JE MEURS|AAAA|VITE VITE)\b', t): | |
| return True | |
| if len(t) > 5 and sum(1 for c in texte if c.isupper()) / len(texte) > 0.6: | |
| return True | |
| return False | |
| def verifier_presence_localisation(self, texte): | |
| t = texte.lower() | |
| if re.search(r'\b(gare|aéroport|aeroport|hôpital|hopital|clinique|mairie|préfecture|prefecture|super u|élysée|elysee|banque)\b', t): | |
| return True | |
| if re.search(r'\b(rue|avenue|boulevard|impasse|allée|chemin|route|place|square|pont)\b', t): | |
| return True | |
| if re.search(r'\b\d{5}\b', t): | |
| return True | |
| return False | |
| def get_regex_urgence_vitale(self): | |
| return r'\b(arrêt cardiaque|arret cardiaque|crise cardiaque|infarctus|mal au coeur|mal au c[oœ]ur|poitrine|coeur|cœur|cardiaque|' \ | |
| r'inconscient|coma|évanoui|evanoui|évanouie|evanouie|malaise|tomber dans les pommes|' \ | |
| r'étouffe|etouffe|respire plus|asphyxie|' \ | |
| r'saigne|sang|hémorragie|hemorragie|amputé|ampute|' \ | |
| r'mourir|meurt|vie en danger|vie et mort)\b' | |
| def verifier_protocole_jupiter(self, texte): | |
| t = str(texte).lower() | |
| cible_president = re.search(r'\b(président|president|macron|élysée|elysée|elysee|centrale|nucléaire|nucleaire)\b', t) | |
| if not cible_president: | |
| return None | |
| # FUZZY MATCHING : Tolérance aux erreurs de frappe en urgence souveraine | |
| mots_menaces_internes = [ | |
| 'arme', 'tir', 'tirs', 'assassinat', 'tuer', 'bombe', 'attaque', | |
| 'sang', 'inconscient', 'coma', 'évanoui', 'évanouie', 'étouffe', 'cardiaque', | |
| 'coeur', 'poitrine', 'infarctus', 'saigne', 'hémorragie', 'mourir', | |
| 'feu', 'incendie', 'explosion', 'fuite', 'toxique', 'chimique' | |
| ] | |
| mots_texte = re.findall(r'\w+', t) | |
| menace_valide = False | |
| if re.search(self.get_regex_urgence_vitale() + r'|\b(arme|tir|tirs|assassinat|tuer|bombe|attaque|feu|incendie|explosion|fuite)\b', t): | |
| menace_valide = True | |
| else: | |
| for mot in mots_texte: | |
| if len(mot) < 4: continue | |
| match = difflib.get_close_matches(mot, mots_menaces_internes, n=1, cutoff=0.8) | |
| if match: | |
| menace_valide = True | |
| break | |
| if menace_valide: | |
| if re.search(self.get_regex_urgence_vitale(), t) or any(difflib.get_close_matches(m, mots_menaces_internes[8:20], 1, 0.8) for m in mots_texte): | |
| return "MÉDICAL", 20.0, ["POLICE", "POMPIER", "TRANSPORT & MOBILITÉ", "RISQUES ENVIRONNEMENTAUX"] | |
| return "POLICE", 20.0, ["MÉDICAL", "POMPIER", "CYBERSÉCURITÉ", "TRANSPORT & MOBILITÉ"] | |
| return None | |
| def verifier_mots_clefs_vitaux(self, texte): | |
| t_lower = str(texte).lower() | |
| if re.search(r'\b(pété mon|cassé mon|va me tuer|téléphone|telephone|ordi|clavier|souris)\b', t_lower): | |
| return False | |
| if re.search(self.get_regex_urgence_vitale(), t_lower): | |
| return True | |
| # FUZZY MATCHING : Pour le citoyen normal | |
| mots_vitaux_isoles = [ | |
| 'évanoui', 'évanouie', 'étouffe', 'inconscient', 'malaise', 'coeur', | |
| 'poitrine', 'infarctus', 'mourir', 'saigne', | |
| 'amputé', 'hémorragie', 'suicide', 'cardiaque' | |
| ] | |
| mots_texte = re.findall(r'\w+', t_lower) | |
| for mot in mots_texte: | |
| if len(mot) < 4: continue | |
| correspondances = difflib.get_close_matches(mot, mots_vitaux_isoles, n=1, cutoff=0.8) | |
| if correspondances: | |
| return True | |
| return False | |
| def verifier_urgences_combinees(self, texte): | |
| t_lower = str(texte).lower() | |
| if re.search(r'\b(clavier|souris|ordi|ordinateur|téléphone|telephone|connexion|wifi|mot de passe|imprimante)\b', t_lower): | |
| return "DIGITAL SUPPORT", 1.5, [] | |
| has_transport = re.search(r'\b(bus|cars|car|train|métro|metro|tram|avion)\b', t_lower) | |
| has_accident = re.search(r'\b(accident|crash|collision|déraillement|renversé)\b', t_lower) | |
| has_groupe = re.search(r'\b(groupe|foule|plusieurs|masse|nombreux|personnes|gens|équipe|classe|centaine|cinquantaine)\b', t_lower) | |
| has_feu = re.search(r'\b(feu|incendie|brûle|brule|flammes)\b', t_lower) | |
| has_chimique = re.search(r'\b(chimique|fuite|gaz|toxique|nuage)\b', t_lower) | |
| has_detresse = re.search(self.get_regex_urgence_vitale() + r'|\b(blessé|blesse|victime|victimes|tête|tete|fièvre|fievre)\b', t_lower) | |
| bonus_masse = 0.0 | |
| if has_groupe: bonus_masse += 1.8 | |
| if has_transport: bonus_masse += 1.2 | |
| bonus_degats = 0.0 | |
| if has_accident: bonus_degats += 1.4 | |
| if has_feu: bonus_degats += 2.1 | |
| if has_chimique: bonus_degats += 2.7 | |
| if has_detresse: bonus_degats += 1.5 | |
| if (has_transport or has_groupe) and (has_accident or has_feu or has_chimique or has_detresse): | |
| score_total_crise = 10.0 + bonus_masse + bonus_degats | |
| domaine = "POMPIER" if (has_feu or has_accident) else "MÉDICAL" | |
| if has_chimique: domaine = "RISQUES ENVIRONNEMENTAUX" | |
| renforts = ["MÉDICAL", "POLICE"] | |
| if has_transport: renforts.append("TRANSPORT & MOBILITÉ") | |
| if has_feu or has_chimique: renforts.append("POMPIER") | |
| return domaine, min(round(score_total_crise, 1), 19.9), list(set(renforts)) | |
| if re.search(r'\b(suicide|suicidaire|suicider)\b', t_lower): | |
| return "MÉDICAL", 10.0, ["POLICE"] | |
| if has_feu and has_detresse: | |
| return "POMPIER", 11.5, ["MÉDICAL", "POLICE"] | |
| return None | |
| async def generer_question_bot(self, transcript, texte_utilisateur, domaine, score_actuel, state): | |
| texte_utilisateur = str(texte_utilisateur).encode('utf-8', errors='ignore').decode('utf-8') | |
| transcript = str(transcript).encode('utf-8', errors='ignore').decode('utf-8') | |
| if self.est_salutation_basique(texte_utilisateur): | |
| return "EN_COURS", "Ici les urgences. Quel est votre problème et où vous trouvez-vous ?" | |
| if not self.groq_disponible and not self.llm_en_ligne: | |
| return "EN_COURS", "Décrivez votre urgence et votre adresse exacte." | |
| # RÈGLES DYNAMIQUES DE PROMPT (Fin de la boucle infinie pour l'IT) | |
| is_basse_priorite = domaine in ["EN_ATTENTE", "NON_URGENT", "DIGITAL SUPPORT", "ANIMALIER"] or score_actuel < 3.0 | |
| if is_basse_priorite: | |
| instruction_lieu = "- LIEU : INUTILE. C'est une basse priorité. Ne demande pas d'adresse. Fais un diagnostic technique ou administratif simple." | |
| etat_dispatch = "NON APPLICABLE (Basse priorité)." | |
| regles_dynamiques = """ | |
| - AUCUNE politesse. MAXIMUM 2 phrases. | |
| - [DÉCLENCHEMENT] INTERDIT. Ne génère jamais le token ###DISPATCH###. | |
| - [CLÔTURE] Dès que tu as réorienté l'utilisateur vers le bon service ou donné ton conseil final, réponds EXCLUSIVEMENT par le token : ###CLOS###""" | |
| else: | |
| if state.get("localisation_obtenue", False): | |
| instruction_lieu = "- LIEU : [DÉJÀ OBTENU]. NE DEMANDE PLUS L'ADRESSE. Concentre-toi EXCLUSIVEMENT sur la nature du danger ou l'état de la victime." | |
| else: | |
| instruction_lieu = "- LIEU : Demande un repère ou le nom d'un bâtiment si aucune adresse n'est donnée." | |
| etat_dispatch = "DÉJÀ DÉCLENCHÉS. Les unités sont en route. Concentre-toi sur le maintien en vie." if state.get("secours_declenches", False) else "NON DÉCLENCHÉS. Objectif: Obtenir Lieu + Nature pour déclencher les secours." | |
| regles_dynamiques = """ | |
| - AUCUNE politesse. MAXIMUM 2 phrases. | |
| - [DÉCLENCHEMENT] Si les secours sont NON DÉCLENCHÉS ET que tu as compris le lieu exact ET la nature du danger, commence OBLIGATOIREMENT ta réponse par le token ###DISPATCH###. Annonce que les secours sont en route. | |
| - [CLÔTURE] Si les secours sont DÉJÀ DÉCLENCHÉS ET que l'arrivée physique des unités est confirmée, réponds EXCLUSIVEMENT par : ###CLOS###""" | |
| prompt_base = f"""Tu es NEXUS, l'IA de régulation d'urgence de l'État français. | |
| Ton rôle : Autorité bienveillante, directivité chirurgicale. Tu N'ES PAS un robot de discussion, tu es un opérateur tactique. | |
| === DONNÉES SYSTÈME === | |
| - Domaine actuel : {domaine} | |
| - Gravité estimée (Score) : {score_actuel}/10 | |
| - Statut des Secours : {etat_dispatch} | |
| === HISTORIQUE DE LA LIAISON === | |
| {transcript} | |
| === PROTOCOLE D'ANALYSE === | |
| 1. Qualification de l'appelant : Victime ou Témoin ? | |
| 2. Extraction : {instruction_lieu} | |
| === RÈGLES ABSOLUES === | |
| {regles_dynamiques} | |
| Génère ta réponse finale directe à l'appelant :""" | |
| contenu = "" | |
| if self.groq_disponible: | |
| try: | |
| response = await asyncio.wait_for( | |
| self.client_groq.chat.completions.create( | |
| model=nexus_config.MODEL_CLOUD, | |
| messages=[ | |
| {'role': 'system', 'content': prompt_base}, | |
| {'role': 'user', 'content': texte_utilisateur} | |
| ], | |
| temperature=0.0, max_tokens=60 | |
| ), | |
| timeout=5.0 | |
| ) | |
| contenu = response.choices[0].message.content.strip() | |
| except Exception: | |
| self.groq_disponible = False | |
| if not contenu and self.llm_en_ligne: | |
| try: | |
| prompt_complet = prompt_base + f"\n\nMessage du client : \"{texte_utilisateur}\"" | |
| reponse = await asyncio.wait_for( | |
| self.client_llm.chat( | |
| model=self.local_model, | |
| messages=[{'role': 'user', 'content': prompt_complet}], | |
| options={'temperature': 0.0} | |
| ), | |
| timeout=45.0 | |
| ) | |
| contenu = reponse['message']['content'].strip() | |
| contenu = re.sub(r'<think>.*?</think>', '', contenu, flags=re.DOTALL | re.IGNORECASE).strip() | |
| except Exception: | |
| pass | |
| if not contenu: | |
| if is_basse_priorite: | |
| return "COMPLET", "" | |
| if state.get("localisation_obtenue", False): | |
| return "EN_COURS", "Analyse en cours. Précisez immédiatement l'état des victimes et la nature du danger." | |
| else: | |
| return "EN_COURS", "Analyse en cours. Où vous trouvez-vous exactement ?" | |
| is_dispatch = False | |
| if "###CLOS###" in contenu: | |
| if is_basse_priorite: | |
| return "COMPLET", "" | |
| if not state.get("secours_declenches", False) and not state.get("localisation_obtenue", False): | |
| return "EN_COURS", "Urgence identifiée. Donnez-moi un repère visuel précis ou une rue pour envoyer les secours." | |
| return "COMPLET", "" | |
| if "###DISPATCH###" in contenu: | |
| if state.get("localisation_obtenue", False): | |
| if not is_basse_priorite and score_actuel >= 5.0: | |
| is_dispatch = True | |
| contenu = contenu.replace("###DISPATCH###", "").strip() | |
| contenu = re.sub(r'^(Régulateur\s*:|Client\s*:|Ta réponse\s*:|Réponse\s*:|:\s*|"\s*)', '', contenu, flags=re.IGNORECASE).strip('" ') | |
| return "DISPATCH" if is_dispatch else "EN_COURS", contenu | |
| async def extraire_domaines_secondaires(self, texte_utilisateur, domaine_principal): | |
| texte_utilisateur = str(texte_utilisateur).encode('utf-8', errors='ignore').decode('utf-8') | |
| if domaine_principal in ["NON_URGENT", "EN_ATTENTE", "DIGITAL SUPPORT", "ANIMALIER"]: return [] | |
| prompt = f"Analyse : '{texte_utilisateur}'. Principal : {domaine_principal}. Besoins de renforts ? Choix : {', '.join(DOMAINES)}. Règles : Pas le principal. Si rien : AUCUN. Si plusieurs, sépare par virgules. Aucun autre texte." | |
| res = "" | |
| if self.groq_disponible: | |
| try: | |
| response = await asyncio.wait_for( | |
| self.client_groq.chat.completions.create( | |
| model=nexus_config.MODEL_CLOUD, messages=[{'role': 'user', 'content': prompt}], | |
| temperature=0.0, max_tokens=25 | |
| ), | |
| timeout=3.0 | |
| ) | |
| res = response.choices[0].message.content.strip().upper() | |
| except: | |
| pass | |
| if not res and self.llm_en_ligne: | |
| try: | |
| reponse = await asyncio.wait_for( | |
| self.client_llm.chat( | |
| model=self.local_model, | |
| messages=[{'role': 'user', 'content': prompt}], | |
| options={'temperature': 0.0, 'num_predict': 30} | |
| ), | |
| timeout=30.0 | |
| ) | |
| res = reponse['message']['content'].strip().upper() | |
| res = re.sub(r'<think>.*?</think>', '', res, flags=re.DOTALL | re.IGNORECASE).strip() | |
| except Exception: | |
| pass | |
| if not res or "AUCUN" in res: return [] | |
| return list(set([d.strip() for d in res.split(',') if d.strip() in DOMAINES and d.strip() != domaine_principal])) | |
| # ===================================================================== | |
| # SOUS-ROUTINE ASYNCHRONE OPTIMISÉE | |
| # ===================================================================== | |
| async def async_input(prompt: str, timeout: float): | |
| loop = asyncio.get_event_loop() | |
| print(prompt, end="", flush=True) | |
| queue = asyncio.Queue() | |
| def got_input(): | |
| line = sys.stdin.readline() | |
| loop.call_soon_threadsafe(queue.put_nowait, line) | |
| loop.add_reader(sys.stdin.fileno(), got_input) | |
| try: | |
| ligne = await asyncio.wait_for(queue.get(), timeout) | |
| # Éradication de la faille de la touche d'effacement (\udcc3) | |
| texte_propre = ligne.strip() | |
| return str(texte_propre).encode('utf-8', errors='ignore').decode('utf-8') | |
| except asyncio.TimeoutError: | |
| print() | |
| return "" | |
| finally: | |
| loop.remove_reader(sys.stdin.fileno()) | |
| # ===================================================================== | |
| # BOUCLE PRINCIPALE DE RÉGULATION | |
| # ===================================================================== | |
| async def run_terminal(): | |
| nexus = NexusAgenticSystem() | |
| await nexus.prechauffer_cerveau() | |
| print("\n" + "=" * 70) | |
| print(f"🚀 NEXUS COMMAND CENTER — PIPELINE TEMPS RÉEL V42 (RÉGULATION TOTALE)") | |
| print("=" * 70 + "\n") | |
| while True: | |
| raw = await async_input("📝 Client (Début d'appel) : ", timeout=86400) | |
| if raw.lower() in {"exit", "q", "quit"}: break | |
| if not raw: continue | |
| ticket_final = raw | |
| transcript = f"Client : {raw}\n" | |
| ticket_complet = False | |
| silence_count = 0 | |
| motif_fermeture = "STANDARD" | |
| skip_generation = False | |
| forced_secondaries = [] | |
| session_state = { | |
| "domaine_verrouille": "EN_ATTENTE", | |
| "score_verrouille": 0.0, | |
| "localisation_obtenue": False, | |
| "secours_declenches": False, | |
| "domaines_secondaires_lock": [], | |
| "frustration_count": 0 | |
| } | |
| if not nexus.est_salutation_basique(ticket_final): | |
| dom_c, sco_c, _ = nexus.evaluator.evaluer_ticket(ticket_final) | |
| redressement_jupiter = nexus.verifier_protocole_jupiter(ticket_final) | |
| if redressement_jupiter: | |
| dom_c, sco_c, forced_secondaries = redressement_jupiter | |
| print(" [CONSEIL DE DÉFENSE] ⚡ PROTOCOLE JUPITER ENGAGÉ : URGENCE ABSOLUE D'ÉTAT (20/10).") | |
| else: | |
| redressement = nexus.verifier_urgences_combinees(ticket_final) | |
| if redressement: | |
| dom_c, sco_c, forced_secondaries = redressement | |
| print(f" [ALERTE] ⚡ SURCHARGE DIRECTE INTER-CLASSES ACTIVÉE : {dom_c} ({sco_c}/10)") | |
| elif nexus.verifier_mots_clefs_vitaux(ticket_final): | |
| dom_c = "MÉDICAL" | |
| sco_c = max(sco_c, 10.0) | |
| print(" [ALERTE] 🛡️ BOUCLIER HEURISTIQUE ENGAGÉ : URGENCE VITALE FORCÉE.") | |
| session_state["domaine_verrouille"] = dom_c | |
| session_state["score_verrouille"] = sco_c | |
| while not ticket_complet: | |
| if not session_state["localisation_obtenue"] and nexus.verifier_presence_localisation(ticket_final): | |
| session_state["localisation_obtenue"] = True | |
| if nexus.detecter_choc_acoustique(ticket_final): | |
| print(" [ALERTE] 💥 PANIQUE ACOUSTIQUE DÉTECTÉE - SEUIL PLANCHER D'URGENCE ACTIVÉ") | |
| session_state["domaine_verrouille"] = "MÉDICAL" | |
| session_state["score_verrouille"] = 10.0 | |
| motif_fermeture = "CRI_DETECTE" | |
| if silence_count > 0: | |
| ticket_complet = True | |
| break | |
| if re.search(r'\b(fausse alerte|je rigolais|un film)\b', ticket_final.lower()): | |
| print(" [INFO] 📉 Désescalade détectée. Clôture immédiate pour appel nul.") | |
| session_state["score_verrouille"] = 0.2 | |
| session_state["domaine_verrouille"] = "DIGITAL SUPPORT" | |
| ticket_complet = True | |
| break | |
| pattern_frust = re.compile(r'\b(bordel|foutre|con|merde|putain|répéter|déjà dit|tu sers à rien)\b') | |
| if pattern_frust.search(ticket_final.lower()): | |
| session_state["frustration_count"] += 1 | |
| if session_state["frustration_count"] >= 2: | |
| if session_state["score_verrouille"] >= 5.0: | |
| motif_fermeture = "FRUSTRATION_CRITIQUE" | |
| else: | |
| motif_fermeture = "ABANDON" | |
| ticket_complet = True | |
| break | |
| if not nexus.est_salutation_basique(ticket_final) and motif_fermeture != "CRI_DETECTE": | |
| domaine_courant, score_courant, _ = nexus.evaluator.evaluer_ticket(ticket_final) | |
| redressement_jupiter = nexus.verifier_protocole_jupiter(ticket_final) | |
| if redressement_jupiter: | |
| domaine_courant, score_courant, forced_secondaries = redressement_jupiter | |
| else: | |
| redressement = nexus.verifier_urgences_combinees(ticket_final) | |
| if redressement: | |
| domaine_courant, score_courant, forced_secondaries = redressement | |
| elif nexus.verifier_mots_clefs_vitaux(ticket_final): | |
| domaine_courant = "MÉDICAL" | |
| score_courant = max(score_courant, 10.0) | |
| domaines_regaliens = ["MÉDICAL", "POLICE", "POMPIER", "CYBERSÉCURITÉ", "ÉNERGIE & INFRASTRUCTURES", | |
| "RISQUES ENVIRONNEMENTAUX"] | |
| if score_courant >= 20.0: | |
| session_state["domaine_verrouille"] = domaine_courant | |
| session_state["score_verrouille"] = score_courant | |
| else: | |
| if session_state["domaine_verrouille"] in domaines_regaliens: | |
| if score_courant > session_state["score_verrouille"]: | |
| session_state["score_verrouille"] = score_courant | |
| else: | |
| session_state["domaine_verrouille"] = domaine_courant | |
| session_state["score_verrouille"] = score_courant | |
| domaine_maitre = session_state["domaine_verrouille"] | |
| score_maitre = session_state["score_verrouille"] | |
| if not skip_generation: | |
| statut, question_bot = await nexus.generer_question_bot(transcript, ticket_final, domaine_maitre, | |
| score_maitre, session_state) | |
| if statut == "COMPLET": | |
| ticket_complet = True | |
| break | |
| wrapped_bot = textwrap.fill( | |
| f"🤖 NEXUS ({domaine_maitre} | Score: {score_maitre}/10) : {question_bot}", | |
| width=70, initial_indent=" ", subsequent_indent=" " | |
| ) | |
| print(wrapped_bot) | |
| if statut == "DISPATCH" and not session_state["secours_declenches"]: | |
| print("\n [SYSTÈME] ⚠️ TRANSMISSION ANTICIPÉE AUX UNITÉS ENGAGÉE (Liaison maintenue) ⚠️") | |
| if score_maitre >= 20.0: | |
| niveau = "⚡ PROTOCOLE JUPITER" | |
| elif score_maitre >= 15.0: | |
| niveau = "🟣 CONTINUITÉ DE L'ÉTAT" | |
| elif score_maitre >= 10.1: | |
| niveau = "⚫ PLAN NOVI (MASSE)" | |
| elif score_maitre >= 10.0: | |
| niveau = "🔴 URGENCE VITALE ABSOLUE" | |
| elif score_maitre >= 5.0: | |
| niveau = "🟠 URGENCE MAJEURE" | |
| else: | |
| niveau = "🟢 ROUTINE" | |
| domaines_secondaires = await nexus.extraire_domaines_secondaires(ticket_final, domaine_maitre) | |
| if forced_secondaries: | |
| domaines_secondaires = list(set(domaines_secondaires + forced_secondaries)) | |
| domaines_secondaires = [d for d in domaines_secondaires if d != domaine_maitre] | |
| domaines_impliques = [domaine_maitre] + domaines_secondaires | |
| nexus.executor.declencher_protocoles(domaines_impliques, niveau, ticket_final, "ANTICIPÉ") | |
| session_state["secours_declenches"] = True | |
| session_state["domaines_secondaires_lock"] = domaines_secondaires | |
| print(" " + "-" * 67 + "\n") | |
| skip_generation = False | |
| complement = await async_input(" 💬 Client : ", timeout=45.0) | |
| if complement == "": | |
| silence_count += 1 | |
| if domaine_maitre in ["MÉDICAL", "POLICE", "POMPIER", "RISQUES ENVIRONNEMENTAUX"] or score_maitre >= 5.0: | |
| if silence_count == 1: | |
| print(" 🤖 NEXUS (RELANCE CRITIQUE) : Allô ? Restez en ligne, répondez si vous m'entendez !") | |
| skip_generation = True | |
| continue | |
| else: | |
| motif_fermeture = "SILENCE_CRITIQUE" | |
| ticket_complet = True | |
| break | |
| else: | |
| print(" ⚠️ APPEL DE BASSE PRIORITÉ ABANDONNÉ PAR L'UTILISATEUR (Délai de réponse > 45s).") | |
| motif_fermeture = "ABANDON" | |
| ticket_complet = True | |
| break | |
| silence_count = 0 | |
| if complement.lower() in {"exit", "q", "quit"}: | |
| ticket_final = "exit" | |
| break | |
| ticket_final += " " + complement | |
| transcript += f"Régulateur : {question_bot}\nClient : {complement}\n" | |
| if ticket_final == "exit": break | |
| if not session_state.get("secours_declenches", False): | |
| if score_maitre >= 20.0: | |
| niveau = "⚡ PROTOCOLE JUPITER" | |
| elif score_maitre >= 15.0: | |
| niveau = "🟣 CONTINUITÉ DE L'ÉTAT" | |
| elif score_maitre >= 10.1: | |
| niveau = "⚫ PLAN NOVI (MASSE)" | |
| elif score_maitre >= 10.0: | |
| niveau = "🔴 URGENCE VITALE ABSOLUE" | |
| elif score_maitre >= 5.0: | |
| niveau = "🟠 URGENCE MAJEURE" | |
| else: | |
| niveau = "🟢 ROUTINE" | |
| print(f"\n ✅ APPEL CLOS — TRANSMISSION AUX UNITÉS") | |
| print(f" 🎯 Service Principal : {domaine_maitre} | 🔢 Score Tactique : {score_maitre}/10 → {niveau}") | |
| domaines_secondaires = await nexus.extraire_domaines_secondaires(ticket_final, domaine_maitre) | |
| if forced_secondaries: | |
| domaines_secondaires = list(set(domaines_secondaires + forced_secondaries)) | |
| if motif_fermeture in ["CRI_DETECTE", "SILENCE_CRITIQUE", "FRUSTRATION_CRITIQUE"] or score_maitre >= 15.0: | |
| domaines_secondaires.extend(["POLICE", "POMPIER", "MÉDICAL"]) | |
| domaines_secondaires = list(set(domaines_secondaires)) | |
| domaines_secondaires = [d for d in domaines_secondaires if d != domaine_maitre] | |
| if domaines_secondaires: | |
| print(f" 🚨 Renforts requis identifiés : {', '.join(domaines_secondaires)}") | |
| domaines_impliques = [domaine_maitre] + domaines_secondaires | |
| nexus.executor.declencher_protocoles(domaines_impliques, niveau, ticket_final, motif_fermeture) | |
| else: | |
| print(f"\n ✅ APPEL CLOS — UNITÉS DÉJÀ DÉPLOYÉES SUR LE TERRAIN") | |
| domaines_secondaires = session_state.get("domaines_secondaires_lock", []) | |
| nexus.logger.log(ticket_final, domaine_maitre, domaines_secondaires, score_maitre, statut=motif_fermeture) | |
| print("-" * 70 + "\n📝 En attente du prochain appelant...\n") | |
| if __name__ == "__main__": | |
| asyncio.run(run_terminal()) |