DataMine commited on
Commit
08df3af
ยท
verified ยท
1 Parent(s): b20575a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +437 -175
src/streamlit_app.py CHANGED
@@ -10,6 +10,7 @@ import plotly.graph_objects as go
10
  import pandas as pd
11
  from datetime import datetime, timedelta
12
  import streamlit.components.v1 as components
 
13
 
14
  # Configure page
15
  st.set_page_config(
@@ -77,6 +78,37 @@ def load_custom_css():
77
  z-index: 1;
78
  }
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  /* Card Styling */
81
  .custom-card {
82
  background: white;
@@ -222,12 +254,6 @@ def load_custom_css():
222
  animation: slideInUp 0.6s ease-out;
223
  }
224
 
225
- @keyframes pulse {
226
- 0% { transform: scale(1); }
227
- 50% { transform: scale(1.05); }
228
- 100% { transform: scale(1); }
229
- }
230
-
231
  .pulse-animation {
232
  animation: pulse 2s infinite;
233
  }
@@ -270,6 +296,11 @@ def load_custom_css():
270
  .question-card {
271
  padding: 1rem;
272
  }
 
 
 
 
 
273
  }
274
  body, .main, .block-container {
275
  background: #f7f9fb !important;
@@ -371,106 +402,271 @@ class MathOlympiadExam:
371
  return None
372
 
373
  def generate_questions(self, grade: int, num_questions: int, difficulty: str = "medium") -> List[Dict]:
374
- """Generate math olympiad questions with enhanced difficulty options"""
375
  if not self.client:
376
  return []
377
 
378
  try:
379
  difficulty_map = {
380
- 1: "very basic arithmetic, counting 1-20, simple shapes recognition, basic patterns",
381
- 2: "addition and subtraction within 100, simple word problems, basic time and money",
382
- 3: "multiplication tables up to 10, basic division, simple fractions, pattern recognition",
383
- 4: "advanced multiplication and division, fractions, basic decimals, area and perimeter",
384
- 5: "advanced arithmetic, introduction to algebra, geometry, data interpretation",
385
- 6: "pre-algebra, ratios and proportions, percentages, coordinate geometry, statistics",
386
- 7: "algebra fundamentals, advanced geometry, probability, scientific notation",
387
- 8: "linear equations, quadratic basics, trigonometry introduction, advanced geometry",
388
- 9: "advanced algebra, geometry proofs, probability and statistics, trigonometry",
389
- 10: "pre-calculus, advanced trigonometry, complex numbers, advanced problem solving"
390
  }
391
-
392
  difficulty_modifiers = {
393
- "easy": "Make questions slightly easier than typical olympiad level, focusing on fundamental concepts",
394
- "medium": "Standard olympiad difficulty with moderate complexity",
395
- "hard": "Advanced olympiad level with complex multi-step problems"
396
  }
397
 
398
  topics = difficulty_map.get(grade, "general math concepts")
399
  modifier = difficulty_modifiers.get(difficulty, "")
400
-
401
- prompt = f"""Generate exactly {num_questions} Math Olympiad-style multiple choice questions for Grade {grade} students.
402
 
403
- Topics focus: {topics}
404
- Difficulty: {modifier}
 
 
 
 
 
 
 
 
 
405
 
406
- Format each question EXACTLY as follows:
407
- **Question X:** [Question text here]
408
- A) [Option A]
409
- B) [Option B]
410
- C) [Option C]
411
- D) [Option D]
412
- **Correct Answer:** [Letter only - A, B, C, or D]
413
- **Explanation:** [Clear step-by-step solution]
414
- **Hint:** [Helpful hint for students]
415
 
416
- Make questions engaging and educational. Include varied problem types: word problems, visual problems, logical reasoning, and computational challenges."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
418
- response = self.client.chat.completions.create(
419
- model="llama-3.3-70b-versatile",
420
- messages=[
421
- {"role": "system", "content": "You are an expert Math Olympiad coach creating engaging, educational questions."},
422
- {"role": "user", "content": prompt}
423
- ],
424
- temperature=0.7,
425
- max_tokens=3000
426
- )
427
-
428
- return self._parse_questions(response.choices[0].message.content)
429
-
430
  except Exception as e:
431
  st.error(f"Failed to generate questions: {e}")
432
  return []
433
 
434
- def _parse_questions(self, content: str) -> List[Dict]:
435
- """Enhanced question parsing with hints"""
 
 
 
 
 
 
 
 
436
  questions = []
437
- question_blocks = re.split(r'\*\*Question \d+:\*\*', content)[1:]
438
 
439
- for i, block in enumerate(question_blocks, 1):
 
 
 
 
 
 
440
  try:
441
  lines = [line.strip() for line in block.strip().split('\n') if line.strip()]
442
 
443
- if len(lines) < 6:
444
  continue
445
 
446
- question_text = lines[0]
447
- options = []
 
 
 
 
 
448
  correct_answer = None
449
  explanation = ""
450
  hint = ""
451
 
 
 
452
  for line in lines[1:]:
453
- if line.startswith(('A)', 'B)', 'C)', 'D)')):
454
- options.append(line)
455
- elif line.startswith('**Correct Answer:**'):
456
- correct_answer = line.replace('**Correct Answer:**', '').strip()
 
 
 
 
 
 
 
 
457
  elif line.startswith('**Explanation:**'):
458
- explanation = line.replace('**Explanation:**', '').strip()
 
 
 
 
459
  elif line.startswith('**Hint:**'):
460
- hint = line.replace('**Hint:**', '').strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
 
462
- if len(options) == 4 and correct_answer:
 
463
  questions.append({
464
  'question_number': i,
465
  'question_text': question_text,
466
- 'options': options,
 
467
  'correct_answer': correct_answer,
468
- 'explanation': explanation,
469
- 'hint': hint,
470
  'user_answer': None,
471
  'time_taken': 0,
472
  'difficulty': self._estimate_difficulty(question_text)
473
  })
 
 
474
 
475
  except Exception as e:
476
  st.warning(f"Error parsing question {i}: {e}")
@@ -480,8 +676,8 @@ Make questions engaging and educational. Include varied problem types: word prob
480
 
481
  def _estimate_difficulty(self, question_text: str) -> str:
482
  """Estimate question difficulty based on content"""
483
- hard_keywords = ['prove', 'complex', 'advanced', 'multiple steps', 'system of']
484
- easy_keywords = ['basic', 'simple', 'find', 'calculate', 'what is']
485
 
486
  text_lower = question_text.lower()
487
 
@@ -518,8 +714,8 @@ def initialize_session_state():
518
  'badges': [],
519
  'streak': 0
520
  }
521
- if 'seen_questions' not in st.session_state:
522
- st.session_state.seen_questions = set()
523
 
524
  @st.cache_resource
525
  def get_exam_system():
@@ -532,12 +728,49 @@ def render_hero_section():
532
  <div class="hero-title">๐Ÿงฎ MathGenius Academy</div>
533
  <div class="hero-subtitle">Master Mathematics Through Interactive Olympiad Training</div>
534
  <div style="margin-top: 1rem;">
535
- <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐ŸŽฏ Adaptive Learning</span>
536
  <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐Ÿ“Š Progress Tracking</span>
537
  <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐Ÿ† Achievement System</span>
538
  </div>
539
  </div>
540
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
 
542
  def render_dashboard():
543
  """Render user dashboard with statistics"""
@@ -596,21 +829,9 @@ def render_dashboard():
596
  paper_bgcolor='rgba(0,0,0,0)',
597
  )
598
  st.plotly_chart(fig, use_container_width=True)
599
-
600
- with col2:
601
- st.markdown("#### ๐ŸŽฏ Grade Distribution")
602
- grade_counts = df['grade'].value_counts().sort_index()
603
- fig = px.bar(x=grade_counts.index, y=grade_counts.values,
604
- title='Exams by Grade Level',
605
- color_discrete_sequence=['#764ba2'])
606
- fig.update_layout(
607
- plot_bgcolor='rgba(0,0,0,0)',
608
- paper_bgcolor='rgba(0,0,0,0)',
609
- )
610
- st.plotly_chart(fig, use_container_width=True)
611
 
612
  def render_question_card(question, question_index, total_questions):
613
- """Render an individual question with enhanced UI"""
614
  progress = ((question_index + 1) / total_questions) * 100
615
 
616
  st.markdown(f"""
@@ -635,17 +856,51 @@ def render_question_card(question, question_index, total_questions):
635
  </div>
636
  """, unsafe_allow_html=True)
637
 
638
- # Options with enhanced styling
639
- option_labels = [opt.split(')', 1)[1].strip() for opt in question['options']]
640
- option_keys = [opt.split(')', 1)[0].strip() for opt in question['options']]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
 
642
- selected = st.radio(
643
- "Choose your answer:",
644
- options=option_keys,
645
- format_func=lambda x, labels=option_labels, keys=option_keys: f"{x}) {labels[keys.index(x)]}",
646
- key=f"q_{question_index}",
647
- index=None
648
- )
649
 
650
  # Hint system
651
  if st.button(f"๐Ÿ’ก Need a hint?", key=f"hint_{question_index}"):
@@ -657,30 +912,12 @@ def render_question_card(question, question_index, total_questions):
657
  return selected
658
 
659
  def render_exam_interface():
660
- """Render the main exam interface"""
661
  questions = st.session_state.questions
662
  total_questions = len(questions)
663
 
664
- if st.session_state.exam_mode == 'timed':
665
- # Timer display
666
- if st.session_state.start_time:
667
- elapsed = time.time() - st.session_state.start_time
668
- remaining = max(0, (total_questions * 120) - elapsed) # 2 minutes per question
669
-
670
- mins, secs = divmod(int(remaining), 60)
671
- st.markdown(f"""
672
- <div style="text-align: center; margin-bottom: 1rem;">
673
- <div style="background: linear-gradient(135deg, #FF6B6B, #FF8E8E); color: white;
674
- padding: 1rem; border-radius: 15px; display: inline-block; font-size: 1.5rem; font-weight: 600;">
675
- โฐ {mins:02d}:{secs:02d}
676
- </div>
677
- </div>
678
- """, unsafe_allow_html=True)
679
-
680
- if remaining <= 0:
681
- st.error("โฐ Time's up! Submitting your exam...")
682
- st.session_state.exam_completed = True
683
- st.rerun()
684
 
685
  # Question navigation
686
  if total_questions > 1:
@@ -695,6 +932,7 @@ def render_exam_interface():
695
  # Current question
696
  current_q = st.session_state.current_question
697
  if current_q < total_questions:
 
698
  selected = render_question_card(questions[current_q], current_q, total_questions)
699
  questions[current_q]['user_answer'] = selected
700
 
@@ -742,7 +980,7 @@ def calculate_badges(score, total, grade):
742
  return badges
743
 
744
  def render_results_section():
745
- """Render enhanced results with animations and insights - REMOVED ACTION BUTTONS"""
746
  st.markdown("---")
747
  st.markdown("### ๐ŸŽ‰ Exam Results")
748
 
@@ -835,7 +1073,7 @@ def render_results_section():
835
  if not is_correct and question.get('hint'):
836
  st.info(f"๐Ÿ’ก **Hint for next time:** {question['hint']}")
837
 
838
- # Performance insights
839
  exam_system = get_exam_system()
840
  if exam_system.client:
841
  st.markdown("#### ๐Ÿง  AI Performance Analysis")
@@ -854,12 +1092,12 @@ def render_results_section():
854
  </div>
855
  """, unsafe_allow_html=True)
