Skip to content

Commit 0aceab9

Browse files
authored
Merge pull request #28 from Yuval-Roth/fixingGraphs
READY
2 parents 6cb1fda + 2af772c commit 0aceab9

6 files changed

Lines changed: 1214 additions & 171 deletions

File tree

manager/src/app.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .managers.game import *
88
from .managers.operators import *
99
from .managers.session_data import *
10+
from .managers.feedback import *
1011
from .managers.logger import Logger
1112
from .ENUMS import *
1213
import json
@@ -70,6 +71,13 @@ def get_parts():
7071
online_participants.append(part)
7172
print(f"online_participants: {online_participants}")
7273
return jsonify(online_participants)
74+
elif all_participants:
75+
all = []
76+
for part in all_participants:
77+
part = json.loads(part)
78+
part['id'] = str(part['pid']).zfill(3)
79+
all.append(part)
80+
return jsonify(all)
7381
return jsonify(all_participants)
7482

7583

@@ -82,7 +90,7 @@ def lobbies_menu():
8290
if lobbies:
8391
lobbies = lobbies.lobbies
8492
else:
85-
lobbies = LOBBIES
93+
lobbies = []
8694
return render_template('lobbies.html', lobbies=lobbies)
8795

8896

@@ -430,6 +438,7 @@ def single_session_data():
430438
game_type = request.args.get('game_type', '')
431439
participants = request.args.get('participants', '')
432440
duration = request.args.get('duration', '')
441+
tolerance = request.args.get('tolerance', '')
433442

