Benny-Tang commited on
Commit
665dfc5
·
verified ·
1 Parent(s): 8763b81

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -70
app.py CHANGED
@@ -1,98 +1,165 @@
1
  import os
2
  import json
3
  import random
4
- import subprocess
5
  import gradio as gr
6
 
7
  from agents import AnalyzerAgent, CoachAgent, PredictiveAgent
8
- from ocr_agent import OcrAgent
9
 
10
  # Paths
11
- DATA_DIR = "data"
12
  QUESTIONS_FILE = "questions.json"
13
- os.makedirs(DATA_DIR, exist_ok=True)
14
 
15
- # Load merged question bank
 
 
 
 
 
 
16
  def load_question_bank():
 
17
  if not os.path.exists(QUESTIONS_FILE) or os.path.getsize(QUESTIONS_FILE) == 0:
18
  return []
19
  try:
20
  with open(QUESTIONS_FILE, "r", encoding="utf-8") as f:
21
  return json.load(f)
22
- except:
23
  return []
24
 
25
- QUESTION_BANK = load_question_bank()
26
 
27
- # Initialize agents
28
- analyzer = AnalyzerAgent()
29
- coach = CoachAgent()
30
- predictor = PredictiveAgent()
31
- ocr_agent = OcrAgent()
32
-
33
- # Functions
34
- def upload_pdf(pdf_file, subject, year, paper):
35
- if pdf_file is None:
36
- return "⚠️ No file uploaded."
37
- questions = ocr_agent.extract_questions(pdf_file, subject, year, paper)
38
- subprocess.run(["python", "merge_questions.py"])
39
- return f"✅ Extracted {len(questions)} questions from {subject.upper()} Paper {paper} ({year})"
40
-
41
- def run_exam(subject, num_questions, include_predicted):
42
- pool = [q for q in QUESTION_BANK if subject.lower() in q["source"]]
43
- if include_predicted:
44
- predicted = predictor.predict(subject)
45
- pool.append({"id": 9999, "text": predicted, "choices": [], "topics": [], "source": "predicted"})
46
- if not pool:
47
- return "⚠️ No questions available.", [], []
48
- selected = random.sample(pool, min(num_questions, len(pool)))
49
- q_texts = [q["text"] for q in selected]
50
- q_ids = [q["id"] for q in selected]
51
- return "\n\n".join(q_texts), q_ids, [q.get("choices", []) for q in selected]
52
-
53
- def submit_answers(q_ids, answers):
54
- correct_answers = ["A"] * len(q_ids) # Placeholder until scheme data ready
55
- score, analysis = analyzer.analyze(answers, correct_answers)
56
- return f"Score: {score}/{len(q_ids)}", "\n".join(analysis)
57
-
58
- def essay_feedback(essay_text):
59
- return coach.coach(essay_text)
60
-
61
- # Gradio UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  with gr.Blocks() as demo:
63
  gr.Markdown("## 📝 SPM Exam Simulator (Form 5)")
64
 
65
- with gr.Tab("Upload Past Papers"):
66
- pdf_file = gr.File(label="Upload PDF", type="filepath")
67
- subject = gr.Dropdown(["BM", "English", "Math", "History", "Science", "MoralStudies"], label="Subject")
68
- year = gr.Number(label="Year", value=2018, precision=0)
69
- paper = gr.Dropdown([1, 2], label="Paper")
70
- upload_btn = gr.Button("Process PDF")
71
- upload_out = gr.Textbox(label="Upload Status")
72
- upload_btn.click(fn=upload_pdf, inputs=[pdf_file, subject, year, paper], outputs=upload_out)
73
-
74
  with gr.Tab("Paper 2 Simulator"):
75
  subject_sim = gr.Dropdown(["BM", "English", "Math", "History", "Science", "MoralStudies"], label="Subject")
76
  num_q = gr.Slider(1, 50, step=1, value=10, label="Number of Questions")
