fix: socket controller
This commit is contained in:
parent
ccb669c592
commit
596f498674
|
@ -38,34 +38,36 @@ 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 (
|
|
||||||
str(user_answer).strip().lower()
|
|
||||||
== str(question["target_answer"]).strip().lower()
|
|
||||||
)
|
|
||||||
elif question["type"] == "true_false":
|
|
||||||
return bool(user_answer) == question["target_answer"]
|
|
||||||
elif question["type"] == "option":
|
|
||||||
# user_answer bisa dikirim sebagai index atau value teks option.
|
|
||||||
# Pastikan di‑cast ke int terlebih dahulu jika memungkinkan.
|
|
||||||
try:
|
|
||||||
return int(user_answer) == question["target_answer"]
|
|
||||||
except ValueError:
|
|
||||||
return (
|
return (
|
||||||
str(user_answer).strip().lower()
|
str(user_answer).strip().lower()
|
||||||
== str(question["options"][question["target_answer"]])
|
== str(question["target_answer"]).strip().lower()
|
||||||
.strip()
|
|
||||||
.lower()
|
|
||||||
)
|
)
|
||||||
return False
|
elif question["type"] == "true_false":
|
||||||
|
return bool(user_answer) == question["target_answer"]
|
||||||
|
elif question["type"] == "option":
|
||||||
|
# Handle both index and text-based answers
|
||||||
|
try:
|
||||||
|
# First try numeric index comparison
|
||||||
|
return int(user_answer) == question["target_answer"]
|
||||||
|
except ValueError:
|
||||||
|
# If not an index, compare text
|
||||||
|
return (
|
||||||
|
str(user_answer).strip().lower()
|
||||||
|
== str(question["options"][question["target_answer"]])
|
||||||
|
.strip()
|
||||||
|
.lower()
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue