GKTEST6TH / app.py
Hidayatmahar's picture
Update app.py
b592dbc verified
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()