856
 
857
- # Show completion message instead of action buttons
858
  st.markdown("#### ๐ŸŽ“ Exam Complete!")
859
  st.info("Great job completing the exam! Use the sidebar to generate a new exam or check out the Dashboard tab to view your progress.")
860
 
861
  def generate_enhanced_summary(grade, questions, score, total, exam_system):
862
- """Generate enhanced AI-powered performance summary"""
863
  try:
864
  # Analyze performance patterns
865
  correct_questions = [q for q in questions if q['user_answer'] == q['correct_answer']]
@@ -885,19 +1123,32 @@ PERFORMANCE SUMMARY:
885
  SAMPLE INCORRECT QUESTIONS:
886
  {chr(10).join([f"- {q['question_text'][:100]}..." for q in incorrect_questions[:2]])}
887
 
888
- Please provide a concise summary (max 4-5 sentences) with:
889
- - 1-2 key strengths
890
- - 1 main area to improve
891
- - 1 motivational message
892
- Use simple, encouraging language for a Grade {grade} student. Avoid long lists or detailed study plans."""
 
 
 
 
 
 
 
 
 
 
 
 
 
893
 
894
  response = exam_system.client.chat.completions.create(
895
- model="llama-3.3-70b-versatile",
896
  messages=[
897
- {"role": "system", "content": "You are an expert Math Olympiad mentor providing detailed, encouraging feedback to help students improve."},
898
  {"role": "user", "content": prompt}
899
  ],
900
- temperature=0.3,
901
  max_tokens=1000
902
  )
903
 
@@ -905,9 +1156,20 @@ Use simple, encouraging language for a Grade {grade} student. Avoid long lists o
905
 
906
  except Exception as e:
907
  return f"""
908
- **Performance Summary:**
909
- You scored {score} out of {total} ({percentage:.1f}%).
910
- Great job! You showed strong skills in several areas. Focus on reviewing the questions you missed. Keep practicing and you'll get even better! ๐ŸŒŸ
 
 
 
 
 
 
 
 
 
 
 
