Skip to content

Commit 684d87f

Browse files
authored
Create ExamResultPredictor.py
1 parent f020916 commit 684d87f

1 file changed

Lines changed: 278 additions & 0 deletions

File tree

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
"""
2+
ExamResultPredictor v2.0 - Academic Performance Predictor
3+
Predicts student exam results based on study hours, attendance, previous grades,
4+
and historical data loaded from CSV.
5+
"""
6+
7+
import os, sys, threading, csv
8+
import tkinter as tk
9+
from tkinter import messagebox, filedialog
10+
from tkinter import ttk
11+
12+
import ttkbootstrap as tb
13+
from ttkbootstrap.constants import *
14+
15+
# For predictive model
16+
from sklearn.linear_model import LinearRegression
17+
import numpy as np
18+
19+
# ---------------------- UTILS ----------------------
20+
def resource_path(file_name):
21+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
22+
return os.path.join(base_path, file_name)
23+
24+
25+
# ---------------------- PREDICTOR WORKER ----------------------
26+
class PredictorWorker:
27+
def __init__(self, data, model, callbacks):
28+
self.data = data
29+
self.model = model
30+
self.callbacks = callbacks
31+
self._running = True
32+
33+
def stop(self):
34+
self._running = False
35+
36+
def run(self):
37+
results = []
38+
for i, student in enumerate(self.data):
39+
if not self._running:
40+
break
41+
42+
# Predict grade using model
43+
features = np.array([[student["study_hours"],
44+
student["attendance"],
45+
student["previous_grade"]]])
46+
predicted_score = self.model.predict(features)[0]
47+
48+
# Map score to letter grade
49+
grade = "A" if predicted_score >= 80 else "B" if predicted_score >= 60 else "C" if predicted_score >= 40 else "F"
50+
results.append({**student, "predicted_grade": grade})
51+
52+
if "found" in self.callbacks:
53+
self.callbacks["found"](student, grade)
54+
55+
if "progress" in self.callbacks:
56+
self.callbacks["progress"](int((i + 1) / len(self.data) * 100))
57+
58+
if "finished" in self.callbacks:
59+
self.callbacks["finished"](results)
60+
61+
62+
# ---------------------- MAIN APP ----------------------
63+
class ExamResultPredictorApp:
64+
APP_NAME = "ExamResultPredictor"
65+
APP_VERSION = "2.0"
66+
67+
def __init__(self):
68+
self.root = tb.Window(themename="darkly")
69+
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
70+
self.root.minsize(950, 650)
71+
72+
try:
73+
self.root.iconbitmap(resource_path("logo.ico"))
74+
except: pass
75+
76+
self.worker_obj = None
77+
self.smooth_value = 0
78+
self.target_progress = 0
79+
self.student_data = []
80+
self.model = self._train_dummy_model()
81+
82+
self._build_ui()
83+
self._apply_styles()
84+
85+
# ---------------------- TRAIN DUMMY MODEL ----------------------
86+
def _train_dummy_model(self):
87+
# Example training data: [study_hours, attendance, previous_grade] => current_score
88+
X = np.array([
89+
[5, 80, 70],
90+
[10, 90, 80],
91+
[2, 60, 50],
92+
[8, 100, 90],
93+
[3, 50, 40],
94+
[6, 70, 65]
95+
])
96+
y = np.array([60, 90, 40, 95, 35, 70])
97+
model = LinearRegression()
98+
model.fit(X, y)
99+
return model
100+
101+
# ---------------------- UI ----------------------
102+
def _build_ui(self):
103+
main = tb.Frame(self.root, padding=10)
104+
main.pack(fill=BOTH, expand=True)
105+
106+
tb.Label(main, text=f"📚 {self.APP_NAME} - Academic Predictor",
107+
font=("Segoe UI", 22, "bold")).pack(pady=(0, 4))
108+
tb.Label(main, text="Predict Exam Results Based on Input Parameters or CSV",
109+
font=("Segoe UI", 10, "italic"), foreground="#9ca3af").pack(pady=(0, 20))
110+
111+
# Input Form
112+
form_frame = tb.Frame(main)
113+
form_frame.pack(fill=X, pady=(0,6))
114+
115+
self.name_input = self._create_form_row(form_frame, "Student Name:")
116+
self.study_input = self._create_form_row(form_frame, "Study Hours per Week:")
117+
self.attendance_input = self._create_form_row(form_frame, "Attendance %:")
118+
self.prev_grade_input = self._create_form_row(form_frame, "Previous Grade (0-100):")
119+
120+
add_btn = tb.Button(form_frame, text="➕ Add Student", bootstyle=SUCCESS, command=self.add_student)
121+
add_btn.grid(row=4, column=0, columnspan=2, pady=5)
122+
123+
csv_btn = tb.Button(form_frame, text="📁 Import CSV", bootstyle=INFO, command=self.import_csv)
124+
csv_btn.grid(row=5, column=0, columnspan=2, pady=5)
125+
126+
# Progress bar
127+
self.progress = tb.Progressbar(main, bootstyle="success-striped", maximum=100)
128+
self.progress.pack(fill=X, pady=(10,6))
129+
130+
# Treeview for results
131+
columns = ("name", "study_hours", "attendance", "previous_grade", "predicted_grade")
132+
self.tree = ttk.Treeview(main, columns=columns, show="headings", selectmode="extended", height=15)
133+
for col in columns:
134+
self.tree.heading(col, text=col.replace("_", " ").title())
135+
self.tree.column(col, width=130)
136+
self.tree.pack(fill=BOTH, expand=True, pady=(0,6))
137+
138+
# Action buttons
139+
row_btns = tb.Frame(main)
140+
row_btns.pack(fill=X, pady=(5,0))
141+
self.start_btn = tb.Button(row_btns, text="🚀 Predict Grades", bootstyle=PRIMARY, command=self.start)
142+
self.start_btn.pack(side=LEFT, padx=3)
143+
self.cancel_btn = tb.Button(row_btns, text="⏹ Cancel", bootstyle=DANGER, command=self.cancel)
144+
self.cancel_btn.pack(side=LEFT, padx=3)
145+
self.cancel_btn.config(state=DISABLED)
146+
export_btn = tb.Button(row_btns, text="💾 Export Results", bootstyle=INFO, command=self.export_results)
147+
export_btn.pack(side=LEFT, padx=3)
148+
149+
# Timer for smooth progress
150+
self.root.after(15, self.animate_progress)
151+
152+
def _create_form_row(self, parent, label_text):
153+
row = tb.Frame(parent)
154+
row.grid(sticky=W, pady=2)
155+
tb.Label(row, text=label_text).pack(side=LEFT, padx=(0,5))
156+
entry = tb.Entry(row, width=30)
157+
entry.pack(side=LEFT)
158+
return entry
159+
160+
# ---------------------- Actions ----------------------
161+
def add_student(self):
162+
try:
163+
student = {
164+
"name": self.name_input.get().strip(),
165+
"study_hours": float(self.study_input.get()),
166+
"attendance": float(self.attendance_input.get()),
167+
"previous_grade": float(self.prev_grade_input.get())
168+
}
169+
self.student_data.append(student)
170+
self.tree.insert("", END, values=(student["name"], student["study_hours"],
171+
student["attendance"], student["previous_grade"], "Pending"))
172+
self.name_input.delete(0, END)
173+
self.study_input.delete(0, END)
174+
self.attendance_input.delete(0, END)
175+
self.prev_grade_input.delete(0, END)
176+
except ValueError:
177+
messagebox.showerror("Invalid Input", "Please enter valid numbers for hours, attendance, and previous grade.")
178+
179+
def import_csv(self):
180+
path = filedialog.askopenfilename(filetypes=[("CSV Files", "*.csv")])
181+
if not path:
182+
return
183+
try:
184+
with open(path, newline="", encoding="utf-8") as csvfile:
185+
reader = csv.DictReader(csvfile)
186+
count = 0
187+
for row in reader:
188+
try:
189+
student = {
190+
"name": row["name"].strip(),
191+
"study_hours": float(row["study_hours"]),
192+
"attendance": float(row["attendance"]),
193+
"previous_grade": float(row["previous_grade"])
194+
}
195+
self.student_data.append(student)
196+
self.tree.insert("", END, values=(student["name"], student["study_hours"],
197+
student["attendance"], student["previous_grade"], "Pending"))
198+
count += 1
199+
except:
200+
continue
201+
messagebox.showinfo("CSV Import", f"Successfully imported {count} students.")
202+
except Exception as e:
203+
messagebox.showerror("CSV Import Error", f"Failed to read CSV: {e}")
204+
205+
def start(self):
206+
if not self.student_data:
207+
messagebox.showwarning("No Data", "Add at least one student before predicting.")
208+
return
209+
210+
self.start_btn.config(state=DISABLED)
211+
self.cancel_btn.config(state=NORMAL)
212+
threading.Thread(target=self._run_worker, daemon=True).start()
213+
214+
def _run_worker(self):
215+
self.worker_obj = PredictorWorker(
216+
self.student_data,
217+
model=self.model,
218+
callbacks={
219+
"found": self.update_student_grade,
220+
"progress": self.set_target,
221+
"finished": self.finish
222+
}
223+
)
224+
self.worker_obj.run()
225+
226+
def update_student_grade(self, student, grade):
227+
for i in self.tree.get_children():
228+
vals = self.tree.item(i)["values"]
229+
if vals[0] == student["name"]:
230+
self.tree.item(i, values=(vals[0], vals[1], vals[2], vals[3], grade))
231+
break
232+
233+
def set_target(self, v):
234+
self.target_progress = v
235+
236+
def animate_progress(self):
237+
if self.smooth_value < self.target_progress:
238+
self.smooth_value += 1
239+
self.progress["value"] = self.smooth_value
240+
self.root.after(15, self.animate_progress)
241+
242+
def cancel(self):
243+
if self.worker_obj:
244+
self.worker_obj.stop()
245+
self.finish()
246+
247+
def finish(self, results=None):
248+
self.start_btn.config(state=NORMAL)
249+
self.cancel_btn.config(state=DISABLED)
250+
self.progress["value"] = 100
251+
252+
def export_results(self):
253+
if not self.student_data:
254+
messagebox.showwarning("Export", "No results to export.")
255+
return
256+
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV Files","*.csv")])
257+
if path:
258+
with open(path,"w",newline="",encoding="utf-8") as csvfile:
259+
writer = csv.writer(csvfile)
260+
writer.writerow(["name","study_hours","attendance","previous_grade","predicted_grade"])
261+
for i in self.tree.get_children():
262+
writer.writerow(self.tree.item(i)["values"])
263+
messagebox.showinfo("Export", "Export completed successfully.")
264+
265+
# ---------------------- Styles ----------------------
266+
def _apply_styles(self):
267+
self.style = tb.Style(theme="darkly")
268+
self.style.configure("TProgressbar", troughcolor="#1b1f3a", background="#7c3aed", thickness=14)
269+
270+
# ---------------------- Run ----------------------
271+
def run(self):
272+
self.root.mainloop()
273+
274+
275+
# ---------------------- RUN ----------------------
276+
if __name__ == "__main__":
277+
app = ExamResultPredictorApp()
278+
app.run()

0 commit comments

Comments
 (0)