persee-tech commited on
Commit
b8cf921
·
verified ·
1 Parent(s): 0a5c8ec

Update backend/main.py

Browse files
Files changed (1) hide show
  1. backend/main.py +135 -125
backend/main.py CHANGED
@@ -8,158 +8,168 @@ import base64
8
  import cv2
9
  import numpy as np
10
  import random
 
11
  from deepface import DeepFace
12
  from supabase import create_client, Client
13
- import time
14
 
15
- # --- 1. CONFIGURATION ---
16
  SUPABASE_URL = os.getenv("SUPABASE_URL", "https://gwjrwejdjpctizolfkcz.supabase.co")
17
- SUPABASE_KEY = os.getenv("SUPABASE_KEY")
18
 
19
  try:
20
- if SUPABASE_KEY:
21
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
22
- print("☁️ Supabase Connecté")
23
- except: pass
 
 
 
 
24
 
25
- # --- 2. SOCKET IO (Optimisé) ---
26
- sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*', logger=False, engineio_logger=False, always_connect=True)
27
  app = FastAPI()
28
- app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
 
 
 
 
 
 
 
29
  socket_app = socketio.ASGIApp(sio, app)
30
 
31
- # --- 3. MOTEUR LÉGER (Pour le Carré Vert) ---
32
- # On charge le détecteur de visage ultra-rapide d'OpenCV
33
- face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
34
-
35
- # --- 4. ETAT GLOBAL ---
36
- sessions = {}
37
-
38
- def get_default_kpis():
39
- return {
40
- "engagement": 0, "satisfaction": 50, "trust": 50, "loyalty": 50, "opinion": 50,
41
- "lbl_eng": "En attente...", "lbl_sat": "Calibration..."
42
- }
43
-
44
- # --- 5. TÂCHE DE FOND (LOURDE) ---
45
- def deepface_task(frame):
46
- # C'est ici que l'IA réfléchit (ça peut prendre 1 à 2 secondes)
47
  try:
48
- objs = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False, silent=True)
49
- data = objs[0] if isinstance(objs, list) else objs
50
-
51
- # Calculs KPIs simulés basés sur l'émotion réelle
52
- emo = data['dominant_emotion']
53
- val = 0.8 if emo == "happy" else (-0.6 if emo in ["sad", "angry", "fear"] else 0.0)
54
- aro = 0.8 if emo in ["angry", "fear", "surprise"] else 0.3
55
-
56
- def clamp(n): return max(0, min(100, int(n)))
57
- return {
58
- "emotion": emo,
59
- "metrics": {
60
- "engagement": clamp((aro * 100) + random.uniform(0, 10)),
61
- "satisfaction": clamp(((val + 1) / 2) * 100),
62
- "trust": clamp(50 + (val * 20)),
63
- "loyalty": clamp(50 + (val * 10)),
64
- "opinion": clamp(((val + 1) / 2) * 100),
65
- "lbl_eng": "Fort 🔥" if aro > 0.6 else "Moyen",
66
- "lbl_sat": "Positif 😃" if val > 0.2 else "Neutre 😐"
67
- }
68
- }
69
- except: return None
70
-
71
- # --- 6. ÉVÉNEMENTS ---
72
- @sio.event
73
- async def connect(sid, environ):
74
- print(f"✅ CONNECTÉ: {sid}")
75
- sessions[sid] = {
76
- "is_recording": False, "session_time": 0, "db_id": None,
77
- "face_coords": None, "emotion": "neutral", "metrics": get_default_kpis(),
78
- "last_deepface_time": 0
 
 
 
79
  }
80
 