911
  """
912
 
913
  def reset_exam():
@@ -953,10 +1215,10 @@ def render_sidebar():
953
  ["practice", "timed", "challenge"],
954
  format_func=lambda x: {
955
  "practice": "๐ŸŽฏ Practice Mode",
956
- "timed": "โฐ Timed Mode",
957
  "challenge": "๐Ÿ”ฅ Challenge Mode"
958
  }[x],
959
- help="Practice: No time limit | Timed: 2 min/question | Challenge: Extra hard questions"
960
  )
961
  st.session_state.exam_mode = exam_mode
962
 
@@ -966,6 +1228,10 @@ def render_sidebar():
966
  else:
967
  num_questions = st.slider("๐Ÿ“Š Questions:", 3, 15, 5, help="Choose number of questions")
968
 
 
 
 
 
969
  # Difficulty level
970
  difficulty = st.select_slider(
971
  "โšก Difficulty:",
@@ -983,7 +1249,7 @@ def render_sidebar():
983
  if not exam_system.client:
984
  st.error("๐Ÿšซ API not available!")
985
  else:
986
- with st.spinner(f"๐ŸŽญ Creating your {exam_mode} exam..."):
987
  # Add loading animation
988
  progress_bar = st.progress(0)
989
  for i in range(100):
@@ -994,21 +1260,13 @@ def render_sidebar():
994
  questions = exam_system.generate_questions(grade, num_questions, challenge_difficulty)
995
 
996
  if questions:
997
- # Filter out previously seen questions
998
- if 'seen_questions' in st.session_state:
999
- new_questions = [q for q in questions if q['question_text'] not in st.session_state.seen_questions]
1000
- st.session_state.questions = new_questions
1001
- # Update seen_questions with the new questions
1002
- st.session_state.seen_questions.update(q['question_text'] for q in new_questions)
1003
- else:
1004
- st.session_state.questions = questions
1005
-
1006
  st.session_state.exam_completed = False
1007
  st.session_state.grade_selected = grade
1008
  st.session_state.current_question = 0
1009
  st.session_state.start_time = time.time() if exam_mode == "timed" else None
1010
 
1011
- st.success(f"โœจ Generated {len(st.session_state.questions)} questions!")
1012
  st.balloons()
1013
  else:
1014
  st.error("โŒ Failed to generate exam. Try again!")
@@ -1068,26 +1326,26 @@ def render_welcome_screen():
1068
  st.markdown("""
1069
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1070
  <div style="font-size: 3rem; margin-bottom: 1rem;">๐ŸŽฏ</div>
1071
- <h4 style="margin: 0.5rem 0; color: white;">Adaptive Learning</h4>
1072
- <p style="margin: 0; color: rgba(255,255,255,0.9);">Questions adjust to your skill level</p>
1073
  </div>
1074
  """, unsafe_allow_html=True)
1075
 
1076
  with col2:
1077
  st.markdown("""
1078
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1079
- <div style="font-size: 3rem; margin-bottom: 1rem;">๐Ÿ“Š</div>
1080
- <h4 style="margin: 0.5rem 0; color: white;">Progress Tracking</h4>
1081
- <p style="margin: 0; color: rgba(255,255,255,0.9);">See your improvement over time</p>
1082
  </div>
1083
  """, unsafe_allow_html=True)
1084
 
1085
  with col3:
1086
  st.markdown("""
1087
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1088
- <div style="font-size: 3rem; margin-bottom: 1rem;">๐Ÿ†</div>
1089
- <h4 style="margin: 0.5rem 0; color: white;">Achievement System</h4>
1090
- <p style="margin: 0; color: rgba(255,255,255,0.9);">Earn badges and unlock new levels</p>
1091
  </div>
1092
  """, unsafe_allow_html=True)
1093
 
@@ -1103,7 +1361,7 @@ def render_welcome_screen():
1103
  1. **๐ŸŽฏ Choose Your Grade**: Select your current grade level from the sidebar
1104
  2. **๐Ÿ“ Pick Your Mode**:
1105
  - ๐ŸŸข **Practice Mode**: No time pressure, perfect for learning
1106
- - โฐ **Timed Mode**: Challenge yourself with time limits
1107
  - ๐Ÿ”ฅ **Challenge Mode**: Extra difficult questions for advanced learners
1108
  3. **โšก Set Difficulty**: Easy, Medium, or Hard - pick what feels right
1109
  4. **๐Ÿ“Š Choose Questions**: 3-20 questions depending on your time
@@ -1151,15 +1409,18 @@ def main():
1151
  else:
1152
  st.markdown("### ๐Ÿงฎ Math Olympiad Challenge")
1153
 
1154
- if st.session_state.exam_mode == "timed" and st.session_state.start_time:
1155
- # Show exam info
1156
- col1, col2, col3 = st.columns(3)
1157
- with col1:
1158
- st.info(f"๐Ÿ“š Grade {st.session_state.grade_selected}")
1159
- with col2:
 
 
 
1160
  st.info(f"โฑ๏ธ {st.session_state.exam_mode.title()} Mode")
1161
- with col3:
1162
- st.info(f"๐Ÿ“ {len(st.session_state.questions)} Questions")
1163
 
1164
  render_exam_interface()
1165
 
@@ -1243,14 +1504,14 @@ def main():
1243
  <div class="custom-card">
1244
  <h4 style="color: #667eea;">๐Ÿค– AI-Powered Learning</h4>
1245
  <ul style="color: #444;">
1246
- <li><strong>Adaptive Questions:</strong> Problems that match your skill level</li>
1247
  <li><strong>Smart Difficulty:</strong> Automatic adjustment based on performance</li>
1248
- <li><strong>Personalized Feedback:</strong> Detailed insights and recommendations</li>
1249
  </ul>
1250
  <h4 style="color: #667eea;">๐ŸŽฎ Multiple Game Modes</h4>
1251
  <ul style="color: #444;">
1252
  <li><strong>Practice Mode:</strong> Learn at your own pace</li>
1253
- <li><strong>Timed Mode:</strong> Build speed and confidence</li>
1254
  <li><strong>Challenge Mode:</strong> Push your limits with hard problems</li>
1255
  </ul>
1256
  </div>
@@ -1265,7 +1526,7 @@ def main():
1265
  <h4 style="color: #667eea;">๐Ÿ“Š Progress & Analytics</h4>
1266
  <ul style="color: #444;">
1267
  <li><strong>Performance Tracking:</strong> Monitor improvement over time</li>
1268
- <li><strong>Detailed Statistics:</strong> Accuracy, trends, and insights</li>
1269
  <li><strong>Visual Charts:</strong> Interactive graphs and analysis</li>
1270
  </ul>
1271
  <h4 style="color: #667eea;">๐Ÿ† Achievement System</h4>
@@ -1324,8 +1585,8 @@ def main():
1324
  <ul style="color: #444;">
1325
  <li>Basic arithmetic operations</li>
1326
  <li>Simple word problems</li>
1327
- <li>Pattern recognition</li>
1328
- <li>Basic geometry shapes</li>
1329
  <li>Counting and number sense</li>
1330
  </ul>
1331
  </div>
@@ -1362,25 +1623,25 @@ def main():
1362
  # FAQ Section
1363
  with st.expander("โ“ Frequently Asked Questions"):
1364
  st.markdown("""
