math / app.py
Sina Media Lab
Updates
15d45d1
raw
history blame
9.23 kB
import streamlit as st
import os
from fpdf import FPDF
import uuid
# Initialize session state variables
if 'session_id' not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
if 'questions' not in st.session_state:
st.session_state.questions = []
if 'current_index' not in st.session_state:
st.session_state.current_index = 0
if 'current_module' not in st.session_state:
st.session_state.current_module = None
if 'correct_count' not in st.session_state:
st.session_state.correct_count = 0
if 'module_correct_count' not in st.session_state:
st.session_state.module_correct_count = {}
if 'module_question_count' not in st.session_state:
st.session_state.module_question_count = {}
if 'pdf_data' not in st.session_state:
st.session_state.pdf_data = None
if 'selected_answer' not in st.session_state:
st.session_state.selected_answer = None
if 'button_label' not in st.session_state:
st.session_state.button_label = "Submit/New"
def reset_pdf_cache():
st.session_state.pdf_data = None
def generate_pdf_report():
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", size=8)
pdf.cell(200, 8, txt="Quiz Report", ln=True, align="C")
pdf.ln(8)
for i, entry in enumerate(st.session_state.questions):
# Zebra background
if i % 2 == 0:
pdf.set_fill_color(245, 245, 245) # Light gray
else:
pdf.set_fill_color(255, 255, 255) # White
# Module Title
pdf.set_font("Arial", style='B', size=8)
pdf.multi_cell(0, 8, f"Module: {entry['module']}", border=1, fill=True)
# Question
pdf.set_font("Arial", style='B', size=8)
pdf.multi_cell(0, 8, f"Q{i+1}: {entry['question']}", border=1, fill=True)
# Options
pdf.set_font("Arial", size=8)
options = ['a', 'b', 'c', 'd']
for j, option in enumerate(entry['options']):
if option == entry['correct_answer']:
pdf.set_text_color(0, 128, 0) # Green for correct
elif option == entry['selected']:
pdf.set_text_color(255, 0, 0) # Red for incorrect
else:
pdf.set_text_color(0, 0, 0) # Default color for others
pdf.multi_cell(0, 8, f"{options[j]}. {option}", border=1, fill=True)
pdf.set_text_color(0, 0, 0) # Reset color
pdf.multi_cell(0, 8, f"Explanation: {entry['explanation']}", border=1, fill=True)
pdf.multi_cell(0, 8, f"Step-by-Step Solution: {', '.join(entry['step_by_step_solution'])}", border=1, fill=True)
pdf.ln(8)
return pdf.output(dest='S').encode('latin1', 'replace')
def load_modules():
modules = {}
module_dir = "modules"
for filename in os.listdir(module_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3]
# Dynamically import the module only when needed
module = __import__(f"{module_dir}.{module_name}", fromlist=[''])
modules[module_name] = {
"title": getattr(module, "title", module_name),
"description": getattr(module, "description", "No description available."),
"generate_question": module.generate_question # Access the generate_question function
}
return modules
def generate_new_question(module_name, module):
question_data = module['generate_question']()
# Ensure 'answered' is initialized to False and add the 'module' and 'selected' keys
question_data['answered'] = False
question_data['module'] = module_name # Add the module name to the question data
question_data['selected'] = None # Initialize 'selected' to None
# Ensure there are exactly 4 options
if len(question_data['options']) != 4:
st.warning(f"Question in module '{module_name}' does not have 4 options. Found {len(question_data['options'])}.")
return question_data
def navigate_question(direction):
if direction == "prev" and st.session_state.current_index > 0:
st.session_state.current_index -= 1
elif direction == "next" and st.session_state.current_index < len(st.session_state.questions) - 1:
st.session_state.current_index += 1
# Load all modules dynamically
modules = load_modules()
# Streamlit interface
st.sidebar.title("Quiz Modules")
module_name = st.sidebar.radio("Choose a module:", [modules[module]["title"] for module in modules], index=0)
selected_module = None
for module in modules:
if modules[module]["title"] == module_name:
selected_module = module
break
if selected_module != st.session_state.current_module:
st.session_state.current_module = selected_module
st.session_state.current_index = len(st.session_state.questions) # Continue numbering from previous questions
st.session_state.questions.append(generate_new_question(selected_module, modules[selected_module]))
st.session_state.module_question_count[selected_module] = st.session_state.module_question_count.get(selected_module, 0)
st.session_state.module_correct_count[selected_module] = st.session_state.module_correct_count.get(selected_module, 0)
st.session_state.selected_answer = None
st.session_state.button_label = "Submit/New"
# Load the current module's question
current_question = st.session_state.questions[st.session_state.current_index]
# Display module title and description with smaller font
st.markdown(f"<span style='font-size: 12px;'><b>{modules[selected_module]['title']}</b></span>", unsafe_allow_html=True)
st.markdown(f"<span style='font-size: 10px;'>{modules[selected_module]['description']}</span>", unsafe_allow_html=True)
# Navigation and PDF report buttons
col1, col2, col3 = st.columns([1, 1, 2])
with col1:
if st.button("⬅️ Prev", disabled=st.session_state.current_index == 0):
navigate_question("prev")
with col2:
if st.button("➡️ Next", disabled=st.session_state.current_index >= len(st.session_state.questions) - 1):
navigate_question("next")
with col3:
if any(q['answered'] for q in st.session_state.questions):
pdf = generate_pdf_report()
st.session_state.pdf_data = pdf # Reset PDF cache
st.download_button(
label="Download PDF Report 📄",
data=st.session_state.pdf_data,
file_name="quiz_report.pdf",
mime="application/pdf"
)
# Display the current question with larger font
st.markdown(f"<span style='font-size: 14px;'><b>Q{st.session_state.current_index + 1}: {current_question['question']}</b></span>", unsafe_allow_html=True)
# Create the form for the question
with st.form(key=f'question_form_{st.session_state.current_index}'):
options = ['a', 'b', 'c', 'd']
selected_answer = st.radio(
"Choose an answer:",
options=[f"{options[i]}. {opt}" for i, opt in enumerate(current_question['options'])],
key=f"question_{st.session_state.current_index}_options",
index=None if not current_question['answered'] else current_question['options'].index(current_question['selected']),
disabled=current_question['answered']
)
submit_button = st.form_submit_button(label="Submit/New", disabled=current_question['answered'])
# Handle button state and answer submission
if submit_button:
if selected_answer is None:
st.warning("Please select an option before submitting.", icon="⚠️")
else:
# Process the answer
selected_answer_text = selected_answer.split(". ", 1)[1] # Extract the text part
current_question['selected'] = selected_answer_text
current_question['answered'] = True
st.session_state.module_question_count[selected_module] += 1
if selected_answer_text == current_question['correct_answer']:
st.session_state.correct_count += 1
st.session_state.module_correct_count[selected_module] += 1
# Show correct/incorrect feedback, explanation, and step-by-step solution
st.markdown("<div style='background-color:#f0f0f0; padding: 10px;'>", unsafe_allow_html=True)
for i, option in enumerate(current_question['options']):
option_text = f"{options[i]}. {option}"
if option == current_question['correct_answer']:
st.markdown(f"<span style='color:green;'>{option_text} ✅</span>", unsafe_allow_html=True)
elif option == selected_answer_text:
st.markdown(f"<span style='color:red;'>{option_text} ❌</span>", unsafe_allow_html=True)
else:
st.markdown(f"{option_text}", unsafe_allow_html=True)
st.write(f"**Explanation:** {current_question['explanation']}")
st.write("**Step-by-Step Solution:**")
for step in current_question['step_by_step_solution']:
st.write(step)
st.markdown("</div>", unsafe_allow_html=True)
# Generate a new question after submission
new_question = generate_new_question(selected_module, modules[selected_module])
st.session_state.questions.append(new_question)
st.session_state.current_index = len(st.session_state.questions) - 1