77
- include_pred = gr.Checkbox(label="Include AI-Predicted")
78
- run_btn = gr.Button("Start Simulation")
79
- q_out = gr.Textbox(label="Questions")
80
- ids_out = gr.State()
81
- choices_out = gr.State()
82
- run_btn.click(fn=run_exam, inputs=[subject_sim, num_q, include_pred], outputs=[q_out, ids_out, choices_out])
83
-
84
- ans_in = gr.Textbox(label="Your Answers (comma-separated, e.g. A,B,C)")
85
- submit_btn = gr.Button("Submit")
86
- score_out = gr.Textbox(label="Score")
87
- analysis_out = gr.Textbox(label="Analysis")
88
- submit_btn.click(fn=lambda ids, ans: submit_answers(ids, ans.split(",")),
89
- inputs=[ids_out, ans_in], outputs=[score_out, analysis_out])
90
-
91
- with gr.Tab("Paper 1 (Essay Practice)"):
92
- essay_in = gr.Textbox(label="Write your essay here", lines=10)
93
- essay_btn = gr.Button("Get Feedback")
94
- essay_out = gr.Textbox(label="Feedback")
95
- essay_btn.click(fn=essay_feedback, inputs=essay_in, outputs=essay_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
  demo.launch(server_name="0.0.0.0", server_port=7860)
98
 
@@ -117,5 +184,6 @@ demo.launch(server_name="0.0.0.0", server_port=7860)
117
 
118
 
119
 
 
120
 
121
 
 
1
  import os
2
  import json
3
  import random
4
+ import sqlite3
5
  import gradio as gr
6
 
7
  from agents import AnalyzerAgent, CoachAgent, PredictiveAgent
 
8
 
9
  # Paths
 
10
  QUESTIONS_FILE = "questions.json"
11
+ DB_FILE = "exam.db"
12
 
13
+ # Initialize agents
14
+ analyzer = AnalyzerAgent()
15
+ coach = CoachAgent()
16
+ predictor = PredictiveAgent()
17
+
18
+
19
+ # ---------- Helpers ----------
20
  def load_question_bank():
21
+ """Load questions from JSON file."""
22
  if not os.path.exists(QUESTIONS_FILE) or os.path.getsize(QUESTIONS_FILE) == 0:
23
  return []
24
  try:
25
  with open(QUESTIONS_FILE, "r", encoding="utf-8") as f:
26
  return json.load(f)
27
+ except Exception:
28
  return []
29
 
 
30
 
31
+ def init_db():
32
+ """Setup SQLite if not exists and sync with JSON."""
33
+ conn = sqlite3.connect(DB_FILE)
34
+ cur = conn.cursor()
35
+ cur.execute("""
36
+ CREATE TABLE IF NOT EXISTS questions (
37
+ id INTEGER PRIMARY KEY,
38
+ text TEXT,
39
+ choices TEXT,
40
+ answer TEXT,
41
+ subject TEXT,
42
+ paper INTEGER,
43
+ year INTEGER,
44
+ image TEXT
45
+ )""")
46
+ conn.commit()
47
+
48
+ # Sync JSON → DB
49
+ questions = load_question_bank()
50
+ for q in questions:
51
+ cur.execute("INSERT OR IGNORE INTO questions VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
52
+ (q["id"], q["text"], json.dumps(q["choices"]),
53
+ q["answer"], q["subject"], q["paper"], q["year"], q.get("image")))
54
+ conn.commit()
55
+ conn.close()
56
+
57
+
58
+ def get_questions(subject, paper=2, num_questions=10):
59
+ """Fetch random questions from DB."""
60
+ conn = sqlite3.connect(DB_FILE)
61
+ cur = conn.cursor()
62
+ cur.execute("SELECT * FROM questions WHERE subject=? AND paper=?", (subject, paper))
63
+ rows = cur.fetchall()
64
+ conn.close()
65
+
66
+ if not rows:
67
+ return []
68
+
69
+ selected = random.sample(rows, min(num_questions, len(rows)))
70
+ questions = []
71
+ for row in selected:
72
+ questions.append({
73
+ "id": row[0],
74
+ "text": row[1],
75
+ "choices": json.loads(row[2]),
76
+ "answer": row[3],
77
+ "subject": row[4],
78
+ "paper": row[5],
79
+ "year": row[6],
80
+ "image": row[7]
81
+ })
82
+ return questions
83
+
84
+
85
+ # ---------- Exam Logic ----------
86
+ def start_exam(subject, num_questions):
87
+ """Prepare questions for UI rendering."""
88
+ questions = get_questions(subject, paper=2, num_questions=num_questions)
89
+ return questions
90
+
91
+
92
+ def submit_exam(answers, questions):
93
+ """Evaluate submitted answers."""
94
+ correct = 0
95
+ wrong_details = []
96
+
97
+ for q, ans in zip(questions, answers):
98
+ if ans == q["answer"]:
99
+ correct += 1
100
+ else:
101
+ wrong_details.append(f"Q{q['id']}: Correct answer was {q['answer']}")
102
+
103
+ score_text = f"Score: {correct}/{len(questions)}\nCorrect: {correct}, Wrong: {len(questions) - correct}"
104
+ analysis = coach.coach("Student got result: " + score_text)
105
+ return score_text, "\n".join(wrong_details), analysis
106
+
107
+
108
+ # ---------- Gradio UI ----------
109
  with gr.Blocks() as demo:
110
  gr.Markdown("## 📝 SPM Exam Simulator (Form 5)")
111
 
 
 
 
 
 
 
 
 
 
112
  with gr.Tab("Paper 2 Simulator"):
113
  subject_sim = gr.Dropdown(["BM", "English", "Math", "History", "Science", "MoralStudies"], label="Subject")
114
  num_q = gr.Slider(1, 50, step=1, value=10, label="Number of Questions")
115
+ run_btn = gr.Button("Start Exam")
116
+
117
+ question_state = gr.State()
118
+ answer_inputs = []
119
+
120
+ exam_area = gr.Column(visible=False)
121
+ results_area = gr.Column(visible=False)
122
+
123
+ with exam_area:
124
+ question_boxes = []
125
+ for i in range(50): # max 50
126
+ q_text = gr.Markdown(visible=False)
127
+ q_img = gr.Image(type="filepath", visible=False)
128
+ q_choice = gr.Radio(choices=[], label=f"Answer Q{i+1}", visible=False)
129
+ question_boxes.append((q_text, q_img, q_choice))
130
+ answer_inputs.append(q_choice)
131
+ submit_btn = gr.Button("Submit")
132
+
133
+ with results_area:
134
+ score_out = gr.Textbox(label="Score")
135
+ wrong_out = gr.Textbox(label="Wrong Answers")
136
+ advice_out = gr.Textbox(label="AI Advice")
137
+
138
+ # Start exam
139
+ def render_exam(subject, num):
140
+ qs = start_exam(subject, num)
141
+ updates = []
142
+ for i, q in enumerate(qs):
143
+ updates.append(gr.update(value=f"**Q{i+1}. {q['text']}**", visible=True)) # text
144
+ updates.append(gr.update(value=q["image"], visible=bool(q["image"]))) # image
145
+ updates.append(gr.update(choices=q["choices"], visible=True)) # choices
146
+ # Hide unused slots
147
+ for j in range(len(qs), 50):
148
+ updates.extend([gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)])
149
+ return [qs, gr.update(visible=True), *updates]
150
+
151
+ run_btn.click(render_exam, [subject_sim, num_q], [question_state, exam_area] + sum(question_boxes, ()))
152
+
153
+ # Submit exam
154
+ submit_btn.click(
155
+ fn=submit_exam,
156
+ inputs=[answer_inputs, question_state],
157
+ outputs=[score_out, wrong_out, advice_out]
158
+ ).then(lambda: gr.update(visible=True), None, results_area)
159
+
160
+
161
+ # Init DB before launch
162
+ init_db()
163
 
164
  demo.launch(server_name="0.0.0.0", server_port=7860)
165
 
 
184
 
185
 
186
 
187
+
188
 
189