1365
- **Q: How are the questions generated?**
1366
 
1367
- A: We use advanced AI to create unique, grade-appropriate questions that follow Math Olympiad standards.
1368
 
1369
- **Q: Can I retake exams?**
1370
 
1371
- A: Yes! You can generate unlimited exams to practice and improve your skills.
1372
 
1373
- **Q: How is my progress tracked?**
1374
 
1375
- A: We track your accuracy, response times, difficulty preferences, and improvement trends over time.
1376
 
1377
  **Q: What if I get stuck on a question?**
1378
 
1379
  A: Use the hint feature! We provide helpful hints to guide your thinking without giving away the answer.
1380
 
1381
- **Q: Is there a time limit?**
1382
 
1383
- A: Only in Timed Mode. Practice and Challenge modes let you work at your own pace.
1384
 
1385
  **Q: How do I earn badges?**
1386
 
@@ -1393,7 +1654,7 @@ def main():
1393
  <div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; color: white;">
1394
  <h3 style="color: white; margin-bottom: 1rem;">Ready to become a Math Genius? ๐Ÿงฎ</h3>
1395
  <p style="color: rgba(255,255,255,0.9); margin: 0;">
1396
- Join thousands of students improving their mathematical skills every day!
1397
  </p>
1398
  </div>
1399
  """, unsafe_allow_html=True)
@@ -1402,4 +1663,5 @@ def main():
1402
  st.markdown("**Made with โค๏ธ for young mathematicians everywhere!**")
1403
 
1404
  if __name__ == "__main__":
1405
- main()
 
 
10
  import pandas as pd
11
  from datetime import datetime, timedelta
12
  import streamlit.components.v1 as components
13
+ import hashlib
14
 
15
  # Configure page
16
  st.set_page_config(
 
78
  z-index: 1;
79
  }
80
 
81
+ /* Timer styling */
82
+ .timer-display {
83
+ background: linear-gradient(135deg, #FF6B6B, #FF8E8E);
84
+ color: white;
85
+ padding: 1rem 2rem;
86
+ border-radius: 20px;
87
+ text-align: center;
88
+ font-size: 2rem;
89
+ font-weight: 700;
90
+ margin-bottom: 2rem;
91
+ box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3);
92
+ animation: pulse 2s infinite;
93
+ }
94
+
95
+ @keyframes pulse {
96
+ 0% { transform: scale(1); box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3); }
97
+ 50% { transform: scale(1.02); box-shadow: 0 15px 40px rgba(255, 107, 107, 0.5); }
98
+ 100% { transform: scale(1); box-shadow: 0 10px 30px rgba(255, 107, 107, 0.3); }
99
+ }
100
+
101
+ .timer-warning {
102
+ background: linear-gradient(135deg, #FF4757, #FF6B6B) !important;
103
+ animation: fastPulse 1s infinite !important;
104
+ }
105
+
106
+ @keyframes fastPulse {
107
+ 0% { transform: scale(1); }
108
+ 50% { transform: scale(1.05); }
109
+ 100% { transform: scale(1); }
110
+ }
111
+
112
  /* Card Styling */
113
  .custom-card {
114
  background: white;
 
254
  animation: slideInUp 0.6s ease-out;
255
  }
256
 
 
 
 
 
 
 
257
  .pulse-animation {
258
  animation: pulse 2s infinite;
259
  }
 
296
  .question-card {
297
  padding: 1rem;
298
  }
299
+
300
+ .timer-display {
301
+ font-size: 1.5rem;
302
+ padding: 0.75rem 1.5rem;
303
+ }
304
  }
305
  body, .main, .block-container {
306
  background: #f7f9fb !important;
 
402
  return None
403
 
404
  def generate_questions(self, grade: int, num_questions: int, difficulty: str = "medium") -> List[Dict]:
405
+ """Generate unique math olympiad questions with STRICT rules against visual questions - FIXED"""
406
  if not self.client:
407
  return []
408
 
409
  try:
410
  difficulty_map = {
411
+ 1: "Basic counting 1-20, simple addition/subtraction within 20, comparing numbers, basic money problems",
412
+ 2: "Addition/subtraction within 100, word problems about time/money, simple multiplication by 2,5,10",
413
+ 3: "Multiplication tables to 12, basic division, simple fractions (1/2, 1/4), word problems with multiple steps",
414
+ 4: "Multi-digit multiplication/division, equivalent fractions, decimals to tenths, perimeter calculations",
415
+ 5: "Advanced arithmetic, basic algebra (find the missing number), percentages, area calculations, factors/multiples",
416
+ 6: "Pre-algebra equations, ratios/proportions, basic statistics (mean/median), coordinate problems",
417
+ 7: "Linear equations, integers, basic probability, algebraic expressions, geometric formulas",
418
+ 8: "Systems of equations, exponents/roots, advanced probability, quadratic expressions, geometric theorems",
419
+ 9: "Advanced algebra, trigonometry basics, sequences/series, complex word problems, mathematical proofs",
420
+ 10: "Pre-calculus topics, advanced trigonometry, logarithms, complex problem solving, advanced geometry"
421
  }
422
+
423
  difficulty_modifiers = {
424
+ "easy": "Focus on fundamental concepts with straightforward calculations",
425
+ "medium": "Standard olympiad difficulty with moderate complexity requiring 2-3 steps",
426
+ "hard": "Advanced multi-step problems requiring deep mathematical reasoning"
427
  }
428
 
429
  topics = difficulty_map.get(grade, "general math concepts")
430
  modifier = difficulty_modifiers.get(difficulty, "")
 
 
431
 
432
+ # STRICT forbidden keywords - expanded list
433
+ forbidden_keywords = [
434
+ "figure", "diagram", "shape", "block", "square", "triangle", "circle", "rectangle", "polygon",
435
+ "pattern below", "picture", "image", "graph", "chart", "visual", "draw", "sketch", "plot",
436
+ "look at", "observe", "see the", "shown", "displayed", "illustrated", "appears",
437
+ "geometric shape", "arrange", "tessellation", "symmetry", "reflection", "rotation",
438
+ "hexagon", "pentagon", "octagon", "parallelogram", "rhombus", "trapezoid",
439
+ "coordinate plane", "grid", "lattice", "dot", "point on", "line segment",
440
+ "angle", "vertex", "side", "face", "edge", "cube", "cylinder", "sphere",
441
+ "shaded", "colored", "pattern", "sequence of shapes", "visual pattern"
442
+ ]
443
 
444
+ seen_hashes = getattr(st.session_state, 'question_hashes', set())
 
 
 
 
 
 
 
 
445
 
446
+ # ENHANCED prompt with VERY strict formatting requirements
447
+ base_prompt = f"""
448
+ Generate exactly {num_questions} UNIQUE Math Olympiad questions for Grade {grade}.
449
+
450
+ ๐Ÿšจ ABSOLUTE RESTRICTIONS - NEVER INCLUDE:
451
+ โŒ Any visual elements (shapes, figures, diagrams, patterns, images)
452
+ โŒ Questions requiring visual interpretation
453
+ โŒ Geometric shape identification or counting
454
+ โŒ Pattern completion with visual elements
455
+ โŒ Any reference to "figure", "diagram", "shape", "pattern below"
456
+ โŒ Coordinate plotting or graph reading
457
+ โŒ Any visual arrangement problems
458
+
459
+ โœ… ONLY ALLOWED - TEXT-BASED QUESTIONS:
460
+ โœ… Pure arithmetic calculations and word problems
461
+ โœ… Number sequences (like: 2, 4, 6, 8, ?)
462
+ โœ… Logic puzzles with numbers/text only
463
+ โœ… Real-world scenarios (money, time, travel, shopping)
464
+ โœ… Algebraic word problems
465
+ โœ… Statistical problems with given data
466
+ โœ… Mathematical reasoning without visuals
467
+
468
+ TOPICS: {topics}
469
+ DIFFICULTY: {modifier}
470
+
471
+ ๐ŸŽฏ CRITICAL: Answer must be EXACTLY one letter: A, B, C, or D (no explanations in answer line)
472
+
473
+ FORMAT (EXACT - NO DEVIATIONS):
474
+ **Question 1:** [Engaging word problem - NO VISUALS]
475
+ A) [Option A with number/calculation]
476
+ B) [Option B with number/calculation]
477
+ C) [Option C with number/calculation]
478
+ D) [Option D with number/calculation]
479
+ **Answer:** A
480
+ **Explanation:** [Step-by-step solution]
481
+ **Hint:** [Helpful tip for solving]
482
+
483
+ **Question 2:** [Next question...]
484
+ A) [Option A]
485
+ B) [Option B]
486
+ C) [Option C]
487
+ D) [Option D]
488
+ **Answer:** C
489
+ **Explanation:** [Step-by-step solution]
490
+ **Hint:** [Helpful tip for solving]
491
+
492
+ [Continue for all {num_questions} questions]
493
+
494
+ ๐Ÿ”ฅ IMPORTANT FORMATTING RULES:
495
+ - Answer line must contain ONLY the letter (A, B, C, or D)
496
+ - No parentheses, explanations, or additional text in the answer
497
+ - Each option must start with the letter followed by closing parenthesis
498
+ - Keep questions practical and engaging with real scenarios
499
+
500
+ MAKE QUESTIONS PRACTICAL AND ENGAGING - Use real scenarios students can relate to!
501
+ """
502
+
503
+ max_attempts = 3
504
+ all_questions = []
505
+
506
+ for attempt in range(max_attempts):
507
+ try:
508
+ response = self.client.chat.completions.create(
509
+ model="openai/gpt-oss-120b", # More reliable model
510
+ messages=[
511
+ {"role": "system", "content": "You are an expert Math Olympiad question creator. You MUST create only text-based word problems without any visual elements. The answer must be EXACTLY one letter: A, B, C, or D with no additional text or explanations."},
512
+ {"role": "user", "content": base_prompt}
513
+ ],
514
+ temperature=0.7, # Balanced creativity
515
+ max_tokens=4000
516
+ )
517
+
518
+ new_questions = self._parse_questions_enhanced(response.choices[0].message.content)
519
+
520
+ # STRICT filtering
521
+ valid_questions = []
522
+ for q in new_questions:
523
+ q_text = q['question_text'].lower()
524
+
525
+ # Check for forbidden keywords
526
+ if any(forbidden in q_text for forbidden in forbidden_keywords):
527
+ continue
528
+
529
+ # Additional checks for visual elements
530
+ if any(word in q_text for word in ['draw', 'sketch', 'plot', 'shown', 'displayed', 'appears']):
531
+ continue
532
+
533
+ # Check uniqueness
534
+ q_hash = self._get_question_hash(q['question_text'] + str(q['options']))
535
+ if q_hash not in seen_hashes:
536
+ q['hash'] = q_hash
537
+ valid_questions.append(q)
538
+ seen_hashes.add(q_hash)
539
+
540
+ all_questions.extend(valid_questions)
541
+
542
+ if len(all_questions) >= num_questions:
543
+ break
544
+
545
+ # Add retry hint with more specific guidance
546
+ base_prompt += f"\n\nNEED MORE QUESTIONS: Generated {len(all_questions)} so far. Create MORE PRACTICAL WORD PROBLEMS about real-life scenarios (shopping, travel, sports, cooking, etc.). REMEMBER: Answer must be EXACTLY one letter (A, B, C, or D)."
547
+
548
+ except Exception as e:
549
+ st.error(f"Error in attempt {attempt + 1}: {e}")
550
+ continue
551
+
552
+ st.session_state.question_hashes = seen_hashes
553
+ final_questions = all_questions[:num_questions]
554
+
555
+ if len(final_questions) < num_questions:
556
+ st.warning(f"โš ๏ธ Generated {len(final_questions)} unique valid questions (requested {num_questions}). Some were filtered out for containing visual elements.")
557
+
558
+ return final_questions
559
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  except Exception as e:
561
  st.error(f"Failed to generate questions: {e}")
562
  return []
563
 
564
+ def _get_question_hash(self, question_text: str) -> str:
565
+ """Generate hash for question to track uniqueness"""
566
+ # Remove numbers and normalize text for better uniqueness detection
567
+ normalized = re.sub(r'\d+', 'X', question_text.lower().strip())
568
+ normalized = re.sub(r'[^\w\s]', '', normalized) # Remove punctuation
569
+ return hashlib.md5(normalized.encode()).hexdigest()
570
+
571
+
572
+ def _parse_questions_enhanced(self, content: str) -> List[Dict]:
573
+ """Enhanced question parsing with better error handling and validation"""
574
  questions = []
 
575
 
576
+ # Split by question markers
577
+ question_blocks = re.split(r'\*\*Question \d+:\*\*', content)
578
+ if len(question_blocks) < 2:
579
+ # Try alternative splitting
580
+ question_blocks = re.split(r'Question \d+:', content)
581
+
582
+ for i, block in enumerate(question_blocks[1:], 1):
583
  try:
584
  lines = [line.strip() for line in block.strip().split('\n') if line.strip()]
585
 
586
+ if len(lines) < 6: # Need at least question + 4 options + answer
587
  continue
588
 
589
+ question_text = lines[0].strip()
590
+ if not question_text:
591
+ continue
592
+
593
+ # Parse options - FIXED: Create both dictionary and list properly
594
+ options_dict = {}
595
+ options_list = []
596
  correct_answer = None
597
  explanation = ""
598
  hint = ""
599
 
600
+ current_section = "options"
601
+
602
  for line in lines[1:]:
603
+ line = line.strip()
604
+
605
+ # Detect sections
606
+ if line.startswith('**Answer:**') or line.startswith('**Correct Answer:**'):
607
+ current_section = "answer"
608
+ answer_part = line.replace('**Answer:**', '').replace('**Correct Answer:**', '').strip()
609
+ if answer_part:
610
+ # Extract only the letter from the answer (A, B, C, or D)
611
+ answer_match = re.search(r'[ABCD]', answer_part.upper())
612
+ if answer_match:
613
+ correct_answer = answer_match.group()
614
+ continue
615
  elif line.startswith('**Explanation:**'):
616
+ current_section = "explanation"
617
+ exp_part = line.replace('**Explanation:**', '').strip()
618
+ if exp_part:
619
+ explanation = exp_part
620
+ continue
621
  elif line.startswith('**Hint:**'):
622
+ current_section = "hint"
623
+ hint_part = line.replace('**Hint:**', '').strip()
624
+ if hint_part:
625
+ hint = hint_part
626
+ continue
627
+
628
+ # Process based on current section
629
+ if current_section == "options" and line.startswith(('A)', 'B)', 'C)', 'D)')):
630
+ option_key = line[0] # A, B, C, or D
631
+ option_text = line[2:].strip() # Remove "A) "
632
+ options_dict[option_key] = option_text
633
+ options_list.append(line) # Keep full format "A) text"
634
+ elif current_section == "answer":
635
+ # Try to extract single letter answer
636
+ if line.strip() in ['A', 'B', 'C', 'D']:
637
+ correct_answer = line.strip()
638
+ elif not correct_answer:
639
+ # Fallback: extract first letter found
640
+ answer_match = re.search(r'[ABCD]', line.upper())
641
+ if answer_match:
642
+ correct_answer = answer_match.group()
643
+ elif current_section == "explanation" and line:
644
+ if explanation:
645
+ explanation += " " + line
646
+ else:
647
+ explanation = line
648
+ elif current_section == "hint" and line:
649
+ if hint:
650
+ hint += " " + line
651
+ else:
652
+ hint = line
653
 
654
+ # Validate question - ensure we have exactly 4 options and a valid answer
655
+ if len(options_dict) == 4 and correct_answer and correct_answer in options_dict:
656
  questions.append({
657
  'question_number': i,
658
  'question_text': question_text,
659
+ 'options': options_list, # List format for compatibility: ['A) text', 'B) text', ...]
660
+ 'options_dict': options_dict, # Dictionary format: {'A': 'text', 'B': 'text', ...}
661
  'correct_answer': correct_answer,
662
+ 'explanation': explanation or "Work through the problem step by step.",
663
+ 'hint': hint or "Read the question carefully and identify what you need to find.",
664
  'user_answer': None,
665
  'time_taken': 0,
666
  'difficulty': self._estimate_difficulty(question_text)
667
  })
668
+ else:
669
+ st.warning(f"Question {i} validation failed: options={len(options_dict)}, answer='{correct_answer}', keys={list(options_dict.keys())}")
670
 
671
  except Exception as e:
672
  st.warning(f"Error parsing question {i}: {e}")
 
676
 
677
  def _estimate_difficulty(self, question_text: str) -> str:
678
  """Estimate question difficulty based on content"""
679
+ hard_keywords = ['system', 'equation', 'quadratic', 'prove', 'theorem', 'complex', 'advanced']
680
+ easy_keywords = ['add', 'subtract', 'simple', 'basic', 'find the', 'what is', 'how many']
681
 
682
  text_lower = question_text.lower()
683
 
 
714
  'badges': [],
715
  'streak': 0
716
  }
717
+ if 'question_hashes' not in st.session_state:
718
+ st.session_state.question_hashes = set()
719
 
720
  @st.cache_resource
721
  def get_exam_system():
 
728
  <div class="hero-title">๐Ÿงฎ MathGenius Academy</div>
729
  <div class="hero-subtitle">Master Mathematics Through Interactive Olympiad Training</div>
730
  <div style="margin-top: 1rem;">
731
+ <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐ŸŽฏ Practical Problems</span>
732
  <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐Ÿ“Š Progress Tracking</span>
733
  <span style="background: rgba(255,255,255,0.2); padding: 0.5rem 1rem; border-radius: 25px; margin: 0 0.5rem;">๐Ÿ† Achievement System</span>
734
  </div>
735
  </div>
736
+ """, unsafe_allow_html = True)
737
+
738
+ def render_timer_display():
739
+ """Render enhanced timer display with better positioning"""
740
+ if st.session_state.exam_mode == 'timed' and st.session_state.start_time:
741
+ total_time = len(st.session_state.questions) * 60 # 1 minute per question
742
+ elapsed = time.time() - st.session_state.start_time
743
+ remaining = max(0, total_time - elapsed)
744
+
745
+ mins, secs = divmod(int(remaining), 60)
746
+
747
+ # Add warning class if time is running low
748
+ warning_class = "timer-warning" if remaining <= 60 else ""
749
+
750
+ timer_html = f"""
751
+ <div class="timer-display {warning_class}">
752
+ <div style="display: flex; align-items: center; justify-content: center; gap: 1rem;">
753
+ <div style="font-size: 3rem;">โฐ</div>
754
+ <div>
755
+ <div style="font-size: 2.5rem; font-weight: 800;">{mins:02d}:{secs:02d}</div>
756
+ <div style="font-size: 1rem; opacity: 0.9;">Time Remaining</div>
757
+ </div>
758
+ </div>
759
+ </div>
760
+ """
761
+
762
+ st.markdown(timer_html, unsafe_allow_html=True)
763
+
764
+ # Auto-submit when time is up
765
+ if remaining <= 0:
766
+ st.error("โฐ Time's up! Auto-submitting your exam...")
767
+ st.session_state.exam_completed = True
768
+ time.sleep(1)
769
+ st.rerun()
770
+
771
+ # Show warning when time is low
772
+ elif remaining <= 60:
773
+ st.warning(f"โš ๏ธ Only {int(remaining)} seconds left!")
774
 