81
- @sio.event
82
- async def disconnect(sid):
83
- if sid in sessions: del sessions[sid]
84
-
85
- @sio.event
86
- async def start_session(sid, data):
87
- if sid in sessions:
88
- sessions[sid]["is_recording"] = True
89
- sessions[sid]["session_time"] = 0
90
- try:
91
- res = supabase.table('sessions').insert({"first_name": data.get('firstName', ''), "last_name": data.get('lastName', ''), "client_id": data.get('clientId', '')}).execute()
92
- sessions[sid]["db_id"] = res.data[0]['id']
93
- except: pass
94
-
95
- @sio.event
96
- async def stop_session(sid):
97
- if sid in sessions: sessions[sid]["is_recording"] = False
98
 
99
  @sio.event
100
  async def process_frame(sid, data_uri):
101
- if sid not in sessions: return
102
-
103
  try:
104
- # 1. Décodage Rapide
105
  encoded_data = data_uri.split(',')[1]
106
  nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
107
  frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
108
 
109
- # 2. DÉTECTION RAPIDE (CARRÉ VERT) - Ça prend 0.01s
110
- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
111
- faces = face_cascade.detectMultiScale(gray, 1.1, 4)
 
 
112
 
113
- if len(faces) > 0:
114
- (x, y, w, h) = faces[0] # On prend le premier visage
115
- sessions[sid]["face_coords"] = {'x': int(x), 'y': int(y), 'w': int(w), 'h': int(h)}
116
  else:
117
- sessions[sid]["face_coords"] = None # Pas de visage = Pas de carré
118
-
119
- # 3. ANALYSE LENTE (ÉMOTIONS) - Seulement toutes les 1.5 secondes
120
- now = time.time()
121
- if now - sessions[sid]["last_deepface_time"] > 1.5:
122
- sessions[sid]["last_deepface_time"] = now
123
- # On lance l'IA en arrière-plan sans bloquer
124
- asyncio.create_task(run_deepface_background(sid, frame))
125
-
126
- except Exception as e:
127
- print(f"Erreur Frame: {e}")
128
 
129
- async def run_deepface_background(sid, frame):
130
- # Wrapper pour exécuter DeepFace sans bloquer le serveur
131
- try:
132
- result = await asyncio.to_thread(deepface_task, frame)
133
- if result and sid in sessions:
134
- sessions[sid]["emotion"] = result["emotion"]
135
- sessions[sid]["metrics"] = result["metrics"]
136
- except: pass
137
-
138
- # --- 7. BOUCLE DE D'ENVOI (BROADCAST) ---
139
- async def broadcast_loop():
140
  while True:
141
- await asyncio.sleep(0.5) # Mise à jour fluide (2 fois par seconde)
142
-
143
- for sid in list(sessions.keys()):
144
- if sid not in sessions: continue
145
- sess = sessions[sid]
146
-
147
- if sess["is_recording"]:
148
- sess["session_time"] += 0.5 # On ajoute 0.5s au chrono
149
- # Sauvegarde DB allégée ici si besoin
150
 
151
- # On envoie TOUT ce qu'on a (le carré vert est à jour, l'émotion peut dater d'il y a 1s)
152
- payload = {
153
- "emotion": sess["emotion"],
154
- "face_coords": sess["face_coords"], # C'est ça qui affiche le carré vert !
155
- "metrics": sess["metrics"],
156
- "session_time": int(sess["session_time"]),
157
- "is_recording": sess["is_recording"]
158
- }
159
- try: await sio.emit('metrics_update', payload, room=sid)
160
- except: pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  if __name__ == "__main__":
163
  @app.on_event("startup")
164
- async def startup(): asyncio.create_task(broadcast_loop())
 
165
  uvicorn.run(socket_app, host="0.0.0.0", port=7860)
 
8
  import cv2
9
  import numpy as np
10
  import random
11
+ from datetime import datetime
12
  from deepface import DeepFace
13
  from supabase import create_client, Client
 
14
 
15
+ # --- CONFIGURATION SUPABASE (COMPATIBLE CLOUD & LOCAL) ---
16
  SUPABASE_URL = os.getenv("SUPABASE_URL", "https://gwjrwejdjpctizolfkcz.supabase.co")
