import os import sys current_dir = os.path.dirname(os.path.abspath(__file__)) parent_dir = os.path.dirname(current_dir) sys.path.append(parent_dir) import streamlit as st import numpy as np import pandas as pd import tensorflow as tf import pickle from utils.data_helper import * from environment import raw_world as Env from utils import irl_helper from utils.trajectory_comparison import analyze_chapter_engagement from typing import List, Dict, Union, Tuple import plotly.graph_objects as go st.set_page_config(layout="wide") from utils.evaluation import * from utils.rnn_models import * from PIL import Image import re def performance_prediction(model, x_test): y_pred = model.predict(x_test) y_pred = np.array([1 if y[0] >= 0.5 else 0 for y in y_pred]) num_fail = sum(y_pred) num_pass = len(y_pred) - num_fail return num_fail, num_pass, y_pred def extract_signal_name_regex(input_string): match = re.match(r"^\d+\.\d\s*-\s*(.*?)\s*\[\d{1,2}:\d{2}\]$", input_string) if match: return match.group(1).strip() return input_string # Return original if no match def plot_performance(df=None, show_image=False, show_performance=False, col_image=None, col_performance=None): if show_image and col_image is not None: with col_image: fig = Image.open('streamlit-assets/whatif_results_week_6.png') st.image(fig, caption="Predicted effectiveness of interventions in week 6. \ For example, adding content from topic 7 might improve students' performance, \ while adding content from week 2 might harm", use_container_width=True) elif show_image and col_image is None: fig = Image.open('streamlit-assets/whatif_results_week_6.png') st.image(fig, caption="Predicted effectiveness of interventions in week 6. \ For example, adding content from topic 7 might improve students' performance, \ while adding content from week 2 might harm", use_container_width=False) if show_performance and df is not None and col_performance is not None: with col_performance: fig = go.Figure() bar_height_px = 250 total_height = bar_height_px * len(df) fig.add_trace(go.Bar( y=df['class'], x=df['fail_percent'], name='Fail', orientation='h', text=[f'{x:.1f}%' for x in df['fail_percent']], textfont=dict(size=25), textposition='auto', customdata=df[['fail_num']], hovertemplate='%{y}
No. Students: %{customdata[0]}', marker=dict(color='#c0392b') )) fig.add_trace(go.Bar( y=df['class'], x=df['pass_percent'], name='Pass', orientation='h', text=[f'{x:.1f}%' for x in df['pass_percent']], textfont=dict(size=25), textposition='auto', customdata=df[['pass_num']], hovertemplate='%{y}
No. Students: %{customdata[0]}', marker=dict(color='#27ae60') )) fig.update_layout( barmode='stack', xaxis=dict(title='Percentage', range=[0, 100]), legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1), height=total_height, margin=dict(l=200, r=40, t=120, b=60), ) st.plotly_chart(fig, use_container_width=True) def compare_chapter_engagement(syn_trajectories: Union[List, np.ndarray, pd.Series], real_trajectories: Union[List, np.ndarray, pd.Series], world, save_dir=None) -> Tuple[Dict, Dict]: """ Compare engagement metrics between real and synthetic students per chapter Parameters: ----------- real_trajectories : Union[List, np.ndarray, pd.Series] Real student trajectories syn_trajectories : Union[List, np.ndarray, pd.Series] Synthetic student trajectories world : ClickstreamWorld World object containing state and action information save_dir : str, optional Directory to save visualizations Returns: -------- Tuple[Dict, Dict]: Real and synthetic engagement metrics """ st.header("Engagement Analysis: Before vs. After Intervention") real_metrics = analyze_chapter_engagement(real_trajectories, world, "Real") # print('finished real metrics, starting synthetic metrics') syn_metrics = analyze_chapter_engagement(syn_trajectories, world, "Synthetic") chapter_id_to_name = st.session_state.metadata.set_index('Topic')['Skills'].to_dict() metrics_to_plot = [ ('visit_count', 'Total Visits'), ('completion_rate', 'Completion Rate (%)'), ('problem_attempts', 'Quiz Attempts'), ('video_views', 'Video Views') ] increase_color = '#27ae60' # Darker green decrease_color = '#c0392b' neutral_color = '#bdc3c7' plotly_figures = [] for idx, (metric, ylabel) in enumerate(metrics_to_plot): chapters = sorted(set(real_metrics.keys()) | set(syn_metrics.keys())) chapters_name = [chapter_id_to_name.get(ch, f'Chapter {ch}') for ch in chapters] real_values = [real_metrics.get(ch, {}).get(metric, 0) for ch in chapters] syn_values = [syn_metrics.get(ch, {}).get(metric, 0) for ch in chapters] pct_diffs = [] colors = [] for real_val, syn_val in zip(real_values, syn_values): if metric == 'completion_rate': # print(syn_val, real_val) pct_diff = syn_val - real_val colors.append(increase_color if pct_diff > 0 else decrease_color) else: if real_val > 0: pct_diff = ((syn_val - real_val) / real_val) * 100 colors.append(increase_color if pct_diff > 0 else decrease_color) else: # print(real_values, syn_values, metric, chapters_name) # pct_diff = 100 if syn_val > 0 else 0 pct_diff = 0 colors.append(increase_color if syn_val > 0 else neutral_color) pct_diffs.append(pct_diff) fig = go.Figure() fig.add_trace( go.Bar( x=chapters, y=pct_diffs, marker=dict(color=colors, line=dict(color='#2c3e50', width=0.5)), name=ylabel, showlegend=False, text=[f'{val:+.1f}%' for val in pct_diffs], textposition='auto', hovertemplate='%{x}
%{y:.1f}%', width=0.6 ) ) fig.update_layout( title=dict( text=ylabel, x=0.5, xanchor='center', font=dict(size=20) ), font=dict(size=14), margin=dict(l=50, r=40, t=60, b=50), hovermode="x unified", bargap=0.25, template='plotly' ) fig.update_xaxes( title_text='Topic', title_font=dict(size=12), tickvals=chapters, ticktext=[f'{ch}' for ch in chapters_name], tickfont=dict(size=13), showgrid=False, zeroline=False ) fig.update_yaxes( title_text='Change (%)', title_font=dict(size=16), tickfont=dict(size=13), zeroline=True, zerolinecolor='white', zerolinewidth=1.5, showgrid=True, ) plotly_figures.append(fig) col1, col2 = st.columns(2) with col1: st.caption("Total Visits: Number of interactions made by students for every course material group by Topic") st.plotly_chart(plotly_figures[0], use_container_width=True) with col2: st.caption("""Completion Rate: The percentage of course materials that students interacted with within each topic. A value of 100% means the student engaged with every material in that topic at least once.\\ For example: if there are 5 course material in a topic, the completion rate would be 80% if they interacted with 4 unique materials.""") st.plotly_chart(plotly_figures[1], use_container_width=True) col3, col4 = st.columns(2) with col3: st.caption("Quiz Attempts: Number of quiz attempts made by students for every quiz group by Topic") st.plotly_chart(plotly_figures[2], use_container_width=True) with col4: st.caption("Video Views: Number of video views made by students for every video group by Topic") st.plotly_chart(plotly_figures[3], use_container_width=True) def add_new_state(world, trajectories, event_data, week_list=range(7,11), test_ids=None, OUTPUT_DIR='streamlit-assets/dsp-004', trajectories_each_week=None, trajectories_each_week_pass=None, trajectories_each_week_fail=None, history_whatif_pass=None, history_whatif_fail=None, fail_only=False): print("Adding new state with parameters:", event_data) results = { 'week': [], 'real_trajectories': [], 'syn_trajectories': [], 'world': [] } for week in week_list: print(f"\nProcessing week {week}") world._reset_transition_prob_table() world._update_transition_prob_table(trajectories) # add_feature_matrix = world.designed_features(world.values) chapter = event_data['chapter'] is_problem = True if event_data['event_type'] in 'quiz' else False value = float(event_data['difficulty']) if is_problem else float(event_data['duration']) print(f'Adding event - Topic: {chapter}, Type: {"Quiz" if is_problem else "Video"}, Value: {value}') add_feature_matrix = world.add_new_state(chapter=chapter, value=value, is_problem=is_problem, predict=False) pred = pd.read_csv(f'{OUTPUT_DIR}/predictions_weeks_{week}.csv') pred = pred['y_pred'] if test_ids is None: real_student = trajectories_each_week[week-1] else: real_student = trajectories_each_week[week-1][test_ids] if fail_only: real_student = [real_student.iloc[i] for i in range(len(real_student)) if pred[i] == 1] else: real_student = [real_student.iloc[i] for i in range(len(real_student))] syn_students = irl_helper.make_syn_student_personalized( trajectories_pass=[trajectories_each_week_pass[week-1]], trajectories_fail=[trajectories_each_week_fail[week-1]], students=[trajectories_each_week[week-1]], history_whatif_pass=[history_whatif_pass[week-1]], history_whatif_fail=[history_whatif_fail[week-1]], feature_matrix=add_feature_matrix, world=world, num_week=1, is_start_state_arr=False, test_labels=pred, new_state=True, test_ids=test_ids, fail_only=fail_only ) syn_students = [syn_students[i][0] for i in range(len(syn_students))] if len(week_list) > 1: results['week'].append(week) results['syn_trajectories'].append(syn_students) results['real_trajectories'].append(real_student) results['world'].append(world) else: return syn_students, real_student, world return results def get_top_rewards_per_week(df, top_n=3): # Ensure 'Week' is a column to group by if 'Week' not in df.columns: st.error(f"DataFrame is missing 'Week' column: {df.columns}") return pd.DataFrame() df_sorted = df.sort_values(by=['Week', 'Reward'], ascending=[True, False]) top_n_df = df_sorted.groupby('Week').head(top_n).reset_index(drop=True) return top_n_df def show_highest_rewards(course_id='dsp-002'): st.header("🏆 Popular Picks by Passing Students by Week") st.markdown("This section displays the top 3 videos and quizzes each week that passing students in previous year are most likely to interact with.") df_video = pd.read_csv(f'streamlit-assets/{course_id}/sorted_reward_video.csv') df_problem = pd.read_csv(f'streamlit-assets/{course_id}/sorted_reward_problem.csv') top_3_video = get_top_rewards_per_week(df_video, top_n=3) top_3_problem = get_top_rewards_per_week(df_problem, top_n=3) tab_video, tab_problem = st.tabs(["Videos", "Quizzes"]) # merge df and top_3_video on 'chapter' top_3_video = top_3_video.merge(st.session_state.metadata[['Topic', 'Skills']], left_on='chapter', right_on='Topic', how='left') top_3_problem = top_3_problem.merge(st.session_state.metadata[['Topic', 'Skills']], left_on='chapter', right_on='Topic', how='left') with tab_video: # st.subheader("Top Videos by Week") if not top_3_video.empty: weeks = sorted(top_3_video['Week'].unique()) week_tabs = st.tabs([f"Week {w+1}" for w in weeks]) for week, week_tab in zip(weeks, week_tabs): with week_tab: st.markdown(f"**Top Videos - Week {week+1}**") week_data = top_3_video[top_3_video['Week'] == week] week_data['duration'] = week_data['duration'].apply(lambda x: f"{int(x)//60} min {int(x)%60} sec") week_data['title'] = week_data['title'].apply(extract_signal_name_regex) display_cols = ['title', 'Skills', 'subchapter', 'duration'] rename_cols = {'title': 'Name', 'Skills': 'Topic', 'subchapter': 'Sub-Topic','duration': 'Duration'} display_data = week_data[display_cols].dropna(axis=1, how='all').rename(columns=rename_cols) st.dataframe(display_data, use_container_width=False, hide_index=True) else: st.info("No video event data available to display top rewards.") with tab_problem: # st.subheader("Top Quizzes by Week") if not top_3_problem.empty: weeks = sorted(top_3_problem['Week'].unique()) week_tabs = st.tabs([f"Week {w+1}" for w in weeks]) for week, week_tab in zip(weeks, week_tabs): with week_tab: st.markdown(f"**Top Quizzes - Week {week+1}**") week_data = top_3_problem[top_3_problem['Week'] == week] week_data['Difficulty'] = week_data['Difficulty'].apply(lambda x: f"{x:.2f}") display_cols = ['title', 'chapter', 'subchapter', 'grade_max', 'Difficulty'] rename_cols = {'title': 'Name', 'chapter': 'Topic', 'subchapter': 'Sub-Topic', 'grade_max': 'Max Grade', 'Difficulty': 'Difficulty Level (0-1)'} display_data = week_data[display_cols].dropna(axis=1, how='all').rename(columns=rename_cols) st.dataframe(display_data, use_container_width=False, hide_index=True) else: st.info("No problem event data available to display top rewards.") class StateManager: def __init__(self, **kwargs): self._kwargs = kwargs week = kwargs.get('week_list', [6])[0] course_id = 'dsp-004' DATA_DIR = f'streamlit-assets/{course_id}/' model_path = { 5: 'lstm-bi-32-64-5-1722490972.1859/model.keras_final_e.keras', 6: 'lstm-bi-32-64-6-1722494926.4949/model.keras_final_e.keras', 7: 'lstm-bi-32-64-7-1722499225.71723/model.keras_final_e.keras', 8: 'lstm-bi-32-64-8-1722504182.3553/model.keras_final_e.keras', 9: 'lstm-bi-32-64-9-1722511435.7777/model.keras_final_e.keras', 10: 'lstm-bi-32-64-10-1722519098.62673/model.keras_final_e.keras', } self.reconstructed_model = tf.keras.models.load_model(f'checkpoints/{model_path[week]}') # self.test_ids = np.load(f'{DATA_DIR}/test_students_5.npy') self.test_ids = None self.x_test = np.load(f'{DATA_DIR}/real-data-early-prediction_{course_id}_1to10_ver2.npy_features.npy') print(f"Loaded x_test shape: {self.x_test.shape}") def show_metadata(): st.subheader("Course Information") st.markdown("""On the left is the skill set for each topic; on the right is a visual representation of the prerequisite skill structure. The taught skills are divided into three categories: 1. Core skills: Fundamental skills that are essential for understanding the course material. 2. Applied skills: Skills that apply the core skills in practical scenarios. 3. Theory-based extension: Advanced skills that build upon the core skills. """) col1, col2 = st.columns([1, 2]) df = pd.read_csv('streamlit-assets/dsp_prerequisite_skills_2.csv') st.session_state.metadata = df with col1: st.subheader("📋 Topic Table") st.dataframe(df[['Topic', 'Skills', 'No. Videos', 'No. Quizzes']], use_container_width=False, hide_index=True) with col2: st.subheader("Prerequisite skill structure") fig = Image.open('streamlit-assets/prerequisite_skills_structure.png') st.image(fig, caption='**Blue**: Core skills, **Pink**: Applied skills, **Purple**: Theory-based extension', use_container_width=False) if 'current_page' not in st.session_state: st.session_state.current_page = 1 def show_navigation(): """Display navigation buttons""" col1, col2, col3 = st.columns(3) with col1: if st.button("Page 1: Course Description", key="nav1"): st.session_state.current_page = 1 with col2: if st.button("Page 2: Course Analysis", key="nav2"): st.session_state.current_page = 2 with col3: if st.button("Page 3: What-If Simulation", key="nav3"): st.session_state.current_page = 3 def get_reward(history_whatif, whatif_world, feature_matrix): transition_probability = whatif_world.p_transition for i in range(len(history_whatif)): alpha = history_whatif[i]['alpha'] reward = feature_matrix @ alpha not_valid_state_action = transition_probability.sum(axis=-1) <= 0 reward[not_valid_state_action] = np.nan reward = (reward - np.nanmean(reward.flatten())) / np.nanstd(reward.flatten()) reward[not_valid_state_action] = -np.inf history_whatif[i]['reward'] = reward return history_whatif def load_data(): """Load all required data (only once)""" if 'data_loaded' not in st.session_state: # try: course_id = 'dsp-004' # Load trajectory data with open(f'streamlit-assets/{course_id}/trajectories_each_week.pkl', 'rb') as f: st.session_state.trajectories_each_week = pickle.load(f) with open(f'streamlit-assets/{course_id}/trajectories_each_week_pass.pkl', 'rb') as f: st.session_state.trajectories_each_week_pass = pickle.load(f) with open(f'streamlit-assets/{course_id}/trajectories_each_week_fail.pkl', 'rb') as f: st.session_state.trajectories_each_week_fail = pickle.load(f) with open(f'streamlit-assets/{course_id}/trajectories.pkl', 'rb') as f: st.session_state.trajectories = pickle.load(f) with open('history_whatif_fail_300.pkl', 'rb') as f: history_whatif_fail = pickle.load(f) with open('history_whatif_pass_300.pkl', 'rb') as f: history_whatif_pass = pickle.load(f) # Load course data # st.session_state.combinedg = pd.read_csv(f'data/{course_id}/combinedg_features_{course_id}_processed.csv') # Load dictionaries and mappings with open(f'streamlit-assets/{course_id}/dict_event.pkl', 'rb') as f: st.session_state.dict_event = pickle.load(f) with open(f'streamlit-assets/{course_id}/dict_action.pkl', 'rb') as f: st.session_state.dict_action = pickle.load(f) # with open(f'data/{course_id}/map_week.pkl', 'rb') as f: # st.session_state.map_week = pickle.load(f) # with open(f'data/{course_id}/map_event_id.pkl', 'rb') as f: # st.session_state.map_event_id = pickle.load(f) # with open(f'data/{course_id}/map_action.pkl', 'rb') as f: # st.session_state.map_action = pickle.load(f) with open(f'streamlit-assets/{course_id}/problem_event.pkl', 'rb') as f: st.session_state.problem_event = pickle.load(f) with open(f'streamlit-assets/{course_id}/video_event.pkl', 'rb') as f: st.session_state.video_event = pickle.load(f) pred = pd.read_csv(f'streamlit-assets/{course_id}/predictions_weeks_6.csv') st.session_state.pred = pred['y_pred'] # Load schedule and other data # st.session_state.schedule = pd.read_csv(f'{datadir}/schedule/{course_id}.csv') # st.session_state.values_df, st.session_state.duration = whatif_values(st.session_state.combinedg, # st.session_state.schedule, # map_event_id=st.session_state.map_event_id, # problem_event=st.session_state.problem_event) # dump to numpy file and load when needed # st.session_state.values_df.to_csv(f'streamlit-assets/value_df_duration_{course_id}.csv') st.session_state.values_df = pd.read_csv(f'streamlit-assets/value_df_duration_{course_id}.csv') st.session_state.duration = np.load(f'streamlit-assets/duration_{course_id}.npy') # print(f"Loaded values shape: {st.session_state.values_df}") # st.session_state.ids = np.load(f'results/whatif/{course_id}//test_students_5.npy') st.session_state.ids = None st.session_state.world = Env.ClickstreamWorld(trajectories=st.session_state.trajectories, dict_action=st.session_state.dict_action, dict_event=st.session_state.dict_event, video_arr=st.session_state.video_event, problem_arr=st.session_state.problem_event, values=st.session_state.values_df, add_state=True) # print(len(st.session_state.world.values)) feature_matrix = st.session_state.world.designed_features(st.session_state.world.values) st.session_state.history_whatif_fail = get_reward(history_whatif_fail, st.session_state.world, feature_matrix=feature_matrix) st.session_state.history_whatif_pass = get_reward(history_whatif_pass, st.session_state.world, feature_matrix=feature_matrix) st.session_state.state_manager = StateManager(world=st.session_state.world, trajectories=st.session_state.trajectories, test_ids=st.session_state.ids, trajectories_each_week=st.session_state.trajectories_each_week, trajectories_each_week_pass=st.session_state.trajectories_each_week_pass, trajectories_each_week_fail=st.session_state.trajectories_each_week_fail, history_whatif_pass=st.session_state.history_whatif_pass, history_whatif_fail=st.session_state.history_whatif_fail, week_list=[6]) st.session_state.data_loaded = True def page_1_description(): st.title("What-if Classroom for Course Design: Digital Signal Processing (DSP)") st.markdown( """

