import gradio as gr import random import json from fpdf import FPDF import os # Load question bank with open("questions.json", "r", encoding="utf-8") as f: question_bank = json.load(f) # Function to get random 50 questions def get_random_questions(): return random.sample(question_bank, 50) # Function to evaluate answers (handles single or multiple-correct formats) def evaluate_test(student_name, father_name, roll_no, answers, questions): # answers: list of 50 selected option strings (or None) attempted = sum(1 for a in answers if a is not None and a != "") correct = 0 for q, ans in zip(questions, answers): if ans is None or ans == "": continue correct_key = q.get("answer") # support either a single string or a list of accepted answers if isinstance(correct_key, list): if ans in correct_key: correct += 1 else: if ans == correct_key: correct += 1 total = len(questions) if questions else 0 percentage = (correct / total) * 100 if total > 0 else 0.0 # Generate PDF report (safe encoding) pdf = FPDF() pdf.add_page() pdf.set_font("Arial", size=12) pdf.cell(200, 10, txt="Test Report - Class 6 General Knowledge", ln=True, align="C") pdf.ln(10) # Use latin-1 replacement to avoid crashes for non-latin characters def safe_text(t): return str(t).encode("latin-1", "replace").decode("latin-1") pdf.cell(200, 8, txt=safe_text(f"Student Name: {student_name}"), ln=True) pdf.cell(200, 8, txt=safe_text(f"Father's Name: {father_name}"), ln=True) pdf.cell(200, 8, txt=safe_text(f"Roll No: {roll_no}"), ln=True) pdf.ln(6) pdf.cell(200, 8, txt=f"Total Questions: {total}", ln=True) pdf.cell(200, 8, txt=f"Attempted: {attempted}", ln=True) pdf.cell(200, 8, txt=f"Correct: {correct}", ln=True) pdf.cell(200, 8, txt=f"Percentage: {percentage:.2f}%", ln=True) # Save file safe_roll = roll_no if roll_no else "student" file_name = f"{safe_roll}_result.pdf" # ensure unique filename to avoid race conditions idx = 0 base_name = file_name while os.path.exists(file_name): idx += 1 file_name = f"{safe_roll}_result_{idx}.pdf" pdf.output(file_name) result_summary = ( f"Student: {student_name}\n" f"Father's Name: {father_name}\n" f"Roll No: {roll_no}\n\n" f"Attempted: {attempted}\n" f"Correct: {correct}\n" f"Percentage: {percentage:.2f}%" ) return result_summary, file_name # Build Gradio interface using Blocks def build_interface(): def run_test(*args): """ args layout: 0: student_name 1: father_name 2: roll_no 3..52: answer values for Q1..Q50 53: questions state (list of 50 question dicts) """ if len(args) < 4: return "Invalid input.", None student_name = args[0] father_name = args[1] roll_no = args[2] # answers are args[3] .. args[-2], last arg is questions if len(args) < 5: # no answers provided answers = [] questions = [] else: questions = args[-1] answers = list(args[3:-1]) # Safety: if questions is a single dict (shouldn't be) or None, handle gracefully if not isinstance(questions, list): questions = list(questions) if questions else [] return evaluate_test(student_name, father_name, roll_no, answers, questions) def update_questions_ui(questions): """Return 50 gr.update objects to populate radio labels and choices.""" updates = [] for i, q in enumerate(questions): updates.append( gr.update(label=f"Q{i+1}: {q.get('question','')}", choices=q.get("options", []), value=None) ) return updates with gr.Blocks() as demo: gr.Markdown("## Class 6 General Knowledge Test (GFT/O Exam)") gr.Markdown("Answer 50 random questions and get your result in PDF.") student_name = gr.Textbox(label="Student Name") father_name = gr.Textbox(label="Father's Name") roll_no = gr.Textbox(label="Roll No") # State to store current random questions (list of 50 question dicts) question_state = gr.State(get_random_questions()) # Create 50 radio inputs answer_inputs = [] with gr.Accordion("Questions", open=True): for i in range(50): r = gr.Radio(label=f"Q{i+1}", choices=[]) answer_inputs.append(r) output_text = gr.Textbox(label="Result Summary") output_file = gr.File(label="Download Result PDF") submit_btn = gr.Button("Submit Test", variant="primary") new_test_btn = gr.Button("New Test", variant="secondary") # On load, populate the 50 radios using the question_state demo.load(update_questions_ui, inputs=question_state, outputs=answer_inputs) # Submit test -> run_test which parses args and calls evaluate_test submit_btn.click( run_test, inputs=[student_name, father_name, roll_no] + answer_inputs + [question_state], outputs=[output_text, output_file], ) # New Test -> generate fresh questions and update state + radios def refresh(): qs = get_random_questions() # return list whose first element will set question_state, followed by 50 radio updates return [qs] + update_questions_ui(qs) new_test_btn.click( refresh, inputs=None, outputs=[question_state] + answer_inputs, ) return demo demo = build_interface() if __name__ == "__main__": demo.launch()