775
  def render_dashboard():
776
  """Render user dashboard with statistics"""
 
829
  paper_bgcolor='rgba(0,0,0,0)',
830
  )
831
  st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
832
 
833
  def render_question_card(question, question_index, total_questions):
834
+ """Render an individual question with enhanced UI - FIXED"""
835
  progress = ((question_index + 1) / total_questions) * 100
836
 
837
  st.markdown(f"""
 
856
  </div>
857
  """, unsafe_allow_html=True)
858
 
859
+ # FIXED: Handle both list and dictionary options formats
860
+ if isinstance(question['options'], list):
861
+ # List format: ['A) text', 'B) text', ...]
862
+ option_keys = []
863
+ option_labels = []
864
+ for opt in question['options']:
865
+ if ')' in opt:
866
+ key, label = opt.split(')', 1)
867
+ option_keys.append(key.strip())
868
+ option_labels.append(label.strip())
869
+
870
+ # Create format function for radio buttons
871
+ def format_option(key):
872
+ try:
873
+ idx = option_keys.index(key)
874
+ return f"{key}) {option_labels[idx]}"
875
+ except (ValueError, IndexError):
876
+ return f"{key}) Option not found"
877
+
878
+ selected = st.radio(
879
+ "Choose your answer:",
880
+ options=option_keys,
881
+ format_func=format_option,
882
+ key=f"q_{question_index}",
883
+ index=None
884
+ )
885
+
886
+ elif isinstance(question['options'], dict):
887
+ # Dictionary format: {'A': 'text', 'B': 'text', ...}
888
+ option_keys = list(question['options'].keys())
889
+
890
+ def format_option_dict(key):
891
+ return f"{key}) {question['options'].get(key, 'Option not found')}"
892
+
893
+ selected = st.radio(
894
+ "Choose your answer:",
895
+ options=option_keys,
896
+ format_func=format_option_dict,
897
+ key=f"q_{question_index}",
898
+ index=None
899
+ )
900
 
