Spaces:
Sleeping
Sleeping
File size: 6,478 Bytes
dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf f2bd7d5 dfcacaf | 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 | # executives/nexus_qualification.py
import joblib
import os
import re
MODEL_UNIFIED_PATH = "../pickle_result/nexus_modele_final_V35.pkl"
# =====================================================================
# MATRICE LÉGALE DE L'ÉTAT FRANÇAIS (SGDSN / DISPOSITIF ORSEC)
# =====================================================================
MATRICE_CIBLES_ISOLEES = {
"MÉDICAL": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 5},
"POLICE": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 5},
"POMPIER": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 5},
"CYBERSÉCURITÉ": {"CITOYEN": 1, "VIP_LOCAL": 3, "PRESIDENT": 4},
"ÉNERGIE & INFRASTRUCTURES": {"CITOYEN": 1, "VIP_LOCAL": 3, "PRESIDENT": 4},
"TRANSPORT & MOBILITÉ": {"CITOYEN": 1, "VIP_LOCAL": 3, "PRESIDENT": 4},
"RISQUES ENVIRONNEMENTAUX": {"CITOYEN": 1, "VIP_LOCAL": 3, "PRESIDENT": 3},
"SERVICES URBAINS": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 2},
"ASSISTANCE SOCIALE": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 2},
"ANIMALIER": {"CITOYEN": 1, "VIP_LOCAL": 2, "PRESIDENT": 2},
"DIGITAL SUPPORT": {"CITOYEN": 1, "VIP_LOCAL": 1, "PRESIDENT": 1}
}
class QualificationEngine:
def __init__(self):
if os.path.exists(MODEL_UNIFIED_PATH):
self.model = joblib.load(MODEL_UNIFIED_PATH)
self.mode = "ML"
print(f"🔌 Moteur d'inférence chargé : {MODEL_UNIFIED_PATH}")
else:
self.model = None
self.mode = "SIMULATION"
print(f"⚠️ ERREUR FATALE : Modèle ML introuvable à {MODEL_UNIFIED_PATH}. Mode SIMULATION (0.2) activé.")
def traduire_cible_ml(self, cible_id):
cible_id = int(cible_id)
if cible_id == 0: return "CITOYEN"
elif cible_id == 2: return "VIP_LOCAL"
elif cible_id == 4: return "PRESIDENT"
return "CITOYEN"
def calculer_priorite_sgdsn(self, domaine, severite, impact, profil_cible, texte_brut=""):
t_lower = str(texte_brut).lower()
# 1. COUPE-CIRCUIT ABSOLU (PROTOCOLE JUPITER : 18.0 - 20.0)
# CORRECTION : Une centrale nucléaire ou un site souverain est traité au même niveau que le Chef de l'État
is_souverain = (profil_cible == "PRESIDENT") or bool(re.search(r'\b(centrale|nucléaire|nucleaire)\b', t_lower))
if is_souverain:
if domaine in ["MÉDICAL", "POLICE", "POMPIER", "RISQUES ENVIRONNEMENTAUX"] and severite >= 3:
return 20.0
elif domaine in ["CYBERSÉCURITÉ", "ÉNERGIE & INFRASTRUCTURES", "TRANSPORT & MOBILITÉ"]:
return 20.0
# 2. PLAFONDS DE VERRE (Niveau 1 : Domaines Bridés)
if domaine in ["NON_URGENT", "EN_ATTENTE"]:
return 0.2
if domaine == "DIGITAL SUPPORT":
return min((0.5 + severite * 0.5 + impact * 0.2), 2.9) if profil_cible == "CITOYEN" else 4.9
if domaine == "ANIMALIER":
return min((0.5 + severite * 1.0 + impact * 0.5), 2.9)
if domaine in ["SERVICES URBAINS", "ASSISTANCE SOCIALE"]:
return min((0.5 + severite * 1.0 + impact * 1.0), 4.9)
# SCANNER DE MASSE (PLAN NOVI)
has_groupe = re.search(r'\b(groupe|foule|plusieurs|masse|nombreux|personnes|gens|équipe|classe|centaine|cinquantaine)\b', t_lower)
has_transport = re.search(r'\b(bus|cars|car|train|métro|metro|tram|avion|navire)\b', t_lower)
# 3. DOCTRINE DE GUERRE / MASSE (PLAN NOVI / CIBLES STRATÉGIQUES : 10.1 - 15.0)
if (severite == 4 and domaine in ["MÉDICAL", "POMPIER", "POLICE", "RISQUES ENVIRONNEMENTAUX", "CYBERSÉCURITÉ"]) or \
(severite >= 3 and profil_cible == "VIP_LOCAL" and domaine in ["CYBERSÉCURITÉ", "ÉNERGIE & INFRASTRUCTURES", "MÉDICAL", "TRANSPORT & MOBILITÉ"]) or \
(has_groupe and (domaine in ["POMPIER", "MÉDICAL", "POLICE"])):
base_vitale = 10.0
delta_masse = 0.0
if impact == 2: delta_masse = 1.5
elif impact == 3: delta_masse = 3.0
elif impact == 4: delta_masse = 4.5
if has_groupe: delta_masse = max(delta_masse, 3.5)
if has_transport: delta_masse = max(delta_masse, 4.0)
delta_infra = 0.0
if profil_cible == "VIP_LOCAL" or re.search(r'\b(hôpital|hopital|clinique|école|ecole|gare|aéroport)\b', t_lower):
delta_infra = 4.0
score_final = base_vitale + delta_masse + delta_infra
if domaine in ["CYBERSÉCURITÉ", "ÉNERGIE & INFRASTRUCTURES"]:
return min(round(score_final, 1), 15.0)
return min(round(score_final, 1), 19.9)
# 4. ÉCHELLE CIVILE DYNAMIQUE (Urgence Vitale Individuelle : 5.0 - 10.0)
poids_cible = MATRICE_CIBLES_ISOLEES.get(domaine, {}).get(profil_cible, 1)
score_base = 0.5 + (severite * 1.5) + (impact * 1.0) + (poids_cible * 0.5)
bonus_gravite = 0.0
mots_stress = ['très', 'tres', 'ultra', 'grave', 'gravement', 'fort', 'forte', 'énorme', 'enorme', 'extrême', 'extreme', 'pire', 'vite', 'urgence']
if any(mot in t_lower for mot in mots_stress):
bonus_gravite += 1.2
mots_cliniques = ['sang', 'saigne', 'douleur', 'pression', 'brûlure', 'brule', 'coupé', 'coupe', 'cassé', 'casse', 'tomber', 'tombé']
if any(mot in t_lower for mot in mots_cliniques):
bonus_gravite += 1.8
mots_liste = t_lower.split()
if len(mots_liste) <= 4 and bonus_gravite > 0:
bonus_gravite += 0.8
score_normalise = score_base + bonus_gravite
return min(round(score_normalise, 1), 10.0)
def evaluer_ticket(self, texte):
if self.mode == "ML":
prediction = self.model.predict([texte])[0]
domaine = str(prediction[0]).upper()
severite = int(float(prediction[1]))
impact = int(float(prediction[2]))
cible_id = int(float(prediction[3]))
friction = str(prediction[4]).upper()
profil_cible = self.traduire_cible_ml(cible_id)
score = self.calculer_priorite_sgdsn(domaine, severite, impact, profil_cible, str(texte))
return domaine, score, friction
else:
return "DIGITAL SUPPORT", 0.2, "MANQUE_LIEU" |