Skip to content

Commit 446f78a

Browse files
authored
Create AttritionPredictor.py
1 parent bf6aa77 commit 446f78a

1 file changed

Lines changed: 205 additions & 0 deletions

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
"""
2+
AttritionPredictor v1.0 - Enterprise Edition
3+
Employee Attrition Prediction GUI
4+
Upload employee datasets, predict attrition, export results
5+
"""
6+
7+
import os, sys, threading
8+
import pandas as pd
9+
import tkinter as tk
10+
from tkinter import filedialog, messagebox, ttk
11+
12+
import ttkbootstrap as tb
13+
from ttkbootstrap.constants import *
14+
15+
try:
16+
from tkinterdnd2 import TkinterDnD, DND_FILES
17+
DND_ENABLED = True
18+
except ImportError:
19+
DND_ENABLED = False
20+
print("Drag & Drop requires tkinterdnd2: pip install tkinterdnd2")
21+
22+
# ---------------------- UTIL ----------------------
23+
def resource_path(file_name):
24+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
25+
return os.path.join(base_path, file_name)
26+
27+
# ---------------------- WORKER ----------------------
28+
class AttritionWorker:
29+
def __init__(self, dataframe, callbacks):
30+
self.df = dataframe
31+
self.callbacks = callbacks
32+
self._running = True
33+
34+
def stop(self):
35+
self._running = False
36+
37+
def run(self):
38+
results = []
39+
for idx, row in self.df.iterrows():
40+
if not self._running:
41+
break
42+
# Placeholder prediction logic
43+
attrition_prob = min(1.0, max(0.0, 0.1 + 0.2 * row.get("Age", 30)/50))
44+
prediction = "Yes" if attrition_prob > 0.5 else "No"
45+
results.append((row.get("EmployeeID", idx), row.get("Name", "N/A"), prediction))
46+
if "update" in self.callbacks:
47+
self.callbacks["update"](idx, results[-1])
48+
if "finished" in self.callbacks:
49+
self.callbacks["finished"](results)
50+
51+
# ---------------------- MAIN APP ----------------------
52+
class AttritionPredictorApp:
53+
APP_NAME = "AttritionPredictor"
54+
APP_VERSION = "1.0"
55+
56+
def __init__(self):
57+
if DND_ENABLED:
58+
self.root = TkinterDnD.Tk()
59+
else:
60+
self.root = tb.Window(themename="darkly")
61+
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
62+
self.root.minsize(1100, 600)
63+
64+
try:
65+
self.root.iconbitmap(resource_path("logo.ico"))
66+
except: pass
67+
68+
self.worker_obj = None
69+
self.file_path = None
70+
71+
self._build_ui()
72+
self._apply_styles()
73+
74+
# ---------------------- UI ----------------------
75+
def _build_ui(self):
76+
main = tb.Frame(self.root, padding=10)
77+
main.pack(fill=BOTH, expand=True)
78+
79+
tb.Label(main, text=f"🧠 {self.APP_NAME} - Enterprise Attrition Predictor",
80+
font=("Segoe UI", 20, "bold")).pack(pady=(0,4))
81+
tb.Label(main, text="Upload employee dataset, predict attrition, export results",
82+
font=("Segoe UI", 10, "italic"), foreground="#9ca3af").pack(pady=(0,20))
83+
84+
# Row 1: File selection
85+
row1 = tb.Frame(main)
86+
row1.pack(fill=X, pady=(0,6))
87+
88+
self.path_input = tb.Entry(row1, width=80)
89+
self.path_input.pack(side=LEFT, fill=X, expand=True, padx=(0,6))
90+
self.path_input.insert(0, "Drag & drop employee CSV here…")
91+
92+
browse_btn = tb.Button(row1, text="📂 Browse", bootstyle=INFO, command=self.browse)
93+
browse_btn.pack(side=LEFT, padx=3)
94+
95+
self.start_btn = tb.Button(row1, text="🚀 Predict Attrition", bootstyle=SUCCESS, command=self.start)
96+
self.start_btn.pack(side=LEFT, padx=3)
97+
98+
self.cancel_btn = tb.Button(row1, text="⏹ Cancel", bootstyle=DANGER, command=self.cancel)
99+
self.cancel_btn.pack(side=LEFT, padx=3)
100+
self.cancel_btn.config(state=DISABLED)
101+
102+
export_btn = tb.Button(row1, text="💾 Export Results", bootstyle=PRIMARY, command=self.export_results)
103+
export_btn.pack(side=LEFT, padx=3)
104+
105+
# Row 2: Treeview for results
106+
columns = ("selected", "employee_id", "name", "prediction")
107+
self.tree = ttk.Treeview(main, columns=columns, show="headings", selectmode="extended", height=20)
108+
self.tree.heading("selected", text="✅")
109+
self.tree.heading("employee_id", text="Employee ID", anchor=W)
110+
self.tree.heading("name", text="Name", anchor=W)
111+
self.tree.heading("prediction", text="Attrition", anchor=W)
112+
self.tree.column("selected", width=50, anchor=CENTER)
113+
self.tree.column("employee_id", width=150)
114+
self.tree.column("name", width=300)
115+
self.tree.column("prediction", width=120)
116+
self.tree.pack(fill=BOTH, expand=True, pady=(0,6))
117+
118+
# Progress bar
119+
self.progress = tb.Progressbar(main, bootstyle="success-striped", maximum=100)
120+
self.progress.pack(fill=X, pady=(0,6))
121+
self.progress_value = 0
122+
self.target_progress = 0
123+
self.root.after(15, self.animate_progress)
124+
125+
if DND_ENABLED:
126+
self.tree.drop_target_register(DND_FILES)
127+
self.tree.dnd_bind("<<Drop>>", self.on_drop)
128+
129+
# ---------------------- Actions ----------------------
130+
def browse(self):
131+
path = filedialog.askopenfilename(filetypes=[("CSV Files","*.csv")])
132+
if path:
133+
self.path_input.delete(0, END)
134+
self.path_input.insert(0, path)
135+
self.file_path = path
136+
137+
def on_drop(self, event):
138+
path = self.root.tk.splitlist(event.data)[0]
139+
if os.path.isfile(path) and path.lower().endswith(".csv"):
140+
self.path_input.delete(0, END)
141+
self.path_input.insert(0, path)
142+
self.file_path = path
143+
144+
def start(self):
145+
if not self.file_path or not os.path.isfile(self.file_path):
146+
messagebox.showwarning("No File", "Select a valid CSV file first.")
147+
return
148+
self.start_btn.config(state=DISABLED)
149+
self.cancel_btn.config(state=NORMAL)
150+
self.progress["value"] = 0
151+
self.progress_value = 0
152+
self.target_progress = 0
153+
self.tree.delete(*self.tree.get_children())
154+
155+
df = pd.read_csv(self.file_path)
156+
self.worker_obj = AttritionWorker(df, callbacks={
157+
"update": self.add_row,
158+
"finished": self.finish
159+
})
160+
threading.Thread(target=self.worker_obj.run, daemon=True).start()
161+
162+
def add_row(self, idx, data):
163+
self.tree.insert("", END, values=("☑️", data[0], data[1], data[2]))
164+
self.target_progress = int((idx+1)/len(self.worker_obj.df)*100)
165+
166+
def animate_progress(self):
167+
if self.progress_value < self.target_progress:
168+
self.progress_value += 1
169+
self.progress["value"] = self.progress_value
170+
self.root.after(15, self.animate_progress)
171+
172+
def cancel(self):
173+
if self.worker_obj:
174+
self.worker_obj.stop()
175+
self.finish()
176+
177+
def finish(self, results=None):
178+
self.start_btn.config(state=NORMAL)
179+
self.cancel_btn.config(state=DISABLED)
180+
self.progress["value"] = 100
181+
182+
def export_results(self):
183+
selected = [self.tree.item(i)['values'] for i in self.tree.get_children()
184+
if self.tree.item(i)['values'][0]=="☑️"]
185+
if not selected:
186+
messagebox.showwarning("Export", "No selected rows to export")
187+
return
188+
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV Files","*.csv")])
189+
if path:
190+
pd.DataFrame(selected, columns=["Selected","EmployeeID","Name","Attrition"]).to_csv(path,index=False)
191+
messagebox.showinfo("Export", "Export completed")
192+
193+
# ---------------------- Styles ----------------------
194+
def _apply_styles(self):
195+
self.root.style = tb.Style(theme="darkly")
196+
self.root.style.configure("TProgressbar", troughcolor="#1b1f3a", background="#7c3aed", thickness=14)
197+
198+
# ---------------------- Run ----------------------
199+
def run(self):
200+
self.root.mainloop()
201+
202+
# ---------------------- RUN ----------------------
203+
if __name__ == "__main__":
204+
app = AttritionPredictorApp()
205+
app.run()

0 commit comments

Comments
 (0)