# app.py import gradio as gr import joblib, pandas as pd, numpy as np, shap, json # ------------------------------ # Load optimized model + metadata # ------------------------------ MODEL_PATH = "rf_gpa_model_optimized.joblib" META_PATH = "model_meta.json" model = joblib.load(MODEL_PATH) with open(META_PATH, "r") as f: meta = json.load(f) # ------------------------------ # Helper: GPA → Letter grade # ------------------------------ def gpa_to_letter(g): g = float(np.clip(g, 0, 4)) if g >= 3.7: return "A" if g >= 3.3: return "A-" if g >= 3.0: return "B+" if g >= 2.7: return "B" if g >= 2.3: return "B-" if g >= 2.0: return "C+" if g >= 1.7: return "C" if g >= 1.3: return "C-" if g >= 1.0: return "D+" if g >= 0.7: return "D" return "F" # ------------------------------ # Predictor function with SHAP explainability # ------------------------------ def predict_gpa(*inputs, explain=False): cols = [ "grade_level","semester","subject_type","subject","difficulty_level", "past_avg_gpa","avg_test_score","avg_quiz_grade","attendance_pct", "completion_pct","hours_studied","confidence_before","assignment_type" ] X = pd.DataFrame([dict(zip(cols, inputs[:-1]))]) # Predict GPA pred = model.predict(X)[0] letter = gpa_to_letter(pred) # Optional explanation explanation = "" if inputs[-1]: # "Explain" checkbox try: explainer = shap.TreeExplainer(model.named_steps["rf"]) X_enc = model.named_steps["pre"].transform(X) shap_vals = explainer(X_enc) vals = shap_vals.values[0] top_idx = np.argsort(-np.abs(vals))[:3] feat_names = model.named_steps["pre"].get_feature_names_out() explanation = "Top contributing features:\n" + "\n".join( [f"{feat_names[i]}: {vals[i]:.3f}" for i in top_idx] ) except Exception as e: explanation = f"SHAP explanation unavailable: {e}" return round(float(pred), 2), letter, explanation # ------------------------------ # Gradio UI # ------------------------------ with gr.Blocks() as demo: gr.Markdown("# 🎓 Academic GPA Predictor (Optimized Model)") gr.Markdown(f"**Model Performance:** RMSE={meta['rmse']:.3f}, R²={meta['r2']:.3f}, Letter Acc={meta['letter_acc']:.2f}") with gr.Row(): with gr.Column(): grade_level = gr.Slider(9, 12, 11, step=1, label="Grade Level") semester = gr.Radio([1, 2], value=1, label="Semester") subject_type = gr.Dropdown(["Core", "Elective"], value="Core", label="Subject Type") subject = gr.Dropdown(["English","Math","Science","History","Foreign Language","Art","Computer Science","PE"], value="English", label="Subject") difficulty = gr.Radio([1, 2, 3], value=1, label="Difficulty (1=Reg, 2=Honors, 3=AP)") past_avg_gpa = gr.Slider(0, 4, 3, step=0.01, label="Past GPA") avg_test_score = gr.Slider(0, 100, 80, step=1, label="Average Test Score %") avg_quiz = gr.Slider(0, 100, 82, step=1, label="Average Quiz Score %") attendance = gr.Slider(0, 100, 93, step=1, label="Attendance %") completion = gr.Slider(0, 100, 95, step=1, label="Completion %") hours = gr.Slider(0, 40, 2, step=1, label="Hours Studied (per week)") confidence = gr.Slider(0, 10, 6, step=1, label="Confidence Before Assignment") assignment = gr.Dropdown(["Assignment","Quiz","Test","Project","Exam"], value="Assignment", label="Assignment Type") explain = gr.Checkbox(label="Explain with SHAP") btn = gr.Button("Predict GPA") with gr.Column(): gpa_out = gr.Textbox(label="Predicted GPA") letter_out = gr.Textbox(label="Predicted Letter Grade") exp_out = gr.Textbox(label="Explanation", lines=6) def run(*args): return predict_gpa(*args) btn.click(run, [ grade_level,semester,subject_type,subject,difficulty, past_avg_gpa,avg_test_score,avg_quiz,attendance,completion, hours,confidence,assignment,explain ], [gpa_out,letter_out,exp_out]) if __name__ == "__main__": demo.launch()