17
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imd3anJ3ZWpkanBjdGl6b2xma2N6Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc2OTA5ODEyNCwiZXhwIjoyMDg0Njc0MTI0fQ.EjU1DGTN-jrdkaC6nJWilFtYZgtu-NKjnfiMVMnHal0")
18
 
19
  try:
20
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
21
+ print("☁️ Connecté à Supabase")
22
+ except Exception as e:
23
+ print(f"❌ Erreur Supabase : {e}")
24
+
25
+ # --- CONFIGURATION SERVEUR (CORS FIXED) ---
26
+ # C'est ici que la magie opère : cors_allowed_origins='*' autorise tout le monde
27
+ sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
28
 
 
 
29
  app = FastAPI()
30
+ # Middleware pour autoriser les requêtes API REST
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
  socket_app = socketio.ASGIApp(sio, app)
39
 
40
+ # --- API REST ---
41
+ @app.get("/api/sessions")
42
+ def get_sessions():
43
+ response = supabase.table('sessions').select("*").order('id', desc=True).execute()
44
+ return response.data
45
+
46
+ @app.get("/api/sessions/{session_id}")
47
+ def get_session_details(session_id: int):
48
+ sess = supabase.table('sessions').select("*").eq('id', session_id).execute()
49
+ if not sess.data: raise HTTPException(status_code=404, detail="Session introuvable")
50
+ meas = supabase.table('measurements').select("*").eq('session_id', session_id).order('session_time', desc=False).execute()
51
+ return {"info": sess.data[0], "data": meas.data}
52
+
53
+ @app.delete("/api/sessions/{session_id}")
54
+ def delete_session(session_id: int):
 
55
  try:
56
+ supabase.table('sessions').delete().eq('id', session_id).execute()
57
+ return {"message": "Supprimé"}
58
+ except Exception as e:
59
+ raise HTTPException(status_code=500, detail=str(e))
60
+
61
+ # --- LOGIQUE MÉTIER ---
62
+ def calculate_kpis(emotion):
63
+ valence = 0.0; arousal = 0.0; noise = random.uniform(-0.05, 0.05)
64
+ if emotion == "happy": valence = 0.8 + noise; arousal = 0.6 + noise
65
+ elif emotion == "surprise": valence = 0.2 + noise; arousal = 0.9 + noise
66
+ elif emotion in ["fear", "angry"]: valence = -0.7 + noise; arousal = 0.8 + noise
67
+ elif emotion == "disgust": valence = -0.8 + noise; arousal = 0.5 + noise
68
+ elif emotion == "sad": valence = -0.6 + noise; arousal = 0.2 + noise
69
+ else: valence = 0.0 + noise; arousal = 0.3 + noise
70
+
71
+ def clamp(n): return max(0, min(100, int(n)))
72
+ val_eng = clamp((arousal * 100) + random.uniform(0, 5))
73
+ val_sat = clamp(((valence + 1) / 2) * 100)
74
+ val_tru = clamp(50 + (valence * 40) + random.uniform(0, 5)) if valence > 0 else clamp(50 - (abs(valence) * 40) + random.uniform(0, 5))
75
+ val_loy = clamp((val_sat * 0.7) + (val_tru * 0.3))
76
+ val_opi = val_sat
77
+
78
+ # Labels
79
+ if val_eng >= 75: lbl_eng = "Engagement Fort 🔥"
80
+ elif val_eng >= 40: lbl_eng = "Engagement Moyen"
81
+ else: lbl_eng = "Désengagement 💤"
82
+
83
+ if val_sat >= 70: lbl_sat = "Très Satisfait 😃"
84
+ elif val_sat >= 45: lbl_sat = "Neutre 😐"
85
+ else: lbl_sat = "Insatisfait 😡"
86
+
87
+ return {
88
+ "engagement": val_eng, "satisfaction": val_sat, "trust": val_tru, "loyalty": val_loy, "opinion": val_opi,
89
+ "lbl_eng": lbl_eng, "lbl_sat": lbl_sat
90
  }
