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()