Description:

You are teaching a 10-week Digital Signal Processing (DSP) course on a MOOC platform and have just completed the first 5 weeks. You're now considering whether to add new content in week 6. The platform provides access to detailed clickstream data that tracks how students in previous years interacted with videos and quizzes. You want to identify which content is most effective in supporting student success. This demo walks you through how to use our simulation model to preview the impact of adding new materials before implementing them in reality. """, unsafe_allow_html=True ) st.markdown("---") st.header("Course Overview") st.markdown(""" **Course:** Digital Signal Processing (DSP) **Platform:** MOOC (Massive Open Online Course) **Duration:** 10 weeks **Current Status:** Completed first 5 weeks """) try: show_metadata() except: st.info("Metadata display function not available in this demo.") st.markdown("---") col1, col2, col3 = st.columns([1, 1, 1]) with col3: if st.button("Next: Course Analysis →", key="next_page_1", type="primary"): st.session_state.current_page = 2 st.rerun() def page_2_analysis(): st.title("Course Material Analysis") if st.session_state.get('data_loaded', False): # st.warning("Loading course data...") load_data() if not st.session_state.get('data_loaded', False): st.error("Failed to load data. Please check data files.") st.header("Top Performing Course Materials") try: show_highest_rewards() except: st.info("Highest rewards analysis not available in this demo.") st.header("Current Student Performance Prediction") plot_performance(show_image=True) # col1, col2 = st.columns([1, 2]) # with col1: # try: # except: # st.info("Performance visualization not available.") # print(st.session_state.get('data_loaded')) # with col2: # if st.session_state.get('data_loaded', True): # try: # fail_num, pass_num, _ = performance_prediction( # st.session_state.state_manager.reconstructed_model, # st.session_state.state_manager.x_test # ) # st.session_state.real_pred_df = pd.DataFrame({ # 'class': ['Predicted Performance Before Intervention'], # 'fail_num': [fail_num], # 'pass_num': [pass_num], # 'fail_percent': [fail_num / (fail_num + pass_num) * 100], # 'pass_percent': [pass_num / (fail_num + pass_num) * 100] # }) # st.subheader("Performance Metrics") # plot_performance(df=st.session_state.real_pred_df, # show_performance=True, col_performance=col2) # except Exception as e: # print(f"Error calculating performance prediction: {str(e)}") # else: # st.info("Data not loaded. Cannot display performance metrics.") st.header("Week 6 Intervention Opportunity") st.markdown(""" Based on the analysis of the first 5 weeks, we can now simulate the impact of introducing new content in week 6. The next page allows you to experiment with different interventions and see their predicted effects on student performance. """) st.markdown("---") col1, col2, col3 = st.columns([1, 1, 1]) with col1: if st.button("← Previous: Course Description", key="prev_page_2"): st.session_state.current_page = 1 st.rerun() with col3: if st.button("Next: What-If Simulation →", key="next_page_2", type="primary"): st.session_state.current_page = 3 st.rerun() def page_3_simulation(): show_navigation() st.title("What-If Simulation: Week 6 Interventions") st.header("Simulation Capabilities") st.markdown(""" Our simulation model allows you to: - Preview the impact of new content before implementation - Test different content types (videos, quizzes) and difficulties - Target interventions for specific student populations - Analyze engagement patterns and predict performance outcomes """) if not st.session_state.get('data_loaded', False): st.warning("Loading course data...") load_data() if not st.session_state.get('data_loaded', False): st.error("Failed to load data. Please check data files.") return # Sidebar controls st.header("Introduce New Course Content") st.markdown("You are teaching a 10-week Digital Signal Processing (DSP) course on a MOOC platform and have just completed the first 5 weeks. You're now considering whether to add new content in week 6.\ You want to identify which content is most effective in supporting student success by trying different interventions.") # chapter = st.sidebar.slider('Topic:', 1, 10, 1) st.write("**Step 1: Please select the topic the new content covers**") selected_chapter_name = st.selectbox( 'Topic:', options=st.session_state.metadata['Skills'], # What the user sees in the dropdown index=0 # Default selected option (index of 'Introduction to DSP') ) chapter = st.session_state.metadata[st.session_state.metadata['Skills'] == selected_chapter_name]['Topic'].values[0] st.write("**Step 2: Please select the type of course material you want to add, either a quiz or a video**") event_type = st.selectbox('Course Material Type:', ['quiz', 'video']) max_duration = st.session_state.duration[0]//60 if 'duration' in st.session_state else 0 min_duration = st.session_state.duration[1]//60 if 'duration' in st.session_state else 60 st.write("**Step 3: Please select the difficulty of the quiz or the duration of the video.**\ \nDifficulty: value between 0 and 1; easiest 0, hardest: 1.\ \nDuration: in minutes; shortest: 2-minute, longest: 25-minute.") if event_type == 'quiz': value_label = 'Difficulty:' value = st.slider(value_label, 0.0, 1.0, 0.5, 0.01) else: value_label = 'Duration (minutes):' value = st.slider(value_label, min_duration, max_duration, min_duration, 0.5) # Duration in minutes st.write("**Step 4: Please select whether you want to focus on low-performed students only.**") fail_only = st.selectbox('Simulate low-performed students only:', [False, True], format_func=lambda x: 'Yes' if x else 'No') # Main content area st.subheader("Intervention Settings: ready to run simulation") col1, col2 = st.columns([1, 1]) with col1: st.write(f"**Topic:** {chapter}") st.write(f"**Content Type:** {event_type.title()}") with col2: st.write(f"**{value_label}** {value:.2f}") st.write(f"**Target Low-Performers Only:** {'Yes' if fail_only else 'No'}") if st.button('Run Simulation', key='add_state_button'): st.session_state.world = Env.ClickstreamWorld(trajectories=st.session_state.trajectories, dict_action=st.session_state.dict_action, dict_event=st.session_state.dict_event, video_arr=st.session_state.video_event, problem_arr=st.session_state.problem_event, values=st.session_state.values_df, add_state=True) event_data = { 'chapter': chapter, 'event_type': event_type, 'difficulty': value, 'duration':(value - min_duration)/(max_duration - min_duration), 'fail_only': fail_only } # print(len(st.session_state.state_manager.world.values), len(st.session_state.values_df)) with st.spinner("Analyzing trajectories and simulating intervention..."): try: results = add_new_state(world=st.session_state.world, event_data=event_data, week_list=[6], test_ids=st.session_state.state_manager.test_ids, trajectories=st.session_state.trajectories, trajectories_each_week=st.session_state.trajectories_each_week, trajectories_each_week_pass=st.session_state.trajectories_each_week_pass, trajectories_each_week_fail=st.session_state.trajectories_each_week_fail, history_whatif_pass=st.session_state.history_whatif_pass, history_whatif_fail=st.session_state.history_whatif_fail, fail_only=fail_only) st.session_state.results = results st.success("Simulation completed! ") except Exception as e: st.error(f"Simulation failed: {str(e)}") # Display results if they exist if 'results' in st.session_state: st.header("Simulation Results") try: import utils.data_helper as data_helper if 'real_pred_df' not in st.session_state: fail_num, pass_num, _ = performance_prediction( st.session_state.state_manager.reconstructed_model, st.session_state.state_manager.x_test ) st.session_state.real_pred_df = pd.DataFrame({ 'class': ['Predicted Performance Before Intervention'], 'fail_num': [fail_num], 'pass_num': [pass_num], 'fail_percent': [fail_num / (fail_num + pass_num) * 100], 'pass_percent': [pass_num / (fail_num + pass_num) * 100] }) if isinstance(st.session_state.results, dict): syn = st.session_state.results['syn_trajectories'] real = st.session_state.results['real_trajectories'] world = st.session_state.results['world'] else: syn, real, world = st.session_state.results course_features = data_helper.trajectories_to_features( syn, world, num_week=1, syn=True, save_to_disk=False ) if not fail_only: x_test = np.concatenate((st.session_state.state_manager.x_test[:, :(st.session_state.state_manager.x_test.shape[0]-100), :], course_features), axis=1) else: x_test = np.concatenate((st.session_state.state_manager.x_test[st.session_state.pred == 1, :(st.session_state.state_manager.x_test.shape[0]-100), :], course_features), axis=1) fail_num, pass_num, _ = performance_prediction( st.session_state.state_manager.reconstructed_model, x_test ) temp_df = pd.DataFrame({ 'class': ['Predicted Performance After Intervention'], 'fail_num': [fail_num], 'pass_num': [pass_num], 'fail_percent': [fail_num / (fail_num + pass_num) * 100], 'pass_percent': [pass_num / (fail_num + pass_num) * 100] }) if not fail_only: merged_df = pd.concat([temp_df, st.session_state.real_pred_df], ignore_index=True) st.session_state.performance_df = merged_df st.session_state.num_students = temp_df.iloc[0]['fail_num'] + temp_df.iloc[0]['pass_num'] else: st.session_state.fail_df = pd.DataFrame({ 'class': ['Predicted Performance After Intervention'], 'fail_num': st.session_state.real_pred_df.fail_num, 'pass_num': [0], 'fail_percent': 100, 'pass_percent': 0 }) merged_df = pd.concat([temp_df, st.session_state.fail_df], ignore_index=True) st.session_state.performance_df = merged_df # Performance comparison st.subheader("Performance Prediction: Before and After Intervention") col1, col2 = st.columns(2) with col1: st.metric("Before Intervention - Pass Rate", f"{st.session_state.performance_df.iloc[-1]['pass_percent']:.1f}%") st.metric("Before Intervention - Students Passing", f"{int(st.session_state.performance_df.iloc[-1]['pass_num'])} / {st.session_state.num_students} Students") with col2: new_pass_rate = temp_df.iloc[0]['pass_percent'] old_pass_rate = st.session_state.performance_df.iloc[-1]['pass_percent'] improvement = new_pass_rate - old_pass_rate st.metric("After Intervention - Pass Rate", f"{new_pass_rate:.1f}%", f"{improvement:+.1f}%") st.metric("After Intervention - Students Passing", f"{int(temp_df.iloc[0]['pass_num'])} / {st.session_state.num_students} Students", int(temp_df.iloc[0]['pass_num'] - st.session_state.performance_df.iloc[-1]['pass_num'])) # Visualization try: plot_performance(st.session_state.performance_df, show_performance=True) except: st.info("Performance visualization not available.") try: compare_chapter_engagement(syn, real, world) except Exception as e: raise e st.info(f"Engagement analysis not available: {str(e)}") except Exception as e: raise e st.error(f"Error processing simulation results: {str(e)}") # elif 'real_pred_df' not in st.session_state: # st.info("Please visit Page 2 first to load baseline performance predictions.") else: st.info("Run a simulation using the controls to see results.") st.markdown("---") col1, col2, col3 = st.columns([1, 1, 1]) with col1: if st.button("← Previous: Course Analysis", key="prev_page_3"): st.session_state.current_page = 2 st.rerun() def main(): # Load data once at startup load_data() # Navigation # show_navigation() # st.markdown("---") # Display current page if st.session_state.current_page == 1: page_1_description() elif st.session_state.current_page == 2: page_2_analysis() elif st.session_state.current_page == 3: page_3_simulation() # Page indicator st.markdown("---") st.markdown(f"**Current Page: {st.session_state.current_page} of 3**") if __name__ == "__main__": main()