Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import json | |
| import random | |
| import time | |
| # Load questions from JSON file | |
| def load_questions(): | |
| with open("questionnaire.json", "r") as f: | |
| data = json.load(f) | |
| return data["questions"], data["exam_info"] | |
| QUESTIONS, EXAM_INFO = load_questions() | |
| TIME_LIMIT = 5 * 60 # 5 minutes in seconds | |
| def is_multi_answer(question: dict) -> bool: | |
| return isinstance(question["correct_answer"], list) | |
| def select_random_questions(num_questions: int = 10) -> list: | |
| return random.sample(QUESTIONS, min(num_questions, len(QUESTIONS))) | |
| def format_question(question: dict, index: int) -> str: | |
| suffix = " *(Select all that apply)*" if is_multi_answer(question) else "" | |
| return f"**Question {index + 1}** ({question['section']}){suffix}\n\n{question['question']}" | |
| def calculate_time_remaining(start_time: float) -> tuple[int, int, bool]: | |
| elapsed = time.time() - start_time | |
| remaining = max(0, TIME_LIMIT - elapsed) | |
| minutes = int(remaining // 60) | |
| seconds = int(remaining % 60) | |
| return minutes, seconds, remaining <= 0 | |
| def grade_quiz(selected_questions: list, user_answers: list) -> tuple[int, int, list]: | |
| correct_count = 0 | |
| results = [] | |
| for i, (question, answer) in enumerate(zip(selected_questions, user_answers)): | |
| correct = question["correct_answer"] | |
| if isinstance(correct, int): | |
| correct = [correct] | |
| is_correct = sorted(answer or []) == sorted(correct) | |
| if is_correct: | |
| correct_count += 1 | |
| user_ans_text = ", ".join( | |
| f"{chr(65+a)}. {question['options'][a]}" for a in (answer or []) | |
| ) or "No answer provided" | |
| correct_ans_text = ", ".join( | |
| f"{chr(65+a)}. {question['options'][a]}" for a in correct | |
| ) | |
| results.append({ | |
| "question_num": i + 1, | |
| "question": question["question"], | |
| "section": question["section"], | |
| "user_answer_text": user_ans_text, | |
| "correct_answer_text": correct_ans_text, | |
| "is_correct": is_correct, | |
| "explanation": question["explanation"] | |
| }) | |
| return correct_count, len(selected_questions), results | |
| def format_results(correct: int, total: int, results: list) -> str: | |
| percentage = (correct / total) * 100 | |
| passing = percentage >= EXAM_INFO["passing_score"] | |
| output = "# Quiz Results\n\n" | |
| output += f"## Score: {correct}/{total} ({percentage:.1f}%)\n\n" | |
| output += f"**Status: {'PASSED' if passing else 'FAILED'}** (Passing score: {EXAM_INFO['passing_score']}%)\n\n" | |
| output += "---\n\n" | |
| for result in results: | |
| status_icon = "✅" if result["is_correct"] else "❌" | |
| output += f"### Question {result['question_num']} {status_icon}\n" | |
| output += f"**Section:** {result['section']}\n\n" | |
| output += f"**Question:** {result['question']}\n\n" | |
| output += f"**Your answer:** {result['user_answer_text']}\n\n" | |
| if not result["is_correct"]: | |
| output += f"**Correct answer:** {result['correct_answer_text']}\n\n" | |
| output += f"**Explanation:** {result['explanation']}\n\n" | |
| output += "---\n\n" | |
| return output | |
| def get_answer_components(question: dict, answers: list, q_idx: int): | |
| """Return (radio_update, checkbox_update) for displaying a question.""" | |
| choices = [f"{chr(65+i)}. {opt}" for i, opt in enumerate(question["options"])] | |
| saved = answers[q_idx] or [] | |
| if is_multi_answer(question): | |
| prev = [choices[i] for i in saved] | |
| return ( | |
| gr.update(choices=choices, value=None, visible=False), | |
| gr.update(choices=choices, value=prev, visible=True), | |
| ) | |
| else: | |
| prev = choices[saved[0]] if saved else None | |
| return ( | |
| gr.update(choices=choices, value=prev, visible=True), | |
| gr.update(choices=[], value=[], visible=False), | |
| ) | |
| # Create Gradio interface | |
| with gr.Blocks(title="NVIDIA Certification Practice Quiz") as demo: | |
| # State variables | |
| quiz_questions = gr.State([]) | |
| quiz_start_time = gr.State(0.0) | |
| quiz_active = gr.State(False) | |
| current_question_idx = gr.State(0) | |
| user_answers = gr.State([[] for _ in range(10)]) | |
| gr.Markdown(f""" | |
| # {EXAM_INFO['title']} | |
| **Certifications covered:** {', '.join(EXAM_INFO['certifications'])} | |
| **Instructions:** | |
| - You will be presented with 10 randomly selected questions | |
| - You have **5 minutes** to complete the quiz | |
| - Navigate between questions using the Previous/Next buttons or question selector | |
| - Submit your quiz when ready, or it will auto-submit when time expires | |
| - After submission, you'll see your score and explanations for incorrect answers | |
| """) | |
| with gr.Row(): | |
| start_btn = gr.Button("Start Quiz", variant="primary") | |
| timer_display = gr.Markdown("**Time Remaining:** 05:00") | |
| with gr.Group(visible=False) as quiz_section: | |
| with gr.Row(): | |
| question_selector = gr.Dropdown( | |
| choices=[f"Question {i+1}" for i in range(10)], | |
| value="Question 1", | |
| label="Jump to Question" | |
| ) | |
| answer_status = gr.Markdown("**Answered:** 0/10") | |
| question_display = gr.Markdown("", elem_id="question-display") | |
| answer_radio = gr.Radio(choices=[], label="Select your answer:", interactive=True) | |
| answer_checkbox = gr.CheckboxGroup( | |
| choices=[], label="Select all that apply:", interactive=True, visible=False | |
| ) | |
| with gr.Row(): | |
| prev_btn = gr.Button("◀ Previous") | |
| next_btn = gr.Button("Next ▶") | |
| submit_btn = gr.Button("Submit Quiz", variant="primary") | |
| results_section = gr.Markdown(visible=False) | |
| restart_btn = gr.Button("Start New Quiz", visible=False, variant="primary") | |
| quiz_timer = gr.Timer(value=1) | |
| # --- Event handler functions --- | |
| def start_quiz(): | |
| questions = select_random_questions(10) | |
| start_time = time.time() | |
| answers = [[] for _ in range(10)] | |
| question_text = format_question(questions[0], 0) | |
| radio_update, checkbox_update = get_answer_components(questions[0], answers, 0) | |
| return ( | |
| questions, # quiz_questions | |
| start_time, # quiz_start_time | |
| True, # quiz_active | |
| 0, # current_question_idx | |
| answers, # user_answers | |
| gr.update(visible=True), # quiz_section | |
| question_text, # question_display | |
| radio_update, # answer_radio | |
| checkbox_update, # answer_checkbox | |
| gr.update(visible=False), # results_section | |
| gr.update(visible=False), # restart_btn | |
| gr.update(visible=False), # start_btn | |
| "**Time Remaining:** 05:00", # timer_display | |
| "**Answered:** 0/10", # answer_status | |
| gr.update(value="Question 1"), # question_selector | |
| ) | |
| def navigate_question(direction, current_idx, questions, answers): | |
| new_idx = max(0, current_idx - 1) if direction == "prev" else min(9, current_idx + 1) | |
| question = questions[new_idx] | |
| radio_update, checkbox_update = get_answer_components(question, answers, new_idx) | |
| answered_count = sum(1 for a in answers if a) | |
| return ( | |
| new_idx, | |
| format_question(question, new_idx), | |
| radio_update, | |
| checkbox_update, | |
| f"**Answered:** {answered_count}/10", | |
| gr.update(value=f"Question {new_idx + 1}"), | |
| ) | |
| def jump_to_question(question_label, current_idx, questions, answers): | |
| new_idx = int(question_label.split()[1]) - 1 | |
| question = questions[new_idx] | |
| radio_update, checkbox_update = get_answer_components(question, answers, new_idx) | |
| answered_count = sum(1 for a in answers if a) | |
| return ( | |
| new_idx, | |
| format_question(question, new_idx), | |
| radio_update, | |
| checkbox_update, | |
| f"**Answered:** {answered_count}/10", | |
| ) | |
| def save_answer_radio(answer, current_idx, answers): | |
| answers = answers.copy() | |
| if answer is not None: | |
| answers[current_idx] = [ord(answer[0]) - 65] | |
| answered_count = sum(1 for a in answers if a) | |
| return answers, f"**Answered:** {answered_count}/{len(answers)}" | |
| def save_answer_checkbox(selected, current_idx, answers): | |
| answers = answers.copy() | |
| answers[current_idx] = sorted([ord(s[0]) - 65 for s in selected]) if selected else [] | |
| answered_count = sum(1 for a in answers if a) | |
| return answers, f"**Answered:** {answered_count}/{len(answers)}" | |
| def do_submit(questions, answers): | |
| correct, total, results = grade_quiz(questions, answers) | |
| results_text = format_results(correct, total, results) | |
| return ( | |
| gr.update(visible=False), # quiz_section | |
| gr.update(value=results_text, visible=True), # results_section | |
| gr.update(visible=True), # restart_btn | |
| gr.update(visible=False), # start_btn | |
| False, # quiz_active | |
| "**Quiz Submitted**", # timer_display | |
| ) | |
| def tick_timer(quiz_active_val, start_time_val, questions, answers): | |
| if not quiz_active_val: | |
| return (gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), quiz_active_val) | |
| minutes, seconds, expired = calculate_time_remaining(start_time_val) | |
| if not expired: | |
| return ( | |
| f"**Time Remaining:** {minutes:02d}:{seconds:02d}", | |
| gr.update(), gr.update(), gr.update(), gr.update(), | |
| quiz_active_val, | |
| ) | |
| # Auto-submit on expiry | |
| correct, total, results = grade_quiz(questions, answers) | |
| results_text = format_results(correct, total, results) | |
| return ( | |
| "**Time Expired**", | |
| gr.update(visible=False), | |
| gr.update(value=results_text, visible=True), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| False, | |
| ) | |
| # --- Wire up events --- | |
| start_outputs = [ | |
| quiz_questions, quiz_start_time, quiz_active, current_question_idx, user_answers, | |
| quiz_section, question_display, answer_radio, answer_checkbox, | |
| results_section, restart_btn, start_btn, timer_display, answer_status, question_selector, | |
| ] | |
| start_btn.click(start_quiz, outputs=start_outputs) | |
| restart_btn.click(start_quiz, outputs=start_outputs) | |
| nav_outputs = [ | |
| current_question_idx, question_display, answer_radio, answer_checkbox, | |
| answer_status, question_selector, | |
| ] | |
| prev_btn.click( | |
| lambda *a: navigate_question("prev", *a), | |
| inputs=[current_question_idx, quiz_questions, user_answers], | |
| outputs=nav_outputs, | |
| ) | |
| next_btn.click( | |
| lambda *a: navigate_question("next", *a), | |
| inputs=[current_question_idx, quiz_questions, user_answers], | |
| outputs=nav_outputs, | |
| ) | |
| question_selector.change( | |
| jump_to_question, | |
| inputs=[question_selector, current_question_idx, quiz_questions, user_answers], | |
| outputs=[current_question_idx, question_display, answer_radio, answer_checkbox, answer_status], | |
| ) | |
| answer_radio.change( | |
| save_answer_radio, | |
| inputs=[answer_radio, current_question_idx, user_answers], | |
| outputs=[user_answers, answer_status], | |
| ) | |
| answer_checkbox.change( | |
| save_answer_checkbox, | |
| inputs=[answer_checkbox, current_question_idx, user_answers], | |
| outputs=[user_answers, answer_status], | |
| ) | |
| submit_btn.click( | |
| do_submit, | |
| inputs=[quiz_questions, user_answers], | |
| outputs=[quiz_section, results_section, restart_btn, start_btn, quiz_active, timer_display], | |
| ) | |
| quiz_timer.tick( | |
| tick_timer, | |
| inputs=[quiz_active, quiz_start_time, quiz_questions, user_answers], | |
| outputs=[timer_display, quiz_section, results_section, restart_btn, start_btn, quiz_active], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |