File size: 5,860 Bytes
db2edfa
 
 
 
b592dbc
db2edfa
 
 
 
 
2fbf1a0
db2edfa
 
 
b592dbc
2fbf1a0
b592dbc
 
2fbf1a0
 
b592dbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db2edfa
 
 
 
 
 
b592dbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2fbf1a0
db2edfa
b592dbc
 
 
 
 
 
 
 
db2edfa
 
 
b592dbc
db2edfa
b592dbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2fbf1a0
 
b592dbc
 
2fbf1a0
 
b592dbc
 
 
2fbf1a0
 
 
 
 
 
 
 
 
 
b592dbc
2fbf1a0
 
b592dbc
2fbf1a0
 
 
b592dbc
 
2fbf1a0
 
 
 
 
 
 
b592dbc
 
2fbf1a0
b592dbc
2fbf1a0
 
 
b592dbc
db2edfa
 
b592dbc
2fbf1a0
 
b592dbc
 
db2edfa
2fbf1a0
 
 
b592dbc
2fbf1a0
db2edfa
2fbf1a0
e4f35d3
db2edfa
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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()