901
+ else:
902
+ st.error("Invalid options format in question data")
903
+ return None
 
 
 
 
904
 
905
  # Hint system
906
  if st.button(f"๐Ÿ’ก Need a hint?", key=f"hint_{question_index}"):
 
912
  return selected
913
 
914
  def render_exam_interface():
915
+ """Render the main exam interface with improved timer - FIXED"""
916
  questions = st.session_state.questions
917
  total_questions = len(questions)
918
 
919
+ # Display timer at the top for timed mode
920
+ render_timer_display()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
921
 
922
  # Question navigation
923
  if total_questions > 1:
 
932
  # Current question
933
  current_q = st.session_state.current_question
934
  if current_q < total_questions:
935
+ # FIXED: Call render_question_card as a standalone function
936
  selected = render_question_card(questions[current_q], current_q, total_questions)
937
  questions[current_q]['user_answer'] = selected
938
 
 
980
  return badges
981
 
982
  def render_results_section():
983
+ """Render enhanced results with animations and insights"""
984
  st.markdown("---")
985
  st.markdown("### ๐ŸŽ‰ Exam Results")
986
 
 
1073
  if not is_correct and question.get('hint'):
1074
  st.info(f"๐Ÿ’ก **Hint for next time:** {question['hint']}")
1075
 
