Skip to content

Commit 1eeabd0

Browse files
authored
Create WineSense.py
1 parent 51aba90 commit 1eeabd0

1 file changed

Lines changed: 218 additions & 0 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""
2+
WineSense v1.0 - Wine Quality Prediction Tool
3+
ML-powered Wine Quality Analyzer
4+
Predicts wine quality from physicochemical properties
5+
"""
6+
7+
import os
8+
import sys
9+
import threading
10+
import tkinter as tk
11+
from tkinter import filedialog, messagebox
12+
13+
import pandas as pd
14+
import numpy as np
15+
16+
from sklearn.model_selection import train_test_split
17+
from sklearn.ensemble import RandomForestClassifier
18+
from sklearn.metrics import accuracy_score
19+
20+
import ttkbootstrap as tb
21+
from ttkbootstrap.constants import *
22+
23+
24+
# ---------------------- UTIL ----------------------
25+
def resource_path(file_name):
26+
base_path = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))
27+
return os.path.join(base_path, file_name)
28+
29+
30+
# ---------------------- ML MODEL ----------------------
31+
class WineQualityModel:
32+
def __init__(self):
33+
self.model = RandomForestClassifier(
34+
n_estimators=200,
35+
random_state=42
36+
)
37+
self.trained = False
38+
self.accuracy = 0.0
39+
self.feature_names = []
40+
41+
def train(self, csv_path):
42+
data = pd.read_csv(csv_path)
43+
44+
if "quality" not in data.columns:
45+
raise ValueError("CSV must contain a 'quality' column")
46+
47+
self.feature_names = [c for c in data.columns if c != "quality"]
48+
49+
X = data[self.feature_names]
50+
y = data["quality"]
51+
52+
X_train, X_test, y_train, y_test = train_test_split(
53+
X, y, test_size=0.2, random_state=42
54+
)
55+
56+
self.model.fit(X_train, y_train)
57+
58+
preds = self.model.predict(X_test)
59+
self.accuracy = accuracy_score(y_test, preds)
60+
self.trained = True
61+
62+
def predict(self, features):
63+
if not self.trained:
64+
raise RuntimeError("Model not trained")
65+
66+
# FIX: pass feature names using DataFrame
67+
X = pd.DataFrame([features], columns=self.feature_names)
68+
return int(self.model.predict(X)[0])
69+
70+
71+
# ---------------------- MAIN APP ----------------------
72+
class WineSenseApp:
73+
APP_NAME = "WineSense"
74+
APP_VERSION = "1.0"
75+
76+
def __init__(self):
77+
self.root = tb.Window(themename="darkly")
78+
self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
79+
self.root.minsize(900, 600)
80+
81+
self.model = WineQualityModel()
82+
self.feature_vars = {}
83+
84+
self._build_ui()
85+
86+
# ---------------------- UI ----------------------
87+
def _build_ui(self):
88+
main = tb.Frame(self.root, padding=15)
89+
main.pack(fill=BOTH, expand=True)
90+
91+
tb.Label(
92+
main,
93+
text="🍷 WineSense - Quality Prediction",
94+
font=("Segoe UI", 22, "bold")
95+
).pack(pady=(0, 5))
96+
97+
tb.Label(
98+
main,
99+
text="Machine Learning Based Wine Quality Estimator",
100+
font=("Segoe UI", 10, "italic"),
101+
foreground="#9ca3af"
102+
).pack(pady=(0, 20))
103+
104+
# ---------------- Training Section ----------------
105+
train_frame = tb.Labelframe(main, text="Model Training", padding=10)
106+
train_frame.pack(fill=X, pady=(0, 10))
107+
108+
tb.Button(
109+
train_frame,
110+
text="📊 Load Wine CSV & Train Model",
111+
bootstyle=SUCCESS,
112+
command=self.train_model
113+
).pack(side=LEFT, padx=5)
114+
115+
self.train_status = tb.Label(train_frame, text="Model not trained")
116+
self.train_status.pack(side=LEFT, padx=10)
117+
118+
# ---------------- Feature Input ----------------
119+
input_frame = tb.Labelframe(main, text="Wine Properties", padding=10)
120+
input_frame.pack(fill=BOTH, expand=True, pady=(0, 10))
121+
122+
self.features = [
123+
"fixed acidity",
124+
"volatile acidity",
125+
"citric acid",
126+
"residual sugar",
127+
"chlorides",
128+
"free sulfur dioxide",
129+
"total sulfur dioxide",
130+
"density",
131+
"pH",
132+
"sulphates",
133+
"alcohol",
134+
]
135+
136+
grid = tb.Frame(input_frame)
137+
grid.pack()
138+
139+
for i, feat in enumerate(self.features):
140+
tb.Label(grid, text=feat.title()).grid(
141+
row=i, column=0, sticky=W, pady=4, padx=6
142+
)
143+
var = tk.DoubleVar(value=0.0)
144+
self.feature_vars[feat] = var
145+
tb.Entry(grid, textvariable=var, width=18).grid(
146+
row=i, column=1, pady=4
147+
)
148+
149+
# ---------------- Prediction Section ----------------
150+
predict_frame = tb.Frame(main)
151+
predict_frame.pack(fill=X, pady=(10, 0))
152+
153+
tb.Button(
154+
predict_frame,
155+
text="🔮 Predict Quality",
156+
bootstyle=PRIMARY,
157+
command=self.predict_quality
158+
).pack(side=LEFT, padx=5)
159+
160+
self.result_label = tb.Label(
161+
predict_frame,
162+
text="Quality: -",
163+
font=("Segoe UI", 14, "bold")
164+
)
165+
self.result_label.pack(side=LEFT, padx=20)
166+
167+
# ---------------------- Actions ----------------------
168+
def train_model(self):
169+
path = filedialog.askopenfilename(
170+
title="Select Wine Quality CSV",
171+
filetypes=[("CSV Files", "*.csv")]
172+
)
173+
if not path:
174+
return
175+
176+
self.train_status.config(text="Training model...")
177+
threading.Thread(
178+
target=self._train_thread,
179+
args=(path,),
180+
daemon=True
181+
).start()
182+
183+
def _train_thread(self, path):
184+
try:
185+
self.model.train(path)
186+
self.root.after(
187+
0,
188+
lambda: self.train_status.config(
189+
text=f"Model trained | Accuracy: {self.model.accuracy:.2%}"
190+
)
191+
)
192+
except Exception as e:
193+
self.root.after(
194+
0,
195+
lambda: messagebox.showerror("Training Error", str(e))
196+
)
197+
198+
def predict_quality(self):
199+
if not self.model.trained:
200+
messagebox.showwarning("Model", "Please train the model first")
201+
return
202+
203+
try:
204+
features = [self.feature_vars[f].get() for f in self.features]
205+
quality = self.model.predict(features)
206+
self.result_label.config(text=f"Quality: {quality}")
207+
except Exception as e:
208+
messagebox.showerror("Prediction Error", str(e))
209+
210+
# ---------------------- Run ----------------------
211+
def run(self):
212+
self.root.mainloop()
213+
214+
215+
# ---------------------- RUN ----------------------
216+
if __name__ == "__main__":
217+
app = WineSenseApp()
218+
app.run()

0 commit comments

Comments
 (0)