434443
# ── metrics ──
435444
heart = get_heartrate(sid)
@@ -450,7 +459,9 @@ def single_session_data():
450459
"gameType" : game_type,
451460
"participants": [p.strip() for p in participants.split(',') if p],
452461
"sessionId" : sid,
453-
"duration" : duration
462+
"duration" : duration,
463+
"tolerance" : tolerance
464+
454465
}
455466
data = {
456467
"heart" : heart,
@@ -480,6 +491,47 @@ def get_all_sessions_route():
480491
return jsonify({"status": "error",
481492
"message": "Internal server error"}), 500
482493

494+
495+
496+
@app.route('/session_data/feedback', methods=['GET'])
497+
def session_feedback():
498+
# 1) Get session_id from query string
499+
sid = request.args.get('session_id')
500+
if not sid:
501+
return jsonify({ "success": False, "message": "Missing session_id" }), 400
502+
503+
# 2) Call internal helper to fetch feedback
504+
try:
505+
feedback_list = get_feedback(sid)
506+
except Exception as e:
507+
Logger.log_error(f"session_feedback – Exception: {e}")
508+
return jsonify({ "success": False, "message": "Internal server error" }), 500
509+
510+
return jsonify({
511+
"success": True,
512+
"payload": feedback_list
513+
})
514+
515+
516+
@app.route('/session_data/experiment_feedback', methods=['GET'])
517+
def experiment_feedback():
518+
exp_id = request.args.get('exp_id')
519+
if not exp_id:
520+
return jsonify({"success": False, "message": "Missing exp_id"}), 400
521+
522+
try:
523+
feedback_list = get_experiment_feedback(exp_id)
524+
except Exception as e:
525+
Logger.log_error(f"experiment_feedback – Exception: {e}")
526+
return jsonify({"success": False, "message": "Internal server error"}), 500
527+
528+
return jsonify({
529+
"success": True,
530+
"payload": feedback_list
531+
})
532+
533+
534+
483535
@app.route('/experiment_questions', methods=['GET'])
484536
def get_experiment_questions_route():
485537
if RUNNING_LOCAL:

manager/src/managers/feedback.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# managers/feedback.py
2+
3+
from typing import List
4+
from flask import session
5+
from . import post_auth, server_response, URL
6+
from .logger import Logger
7+
import json
8+
9+
10+
def get_feedback(sid: str) -> List[dict]:
11+
"""
12+
Fetches feedback for a given session ID by calling the internal
13+
/data/session/select/feedback endpoint, then returns a list of
14+
{ "question": ..., "answer": ... } dictionaries.
15+
"""
16+
feedback_list: List[dict] = []
17+
try:
18+
# Build request body
19+
body = {"sessionId": sid}
20+
21+
# Perform authenticated POST to the internal endpoint
22+
token = session.get('token', '')
23+
headers = {
24+
"Authorization": f"Bearer {token}",
25+
"Content-Type": "application/json"
26+
}
27+
response = post_auth(f"{URL}/data/session/select/feedback", json=body, headers=headers)
28+
29+
# Only proceed if we got a 200 or 201
30+
if response.status_code in (200, 201):
31+
ser_res = server_response(response)
32+
if ser_res.get_success():
33+
raw_payload = ser_res.get_payload() or []
34+
for item in raw_payload:
35+
try:
36+
# If payload items are JSON‐strings, parse them
37+
feedback_list.append(json.loads(item))
38+
except Exception:
39+
# If already a dict, just append
40+
if isinstance(item, dict):
41+
feedback_list.append(item)
42+
return feedback_list
43+
44+
Logger.log_error(f"get_feedback – API returned error: {ser_res.get_message()}")
45+
return []
46+
47+
Logger.log_error(f"get_feedback – HTTP {response.status_code}")
48+
return []
49+
50+
except Exception as e:
51+
Logger.log_error(f"get_feedback – Exception: {e}")
52+
return []
53+
54+
55+
def get_experiment_feedback(exp_id: str) -> List[dict]:
56+
"""
57+
Fetch all feedback for experiment (= lobby) with ID = exp_id.
58+
Calls internal /data/experiment/select/feedback endpoint.
59+
Returns a list of dictionaries like { "pid": ..., "question": ..., "answer": ... }.
60+
"""
61+
feedback_list: List[dict] = []
62+
try:
63+
# Build request body
64+
body = {"expId": exp_id}
65+
66+
# Perform authenticated POST to the internal endpoint
67+
token = session.get('token', '')
68+
headers = {
69+
"Authorization": f"Bearer {token}",
70+
"Content-Type": "application/json"
71+
}
72+
response = post_auth(f"{URL}/data/experiment/select/feedback", json=body, headers=headers)
73+
74+
# Check HTTP status
75+
if response.status_code in (200, 201):
76+
ser_res = server_response(response)
77+
if ser_res.get_success():
78+
raw_payload = ser_res.get_payload() or []
79+
for item in raw_payload:
80+
try:
81+
# If payload items are JSON‐strings, parse them
82+
feedback_list.append(json.loads(item))
83+
except Exception:
84+
# If already a dict, just append
85+
if isinstance(item, dict):
86+
feedback_list.append(item)
87+
return feedback_list
88+
89+
Logger.log_error(f"get_experiment_feedback – API error: {ser_res.get_message()}")
90+
return []
91+
else:
92+
Logger.log_error(f"get_experiment_feedback – HTTP {response.status_code}")
93+
return []
94+
95+
except Exception as e:
96+
Logger.log_error(f"get_experiment_feedback – Exception: {e}")
97+
return []
98+

manager/src/managers/session_data.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -205,33 +205,41 @@ def get_swipe_game_frequency(session_id: str):
205205
except (ValueError, TypeError) as e:
206206
Logger.log_error(f"Invalid FREQUENCY data: {ev['data']} error={e}")
207207

208-
# angle data
209-
208+
# --- ANGLE DATA: drop any value == 600, then convert degrees → sin(radians) so y ∈ [-1,1] ---
210209
for ev in angle_events:
211210
try:
211+
raw_value = float(ev["data"])
212+
if raw_value == 600:
213+
# skip any outlier or placeholder of 600
214+
continue
215+
212216
timestamp_sec = ev["timestamp"] / 1000.0
213-
value = float(ev["data"])
217+
# convert degrees → radians, then take sine for unit-circle range
218+
rad = math.radians(raw_value)
219+
unit_y = math.sin(rad)
220+
214221
angle_data[ev["actor"]]["timestamps"].append(f"{timestamp_sec:.3f}")
215-
angle_data[ev["actor"]]["values"].append(value)
222+
angle_data[ev["actor"]]["values"].append(unit_y)
216223
except (ValueError, TypeError) as e:
217224
Logger.log_error(f"Invalid ANGLE data: {ev['data']} error={e}")
218225

219-
# Sort data for each actor by timestamp
226+
# --- SORT FREQUENCY DATA BY TIMESTAMP ---
220227
for actor, data in frequency_data.items():
221228
combined = list(zip(map(float, data["timestamps"]), data["values"]))
222229
combined.sort(key=lambda x: x[0])
223230
data["timestamps"] = [f"{t:.3f}" for t, _ in combined]
224-
data["values"] = [v for _, v in combined]
231+
data["values"] = [v for _, v in combined]
225232

226-
# Sort angle data for each actor by timestamp
233+
# --- SORT ANGLE DATA BY TIMESTAMP ---
227234
for actor, data in angle_data.items():
228235
combined = list(zip(map(float, data["timestamps"]), data["values"]))
229236
combined.sort(key=lambda x: x[0])
230237
data["timestamps"] = [f"{t:.3f}" for t, _ in combined]
231-
data["values"] = [v for _, v in combined]
238+
data["values"] = [v for _, v in combined]
232239

240+
# --- SYNC INTERVALS ---
233241
sync_start_times = sorted(ev["timestamp"] / 1000.0 for ev in sync_start_events)
234-
sync_end_times = sorted(ev["timestamp"] / 1000.0 for ev in sync_end_events)
242+
sync_end_times = sorted(ev["timestamp"] / 1000.0 for ev in sync_end_events)
235243

236244
if len(sync_start_times) != len(sync_end_times):
237245
Logger.log_error(f"Session {session_id}: Mismatched SYNC_START_TIME and SYNC_END_TIME events")
@@ -241,6 +249,7 @@ def get_swipe_game_frequency(session_id: str):
241249
return dict(frequency_data), dict(angle_data), sync_intervals
242250

243251

252+
244253
def get_lobbies_data() -> list[dict]:
245254
try:
246255
res = server_response(

manager/src/static/session_data.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,23 @@
104104
.session-container::-webkit-scrollbar-track {
105105
background-color: #f1f1f1;
106106
}
107+
108+
.reset-btn {
109+
position: absolute;
110+
top: 12px;
111+
right: 12px;
112+
z-index: 10;
113+
font-size: 12px;
114+
padding: 4px 8px;
115+
background-color: #4CAF50;
116+
color: white;
117+
border: 1px solid #45a049;
118+
border-radius: 4px;
119+
cursor: pointer;
120+
opacity: 1;
121+
transition: opacity 0.2s;
122+
}
123+
124+
.reset-btn:hover {
125+
opacity: 0.8;
126+
}

0 commit comments

Comments
 (0)