91
 
92
+ # --- GESTION WEBSOCKET ---
93
+ camera_state = { "emotion": "neutral", "emotion_score": 0, "face_coords": None }
94
+ active_sessions = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  @sio.event
97
  async def process_frame(sid, data_uri):
 
 
98
  try:
 
99
  encoded_data = data_uri.split(',')[1]
100
  nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
101
  frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
102
 
103
+ result = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False, silent=True)
104
+ data = result[0] if isinstance(result, list) else result
105
+
106
+ camera_state["emotion"] = data['dominant_emotion']
107
+ camera_state["emotion_score"] = data['emotion'][data['dominant_emotion']]
108
 
109
+ region = data['region']
110
+ if region['w'] > 0:
111
+ camera_state["face_coords"] = {'x': region['x'], 'y': region['y'], 'w': region['w'], 'h': region['h']}
112
  else:
113
+ camera_state["face_coords"] = None
114
+ except:
115
+ camera_state["face_coords"] = None
 
 
 
 
 
 
 
 
116
 
117
+ async def session_manager_loop():
 
 
 
 
 
 
 
 
 
 
118
  while True:
119
+ for sid, user_data in list(active_sessions.items()):
120
+ kpis = calculate_kpis(camera_state["emotion"])
 
 
 
 
 
 
 
121
 
122
+ if user_data["is_recording"]:
123
+ user_data["session_time"] += 1
124
+ if user_data["db_id"]:
125
+ try:
126
+ data_to_insert = {
127
+ "session_id": user_data["db_id"],
128
+ "session_time": user_data["session_time"],
129
+ "emotion": camera_state["emotion"],
130
+ "emotion_score": camera_state["emotion_score"],
131
+ "engagement_val": kpis["engagement"], "engagement_lbl": kpis["lbl_eng"],
132
+ "satisfaction_val": kpis["satisfaction"], "satisfaction_lbl": kpis["lbl_sat"],
133
+ "trust_val": kpis["trust"], "loyalty_val": kpis["loyalty"], "opinion_val": kpis["opinion"]
134
+ }
135
+ supabase.table('measurements').insert(data_to_insert).execute()
136
+ except Exception as e: print(f"DB Error: {e}")
137
+
138
+ await sio.emit('metrics_update', {
139
+ "emotion": camera_state["emotion"], "metrics": kpis,
140
+ "face_coords": camera_state["face_coords"],
141
+ "session_time": user_data["session_time"],
142
+ "is_recording": user_data["is_recording"]
143
+ }, room=sid)
144
+ await asyncio.sleep(1)
145
+
146
+ @sio.event
147
+ async def connect(sid, environ):
148
+ print(f"Client connecté: {sid}")
149
+ active_sessions[sid] = { "is_recording": False, "session_time": 0, "db_id": None }
150
+
151
+ @sio.event
152
+ async def disconnect(sid):
153
+ if sid in active_sessions: del active_sessions[sid]
154
+
155
+ @sio.event
156
+ async def start_session(sid, data):
157
+ user_session = active_sessions.get(sid)
158
+ if user_session:
159
+ user_session["is_recording"] = True
160
+ user_session["session_time"] = 0
161
+ try:
162
+ new_session = { "first_name": data.get('firstName'), "last_name": data.get('lastName'), "client_id": data.get('clientId') }
163
+ res = supabase.table('sessions').insert(new_session).execute()
164
+ user_session["db_id"] = res.data[0]['id']
165
+ except Exception as e: print(f"Start Session Error: {e}")
166
+
167
+ @sio.event
168
+ async def stop_session(sid):
169
+ if sid in active_sessions: active_sessions[sid]["is_recording"] = False
170
 
171
  if __name__ == "__main__":
172
  @app.on_event("startup")
173
+ async def startup_event():
174
+ asyncio.create_task(session_manager_loop())
175
  uvicorn.run(socket_app, host="0.0.0.0", port=7860)