diff --git a/README.md b/README.md
index 80d1a11..c0a53a9 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ pip install -r requirements.txt
### 3. Launch the Dashboard
```bash
-streamlit run streamlit_app.py
+streamlit run app.py
```
The dashboard will open in your browser at `http://localhost:8501`
@@ -90,9 +90,23 @@ The dashboard will open in your browser at `http://localhost:8501`
predictive_analysis/
├── data/
│ └── data.csv # Original dataset (4,424 students)
+├── src/ # Source code modules
+│ ├── config/ # Configuration files
+│ │ ├── __init__.py
+│ │ ├── theme.py # Rose Pine theme and constants
+│ │ └── mappings.py # Feature mappings and presets
+│ ├── utils/ # Utility functions
+│ │ ├── __init__.py
+│ │ └── model_utils.py # Model loading and preprocessing
+│ ├── components/ # UI components
+│ │ ├── __init__.py
+│ │ ├── form_components.py # Input form sections
+│ │ └── prediction_components.py # Results and charts
+│ └── __init__.py
├── predictive_analysis.ipynb # Original analysis notebook
├── train_and_save_model.py # Model training and pickle export
-├── streamlit_app.py # Main dashboard application
+├── streamlit_app.py # Legacy monolithic app (deprecated)
+├── app.py # New modular main application
├── requirements.txt # Python dependencies
├── random_forest_model.pkl # Trained model (generated)
├── preprocessing_info.pkl # Preprocessing pipeline (generated)
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..652baf5
--- /dev/null
+++ b/app.py
@@ -0,0 +1,117 @@
+"""
+Main Streamlit application for Student Success Predictor
+Modular version with separated components
+"""
+
+import streamlit as st
+import sys
+from pathlib import Path
+
+# Add src directory to path for imports
+src_path = Path(__file__).parent
+sys.path.append(str(src_path))
+
+from src.config.theme import ROSE_PINE_THEME, PAGE_CONFIG, APP_TITLE, APP_SUBTITLE
+from src.config.mappings import create_feature_mappings
+from src.utils.model_utils import load_model_and_info
+from src.components.form_components import (
+ render_preset_selector,
+ render_personal_info_section,
+ render_academic_background_section,
+ render_family_background_section,
+ render_financial_social_section,
+ render_academic_performance_section,
+ render_economic_indicators_section
+)
+from src.components.prediction_components import (
+ render_prediction_section,
+ render_model_info_sidebar,
+ render_export_section,
+ render_footer
+)
+
+def apply_theme():
+ """Apply the Rose Pine dark theme"""
+ st.markdown(ROSE_PINE_THEME, unsafe_allow_html=True)
+
+def render_header():
+ """Render the main application header"""
+ st.markdown(f'
{APP_TITLE}
', unsafe_allow_html=True)
+ st.markdown(f'{APP_SUBTITLE}
', unsafe_allow_html=True)
+
+def collect_user_input():
+ """Collect all user input from the sidebar form"""
+ feature_maps = create_feature_mappings()
+
+ with st.sidebar:
+ # Preset selector
+ preset = render_preset_selector()
+
+ # Personal information
+ personal_info = render_personal_info_section(preset, feature_maps)
+
+ # Academic background
+ academic_bg = render_academic_background_section(preset, feature_maps)
+
+ # Family background
+ family_bg = render_family_background_section(feature_maps)
+
+ # Financial and social factors
+ financial_social = render_financial_social_section()
+
+ # Academic performance
+ academic_performance = render_academic_performance_section(preset)
+
+ # Economic indicators
+ economic_indicators = render_economic_indicators_section()
+
+ # Combine all input data
+ user_input = {
+ **personal_info,
+ **academic_bg,
+ **family_bg,
+ **financial_social,
+ **academic_performance,
+ **economic_indicators
+ }
+
+ return user_input
+
+def main():
+ """Main Streamlit application"""
+ # Configure page
+ st.set_page_config(**PAGE_CONFIG)
+
+ # Apply theme
+ apply_theme()
+
+ # Load model and preprocessing info
+ model, preprocessing_info, feature_names = load_model_and_info()
+
+ # Render header
+ render_header()
+
+ # Collect user input
+ user_input = collect_user_input()
+
+ # Main content area
+ col1, col2 = st.columns([2, 1])
+
+ with col1:
+ st.markdown('', unsafe_allow_html=True)
+
+ # Render prediction section
+ render_prediction_section(model, preprocessing_info, user_input)
+
+ with col2:
+ # Render model info and validation
+ render_model_info_sidebar(user_input)
+
+ # Render export section
+ render_export_section(user_input)
+
+ # Render footer
+ render_footer()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
new file mode 100644
index 0000000..2c81ce9
--- /dev/null
+++ b/src/__init__.py
@@ -0,0 +1,3 @@
+"""
+Source package for Student Success Predictor
+"""
\ No newline at end of file
diff --git a/src/components/__init__.py b/src/components/__init__.py
new file mode 100644
index 0000000..93fc3d3
--- /dev/null
+++ b/src/components/__init__.py
@@ -0,0 +1,3 @@
+"""
+UI Components
+"""
\ No newline at end of file
diff --git a/src/components/form_components.py b/src/components/form_components.py
new file mode 100644
index 0000000..96d48e2
--- /dev/null
+++ b/src/components/form_components.py
@@ -0,0 +1,352 @@
+"""
+Form input components for the Student Success Predictor
+"""
+
+import streamlit as st
+from typing import Dict, Any, Tuple
+from ..config.mappings import create_feature_mappings, PRESET_CONFIGURATIONS, ACADEMIC_DEFAULTS
+
+def render_preset_selector() -> str:
+ """Render the preset selector section"""
+ st.markdown('', unsafe_allow_html=True)
+
+ if preset != "Custom Entry":
+ st.markdown(f'✅ Loading {preset} preset values. You can modify any field below.
', unsafe_allow_html=True)
+
+ return preset
+
+def render_personal_info_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
+ """Render personal information input section"""
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 👤 Personal Information")
+ st.markdown('Basic demographic information about the student
', unsafe_allow_html=True)
+
+ defaults = PRESET_CONFIGURATIONS[preset]
+
+ marital_status = st.selectbox(
+ "Marital Status",
+ options=list(feature_maps['marital_status'].keys()),
+ format_func=lambda x: feature_maps['marital_status'][x],
+ index=list(feature_maps['marital_status'].keys()).index(defaults["marital"])
+ )
+
+ gender = st.selectbox(
+ "Gender",
+ [0, 1],
+ format_func=lambda x: "Female" if x == 0 else "Male",
+ index=defaults["gender"]
+ )
+
+ age_at_enrollment = st.number_input(
+ "Age at Enrollment",
+ min_value=17,
+ max_value=70,
+ value=defaults["age"],
+ step=1
+ )
+
+ nationality = st.selectbox(
+ "Nationality",
+ [1, 2, 6, 11, 13, 14, 17, 21, 22, 24, 25, 26, 32, 41, 62, 100, 101, 103, 105, 108, 109],
+ index=0
+ )
+
+ return {
+ 'Marital status': marital_status,
+ 'Gender': gender,
+ 'Age at enrollment': age_at_enrollment,
+ 'Nationality': nationality
+ }
+
+def render_academic_background_section(preset: str, feature_maps: Dict) -> Dict[str, Any]:
+ """Render academic background input section"""
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 🎓 Academic Background")
+ st.markdown('Information about the student\'s application and academic history
', unsafe_allow_html=True)
+
+ defaults = PRESET_CONFIGURATIONS[preset]
+
+ application_mode = st.selectbox(
+ "Application Mode",
+ options=list(feature_maps['application_mode'].keys()),
+ format_func=lambda x: feature_maps['application_mode'].get(x, f"Mode {x}"),
+ index=list(feature_maps['application_mode'].keys()).index(defaults["app_mode"]) if defaults["app_mode"] in feature_maps['application_mode'] else 0
+ )
+
+ application_order = st.selectbox(
+ "Application Order (1st choice, 2nd choice, etc.)",
+ [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ index=0
+ )
+
+ course = st.selectbox(
+ "Course",
+ options=list(feature_maps['course'].keys()),
+ format_func=lambda x: feature_maps['course'].get(x, f"Course {x}"),
+ index=list(feature_maps['course'].keys()).index(defaults["course"]) if defaults["course"] in feature_maps['course'] else 0
+ )
+
+ daytime_evening = st.selectbox(
+ "Attendance",
+ [0, 1],
+ format_func=lambda x: "Evening" if x == 0 else "Daytime"
+ )
+
+ previous_qualification = st.selectbox(
+ "Previous Qualification",
+ options=list(feature_maps['qualification'].keys()),
+ format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
+ )
+
+ col1, col2 = st.columns(2)
+ with col1:
+ previous_qualification_grade = st.number_input(
+ "Previous Qualification Grade",
+ min_value=95,
+ max_value=190,
+ value=defaults["prev_grade"],
+ step=1
+ )
+ with col2:
+ admission_grade = st.number_input(
+ "Admission Grade",
+ min_value=95,
+ max_value=190,
+ value=defaults["adm_grade"],
+ step=1
+ )
+
+ return {
+ 'Application mode': application_mode,
+ 'Application order': application_order,
+ 'Course': course,
+ 'Daytime/evening attendance': daytime_evening,
+ 'Previous qualification': previous_qualification,
+ 'Previous qualification (grade)': previous_qualification_grade,
+ 'Admission grade': admission_grade
+ }
+
+def render_family_background_section(feature_maps: Dict) -> Dict[str, Any]:
+ """Render family background input section"""
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 👨👩👧👦 Family Background")
+ st.markdown('Parent education and occupation information
', unsafe_allow_html=True)
+
+ mothers_qualification = st.selectbox(
+ "Mother's Qualification",
+ options=list(feature_maps['qualification'].keys()),
+ format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
+ )
+
+ fathers_qualification = st.selectbox(
+ "Father's Qualification",
+ options=list(feature_maps['qualification'].keys()),
+ format_func=lambda x: feature_maps['qualification'].get(x, f"Qualification {x}")
+ )
+
+ mothers_occupation = st.selectbox(
+ "Mother's Occupation",
+ options=list(feature_maps['occupation'].keys()),
+ format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
+ )
+
+ fathers_occupation = st.selectbox(
+ "Father's Occupation",
+ options=list(feature_maps['occupation'].keys()),
+ format_func=lambda x: feature_maps['occupation'].get(x, f"Occupation {x}")
+ )
+
+ return {
+ 'Mothers qualification': mothers_qualification,
+ 'Fathers qualification': fathers_qualification,
+ 'Mothers occupation': mothers_occupation,
+ 'Fathers occupation': fathers_occupation
+ }
+
+def render_financial_social_section() -> Dict[str, Any]:
+ """Render financial and social factors input section"""
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 💰 Financial & Social")
+ st.markdown('Student financial status and special circumstances
', unsafe_allow_html=True)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ displaced = st.radio(
+ "Displaced Student",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+ educational_special_needs = st.radio(
+ "Educational Special Needs",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+ debtor = st.radio(
+ "Has Outstanding Debts",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+ with col2:
+ tuition_fees_up_to_date = st.radio(
+ "Tuition Fees Up to Date",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+ scholarship_holder = st.radio(
+ "Scholarship Holder",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+ international = st.radio(
+ "International Student",
+ [0, 1],
+ format_func=lambda x: "No" if x == 0 else "Yes",
+ horizontal=True
+ )
+
+ return {
+ 'Displaced': displaced,
+ 'Educational special needs': educational_special_needs,
+ 'Debtor': debtor,
+ 'Tuition fees up to date': tuition_fees_up_to_date,
+ 'Scholarship holder': scholarship_holder,
+ 'International': international
+ }
+
+def render_academic_performance_section(preset: str) -> Dict[str, Any]:
+ """Render academic performance input sections"""
+ academic_defaults = ACADEMIC_DEFAULTS[preset]
+
+ # 1st Semester
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 📚 1st Semester Performance")
+ st.markdown('⚠️ Important: Academic performance is a key predictor of student success
', unsafe_allow_html=True)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ curricular_units_1st_sem_enrolled = st.number_input(
+ "Enrolled Units", min_value=0, max_value=26,
+ value=academic_defaults["1st_sem"]["enrolled"], step=1,
+ key="1st_enrolled", help="Number of courses registered for"
+ )
+ curricular_units_1st_sem_approved = st.number_input(
+ "Approved Units", min_value=0, max_value=26,
+ value=academic_defaults["1st_sem"]["approved"], step=1,
+ key="1st_approved", help="Number of courses passed"
+ )
+ curricular_units_1st_sem_credited = st.number_input(
+ "Credited Units", min_value=0, max_value=26,
+ value=academic_defaults["1st_sem"]["credited"], step=1,
+ key="1st_credited", help="Credits from previous education"
+ )
+ with col2:
+ curricular_units_1st_sem_evaluations = st.number_input(
+ "Evaluations", min_value=0, max_value=45,
+ value=academic_defaults["1st_sem"]["evaluations"], step=1,
+ key="1st_evaluations", help="Number of exams taken"
+ )
+ curricular_units_1st_sem_grade = st.number_input(
+ "Average Grade (0-20)", min_value=0, max_value=20,
+ value=academic_defaults["1st_sem"]["grade"], step=1,
+ key="1st_grade", help="Average grade on 0-20 scale"
+ )
+ curricular_units_1st_sem_without_evaluations = st.number_input(
+ "Without Evaluations", min_value=0, max_value=14,
+ value=academic_defaults["1st_sem"]["without"], step=1,
+ key="1st_without", help="Courses registered but not evaluated"
+ )
+
+ # 2nd Semester
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 📖 2nd Semester Performance")
+ st.markdown('Second semester data helps improve prediction accuracy
', unsafe_allow_html=True)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ curricular_units_2nd_sem_enrolled = st.number_input(
+ "Enrolled Units", min_value=0, max_value=23,
+ value=academic_defaults["2nd_sem"]["enrolled"], step=1,
+ key="2nd_enrolled", help="Number of courses registered for"
+ )
+ curricular_units_2nd_sem_approved = st.number_input(
+ "Approved Units", min_value=0, max_value=20,
+ value=academic_defaults["2nd_sem"]["approved"], step=1,
+ key="2nd_approved", help="Number of courses passed"
+ )
+ curricular_units_2nd_sem_credited = st.number_input(
+ "Credited Units", min_value=0, max_value=20,
+ value=academic_defaults["2nd_sem"]["credited"], step=1,
+ key="2nd_credited", help="Credits from previous education"
+ )
+ with col2:
+ curricular_units_2nd_sem_evaluations = st.number_input(
+ "Evaluations", min_value=0, max_value=33,
+ value=academic_defaults["2nd_sem"]["evaluations"], step=1,
+ key="2nd_evaluations", help="Number of exams taken"
+ )
+ curricular_units_2nd_sem_grade = st.number_input(
+ "Average Grade (0-20)", min_value=0, max_value=20,
+ value=academic_defaults["2nd_sem"]["grade"], step=1,
+ key="2nd_grade", help="Average grade on 0-20 scale"
+ )
+ curricular_units_2nd_sem_without_evaluations = st.number_input(
+ "Without Evaluations", min_value=0, max_value=12,
+ value=academic_defaults["2nd_sem"]["without"], step=1,
+ key="2nd_without", help="Courses registered but not evaluated"
+ )
+
+ return {
+ 'Curricular units 1st sem (credited)': curricular_units_1st_sem_credited,
+ 'Curricular units 1st sem (enrolled)': curricular_units_1st_sem_enrolled,
+ 'Curricular units 1st sem (evaluations)': curricular_units_1st_sem_evaluations,
+ 'Curricular units 1st sem (approved)': curricular_units_1st_sem_approved,
+ 'Curricular units 1st sem (grade)': curricular_units_1st_sem_grade,
+ 'Curricular units 1st sem (without evaluations)': curricular_units_1st_sem_without_evaluations,
+ 'Curricular units 2nd sem (credited)': curricular_units_2nd_sem_credited,
+ 'Curricular units 2nd sem (enrolled)': curricular_units_2nd_sem_enrolled,
+ 'Curricular units 2nd sem (evaluations)': curricular_units_2nd_sem_evaluations,
+ 'Curricular units 2nd sem (approved)': curricular_units_2nd_sem_approved,
+ 'Curricular units 2nd sem (grade)': curricular_units_2nd_sem_grade,
+ 'Curricular units 2nd sem (without evaluations)': curricular_units_2nd_sem_without_evaluations
+ }
+
+def render_economic_indicators_section() -> Dict[str, Any]:
+ """Render economic indicators input section"""
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown("### 📊 Economic Indicators")
+ st.markdown('📈 Economic context data - typically set by institution based on current conditions
', unsafe_allow_html=True)
+
+ col1, col2, col3 = st.columns(3)
+ with col1:
+ unemployment_rate = st.number_input(
+ "Unemployment Rate (%)",
+ min_value=7.0, max_value=16.2, value=10.8, step=0.1, format="%.1f"
+ )
+ with col2:
+ inflation_rate = st.number_input(
+ "Inflation Rate (%)",
+ min_value=-0.8, max_value=3.7, value=1.4, step=0.1, format="%.1f"
+ )
+ with col3:
+ gdp = st.number_input(
+ "GDP Growth",
+ min_value=-4.06, max_value=3.51, value=1.74, step=0.1, format="%.2f"
+ )
+
+ return {
+ 'Unemployment rate': unemployment_rate,
+ 'Inflation rate': inflation_rate,
+ 'GDP': gdp
+ }
\ No newline at end of file
diff --git a/src/components/prediction_components.py b/src/components/prediction_components.py
new file mode 100644
index 0000000..a674db6
--- /dev/null
+++ b/src/components/prediction_components.py
@@ -0,0 +1,261 @@
+"""
+Prediction and results display components
+"""
+
+import streamlit as st
+import plotly.express as px
+import plotly.graph_objects as go
+import time
+import json
+from typing import Dict, Any, Tuple, List
+
+def render_prediction_section(model, preprocessing_info, user_input: Dict[str, Any]) -> None:
+ """Render the prediction section with enhanced animations"""
+ from ..utils.model_utils import preprocess_input, generate_recommendations
+
+ # Prediction button with enhanced styling
+ predict_button = st.button("🔬 Analyze Student Data", type="primary", use_container_width=True)
+
+ if predict_button:
+ # Enhanced loading animation
+ progress_bar = st.progress(0)
+ status_text = st.empty()
+
+ # Simulate processing steps
+ steps = [
+ ("Loading student data...", 20),
+ ("Preprocessing features...", 40),
+ ("Running machine learning model...", 70),
+ ("Calculating prediction confidence...", 90),
+ ("Finalizing results...", 100)
+ ]
+
+ for step, progress in steps:
+ status_text.text(step)
+ progress_bar.progress(progress)
+ time.sleep(0.3)
+
+ # Clear progress indicators
+ progress_bar.empty()
+ status_text.empty()
+
+ # Preprocess input
+ processed_input = preprocess_input(user_input, preprocessing_info)
+
+ # Make prediction
+ prediction = model.predict(processed_input)[0]
+ prediction_proba = model.predict_proba(processed_input)[0]
+
+ # Get prediction label
+ target_reverse_mapping = preprocessing_info['target_reverse_mapping']
+ predicted_outcome = target_reverse_mapping[prediction]
+
+ # Store in session state for export
+ st.session_state['last_prediction'] = {
+ 'outcome': predicted_outcome,
+ 'probabilities': prediction_proba,
+ 'timestamp': time.time()
+ }
+
+ # Enhanced prediction display with confidence indicators
+ confidence_level = max(prediction_proba)
+ confidence_text = "High" if confidence_level > 0.7 else "Medium" if confidence_level > 0.5 else "Low"
+
+ # Display prediction with enhanced styling and confidence
+ if predicted_outcome == "Graduate":
+ st.markdown(f'''
+ 🎓 GRADUATE
+
Confidence: {confidence_level:.1%} ({confidence_text})
+
''', unsafe_allow_html=True)
+ st.balloons()
+ elif predicted_outcome == "Dropout":
+ st.markdown(f'''
+ ⚠️ AT RISK OF DROPOUT
+
Confidence: {confidence_level:.1%} ({confidence_text})
+
''', unsafe_allow_html=True)
+ else:
+ st.markdown(f'''
+ 📚 LIKELY TO STAY ENROLLED
+
Confidence: {confidence_level:.1%} ({confidence_text})
+
''', unsafe_allow_html=True)
+
+ # Add personalized recommendations
+ render_recommendations(predicted_outcome, user_input)
+
+ # Display probability charts
+ render_probability_charts(prediction_proba)
+
+def render_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> None:
+ """Render personalized recommendations section"""
+ from ..utils.model_utils import generate_recommendations
+
+ st.markdown("### 💡 Personalized Recommendations")
+
+ recommendations = generate_recommendations(predicted_outcome, user_input)
+
+ if predicted_outcome == "Graduate":
+ st.markdown('🎯 Success Indicators: This student shows strong potential for graduation. Continue supporting their academic journey.
', unsafe_allow_html=True)
+ elif predicted_outcome == "Dropout":
+ if recommendations:
+ rec_text = '
'.join(recommendations)
+ st.markdown(f'🎯 Intervention Strategies:
{rec_text}
', unsafe_allow_html=True)
+ else:
+ st.markdown('📈 Monitoring Needed: This student may benefit from regular check-ins and academic support.
', unsafe_allow_html=True)
+
+def render_probability_charts(prediction_proba: List[float]) -> None:
+ """Render prediction probability charts and metrics"""
+ st.markdown("### 📊 Prediction Confidence")
+
+ # Create probability dataframe
+ import pandas as pd
+ prob_df = pd.DataFrame({
+ 'Outcome': ['Dropout', 'Enrolled', 'Graduate'],
+ 'Probability': prediction_proba
+ })
+
+ # Create probability chart
+ fig = px.bar(
+ prob_df,
+ x='Outcome',
+ y='Probability',
+ color='Outcome',
+ color_discrete_map={
+ 'Graduate': '#9ccfd8', # Rose Pine foam
+ 'Dropout': '#eb6f92', # Rose Pine love
+ 'Enrolled': '#f6c177' # Rose Pine gold
+ },
+ title="Prediction Probabilities"
+ )
+ fig.update_layout(
+ showlegend=False,
+ plot_bgcolor='rgba(0,0,0,0)',
+ paper_bgcolor='rgba(0,0,0,0)'
+ )
+ st.plotly_chart(fig, use_container_width=True)
+
+ # Display detailed probabilities in cards
+ col_a, col_b, col_c = st.columns(3)
+
+ with col_a:
+ st.markdown(f"""
+
+
Graduate
+ {prediction_proba[2]:.1%}
+
+ """, unsafe_allow_html=True)
+
+ with col_b:
+ st.markdown(f"""
+
+
Dropout
+ {prediction_proba[0]:.1%}
+
+ """, unsafe_allow_html=True)
+
+ with col_c:
+ st.markdown(f"""
+
+
Enrolled
+ {prediction_proba[1]:.1%}
+
+ """, unsafe_allow_html=True)
+
+def render_model_info_sidebar(user_input: Dict[str, Any]) -> None:
+ """Render model information and validation in sidebar"""
+ from ..utils.model_utils import validate_input
+ from ..config.theme import MODEL_METRICS
+
+ st.markdown('', unsafe_allow_html=True)
+
+ # Model information
+ st.markdown('', unsafe_allow_html=True)
+ st.markdown(f"""
+ **🤖 Model Details:**
+ - **Algorithm:** Random Forest Classifier
+ - **Features:** 36 student characteristics
+ - **Training Accuracy:** 100%
+ - **Test Accuracy:** {MODEL_METRICS['accuracy']}%
+ - **Classes:** Graduate, Dropout, Enrolled
+ """)
+ st.markdown('
', unsafe_allow_html=True)
+
+ # Input validation feedback
+ st.markdown("### 🔍 Input Validation")
+ validation_issues = validate_input(user_input)
+
+ if validation_issues:
+ for issue in validation_issues:
+ st.warning(issue)
+ else:
+ st.success("✅ All inputs look valid!")
+
+ # Model performance visualization
+ st.markdown("### Model Performance")
+
+ metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
+ values = [MODEL_METRICS['accuracy'], MODEL_METRICS['precision'],
+ MODEL_METRICS['recall'], MODEL_METRICS['f1_score']]
+
+ fig_perf = go.Figure(data=go.Scatterpolar(
+ r=values,
+ theta=metrics,
+ fill='toself',
+ line_color='#c4a7e7' # Rose Pine iris
+ ))
+
+ fig_perf.update_layout(
+ polar=dict(
+ radialaxis=dict(
+ visible=True,
+ range=[0, 100]
+ )),
+ showlegend=False,
+ title="Model Performance Metrics",
+ height=300,
+ plot_bgcolor='rgba(0,0,0,0)',
+ paper_bgcolor='rgba(0,0,0,0)'
+ )
+
+ st.plotly_chart(fig_perf, use_container_width=True)
+
+def render_export_section(user_input: Dict[str, Any]) -> None:
+ """Render export functionality"""
+ st.markdown("### 📥 Export Results")
+
+ if st.session_state.get('last_prediction'):
+ export_data = {
+ 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'prediction': st.session_state['last_prediction']['outcome'],
+ 'confidence': f"{max(st.session_state['last_prediction']['probabilities']):.1%}",
+ 'student_data': user_input
+ }
+
+ # Convert to JSON for download
+ json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
+
+ st.download_button(
+ label="📁 Download Prediction Report",
+ data=json_str,
+ file_name=f"student_prediction_{time.strftime('%Y%m%d_%H%M%S')}.json",
+ mime="application/json"
+ )
+
+def render_footer() -> None:
+ """Render enhanced footer with project information"""
+ st.markdown('', unsafe_allow_html=True)
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ st.markdown("**📊 Data Source**")
+ st.caption("UCI ML Repository - Student Academic Success")
+
+ with col2:
+ st.markdown("**🔬 Model Info**")
+ st.caption("Random Forest with 93.84% accuracy")
+
+ with col3:
+ st.markdown("**🛠️ Built With**")
+ st.caption("Streamlit • scikit-learn • Plotly")
+
+ st.markdown("---")
+ st.markdown('🎓 Empowering educational institutions with data-driven insights for student success
', unsafe_allow_html=True)
\ No newline at end of file
diff --git a/src/config/__init__.py b/src/config/__init__.py
new file mode 100644
index 0000000..72af5e6
--- /dev/null
+++ b/src/config/__init__.py
@@ -0,0 +1,3 @@
+"""
+Configuration modules
+"""
\ No newline at end of file
diff --git a/src/config/mappings.py b/src/config/mappings.py
new file mode 100644
index 0000000..82fea5e
--- /dev/null
+++ b/src/config/mappings.py
@@ -0,0 +1,175 @@
+"""
+Data mappings and constants for the Student Success Predictor app
+Contains all the human-readable mappings for categorical features
+"""
+
+def create_feature_mappings():
+ """Create human-readable mappings for categorical features"""
+
+ # Marital status mapping
+ marital_status_map = {
+ 1: "Single",
+ 2: "Married",
+ 3: "Widowed",
+ 4: "Divorced",
+ 5: "Civil Union",
+ 6: "Legally Separated"
+ }
+
+ # Application mode mapping
+ application_mode_map = {
+ 1: "1st Phase - General Contingent",
+ 2: "Ordinance No. 612/93",
+ 5: "1st Phase - Special Contingent (Azores)",
+ 7: "Holders of Other Higher Courses",
+ 10: "Ordinance No. 854-B/99",
+ 15: "International Student (Bachelor)",
+ 16: "1st Phase - Special Contingent (Madeira)",
+ 17: "2nd Phase - General Contingent",
+ 18: "3rd Phase - General Contingent",
+ 26: "Ordinance No. 533-A/99, item b2",
+ 27: "Ordinance No. 533-A/99, item b3",
+ 39: "Over 23 years old",
+ 42: "Transfer",
+ 43: "Change of course",
+ 44: "Technological specialization diploma holders",
+ 51: "Change of institution/course",
+ 53: "Short cycle diploma holders",
+ 57: "Change of institution/course (International)"
+ }
+
+ # Course mapping
+ course_map = {
+ 33: "Biofuel Production Technologies",
+ 171: "Animation and Multimedia Design",
+ 8014: "Social Service (evening attendance)",
+ 9003: "Agronomy",
+ 9070: "Communication Design",
+ 9085: "Veterinary Nursing",
+ 9119: "Informatics Engineering",
+ 9130: "Equinculture",
+ 9147: "Management",
+ 9238: "Social Service",
+ 9254: "Tourism",
+ 9500: "Nursing",
+ 9556: "Oral Hygiene",
+ 9670: "Advertising and Marketing Management",
+ 9773: "Journalism and Communication",
+ 9853: "Basic Education",
+ 9991: "Management (evening attendance)"
+ }
+
+ # Previous qualification mapping
+ qualification_map = {
+ 1: "Secondary education",
+ 2: "Higher education - bachelor's degree",
+ 3: "Higher education - degree",
+ 4: "Higher education - master's",
+ 5: "Higher education - doctorate",
+ 6: "Frequency of higher education",
+ 9: "12th year of schooling - not completed",
+ 10: "11th year of schooling - not completed",
+ 12: "Other - 11th year of schooling",
+ 14: "10th year of schooling",
+ 15: "10th year of schooling - not completed",
+ 19: "Basic education 3rd cycle (9th/10th/11th year) or equiv.",
+ 38: "Basic education 2nd cycle (6th/7th/8th year) or equiv.",
+ 39: "Technological specialization course",
+ 40: "Higher education - degree (1st cycle)",
+ 42: "Professional course",
+ 43: "Higher education - master (2nd cycle)"
+ }
+
+ # Occupation mapping
+ occupation_map = {
+ 0: "Student",
+ 1: "Representatives of the Legislative Power and Executive Bodies",
+ 2: "Specialists in Intellectual and Scientific Activities",
+ 3: "Intermediate Level Technicians and Professions",
+ 4: "Administrative staff",
+ 5: "Personal Services, Security and Safety Workers",
+ 6: "Farmers and Skilled Workers in Agriculture",
+ 7: "Skilled Workers in Industry, Construction and Craftsmen",
+ 8: "Installation and Machine Operators and Assembly Workers",
+ 9: "Unskilled Workers",
+ 10: "Armed Forces Professions",
+ 90: "Other Situation",
+ 99: "Unspecified",
+ 122: "Health professionals",
+ 123: "Teachers",
+ 125: "Specialists in information and communication technologies",
+ 131: "Intermediate level science and engineering technicians",
+ 132: "Technicians and associate professionals, health",
+ 134: "Intermediate level technicians from legal, social, sports",
+ 141: "Office workers, secretaries in general and data processing",
+ 143: "Data, accounting, statistical, financial services",
+ 144: "Other administrative support staff",
+ 151: "Personal service workers",
+ 152: "Sellers",
+ 153: "Personal care workers and the like",
+ 171: "Skilled construction workers and the like, except electricians",
+ 173: "Skilled workers in printing, precision instrument manufacturing",
+ 175: "Workers in food processing, woodworking, clothing",
+ 191: "Cleaning workers",
+ 192: "Unskilled workers in agriculture, animal production",
+ 193: "Unskilled workers in extractive industry, construction",
+ 194: "Meal preparation assistants"
+ }
+
+ return {
+ 'marital_status': marital_status_map,
+ 'application_mode': application_mode_map,
+ 'course': course_map,
+ 'qualification': qualification_map,
+ 'occupation': occupation_map
+ }
+
+# Preset configurations for quick form filling
+PRESET_CONFIGURATIONS = {
+ "Custom Entry": {
+ "age": 20, "gender": 0, "marital": 1, "app_mode": 1, "course": 9254,
+ "prev_grade": 120, "adm_grade": 120
+ },
+ "Typical Graduate Student": {
+ "age": 19, "gender": 1, "marital": 1, "app_mode": 15, "course": 9254,
+ "prev_grade": 160, "adm_grade": 142
+ },
+ "At-Risk Student": {
+ "age": 22, "gender": 1, "marital": 1, "app_mode": 17, "course": 171,
+ "prev_grade": 100, "adm_grade": 110
+ },
+ "International Student": {
+ "age": 21, "gender": 0, "marital": 1, "app_mode": 1, "course": 9238,
+ "prev_grade": 135, "adm_grade": 130
+ }
+}
+
+# Academic performance defaults for presets
+ACADEMIC_DEFAULTS = {
+ "Custom Entry": {
+ "1st_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
+ "2nd_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0}
+ },
+ "Typical Graduate Student": {
+ "1st_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0},
+ "2nd_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0}
+ },
+ "At-Risk Student": {
+ "1st_sem": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0},
+ "2nd_sem": {"enrolled": 6, "approved": 0, "grade": 0, "credited": 0, "evaluations": 0, "without": 0}
+ },
+ "International Student": {
+ "1st_sem": {"enrolled": 6, "approved": 6, "grade": 13, "credited": 0, "evaluations": 6, "without": 0},
+ "2nd_sem": {"enrolled": 6, "approved": 6, "grade": 14, "credited": 0, "evaluations": 6, "without": 0}
+ }
+}
+
+# Validation constants
+VALIDATION_RULES = {
+ "min_age": 17,
+ "max_age": 70,
+ "min_grade": 0,
+ "max_grade": 20,
+ "min_qualification_grade": 95,
+ "max_qualification_grade": 190
+}
\ No newline at end of file
diff --git a/src/config/theme.py b/src/config/theme.py
new file mode 100644
index 0000000..aa30431
--- /dev/null
+++ b/src/config/theme.py
@@ -0,0 +1,308 @@
+"""
+Theme configuration for the Student Success Predictor app
+Rose Pine Dark Theme implementation
+"""
+
+# Rose Pine Dark Theme CSS
+ROSE_PINE_THEME = """
+
+"""
+
+# Page configuration
+PAGE_CONFIG = {
+ "page_title": "Student Success Predictor",
+ "page_icon": "📊",
+ "layout": "wide",
+ "initial_sidebar_state": "expanded"
+}
+
+# App constants
+APP_TITLE = "Student Success Predictor"
+APP_SUBTITLE = "Predict whether a student will Graduate, Dropout, or remain Enrolled based on academic and demographic factors using advanced machine learning."
+
+# Model constants
+MODEL_FILES = {
+ "model": "random_forest_model.pkl",
+ "preprocessing": "preprocessing_info.pkl",
+ "features": "feature_names.pkl"
+}
+
+# Performance metrics (from notebook results)
+MODEL_METRICS = {
+ "accuracy": 93.84,
+ "precision": 92.96,
+ "recall": 94.02,
+ "f1_score": 93.15
+}
\ No newline at end of file
diff --git a/src/utils/__init__.py b/src/utils/__init__.py
new file mode 100644
index 0000000..c7caf80
--- /dev/null
+++ b/src/utils/__init__.py
@@ -0,0 +1,3 @@
+"""
+Utility modules
+"""
\ No newline at end of file
diff --git a/src/utils/model_utils.py b/src/utils/model_utils.py
new file mode 100644
index 0000000..aa4f84c
--- /dev/null
+++ b/src/utils/model_utils.py
@@ -0,0 +1,108 @@
+"""
+Model loading and data preprocessing utilities
+"""
+
+import streamlit as st
+import pandas as pd
+import pickle
+from typing import Dict, Any, List
+
+@st.cache_data
+def load_model_and_info():
+ """Load the trained model and preprocessing information"""
+ try:
+ with open('random_forest_model.pkl', 'rb') as f:
+ model = pickle.load(f)
+
+ with open('preprocessing_info.pkl', 'rb') as f:
+ preprocessing_info = pickle.load(f)
+
+ with open('feature_names.pkl', 'rb') as f:
+ feature_names = pickle.load(f)
+
+ return model, preprocessing_info, feature_names
+ except FileNotFoundError:
+ st.error("❌ Model files not found! Please run 'train_and_save_model.py' first.")
+ st.stop()
+
+def preprocess_input(user_input: Dict[str, Any], preprocessing_info: Dict[str, Any]) -> pd.DataFrame:
+ """Preprocess user input to match the training data format"""
+
+ # Create a DataFrame with the user input
+ input_df = pd.DataFrame([user_input])
+
+ # Get categorical columns that need one-hot encoding
+ categorical_columns = [
+ 'Marital status', 'Application mode', 'Course', 'Daytime/evening attendance',
+ 'Previous qualification', 'Nationality', 'Mothers qualification',
+ 'Fathers qualification', 'Mothers occupation', 'Fathers occupation',
+ 'Gender', 'Displaced', 'Educational special needs', 'Debtor',
+ 'Tuition fees up to date', 'Scholarship holder', 'International',
+ 'Application order'
+ ]
+
+ # Perform one-hot encoding for categorical columns
+ for column in categorical_columns:
+ if column in input_df.columns:
+ # Get dummies with prefix
+ dummies = pd.get_dummies(input_df[column], prefix=column)
+ # Concatenate with original data
+ input_df = pd.concat([input_df, dummies], axis=1)
+ # Drop original column
+ input_df.drop(columns=[column], inplace=True)
+
+ # Ensure all training features are present
+ feature_names = preprocessing_info['feature_names']
+
+ # Add missing columns with 0 values
+ for feature in feature_names:
+ if feature not in input_df.columns:
+ input_df[feature] = 0
+
+ # Select only the features used in training and in the same order
+ input_df = input_df[feature_names]
+
+ return input_df
+
+def validate_input(user_input: Dict[str, Any]) -> List[str]:
+ """Validate user input and return list of issues"""
+ validation_issues = []
+
+ # Check for unrealistic combinations
+ if user_input['Age at enrollment'] < 17:
+ validation_issues.append("⚠️ Age seems too low for enrollment")
+
+ if user_input['Curricular units 1st sem (approved)'] > user_input['Curricular units 1st sem (enrolled)']:
+ validation_issues.append("⚠️ Approved units exceed enrolled units (1st sem)")
+
+ if user_input['Curricular units 2nd sem (approved)'] > user_input['Curricular units 2nd sem (enrolled)']:
+ validation_issues.append("⚠️ Approved units exceed enrolled units (2nd sem)")
+
+ return validation_issues
+
+def generate_recommendations(predicted_outcome: str, user_input: Dict[str, Any]) -> List[str]:
+ """Generate personalized recommendations based on prediction and input"""
+ recommendations = []
+
+ if predicted_outcome == "Dropout":
+ if user_input['Curricular units 1st sem (approved)'] == 0:
+ recommendations.append("📚 **Academic Support:** Provide tutoring for struggling courses")
+
+ if user_input['Scholarship holder'] == 0:
+ recommendations.append("💰 **Financial Aid:** Consider scholarship opportunities")
+
+ if user_input['Age at enrollment'] > 25:
+ recommendations.append("🕐 **Flexible Scheduling:** Offer evening or part-time options")
+
+ if user_input['Debtor'] == 1:
+ recommendations.append("💳 **Financial Counseling:** Address outstanding debts")
+
+ elif predicted_outcome == "Graduate":
+ recommendations.append("🎯 **Success Indicators:** This student shows strong potential for graduation")
+ recommendations.append("🔄 **Continue Support:** Maintain current academic support systems")
+
+ else: # Enrolled
+ recommendations.append("📈 **Regular Monitoring:** Schedule periodic academic check-ins")
+ recommendations.append("🎯 **Goal Setting:** Help establish clear academic objectives")
+
+ return recommendations
\ No newline at end of file