persee-tech commited on
Commit
4fdb12c
·
verified ·
1 Parent(s): 6a6b394

Create server.py

Browse files
Files changed (1) hide show
  1. backend/server.py +159 -0
backend/server.py ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import socketio
3
+ import uvicorn
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ import asyncio
7
+ 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
+
14
+ # --- 1. CONFIGURATION SUPABASE ---
15
+ SUPABASE_URL = os.getenv("SUPABASE_URL", "https://gwjrwejdjpctizolfkcz.supabase.co")
16
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY") # On récupère la clé secrète des variables d'environnement
17
+
18
+ try:
19
+ if SUPABASE_KEY:
20
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
21
+ print("☁️ Connecté à Supabase")
22
+ else:
23
+ print("⚠️ ATTENTION: SUPABASE_KEY manquante !")
24
+ except Exception as e:
25
+ print(f"❌ Erreur connexion Supabase : {e}")
26
+
27
+ # --- 2. CONFIGURATION SOCKET.IO (CORS TOTAL) ---
28
+ # On configure le serveur pour tout accepter, sans restriction.
29
+ mgr = socketio.AsyncRedisManager(os.environ.get('REDIS_URL', '')) if os.environ.get('REDIS_URL') else None
30
+ sio = socketio.AsyncServer(
31
+ async_mode='asgi',
32
+ cors_allowed_origins='*', # L'étoile magique
33
+ logger=True, # Logs activés pour voir les connexions
34
+ engineio_logger=True,
35
+ always_connect=True # Force la connexion même si le transport est bizarre
36
+ )
37
+
38
+ app = FastAPI()
39
+
40
+ # --- 3. MIDDLEWARE CORS API (REST) ---
41
+ app.add_middleware(
42
+ CORSMiddleware,
43
+ allow_origins=["*"],
44
+ allow_credentials=True,
45
+ allow_methods=["*"],
46
+ allow_headers=["*"],
47
+ )
48
+
49
+ socket_app = socketio.ASGIApp(sio, app)
50
+
51
+ # --- 4. API REST (ADMIN) ---
52
+ @app.get("/")
53
+ def home():
54
+ return {"status": "online", "message": "Startech API is running"}
55
+
56
+ @app.get("/api/sessions")
57
+ def get_sessions():
58
+ response = supabase.table('sessions').select("*").order('id', desc=True).execute()
59
+ return response.data
60
+
61
+ @app.get("/api/sessions/{session_id}")
62
+ def get_session_details(session_id: int):
63
+ sess = supabase.table('sessions').select("*").eq('id', session_id).execute()
64
+ if not sess.data: raise HTTPException(status_code=404, detail="Session introuvable")
65
+ meas = supabase.table('measurements').select("*").eq('session_id', session_id).order('session_time', desc=False).execute()
66
+ return {"info": sess.data[0], "data": meas.data}
67
+
68
+ @app.delete("/api/sessions/{session_id}")
69
+ def delete_session(session_id: int):
70
+ supabase.table('sessions').delete().eq('id', session_id).execute()
71
+ return {"message": "Supprimé"}
72
+
73
+ # --- 5. LOGIQUE MÉTIER ---
74
+ camera_state = { "emotion": "neutral", "emotion_score": 0, "face_coords": None }
75
+ active_sessions = {}
76
+
77
+ def calculate_kpis(emotion):
78
+ valence = 0.0; arousal = 0.0; noise = random.uniform(-0.05, 0.05)
79
+ if emotion == "happy": valence = 0.8 + noise; arousal = 0.6 + noise
80
+ elif emotion == "surprise": valence = 0.2 + noise; arousal = 0.9 + noise
81
+ elif emotion in ["fear", "angry"]: valence = -0.7 + noise; arousal = 0.8 + noise
82
+ elif emotion == "disgust": valence = -0.8 + noise; arousal = 0.5 + noise
83
+ elif emotion == "sad": valence = -0.6 + noise; arousal = 0.2 + noise
84
+ else: valence = 0.0 + noise; arousal = 0.3 + noise
85
+
86
+ def clamp(n): return max(0, min(100, int(n)))
87
+ val_eng = clamp((arousal * 100) + random.uniform(0, 5))
88
+ val_sat = clamp(((valence + 1) / 2) * 100)
89
+ val_tru = clamp(50 + (valence * 40) + random.uniform(0, 5)) if valence > 0 else clamp(50 - (abs(valence) * 40) + random.uniform(0, 5))
90
+ val_loy = clamp((val_sat * 0.7) + (val_tru * 0.3))
91
+ val_opi = val_sat
92
+
93
+ lbl_eng = "Engagement Fort 🔥" if val_eng >= 75 else ("Engagement Moyen" if val_eng >= 40 else "Désengagement 💤")
94
+ lbl_sat = "Très Satisfait 😃" if val_sat >= 70 else ("Neutre 😐" if val_sat >= 45 else "Insatisfait 😡")
95
+
96
+ return { "engagement": val_eng, "satisfaction": val_sat, "trust": val_tru, "loyalty": val_loy, "opinion": val_opi, "lbl_eng": lbl_eng, "lbl_sat": lbl_sat }
97
+
98
+ # --- 6. SOCKET EVENTS ---
99
+ @sio.event
100
+ async def connect(sid, environ):
101
+ print(f"✅ Client CONNECTÉ: {sid}")
102
+ active_sessions[sid] = { "is_recording": False, "session_time": 0, "db_id": None }
103
+
104
+ @sio.event
105
+ async def disconnect(sid):
106
+ if sid in active_sessions: del active_sessions[sid]
107
+
108
+ @sio.event
109
+ async def process_frame(sid, data_uri):
110
+ try:
111
+ encoded_data = data_uri.split(',')[1]
112
+ nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
113
+ frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
114
+ result = DeepFace.analyze(frame, actions=['emotion'], enforce_detection=False, silent=True)
115
+ data = result[0] if isinstance(result, list) else result
116
+ camera_state["emotion"] = data['dominant_emotion']
117
+ region = data['region']
118
+ camera_state["face_coords"] = {'x': region['x'], 'y': region['y'], 'w': region['w'], 'h': region['h']} if region['w'] > 0 else None
119
+ except:
120
+ pass
121
+
122
+ @sio.event
123
+ async def start_session(sid, data):
124
+ user = active_sessions.get(sid)
125
+ if user:
126
+ user["is_recording"] = True
127
+ user["session_time"] = 0
128
+ try:
129
+ res = supabase.table('sessions').insert({ "first_name": data.get('firstName'), "last_name": data.get('lastName'), "client_id": data.get('clientId') }).execute()
130
+ user["db_id"] = res.data[0]['id']
131
+ except Exception as e: print(f"Erreur DB: {e}")
132
+
133
+ @sio.event
134
+ async def stop_session(sid):
135
+ if sid in active_sessions: active_sessions[sid]["is_recording"] = False
136
+
137
+ async def loop():
138
+ while True:
139
+ for sid, user in list(active_sessions.items()):
140
+ kpis = calculate_kpis(camera_state["emotion"])
141
+ if user["is_recording"]:
142
+ user["session_time"] += 1
143
+ if user["db_id"]:
144
+ try:
145
+ supabase.table('measurements').insert({
146
+ "session_id": user["db_id"], "session_time": user["session_time"],
147
+ "emotion": camera_state["emotion"],
148
+ "engagement_val": kpis["engagement"], "engagement_lbl": kpis["lbl_eng"],
149
+ "satisfaction_val": kpis["satisfaction"], "satisfaction_lbl": kpis["lbl_sat"],
150
+ "trust_val": kpis["trust"], "loyalty_val": kpis["loyalty"], "opinion_val": kpis["opinion"]
151
+ }).execute()
152
+ except: pass
153
+ await sio.emit('metrics_update', { "emotion": camera_state["emotion"], "metrics": kpis, "face_coords": camera_state["face_coords"], "session_time": user["session_time"], "is_recording": user["is_recording"] }, room=sid)
154
+ await asyncio.sleep(1)
155
+
156
+ if __name__ == "__main__":
157
+ @app.on_event("startup")
158
+ async def startup(): asyncio.create_task(loop())
159
+ uvicorn.run(socket_app, host="0.0.0.0", port=7860)