1076
+ # Performance insights with bullet points
1077
  exam_system = get_exam_system()
1078
  if exam_system.client:
1079
  st.markdown("#### ๐Ÿง  AI Performance Analysis")
 
1092
  </div>
1093
  """, unsafe_allow_html=True)
1094
 
1095
+ # Show completion message
1096
  st.markdown("#### ๐ŸŽ“ Exam Complete!")
1097
  st.info("Great job completing the exam! Use the sidebar to generate a new exam or check out the Dashboard tab to view your progress.")
1098
 
1099
  def generate_enhanced_summary(grade, questions, score, total, exam_system):
1100
+ """Generate enhanced AI-powered performance summary in bullet points"""
1101
  try:
1102
  # Analyze performance patterns
1103
  correct_questions = [q for q in questions if q['user_answer'] == q['correct_answer']]
 
1123
  SAMPLE INCORRECT QUESTIONS:
1124
  {chr(10).join([f"- {q['question_text'][:100]}..." for q in incorrect_questions[:2]])}
1125
 
1126
+ Please provide a summary in BULLET POINTS with emojis using this exact format:
1127
+
1128
+ ๐ŸŽฏ **Strengths:**
1129
+ โ€ข [strength 1 with emoji]
1130
+ โ€ข [strength 2 with emoji]
1131
+
1132
+ ๐Ÿ“ˆ **Areas to Improve:**
1133
+ โ€ข [area to improve 1 with emoji]
1134
+ โ€ข [area to improve 2 with emoji]
1135
+
1136
+ ๐Ÿ’ก **Quick Tips:**
1137
+ โ€ข [tip 1 with emoji]
1138
+ โ€ข [tip 2 with emoji]
1139
+
1140
+ ๐ŸŒŸ **Motivation:**
1141
+ โ€ข [encouraging message with emoji]
1142
+
1143
+ Use Grade {grade} appropriate language and keep each bullet point short and encouraging. Include relevant emojis for each point."""
1144
 
1145
  response = exam_system.client.chat.completions.create(
1146
+ model="openai/gpt-oss-120b",
1147
  messages=[
1148
+ {"role": "system", "content": "You are an expert Math Olympiad mentor providing detailed, encouraging feedback in bullet points with emojis."},
1149
  {"role": "user", "content": prompt}
1150
  ],
1151
+ temperature=0,
1152
  max_tokens=1000
1153
  )
1154
 
 
1156
 
1157
  except Exception as e:
1158
  return f"""
1159
+ ๐ŸŽฏ **Strengths:**
1160
+ โ€ข โœจ You completed {score} out of {total} questions correctly ({percentage:.1f}%)
1161
+ โ€ข ๐Ÿ’ช You showed good problem-solving skills
1162
+
1163
+ ๐Ÿ“ˆ **Areas to Improve:**
1164
+ โ€ข ๐Ÿ“š Review the questions you missed for better understanding
1165
+ โ€ข โฐ Work on time management if needed
1166
+
1167
+ ๐Ÿ’ก **Quick Tips:**
1168
+ โ€ข ๐Ÿ” Read each question carefully before answering
1169
+ โ€ข ๐Ÿ’ญ Use hints when you're unsure
1170
+
1171
+ ๐ŸŒŸ **Motivation:**
1172
+ โ€ข ๐ŸŽ‰ Great effort! Keep practicing and you'll improve even more!
1173
  """
1174
 
1175
  def reset_exam():
 
1215
  ["practice", "timed", "challenge"],
1216
  format_func=lambda x: {
1217
  "practice": "๐ŸŽฏ Practice Mode",
1218
+ "timed": "โฐ Timed Mode (1 min/question)",
1219
  "challenge": "๐Ÿ”ฅ Challenge Mode"
1220
  }[x],
1221
+ help="Practice: No time limit | Timed: 1 minute per question | Challenge: Extra hard questions"
1222
  )
1223
  st.session_state.exam_mode = exam_mode
1224
 
 
1228
  else:
1229
  num_questions = st.slider("๐Ÿ“Š Questions:", 3, 15, 5, help="Choose number of questions")
1230
 
1231
+ # Show timer info for timed mode
1232
+ if exam_mode == "timed":
1233
+ st.info(f"โฐ Total time: {num_questions} minutes ({num_questions} questions ร— 1 min each)")
1234
+
1235
  # Difficulty level
