fix: socket controller

This commit is contained in:
akhdanre 2025-05-15 22:53:21 +07:00
parent ccb669c592
commit 596f498674
1 changed files with 150 additions and 115 deletions

View File

@ -38,14 +38,12 @@ class SocketController:
self.admin_sids: dict[str, str] = {} self.admin_sids: dict[str, str] = {}
self._register_events() self._register_events()
# ---------------------------------------------------------------------
# Helper utilities
# ---------------------------------------------------------------------
@staticmethod @staticmethod
def _is_correct(user_answer, question: dict) -> bool: def _is_correct(user_answer, question: dict) -> bool:
"""Bandingkan jawaban user dengan kunci jawaban.""" """Advanced answer validation method."""
print(f"Validating answer: user={user_answer}, question={question}")
print("user answer", user_answer, "question_target", question["target_answer"]) try:
if question["type"] == "fill_the_blank": if question["type"] == "fill_the_blank":
return ( return (
str(user_answer).strip().lower() str(user_answer).strip().lower()
@ -54,11 +52,12 @@ class SocketController:
elif question["type"] == "true_false": elif question["type"] == "true_false":
return bool(user_answer) == question["target_answer"] return bool(user_answer) == question["target_answer"]
elif question["type"] == "option": elif question["type"] == "option":
# user_answer bisa dikirim sebagai index atau value teks option. # Handle both index and text-based answers
# Pastikan dicast ke int terlebih dahulu jika memungkinkan.
try: try:
# First try numeric index comparison
return int(user_answer) == question["target_answer"] return int(user_answer) == question["target_answer"]
except ValueError: except ValueError:
# If not an index, compare text
return ( return (
str(user_answer).strip().lower() str(user_answer).strip().lower()
== str(question["options"][question["target_answer"]]) == str(question["options"][question["target_answer"]])
@ -66,6 +65,9 @@ class SocketController:
.lower() .lower()
) )
return False return False
except Exception as e:
print(f"Error in answer validation: {e}")
return False
def _questions_key(self, session_code: str) -> str: def _questions_key(self, session_code: str) -> str:
return f"session:{session_code}:questions" return f"session:{session_code}:questions"
@ -239,7 +241,6 @@ class SocketController:
def handle_end_session(data): def handle_end_session(data):
session_code = data.get("session_id") session_code = data.get("session_id")
user_id = data.get("user_id") user_id = data.get("user_id")
if not session_code or not user_id: if not session_code or not user_id:
emit("error", {"message": "session_id and user_id required"}) emit("error", {"message": "session_id and user_id required"})
return return
@ -247,21 +248,6 @@ class SocketController:
# Validasi user berhak mengakhiri session # Validasi user berhak mengakhiri session
self.session_service.end_session(session_id=session_code, user_id=user_id) self.session_service.end_session(session_id=session_code, user_id=user_id)
answers = self.redis_repo.get_data(self._answers_key(session_code)) or []
scores = self.redis_repo.get_data(self._scores_key(session_code)) or {}
print("\n📦 Final Quiz Data for Session", session_code)
print("------------------------------------------------------------")
for entry in answers:
status = "" if entry["correct"] else ""
print(
f"User {entry['user_id']} - Q{entry['question_index']}: {entry['answer']} {status}"
)
print("\n🏁 Rekap Skor:")
for uid, sc in scores.items():
print(f"User {uid}: Benar {sc['correct']} | Salah {sc['incorrect']}")
# Bersihkan semua data session di Redis # Bersihkan semua data session di Redis
for key in [ for key in [
self._answers_key(session_code), self._answers_key(session_code),
@ -288,11 +274,8 @@ class SocketController:
target=self._simulate_quiz_flow, args=(session_code,), daemon=True target=self._simulate_quiz_flow, args=(session_code,), daemon=True
).start() ).start()
# ---------------------------------------------------------------------
# Quiz flow simulation -------------------------------------------------
# ---------------------------------------------------------------------
def _simulate_quiz_flow(self, session_code: str): def _simulate_quiz_flow(self, session_code: str):
"""Mengirim list pertanyaan satu per satu secara otomatis (demo).""" """Enhanced quiz flow with better question management."""
questions = [ questions = [
{ {
"index": 1, "index": 1,
@ -300,6 +283,7 @@ class SocketController:
"target_answer": "Kutai", "target_answer": "Kutai",
"duration": 30, "duration": 30,
"type": "fill_the_blank", "type": "fill_the_blank",
"points": 10,
"options": None, "options": None,
}, },
{ {
@ -308,6 +292,7 @@ class SocketController:
"target_answer": True, "target_answer": True,
"duration": 30, "duration": 30,
"type": "true_false", "type": "true_false",
"points": 10,
"options": None, "options": None,
}, },
{ {
@ -316,88 +301,138 @@ class SocketController:
"target_answer": 2, "target_answer": 2,
"duration": 30, "duration": 30,
"type": "option", "type": "option",
"points": 10,
"options": ["Majapahit", "Tarumanegara", "Sriwijaya", "Mataram Kuno"], "options": ["Majapahit", "Tarumanegara", "Sriwijaya", "Mataram Kuno"],
}, },
{
"index": 4,
"question": "Prasasti Yupa merupakan peninggalan dari kerajaan?",
"target_answer": "Kutai",
"duration": 30,
"type": "fill_the_blank",
"options": None,
},
{
"index": 5,
"question": "Apakah Tarumanegara terletak di wilayah Kalimantan Timur?",
"target_answer": False,
"duration": 30,
"type": "true_false",
"options": None,
},
{
"index": 6,
"question": "Kitab Negarakertagama ditulis oleh?",
"target_answer": 0,
"duration": 30,
"type": "option",
"options": [
"Empu Tantular",
"Empu Prapanca",
"Hayam Wuruk",
"Gajah Mada",
],
},
{
"index": 7,
"question": "Tokoh yang terkenal dengan Sumpah Palapa adalah?",
"target_answer": "Gajah Mada",
"duration": 30,
"type": "fill_the_blank",
"options": None,
},
{
"index": 8,
"question": "Candi Borobudur dibangun oleh kerajaan Hindu?",
"target_answer": False,
"duration": 30,
"type": "true_false",
"options": None,
},
{
"index": 9,
"question": "Raja terkenal dari Kerajaan Sriwijaya adalah?",
"target_answer": 1,
"duration": 30,
"type": "option",
"options": [
"Dapunta Hyang",
"Balaputradewa",
"Airlangga",
"Hayam Wuruk",
],
},
{
"index": 10,
"question": "Candi Prambanan merupakan peninggalan agama?",
"target_answer": "Hindu",
"duration": 30,
"type": "fill_the_blank",
"options": None,
},
] ]
# Simpan ke Redis agar bisa dipakai saat evaluasi jawaban # Simpan ke Redis
self.redis_repo.set_data(self._questions_key(session_code), questions) self.redis_repo.set_data(self._questions_key(session_code), questions)
# Beri sedikit jeda sebelum pertanyaan pertama # Inisialisasi skor untuk semua peserta
scores = self.redis_repo.get_data(self._scores_key(session_code)) or {}
self.redis_repo.set_data(self._scores_key(session_code), scores)
# Beri jeda sebelum pertanyaan pertama
time.sleep(2) time.sleep(2)
for q in questions: for q in questions:
print(f"\n📢 Mengirim pertanyaan {q['index']} ke room {session_code}") print(f"\n📢 Mengirim pertanyaan {q['index']} ke room {session_code}")
q.pop("target_answer")
self.socketio.emit("quiz_question", q, room=session_code) # Kirim pertanyaan tanpa jawaban yang benar
question_to_send = q.copy()
question_to_send.pop("target_answer", None)
self.socketio.emit("quiz_question", question_to_send, room=session_code)
# Tunggu durasi pertanyaan
time.sleep(q["duration"]) time.sleep(q["duration"])
self.socketio.emit( # Generate dan kirim rekap kuis
"quiz_done", {"message": "Quiz has ended!"}, room=session_code self._generate_quiz_recap(session_code)
def _generate_quiz_recap(self, session_code: str):
"""Comprehensive quiz recap generation."""
try:
# Ambil data dari Redis
answers = self.redis_repo.get_data(self._answers_key(session_code)) or []
scores = self.redis_repo.get_data(self._scores_key(session_code)) or {}
questions = (
self.redis_repo.get_data(self._questions_key(session_code)) or []
)
# Persiapkan data rekap
recap_data = {
"session_code": session_code,
"total_questions": len(questions),
"answers": [],
"scores": [],
"questions": [],
}
# Tambahkan detail pertanyaan
for q in questions:
question_recap = {
"index": q["index"],
"question": q["question"],
"type": q["type"],
"points": q.get("points", 10),
}
recap_data["questions"].append(question_recap)
# Tambahkan detail jawaban
for entry in answers:
# Temukan pertanyaan terkait
related_question = next(
(q for q in questions if q["index"] == entry["question_index"]),
None,
)
answer_recap = {
"user_id": entry["user_id"],
"question_index": entry["question_index"],
"answer": entry["answer"],
"correct": entry["correct"],
"question": (
related_question["question"]
if related_question
else "Pertanyaan tidak ditemukan"
),
}
recap_data["answers"].append(answer_recap)
# Tambahkan skor per pengguna
for uid, sc in scores.items():
score_recap = {
"user_id": uid,
"correct": sc.get("correct", 0),
"incorrect": sc.get("incorrect", 0),
"total_score": sc.get("correct", 0)
* 10, # 10 poin per jawaban benar
}
recap_data["scores"].append(score_recap)
# Urutkan skor dari tertinggi ke terendah
recap_data["scores"].sort(key=lambda x: x["total_score"], reverse=True)
# Kirim rekap ke semua peserta
self.socketio.emit(
"quiz_recap",
{
"message": "Kuis telah selesai. Berikut adalah rekap lengkap.",
"recap": recap_data,
},
room=session_code,
)
print(recap_data)
# Cetak rekap di konsol server
print("\n🏁 Rekap Kuis Lengkap")
print("=" * 50)
print(f"Kode Sesi: {session_code}")
print(f"Total Pertanyaan: {len(questions)}")
print("\nPeringkat Peserta:")
for i, score in enumerate(recap_data["scores"], 1):
print(
f"{i}. User {score['user_id']}: Skor {score['total_score']} (Benar: {score['correct']}, Salah: {score['incorrect']})"
)
# Kirim tanda kuis telah berakhir
self.socketio.emit(
"quiz_done",
{"message": "Kuis telah berakhir.", "session_code": session_code},
room=session_code,
)
except Exception as e:
print(f"Error generating quiz recap: {e}")
# Kirim pesan error jika gagal membuat rekap
self.socketio.emit(
"quiz_error",
{
"message": "Terjadi kesalahan saat membuat rekap kuis.",
"error": str(e),
},
room=session_code,
) )