mikaelJ46 commited on
Commit
f322161
·
verified ·
1 Parent(s): a00ba5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +230 -128
app.py CHANGED
@@ -105,130 +105,213 @@ def ask_ai(prompt, temperature=0.7, max_retries=2, image=None):
105
 
106
  return " All AI services failed. Please try again later.", "error"
107
 
108
- # ---------- 3. Uganda Primary Curriculum Syllabus (P6 & P7 Only) ----------
 
 
109
  syllabus_topics = {
110
- "Primary 6": [
111
- "Whole Numbers - Addition & Subtraction",
112
- "Whole Numbers - Multiplication & Division",
113
- "Fractions & Decimals",
114
- "Money & Making Change",
115
- "Measurement - Length, Mass & Capacity",
116
- "Time & Telling Time",
117
- "Geometry & Shapes",
118
- "Data Handling - Graphs & Tables",
119
- "Ratio & Proportion",
120
- "Basic Algebra & Equations"
121
- ],
122
- "Primary 7": [
123
- "Integers & Operations",
124
- "Fractions - Addition & Subtraction",
125
- "Fractions - Multiplication & Division",
126
- "Decimals & Percentages",
127
- "Ratio, Rate & Proportion",
128
- "Geometry - Angles & Properties",
129
- "Mensuration - Area & Perimeter",
130
- "Volumes & Surface Area",
131
- "Statistics & Probability",
132
- "Algebraic Expressions & Equations"
133
- ]
134
- }
135
-
136
- # ---------- 4. Generate Practice Questions (Customizable) ----------
137
- def generate_sample_questions(grade_level, topic, num_questions=10):
138
- """Fallback: Generate sample questions locally when AI is unavailable"""
139
- samples = {
140
- "Integers & Operations": [
141
- "Q1. Calculate the sum of -20 and -15.",
142
- "Q2. A farmer bought 40 oranges and then sold 25 of them. What is the difference between the number of oranges bought and sold?",
143
- "Q3. Simplify: 36 - (-10) + 5. Q10. Find the value of -2(8) + 15.",
144
- "Q11. A car is parked at -10 meters. If it moves up a slope of 15 meters, what is its final position?",
145
- "Q12. Calculate the product of -4 and 5.",
146
- "Q13. A boat descends 12 meters below sea level, then rises 8 meters. What is its final position relative to sea level?",
147
- "Q16. Simplify: 2(-3) + 5(-2).",
148
- "Q17. A rabbit hops 7 meters forward and then 4 meters backward. What is the net distance covered by the rabbit?",
149
- "Q18. Calculate the value of 2/3(-18) + 5.",
150
- "Q19. A plane descends 300 meters and then ascends 200 meters. What is the plane's final position relative to the initial descent point?",
151
  ],
152
- "Fractions - Addition & Subtraction": [
153
- "Q1. Add 1/4 and 1/3.",
154
- "Q2. Subtract 2/5 from 3/5.",
155
- "Q3. What is 1/2 + 1/4 + 1/8?",
156
- "Q4. Calculate 7/8 - 1/4.",
157
- "Q5. Find the sum of 2/3 and 1/6.",
158
- "Q6. Subtract 3/10 from 9/10.",
159
- "Q7. Add 1/5, 2/5, and 1/5.",
160
- "Q8. What is 5/6 - 1/3?",
161
- "Q9. Calculate 3/4 + 2/8.",
162
- "Q10. Find 11/12 - 1/4.",
163
  ],
164
- "Decimals & Percentages": [
165
- "Q1. Convert 0.25 to a percentage.",
166
- "Q2. What is 25% of 80?",
167
- "Q3. Convert 3/4 to a decimal.",
168
- "Q4. Calculate 15% of 200.",
169
- "Q5. What decimal is equivalent to 60%?",
170
- "Q6. Find 10% of 450.",
171
- "Q7. Convert 0.875 to a percentage.",
172
- "Q8. What is 50% of 64?",
173
- "Q9. Calculate 30% of 150.",
174
- "Q10. Convert 45% to a decimal.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  ],
176
- "Geometry - Angles & Properties": [
177
- "Q1. What is the sum of angles in a triangle?",
178
- "Q2. If one angle of a triangle is 60° and another is 50°, find the third angle.",
179
- "Q3. What are the properties of a rectangle?",
180
- "Q4. Calculate the perimeter of a square with side 5 cm.",
181
- "Q5. What is the sum of angles in a quadrilateral?",
182
- "Q6. If two angles are complementary, and one is 35°, what is the other?",
183
- "Q7. Define a right angle.",
184
- "Q8. What is the area of a triangle with base 8 cm and height 6 cm?",
185
- "Q9. Calculate the perimeter of a rectangle with length 10 cm and width 6 cm.",
186
- "Q10. What are supplementary angles?",
187
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  }
189
 
190
- # Use sample questions for the given topic, or create generic ones
 
191
  topic_key = None
192
- for key in samples:
193
  if key.lower() in topic.lower() or topic.lower() in key.lower():
194
  topic_key = key
195
  break
196
 
197
  if topic_key:
198
- qs = samples[topic_key][:num_questions]
199
  else:
200
- qs = [f"Q{i+1}. Sample question {i+1} on {topic}." for i in range(num_questions)]
 
201
 
202
  return "\n\n".join(qs)
203
 
204
-
205
- def generate_practice_questions(grade_level, topic, num_questions=10):
206
- """Generate multiple questions with robust text parsing (not JSON)"""
207
 
208
  if not topic:
209
  return None, "⚠️ Please select a topic first!"
210
 
211
- prompt = f"""Generate exactly {num_questions} UNEB-style mathematics questions for {grade_level} students on: "{topic}"
212
-
213
- Format your response EXACTLY like this:
214
- Q1. [Question text here]
215
- Q2. [Question text here]
216
- Q3. [Question text here]
217
- ... and so on up to Q{num_questions}
218
-
219
- Each question should be:
220
- - Clear and exam-like
221
- - Appropriate difficulty for {grade_level}
222
- - Self-contained (includes all necessary information)
223
- - Solvable in 2-5 minutes
224
-
225
- Start immediately with Q1. Do not include any introduction or explanation."""
226
 
227
  response, source = ask_ai(prompt, temperature=0.6)
228
 
229
- # Fallback: if AI fails, generate sample questions locally
230
- if "All AI services failed" in response or response.startswith(""):
231
- response = generate_sample_questions(grade_level, topic, num_questions)
232
 
233
  # Parse questions robustly using regex to support multi-digit numbers (Q1..Q10..)
234
  questions = []
@@ -267,7 +350,7 @@ Start immediately with Q1. Do not include any introduction or explanation."""
267
  return questions, formatted
268
 
269
  # ---------- 5. Grade/Mark Student Answers ----------
270
- def grade_student_answers(questions, student_answers, grade_level, topic):
271
  """Grade all student answers and provide feedback"""
272
 
273
  if not questions or not student_answers:
@@ -283,7 +366,7 @@ def grade_student_answers(questions, student_answers, grade_level, topic):
283
  continue
284
 
285
  # Create grading prompt
286
- grading_prompt = f"""You are an experienced UNEB examiner for {grade_level} students.
287
 
288
  Question: {question_text}
289
 
@@ -323,17 +406,19 @@ class StudentSession:
323
  session = StudentSession()
324
 
325
  # ---------- 7. Download Functions ----------
326
- def download_questions_file(questions_list, topic, grade_level):
327
  """Download questions as text file"""
328
  if not questions_list:
329
  return None
330
 
331
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
332
- filename = f"questions_{grade_level}_{topic}_{timestamp}.txt"
 
333
 
334
  content = f"""UNEB EXAM PRACTICE QUESTIONS
335
  {'='*50}
336
  Grade Level: {grade_level}
 
337
  Topic: {topic}
338
  Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
339
  Total Questions: {len(questions_list)}
@@ -363,7 +448,7 @@ Good luck!
363
  return filepath
364
 
365
 
366
- def download_questions_pdf(questions_list, topic, grade_level):
367
  """Generate a simple PDF with the questions, return filepath."""
368
  try:
369
  from reportlab.lib.pagesizes import A4
@@ -374,7 +459,8 @@ def download_questions_pdf(questions_list, topic, grade_level):
374
  return None
375
 
376
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
377
- filename = f"questions_{grade_level}_{topic}_{timestamp}.pdf"
 
378
  filepath = f"downloads/{filename}"
379
  os.makedirs("downloads", exist_ok=True)
380
 
@@ -386,7 +472,7 @@ def download_questions_pdf(questions_list, topic, grade_level):
386
 
387
  elems.append(Paragraph("UNEB EXAM PRACTICE QUESTIONS", styles['Title']))
388
  elems.append(Spacer(1, 4*mm))
389
- meta = f"Grade Level: {grade_level}    Topic: {topic}    Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
390
  elems.append(Paragraph(meta, normal))
391
  elems.append(Spacer(1, 6*mm))
392
 
@@ -402,18 +488,20 @@ def download_questions_pdf(questions_list, topic, grade_level):
402
  except Exception:
403
  return None
404
 
405
- def download_feedback_file(feedback_text, topic, grade_level):
406
  """Download AI feedback/corrections"""
407
  if not feedback_text:
408
  return None
409
 
410
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
411
- filename = f"feedback_{grade_level}_{topic}_{timestamp}.txt"
 
412
 
413
  content = f"""AI CORRECTION & FEEDBACK
414
  {'='*50}
415
  Student: {session.student_name}
416
  Grade Level: {grade_level}
 
417
  Topic: {topic}
418
  Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
419
  {'='*50}
@@ -473,7 +561,7 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
473
 
474
  gr.Markdown("""
475
  # UNEB Exam Practice
476
- ## Primary 6 & 7 Mathematics
477
  """)
478
 
479
  # Global state for questions
@@ -496,7 +584,7 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
496
  with gr.Tabs():
497
  # ===== TAB 1: GENERATE QUESTIONS =====
498
  with gr.Tab("1️⃣ Generate Questions"):
499
- gr.Markdown("### Step 1: Generate Practice Questions")
500
 
501
  with gr.Row():
502
  grade_input = gr.Dropdown(
@@ -504,20 +592,30 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
504
  label="Grade Level",
505
  value="Primary 7"
506
  )
 
 
 
 
 
507
  topic_input = gr.Dropdown(
508
  label="Topic",
509
- choices=syllabus_topics["Primary 7"],
510
- value=syllabus_topics["Primary 7"][0]
511
  )
512
 
513
- # Update topics when grade changes
514
- def update_topics(grade):
515
- return gr.Dropdown(choices=syllabus_topics[grade], value=syllabus_topics[grade][0])
 
 
 
516
 
517
- grade_input.change(update_topics, grade_input, topic_input)
 
518
 
519
  with gr.Row():
520
- generate_btn = gr.Button(" Generate 20 Questions", variant="primary", size="lg")
 
521
 
522
  questions_output = gr.Markdown(
523
  value="",
@@ -535,8 +633,8 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
535
  copy_btn = gr.Button(" Copy Questions")
536
 
537
  # Generate questions handler
538
- def generate_and_display(grade, topic):
539
- questions_list, formatted_text = generate_practice_questions(grade, topic, num_questions=20)
540
 
541
  if questions_list is None:
542
  return "", formatted_text, questions_state.value, " Generation failed"
@@ -544,28 +642,30 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
544
  session.current_questions = questions_list
545
  session.current_grade = grade
546
  session.current_topic = topic
 
 
547
 
548
  # For Markdown display, keep spacing and simple formatting
549
  md_text = "\n\n".join([f"**{i+1}.** {q.split('.',1)[1].strip() if '.' in q else q}" for i, q in enumerate(questions_list)])
550
- return md_text, md_text, questions_list, f" Generated {len(questions_list)} questions on {topic}"
551
 
552
  generate_btn.click(
553
  fn=generate_and_display,
554
- inputs=[grade_input, topic_input],
555
  outputs=[questions_output, questions_output, questions_state, status_output]
556
- )
557
-
558
  # Download handler - returns file path for gr.DownloadButton
559
  def download_qns():
560
  if not session.current_questions:
561
  return None
562
  try:
563
  # Prefer PDF export; fall back to plain text if PDF library missing
564
- pdf_path = download_questions_pdf(session.current_questions, session.current_topic, session.current_grade)
 
565
  if pdf_path:
566
  return pdf_path
567
  # Fallback to text
568
- filepath = download_questions_file(session.current_questions, session.current_topic, session.current_grade)
569
  return filepath
570
  except Exception as e:
571
  return None
@@ -710,7 +810,8 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
710
  session.current_questions,
711
  session.current_answers,
712
  session.current_grade,
713
- session.current_topic
 
714
  )
715
 
716
  session.last_feedback = feedback
@@ -727,7 +828,8 @@ with gr.Blocks(title="UNEB Exam Prep - Primary 6 & 7", theme=gr.themes.Soft(), c
727
  if not session.last_feedback:
728
  return None
729
  try:
730
- filepath = download_feedback_file(session.last_feedback, session.current_topic, session.current_grade)
 
731
  return filepath
732
  except Exception as e:
733
  return None
 
105
 
106
  return " All AI services failed. Please try again later.", "error"
107
 
108
+ # ---------- 3. Uganda Primary Curriculum Syllabus (P6 & P7 Only, by Subject) ----------
109
+ # Topics below are expanded and aligned to typical NCDC/UNEB primary curriculum themes
110
+ # (see https://ncdc.go.ug/ and Ministry of Education resources for official documents).
111
  syllabus_topics = {
112
+ "Primary 6": {
113
+ "Mathematics": [
114
+ "Whole Numbers - Addition & Subtraction",
115
+ "Whole Numbers - Multiplication & Division",
116
+ "Factors, Multiples and Prime Numbers",
117
+ "Fractions & Decimals",
118
+ "Money & Making Change",
119
+ "Measurement - Length, Mass & Capacity",
120
+ "Time - Hours, Minutes & Conversion",
121
+ "Geometry - Shapes, Symmetry & Angles (basic)",
122
+ "Data Handling - Tables, Bar Graphs & Pictograms",
123
+ "Ratio & Proportion",
124
+ "Introduction to Algebra - Simple Equations",
125
+ "Basic Percentages and Problem Solving"
126
+ ],
127
+ "English": [
128
+ "Reading Comprehension - Short Passages",
129
+ "Grammar - Tenses, Parts of Speech, Sentence Structure",
130
+ "Vocabulary Building & Spelling",
131
+ "Composition - Story and Letter Writing",
132
+ "Punctuation & Capitalization",
133
+ "Clarity in Expression - Cohesion and Coherence",
134
+ "Cloze Tests & Short Answer Questions",
135
+ "Listening and Speaking Basics"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  ],
137
+ "Social Studies": [
138
+ "Local Community - Roles, Services & Leaders",
139
+ "Local History & Traditions",
140
+ "Civics - Rights, Responsibilities & Good Citizenship",
141
+ "Map Skills - Directions, Symbols, Scale (basic)",
142
+ "Resources and Local Economy - Farming, Trade, Markets",
143
+ "Culture, Customs and Heritage",
144
+ "Environment & Conservation - Local Examples",
145
+ "Health, Sanitation and Community Wellbeing",
146
+ "Basic Local Government Structures and Participation",
147
+ "Road Safety and Community Rules"
148
  ],
149
+ "Science": [
150
+ "Living & Non-Living Things - Characteristics",
151
+ "Plants - Parts and Functions",
152
+ "Animals - Habitats and Adaptations",
153
+ "Human Body - Health, Nutrition & Hygiene",
154
+ "Materials & Their Properties",
155
+ "Forces and Motion - Simple Examples",
156
+ "Light, Heat and Sound (basic concepts)",
157
+ "Environment and Natural Resources",
158
+ "Simple Experiments and Observations"
159
+ ]
160
+ },
161
+ "Primary 7": {
162
+ "Mathematics": [
163
+ "Integers & Operations",
164
+ "Fractions - Addition, Subtraction, Multiplication & Division",
165
+ "Decimals & Percentages",
166
+ "Ratio, Rate & Proportion",
167
+ "Algebraic Expressions & Simple Equations",
168
+ "Geometry - Angles, Triangles and Quadrilaterals",
169
+ "Mensuration - Area, Perimeter and Volume (basic)",
170
+ "Statistics & Probability - Averages and Data Interpretation",
171
+ "Coordinate Geometry - Introduction",
172
+ "Number Theory - Factors, HCF & LCM",
173
+ "Problem Solving Strategies"
174
  ],
175
+ "English": [
176
+ "Comprehension - Longer Passages & Questioning",
177
+ "Grammar - Sentence Transformation, Tenses, Agreement",
178
+ "Composition - Stories, Letters, Reports and Dialogues",
179
+ "Cloze & Summary Writing",
180
+ "Vocabulary - Synonyms, Antonyms & Contextual Use",
181
+ "Listening Skills and Oral Expression",
182
+ "Directed Writing and Examination Techniques"
 
 
 
183
  ],
184
+ "Social Studies": [
185
+ "History - Key Events in Uganda and East Africa (pre-colonial, colonial, independence)",
186
+ "Civics and Governance - Structure of Government, Roles and Rights",
187
+ "Geography - Maps, Physical Features, Weather, Climate and Resources",
188
+ "Economy - Agriculture, Trade, Markets, Production and Consumption",
189
+ "Community Development - Projects, Participation and Leadership",
190
+ "Citizenship Education - Rights, Responsibilities and Human Rights",
191
+ "Culture, National Symbols and Heritage",
192
+ "Local and National Government - Functions and Services",
193
+ "Environmental Issues - Conservation, Deforestation, Pollution",
194
+ "Global Connections - Trade, Aid and Regional Cooperation"
195
+ ],
196
+ "Science": [
197
+ "Living Things - Classification, Life Cycles and Ecosystems",
198
+ "Plants and Animals - Structure and Function",
199
+ "Human Body Systems - Digestive, Respiratory, Circulatory (basic)",
200
+ "Health and Disease Prevention",
201
+ "Forces, Magnets and Motion",
202
+ "Energy - Sources and Uses",
203
+ "Materials and Their Uses (including mixtures and separation)",
204
+ "Environment - Habitats, Conservation and Sustainable Use",
205
+ "Simple Scientific Investigation and Reporting"
206
+ ]
207
+ }
208
+ }
209
+
210
+ # ---------- 4. Generate Practice Questions (Customizable) ----------
211
+ def generate_sample_questions(grade_level, subject, topic, num_questions=10):
212
+ """Fallback: Generate sample questions locally when AI is unavailable.
213
+ This function is subject-aware and supplies simple sample questions for common topics."""
214
+ # Basic sample banks keyed by subject/topic
215
+ samples = {
216
+ "Mathematics": {
217
+ "Integers & Operations": [
218
+ "Q1. Calculate the sum of -20 and -15.",
219
+ "Q2. A farmer bought 40 oranges and then sold 25 of them. What is the difference between the number of oranges bought and sold?",
220
+ "Q3. Simplify: 36 - (-10) + 5.",
221
+ "Q4. Find the value of -2(8) + 15.",
222
+ "Q5. A car is parked at -10 meters. If it moves up 15 meters, what is its final position?",
223
+ "Q6. Calculate the product of -4 and 5.",
224
+ "Q7. A boat descends 12 meters, then rises 8 meters. What is its final position relative to sea level?",
225
+ "Q8. Simplify: 2(-3) + 5(-2).",
226
+ "Q9. A rabbit hops 7 meters forward and then 4 meters backward. What is the net distance covered?",
227
+ "Q10. A plane descends 300 meters and then ascends 200 meters. What is the plane's final position?",
228
+ ],
229
+ "Fractions - Addition & Subtraction": [
230
+ "Q1. Add 1/4 and 1/3.",
231
+ "Q2. Subtract 2/5 from 3/5.",
232
+ "Q3. What is 1/2 + 1/4 + 1/8?",
233
+ "Q4. Calculate 7/8 - 1/4.",
234
+ "Q5. Find the sum of 2/3 and 1/6.",
235
+ "Q6. Subtract 3/10 from 9/10.",
236
+ "Q7. Add 1/5, 2/5, and 1/5.",
237
+ "Q8. What is 5/6 - 1/3?",
238
+ "Q9. Calculate 3/4 + 2/8.",
239
+ "Q10. Find 11/12 - 1/4.",
240
+ ],
241
+ },
242
+ "English": {
243
+ "Comprehension - Passages & Questions": [
244
+ "Q1. Read the passage and answer: What is the main idea of paragraph 2?",
245
+ "Q2. From the passage, extract two reasons the author gives for saving water.",
246
+ "Q3. What does the word 'frugal' mean in the passage?",
247
+ "Q4. Give a title for the passage in not more than five words.",
248
+ "Q5. Why did the character decide to leave home?",
249
+ ],
250
+ "Grammar - Sentence Transformation & Tenses": [
251
+ "Q1. Change to passive voice: 'The teacher marked the tests.'",
252
+ "Q2. Fill in the blank with the correct tense: 'She ___ (go) to school yesterday.'",
253
+ "Q3. Correct the sentence: 'He don't like vegetables.'",
254
+ "Q4. Combine the sentences: 'He ran fast. He missed the bus.'",
255
+ "Q5. Rewrite in reported speech: 'She said, "I will come."'",
256
+ ]
257
+ },
258
+ "Science": {
259
+ "Living Things - Classification": [
260
+ "Q1. State two differences between plants and animals.",
261
+ "Q2. Name three groups of living organisms.",
262
+ "Q3. How do leaves help plants to survive?",
263
+ "Q4. What is photosynthesis? Give a simple definition.",
264
+ "Q5. Explain why animals need oxygen.",
265
+ ],
266
+ "Forces, Magnets and Motion": [
267
+ "Q1. Define force with an example.",
268
+ "Q2. What does a magnet attract?",
269
+ "Q3. Give one example of a push and one example of a pull.",
270
+ ]
271
+ },
272
+ "Social Studies": {
273
+ "History - Uganda & East Africa": [
274
+ "Q1. Name one important event in Uganda's history and explain why it is important.",
275
+ "Q2. Who was the first person to unite (example) ...?",
276
+ ],
277
+ "Geography - Maps, Weather & Resources": [
278
+ "Q1. Give two uses of a map.",
279
+ "Q2. What are the main types of weather in Uganda?",
280
+ ]
281
+ }
282
  }
283
 
284
+ subject_bank = samples.get(subject, {})
285
+ # Try to match topic within subject bank
286
  topic_key = None
287
+ for key in subject_bank:
288
  if key.lower() in topic.lower() or topic.lower() in key.lower():
289
  topic_key = key
290
  break
291
 
292
  if topic_key:
293
+ qs = subject_bank[topic_key][:num_questions]
294
  else:
295
+ # Generic fallback per subject
296
+ qs = [f"Q{i+1}. Sample {subject} question {i+1} on {topic}." for i in range(num_questions)]
297
 
298
  return "\n\n".join(qs)
299
 
300
+ def generate_practice_questions(grade_level, subject, topic, num_questions=10):
301
+ """Generate multiple questions with robust text parsing (not JSON). Subject-aware prompt."""
 
302
 
303
  if not topic:
304
  return None, "⚠️ Please select a topic first!"
305
 
306
+ prompt = f"""Generate exactly {num_questions} UNEB-style {subject} questions for {grade_level} students on: \"{topic}\"\n\n"
307
+ prompt += "Format your response EXACTLY like this:\nQ1. [Question text here]\nQ2. [Question text here]\nQ3. [Question text here]\n... and so on up to Q{num_questions}\n\n"
308
+ prompt += f"Each question should be:\n- Clear and exam-like\n- Appropriate difficulty for {grade_level}\n- Self-contained (includes all necessary information)\n- Solvable in 2-5 minutes for short-answer questions\n\nStart immediately with Q1. Do not include any introduction or explanation."""
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  response, source = ask_ai(prompt, temperature=0.6)
311
 
312
+ # Fallback: if AI fails or returns empty, use local generator (now subject-aware)
313
+ if "All AI services failed" in response or not response or response.strip() == "":
314
+ response = generate_sample_questions(grade_level, subject, topic, num_questions)
315
 
316
  # Parse questions robustly using regex to support multi-digit numbers (Q1..Q10..)
317
  questions = []
 
350
  return questions, formatted
351
 
352
  # ---------- 5. Grade/Mark Student Answers ----------
353
+ def grade_student_answers(questions, student_answers, grade_level, topic, subject=None):
354
  """Grade all student answers and provide feedback"""
355
 
356
  if not questions or not student_answers:
 
366
  continue
367
 
368
  # Create grading prompt
369
+ grading_prompt = f"""You are an experienced UNEB examiner for {grade_level} students. Subject: {subject or 'General'}
370
 
371
  Question: {question_text}
372
 
 
406
  session = StudentSession()
407
 
408
  # ---------- 7. Download Functions ----------
409
+ def download_questions_file(questions_list, topic, grade_level, subject=None):
410
  """Download questions as text file"""
411
  if not questions_list:
412
  return None
413
 
414
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
415
+ subject_tag = f"_{subject}" if subject else ""
416
+ filename = f"questions_{grade_level}{subject_tag}_{topic}_{timestamp}.txt"
417
 
418
  content = f"""UNEB EXAM PRACTICE QUESTIONS
419
  {'='*50}
420
  Grade Level: {grade_level}
421
+ Subject: {subject or 'N/A'}
422
  Topic: {topic}
423
  Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
424
  Total Questions: {len(questions_list)}
 
448
  return filepath
449
 
450
 
451
+ def download_questions_pdf(questions_list, topic, grade_level, subject=None):
452
  """Generate a simple PDF with the questions, return filepath."""
453
  try:
454
  from reportlab.lib.pagesizes import A4
 
459
  return None
460
 
461
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
462
+ subject_tag = f"_{subject}" if subject else ""
463
+ filename = f"questions_{grade_level}{subject_tag}_{topic}_{timestamp}.pdf"
464
  filepath = f"downloads/{filename}"
465
  os.makedirs("downloads", exist_ok=True)
466
 
 
472
 
473
  elems.append(Paragraph("UNEB EXAM PRACTICE QUESTIONS", styles['Title']))
474
  elems.append(Spacer(1, 4*mm))
475
+ meta = f"Grade Level: {grade_level}    Subject: {subject or 'N/A'}    Topic: {topic}    Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
476
  elems.append(Paragraph(meta, normal))
477
  elems.append(Spacer(1, 6*mm))
478
 
 
488
  except Exception:
489
  return None
490
 
491
+ def download_feedback_file(feedback_text, topic, grade_level, subject=None):
492
  """Download AI feedback/corrections"""
493
  if not feedback_text:
494
  return None
495
 
496
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
497
+ subject_tag = f"_{subject}" if subject else ""
498
+ filename = f"feedback_{grade_level}{subject_tag}_{topic}_{timestamp}.txt"
499
 
500
  content = f"""AI CORRECTION & FEEDBACK
501
  {'='*50}
502
  Student: {session.student_name}
503
  Grade Level: {grade_level}
504
+ Subject: {subject or 'N/A'}
505
  Topic: {topic}
506
  Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
507
  {'='*50}
 
561
 
562
  gr.Markdown("""
563
  # UNEB Exam Practice
564
+ ## Primary 6 & 7 — Multiple Subjects
565
  """)
566
 
567
  # Global state for questions
 
584
  with gr.Tabs():
585
  # ===== TAB 1: GENERATE QUESTIONS =====
586
  with gr.Tab("1️⃣ Generate Questions"):
587
+ gr.Markdown("### Step 1: Generate Practice Questions\n\nChoose the Grade, Subject and Topic, then set the Number of Questions (1–100).")
588
 
589
  with gr.Row():
590
  grade_input = gr.Dropdown(
 
592
  label="Grade Level",
593
  value="Primary 7"
594
  )
595
+ subject_input = gr.Dropdown(
596
+ choices=["Mathematics", "English", "Social Studies", "Science"],
597
+ label="Subject",
598
+ value="Mathematics"
599
+ )
600
  topic_input = gr.Dropdown(
601
  label="Topic",
602
+ choices=syllabus_topics["Primary 7"]["Mathematics"],
603
+ value=syllabus_topics["Primary 7"]["Mathematics"][0]
604
  )
605
 
606
+ # Update topics when grade or subject changes
607
+ def update_topics(grade, subject):
608
+ topics = syllabus_topics.get(grade, {}).get(subject, [])
609
+ if not topics:
610
+ topics = ["General - " + subject]
611
+ return gr.Dropdown(choices=topics, value=topics[0])
612
 
613
+ grade_input.change(update_topics, [grade_input, subject_input], topic_input)
614
+ subject_input.change(update_topics, [grade_input, subject_input], topic_input)
615
 
616
  with gr.Row():
617
+ num_questions_input = gr.Slider(minimum=1, maximum=100, step=1, value=20, label="Number of Questions")
618
+ generate_btn = gr.Button(" Generate Questions", variant="primary", size="lg")
619
 
620
  questions_output = gr.Markdown(
621
  value="",
 
633
  copy_btn = gr.Button(" Copy Questions")
634
 
635
  # Generate questions handler
636
+ def generate_and_display(grade, subject, topic, num_questions):
637
+ questions_list, formatted_text = generate_practice_questions(grade, subject, topic, num_questions=num_questions)
638
 
639
  if questions_list is None:
640
  return "", formatted_text, questions_state.value, " Generation failed"
 
642
  session.current_questions = questions_list
643
  session.current_grade = grade
644
  session.current_topic = topic
645
+ # Also store subject
646
+ session.current_subject = subject
647
 
648
  # For Markdown display, keep spacing and simple formatting
649
  md_text = "\n\n".join([f"**{i+1}.** {q.split('.',1)[1].strip() if '.' in q else q}" for i, q in enumerate(questions_list)])
650
+ return md_text, md_text, questions_list, f" Generated {len(questions_list)} {subject} questions on {topic}"
651
 
652
  generate_btn.click(
653
  fn=generate_and_display,
654
+ inputs=[grade_input, subject_input, topic_input, num_questions_input],
655
  outputs=[questions_output, questions_output, questions_state, status_output]
656
+ )
 
657
  # Download handler - returns file path for gr.DownloadButton
658
  def download_qns():
659
  if not session.current_questions:
660
  return None
661
  try:
662
  # Prefer PDF export; fall back to plain text if PDF library missing
663
+ subject = getattr(session, 'current_subject', None)
664
+ pdf_path = download_questions_pdf(session.current_questions, session.current_topic, session.current_grade, subject=subject)
665
  if pdf_path:
666
  return pdf_path
667
  # Fallback to text
668
+ filepath = download_questions_file(session.current_questions, session.current_topic, session.current_grade, subject=subject)
669
  return filepath
670
  except Exception as e:
671
  return None
 
810
  session.current_questions,
811
  session.current_answers,
812
  session.current_grade,
813
+ session.current_topic,
814
+ getattr(session, 'current_subject', None)
815
  )
816
 
817
  session.last_feedback = feedback
 
828
  if not session.last_feedback:
829
  return None
830
  try:
831
+ subject = getattr(session, 'current_subject', None)
832
+ filepath = download_feedback_file(session.last_feedback, session.current_topic, session.current_grade, subject=subject)
833
  return filepath
834
  except Exception as e:
835
  return None