1236
  difficulty = st.select_slider(
1237
  "โšก Difficulty:",
 
1249
  if not exam_system.client:
1250
  st.error("๐Ÿšซ API not available!")
1251
  else:
1252
+ with st.spinner(f"๐ŸŽญ Creating your {exam_mode} exam with unique questions..."):
1253
  # Add loading animation
1254
  progress_bar = st.progress(0)
1255
  for i in range(100):
 
1260
  questions = exam_system.generate_questions(grade, num_questions, challenge_difficulty)
1261
 
1262
  if questions:
1263
+ st.session_state.questions = questions
 
 
 
 
 
 
 
 
1264
  st.session_state.exam_completed = False
1265
  st.session_state.grade_selected = grade
1266
  st.session_state.current_question = 0
1267
  st.session_state.start_time = time.time() if exam_mode == "timed" else None
1268
 
1269
+ st.success(f"โœจ Generated {len(st.session_state.questions)} unique questions!")
1270
  st.balloons()
1271
  else:
1272
  st.error("โŒ Failed to generate exam. Try again!")
 
1326
  st.markdown("""
1327
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1328
  <div style="font-size: 3rem; margin-bottom: 1rem;">๐ŸŽฏ</div>
1329
+ <h4 style="margin: 0.5rem 0; color: white;">Unique Questions</h4>
1330
+ <p style="margin: 0; color: rgba(255,255,255,0.9);">Always new, never repeated</p>
1331
  </div>
1332
  """, unsafe_allow_html=True)
1333
 
1334
  with col2:
1335
  st.markdown("""
1336
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1337
+ <div style="font-size: 3rem; margin-bottom: 1rem;">๐Ÿ“</div>
1338
+ <h4 style="margin: 0.5rem 0; color: white;">Text-Based Only</h4>
1339
+ <p style="margin: 0; color: rgba(255,255,255,0.9);">Pure math, no visual puzzles</p>
1340
  </div>
1341
  """, unsafe_allow_html=True)
1342
 
1343
  with col3:
1344
  st.markdown("""
1345
  <div style="background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 1.5rem; border-radius: 15px; text-align: center; height: 200px; display: flex; flex-direction: column; justify-content: center;">
1346
+ <div style="font-size: 3rem; margin-bottom: 1rem;">โฐ</div>
1347
+ <h4 style="margin: 0.5rem 0; color: white;">Smart Timer</h4>
1348
+ <p style="margin: 0; color: rgba(255,255,255,0.9);">1 minute per question</p>
1349
  </div>
1350
  """, unsafe_allow_html=True)
1351
 
 
1361
  1. **๐ŸŽฏ Choose Your Grade**: Select your current grade level from the sidebar
1362
  2. **๐Ÿ“ Pick Your Mode**:
1363
  - ๐ŸŸข **Practice Mode**: No time pressure, perfect for learning
1364
+ - โฐ **Timed Mode**: 1 minute per question challenge
1365
  - ๐Ÿ”ฅ **Challenge Mode**: Extra difficult questions for advanced learners
1366
  3. **โšก Set Difficulty**: Easy, Medium, or Hard - pick what feels right
1367
  4. **๐Ÿ“Š Choose Questions**: 3-20 questions depending on your time
 
1409
  else:
1410
  st.markdown("### ๐Ÿงฎ Math Olympiad Challenge")
1411
 
1412
+ # Show exam info
1413
+ col1, col2, col3 = st.columns(3)
1414
+ with col1:
1415
+ st.info(f"๐Ÿ“š Grade {st.session_state.grade_selected}")
1416
+ with col2:
1417
+ if st.session_state.exam_mode == "timed":
1418
+ total_time = len(st.session_state.questions)
1419
+ st.info(f"โฑ๏ธ {st.session_state.exam_mode.title()} Mode ({total_time} min)")
1420
+ else:
1421
  st.info(f"โฑ๏ธ {st.session_state.exam_mode.title()} Mode")
1422
+ with col3:
1423
+ st.info(f"๐Ÿ“ {len(st.session_state.questions)} Questions")
1424
 
1425
  render_exam_interface()
1426
 
 
1504
  <div class="custom-card">
1505
  <h4 style="color: #667eea;">๐Ÿค– AI-Powered Learning</h4>
1506
  <ul style="color: #444;">
1507
+ <li><strong>Unique Questions:</strong> Never see the same question twice</li>
1508
  <li><strong>Smart Difficulty:</strong> Automatic adjustment based on performance</li>
1509
+ <li><strong>Text-Only Focus:</strong> Pure mathematical reasoning, no visual puzzles</li>
1510
  </ul>
1511
  <h4 style="color: #667eea;">๐ŸŽฎ Multiple Game Modes</h4>
1512
  <ul style="color: #444;">
1513
  <li><strong>Practice Mode:</strong> Learn at your own pace</li>
1514
+ <li><strong>Timed Mode:</strong> 1 minute per question challenge</li>
1515
  <li><strong>Challenge Mode:</strong> Push your limits with hard problems</li>
1516
  </ul>
1517
  </div>
 
1526
  <h4 style="color: #667eea;">๐Ÿ“Š Progress & Analytics</h4>
1527
  <ul style="color: #444;">
1528
  <li><strong>Performance Tracking:</strong> Monitor improvement over time</li>
1529
+ <li><strong>Detailed Insights:</strong> Bullet-point feedback with emojis</li>
1530
  <li><strong>Visual Charts:</strong> Interactive graphs and analysis</li>
1531
  </ul>
1532
  <h4 style="color: #667eea;">๐Ÿ† Achievement System</h4>
 
1585
  <ul style="color: #444;">
1586
  <li>Basic arithmetic operations</li>
1587
  <li>Simple word problems</li>
1588
+ <li>Number patterns and sequences</li>
1589
+ <li>Basic geometry concepts</li>
1590
  <li>Counting and number sense</li>
1591
  </ul>
1592
  </div>
 
1623
  # FAQ Section
1624
  with st.expander("โ“ Frequently Asked Questions"):
1625
  st.markdown("""
1626
+ **Q: How are questions made unique?**
1627
 
1628
+ A: We use advanced AI with hash tracking to ensure you never see the same question twice, even across different sessions.
1629
 
1630
+ **Q: Why text-based questions only?**
1631
 
1632
+ A: We focus purely on mathematical reasoning and calculations, avoiding visual puzzles that don't translate well to text interfaces.
1633
 
1634
+ **Q: How does the timer work in timed mode?**
1635
 
1636
+ A: You get exactly 1 minute per question. For 5 questions, you get 5 minutes total. The timer is prominently displayed above the questions.
1637
 
1638
  **Q: What if I get stuck on a question?**
1639
 
1640
  A: Use the hint feature! We provide helpful hints to guide your thinking without giving away the answer.
1641
 
1642
+ **Q: How is my progress tracked?**
1643
 
1644
+ A: We track your accuracy, response times, difficulty preferences, and improvement trends with detailed analytics.
1645
 
1646
  **Q: How do I earn badges?**
1647
 
 
1654
  <div style="text-align: center; padding: 2rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; color: white;">
1655
  <h3 style="color: white; margin-bottom: 1rem;">Ready to become a Math Genius? ๐Ÿงฎ</h3>
1656
  <p style="color: rgba(255,255,255,0.9); margin: 0;">
1657
+ Join thousands of students improving their mathematical skills with unique, challenging questions every day!
1658
  </p>
1659
  </div>
1660
  """, unsafe_allow_html=True)
 
1663
  st.markdown("**Made with โค๏ธ for young mathematicians everywhere!**")
1664
 
1665
  if __name__ == "__main__":
1666
+ main()
1667
+