Coverage for app/services/quiz_service.py: 16%
43 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 02:49 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 02:49 +0000
1import json
2from app.controllers.database import client
3from app.controllers.ai import gemini_client
4from app.models.errors import DatabaseError
5import re
7def build_prompt_from_suggestions(user_section_id: str) -> str:
8 try:
9 result = client.table("suggestions").select("prompt, suggestion_array")\
10 .eq("user_section_id", user_section_id).execute()
12 if not result.data:
13 raise ValueError(f"No suggestions found for section {user_section_id}")
15 prompt_parts = [
16 f"Prompt: {item['prompt']}\nAnswer: {item['suggestion_array']}" for item in result.data
17 ]
19 full_prompt = "\n\n".join(prompt_parts)
21 return f"""Generate a quiz based on the following context.
22 Each question should be multiple choice (a, b, c, d) with only one correct answer.
23 Include the correct answer index and a brief explanation.
25 Context:
26 {full_prompt}
27 """
28 except Exception as e:
29 raise DatabaseError(f"Failed to build prompt: {str(e)}")
31def generate_quiz_from_prompt(prompt: str):
32 full_prompt = f"""Generate a programming quiz with EXACTLY these specifications:
34 1. Create 10 multiple-choice questions about JavaScript functions
35 2. Only use CORRECT code examples from this context:
36 {prompt}
38 3. For each question provide:
39 - A clear question
40 - 4 answer choices (1 correct, 3 plausible incorrect)
41 - answer_index (0-3)
42 - A 1-sentence explanation
44 4. Format the response as VALID JSON array with this EXACT structure:
45 [
46 {
47 "question": "text",
48 "choices": ["a", "b", "c", "d"],
49 "answer_index": 0,
50 "explanation": "text"
51 } ,
52 // 9 more questions
53 ]
55 5. Important rules:
56 - Only show proper implementations in questions
57 - Test knowledge of correct patterns
58 - No markdown or backticks
59 - Ensure JSON is valid (no trailing commas)
61 Example of ONE question (you must provide 10):
62 {
63 "question": "What is the correct implementation to square a number?",
64 "choices": [
65 "function square(n) { return n + n; } ",
66 "function square(n) { return n * n; } ",
67 "function square(n) { return Math.pow(n, 3); } ",
68 "function square(n) { return n / n; } "
69 ],
70 "answer_index": 1,
71 "explanation": "Squaring requires multiplying the number by itself"
72 }
73 """
75 response = gemini_client.chat_session.send_message(full_prompt)
77 try:
78 try:
79 quiz = json.loads(response.text.strip())
80 except json.JSONDecodeError as e:
81 # Clean the response by removing trailing commas
82 cleaned_text = re.sub(r',\s*([}\]])', r'\1', response.text)
83 try:
84 quiz = json.loads(cleaned_text.strip())
85 except json.JSONDecodeError:
86 # Fallback: extract the array from a larger response
87 match = re.search(r'\[\s*{.*?}\s*\]', cleaned_text, re.DOTALL)
88 if not match:
89 raise ValueError("No valid JSON array found in Gemini response.")
90 quiz = json.loads(match.group(0))
92 if not isinstance(quiz, list) or len(quiz) != 10:
93 raise ValueError(f"Expected a list of 10 quiz questions, got {len(quiz)}")
95 # Validate structure of each question
96 required_keys = {"question", "choices", "answer_index", "explanation"}
97 for i, q in enumerate(quiz):
98 if not all(k in q for k in required_keys):
99 raise ValueError(f"Question {i+1} is missing required fields")
100 if not isinstance(q["choices"], list) or len(q["choices"]) != 4:
101 raise ValueError(f"Question {i+1} should have exactly 4 choices")
102 if not isinstance(q["answer_index"], int) or not 0 <= q["answer_index"] < 4:
103 raise ValueError(f"Question {i+1} has invalid answer_index")
105 return quiz
106 except Exception as e:
107 raise ValueError(f"Failed to parse Gemini quiz response: {e}")