Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/quiz/services/quiz_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,21 @@ def process_on_demand_quiz(self, chat_id: str, lang: str, topic: str, difficulty
)

if poll_result:
poll_id = str(poll_result.get("poll", {}).get("id", ""))
message_id = poll_result.get("message_id", 0)
self._repo.save_on_demand_quiz_record(
chat_id=chat_id,
question=question["question"],
options=question["options"],
correct_option_id=question["correct_option_index"],
explanation=question.get("explanation"),
category=topic,
lang=lang,
poll_id=poll_id,
message_id=message_id,
difficulty=difficulty,
points=question["points"],
)
logger.info("On-demand quiz sent", extra={"chat_id": chat_id, "topic": topic})
return {"status": "ok", "sent": 1, "total": 1, **self._rpd_payload()}

Expand Down
76 changes: 72 additions & 4 deletions src/quiz/services/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,13 +252,81 @@ def save_quiz_record(
"""Write a daily quiz record for a chat."""
now = datetime.now(_ALMATY_TZ)
today = now.strftime("%Y-%m-%d")
self._save_quiz_record(
chat_id=chat_id,
sort_key=f"DATE#{today}",
question=question,
options=options,
correct_option_id=correct_option_id,
explanation=explanation,
category=category,
lang=lang,
poll_id=poll_id,
message_id=message_id,
difficulty=difficulty,
points=points,
sent_at=now,
log_context={"date": today},
)

def save_on_demand_quiz_record(
self,
chat_id: str,
question: str,
options: list[str],
correct_option_id: int,
explanation: str | None,
category: str,
lang: str,
poll_id: str,
message_id: int,
difficulty: str = "easy",
points: int = 1,
) -> None:
"""Write an on-demand quiz record keyed by poll id for answer lookup."""
now = datetime.now(_ALMATY_TZ)
self._save_quiz_record(
chat_id=chat_id,
sort_key=f"ON_DEMAND#{poll_id}",
question=question,
options=options,
correct_option_id=correct_option_id,
explanation=explanation,
category=category,
lang=lang,
poll_id=poll_id,
message_id=message_id,
difficulty=difficulty,
points=points,
sent_at=now,
log_context={"poll_id": str(poll_id)},
)

def _save_quiz_record(
self,
chat_id: str,
sort_key: str,
question: str,
options: list[str],
correct_option_id: int,
explanation: str | None,
category: str,
lang: str,
poll_id: str,
message_id: int,
difficulty: str,
points: int,
sent_at: datetime,
log_context: dict[str, Any],
) -> None:
"""Persist quiz metadata used by poll_answer scoring."""
ttl = int(time.time()) + (_TTL_DAYS * 86400)

try:
self._table.put_item(
Item={
"PK": f"QUIZ#{chat_id}",
"SK": f"DATE#{today}",
"SK": sort_key,
"question": question,
"options": options,
"correct_option_id": correct_option_id,
Expand All @@ -269,11 +337,11 @@ def save_quiz_record(
"message_id": message_id,
"difficulty": difficulty,
"points": points,
"sent_at": now.isoformat(),
"sent_at": sent_at.isoformat(),
"ttl": ttl,
}
)
logger.info("Quiz record saved", extra={"chat_id": chat_id, "date": today})
logger.info("Quiz record saved", extra={"chat_id": chat_id, **log_context})
except Exception as e:
logger.error("Failed to save quiz record", extra={"chat_id": chat_id, "error": str(e)})
logger.error("Failed to save quiz record", extra={"chat_id": chat_id, **log_context, "error": str(e)})
raise
63 changes: 63 additions & 0 deletions tests/test_quiz_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Tests for quiz service orchestration."""

import os
import sys
from unittest.mock import MagicMock

os.environ.setdefault("QUIZ_LLM_RPD", "1000")

_quiz_dir = os.path.join(os.path.dirname(__file__), "..", "src", "quiz")
_saved_modules: dict[str, object] = {}

try:
for mod_name in list(sys.modules):
if mod_name in ("core", "services") or mod_name.startswith(("core.", "services.")):
_saved_modules[mod_name] = sys.modules.pop(mod_name)

sys.path.insert(0, _quiz_dir)
from services.quiz_service import QuizService # noqa: E402
finally:
if _quiz_dir in sys.path:
sys.path.remove(_quiz_dir)
for mod_name in list(sys.modules):
if mod_name in ("core", "services") or mod_name.startswith(("core.", "services.")):
sys.modules.pop(mod_name, None)
sys.modules.update(_saved_modules)


def test_process_on_demand_quiz_persists_poll_for_answer_lookup() -> None:
service = QuizService.__new__(QuizService)
service._generator = MagicMock()
service._sender = MagicMock()
service._repo = MagicMock()

service._generator.generate_question.return_value = {
"question": "What is S3?",
"options": ["Storage", "Compute", "Network", "Database"],
"correct_option_index": 0,
"explanation": "S3 stores objects.",
"difficulty": "easy",
"points": 1,
}
service._generator.get_rpd_status.return_value = (99, 100)
service._sender.send_quiz_poll.return_value = {
"message_id": 123,
"poll": {"id": "poll-123"},
}

result = service.process_on_demand_quiz("chat-1", "en", "cloud", "easy")

assert result["status"] == "ok"
service._repo.save_on_demand_quiz_record.assert_called_once_with(
chat_id="chat-1",
question="What is S3?",
options=["Storage", "Compute", "Network", "Database"],
correct_option_id=0,
explanation="S3 stores objects.",
category="cloud",
lang="en",
poll_id="poll-123",
message_id=123,
difficulty="easy",
points=1,
)
Loading