namngo commited on
Commit
28757d1
·
verified ·
1 Parent(s): 0206a9e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +307 -175
app.py CHANGED
@@ -7,155 +7,262 @@ from datetime import datetime
7
  import json
8
 
9
  # ===== Load models =====
10
- model_full = joblib.load("random_forest_model_full.pkl")
11
- model_important = joblib.load("random_forest_model_importainfeature.pkl")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  # ===== Setup page =====
 
14
  st.set_page_config(page_title="Dự đoán tốt nghiệp đúng hạn", page_icon="🎓", layout="wide")
15
 
16
  # ===== Custom CSS =====
17
- st.markdown("""
18
- <style>
19
- html, body, [class*="css"] {
20
- font-family: 'Poppins', sans-serif;
21
- }
22
- .block-container {
23
- padding: 2rem 2rem 2rem 2rem;
24
- }
25
- .stButton button {
26
- background: linear-gradient(90deg, #4F8BF9 0%, #8E2DE2 100%);
27
- border: none;
28
- padding: 0.75em 1.5em;
29
- border-radius: 8px;
30
- color: white;
31
- font-size: 18px;
32
- font-weight: bold;
33
- }
34
- /* Make number_input and selectbox shorter */
35
- .stNumberInput input, .stSelectbox select {
36
- width: 15px; /* Set the width to a smaller value */
37
- }
38
- </style>
39
- """, unsafe_allow_html=True)
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  # ===== GSheet integration =====
 
 
 
 
 
 
 
 
 
43
  @st.cache_resource
44
- def get_gsheet_client():
45
- scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
46
- creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
47
- client = gspread.authorize(creds)
48
- return client
 
 
 
 
 
 
 
49
 
50
  # ===== Save to Google Sheets =====
 
51
  def save_to_gsheet(name, student_id, major, prediction, semester_data, sheet_name="Trang tính1"):
 
 
 
 
52
  # Get the current date and time
53
  now = datetime.now()
54
  current_time = now.strftime("%Y-%m-%d %H:%M:%S")
55
-
56
  # Ensure prediction is in the expected format (0 or 1)
57
  if isinstance(prediction, np.ndarray):
58
  prediction = int(prediction[0]) # Convert to int if it's ndarray
59
-
60
  elif isinstance(prediction, np.int64):
61
  prediction = int(prediction) # Convert np.int64 to int
62
-
 
 
 
 
 
 
 
63
  # Flatten semester data to individual columns for each semester
64
- semester_data_flat = [str(val) for val in semester_data] # Convert all semester data values to strings
65
-
 
 
66
  # Create a data row to insert into the sheet
67
  data_row = [name, student_id, major, prediction, current_time] + semester_data_flat
68
 
69
- # Connect to Google Sheets
70
- client = get_gsheet_client()
71
-
72
- # Use the Spreadsheet ID (replace with your actual ID)
73
- sheet_id = "1i7bDNvLVLXN93_e-FN0JLzpg1jb64Z_aEuyPjIfwbdQ" # Use your actual ID here
74
- sheet = client.open_by_key(sheet_id).worksheet(sheet_name)
75
-
76
- # Get all values from the sheet to check if the first row is empty (i.e., headers not created yet)
77
- all_values = sheet.get_all_values()
78
-
79
- # If the sheet is empty or if headers are missing, insert headers
80
- if not all_values or len(all_values[0]) != (len(semester_data_flat) + 5): # Ensure number of columns matches
81
- headers = ["Họ tên", "MSV", "Khoa", "Dự báo", "Thời gian"] + [
82
- f"Số môn không thi - HK{i+1}" for i in range(6)] + [
83
- f"Số tín chỉ không thi - HK{i+1}" for i in range(6)] + [
84
- f"Số tín chỉ nợ - HK{i+1}" for i in range(6)] + [
85
- f"Số môn không đạt - HK{i+1}" for i in range(6)] + [
86
- f"Số tín chỉ qua môn - HK{i+1}" for i in range(6)] + [
87
- f"Tổng tín chỉ học kỳ - HK{i+1}" for i in range(6)] + [
88
- f"Số môn học kỳ - HK{i+1}" for i in range(6)] + [
89
- f"Số môn đạt - HK{i+1}" for i in range(6)] + [
90
- f"GPA - HK{i+1}" for i in range(6)] + [
91
- f"Xếp loại - HK{i+1}" for i in range(6)]
92
-
93
- sheet.append_row(headers) # Create headers if missing
94
-
95
- # Append data to the sheet
96
- sheet.append_row(data_row)
97
-
98
-
99
- # ===== List all sheet names =====
100
- # def list_sheet_names(sheet_id):
101
- # client = get_gsheet_client()
102
- # sheet = client.open_by_key(sheet_id)
103
-
104
- # # List all sheet names
105
- # sheet_names = [worksheet.title for worksheet in sheet.worksheets()]
106
- # return sheet_names
107
-
 
 
 
 
 
108
 
109
 
110
  # ===== HEADER =====
 
111
  st.markdown("<h1 style='text-align: center; color: #003366;'>🎓 DỰ ĐOÁN KHẢ NĂNG TỐT NGHIỆP ĐÚNG HẠN</h1>", unsafe_allow_html=True)
112
  st.markdown("<h4 style='text-align: center; color: #666;'>Áp dụng cho sinh viên năm 3</h4>", unsafe_allow_html=True)
113
  st.write("---")
114
 
115
  # ===== SIDEBAR: chọn mô hình =====
 
116
  model_type = st.sidebar.selectbox("🧠 Chọn mô hình dự báo:", ["Dùng toàn bộ đặc trưng", "Dùng đặc trưng quan trọng"])
117
 
118
  # ===== Giao diện nhập ví dụ =====
 
119
  st.subheader("🔢 Chọn ví dụ mẫu hoặc nhập thông tin cá nhân")
120
 
121
- sample_option = st.selectbox("📝 Chọn dụ:", ["Không dụ", "Dùng ví dụ mẫu ngành Công nghệ thông tin", "Dùng ví dụ mẫu ngành Kinh tế"])
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- # ===== Ví dụ mẫu cho mô hình "Dùng toàn bộ dữ liệu" =====
124
  sample_cntt_example_full = {
125
- "name": "Nguyễn Văn A",
126
  "student_id": "10117367",
127
  "major": "Công nghệ thông tin",
128
  "semester_data": [
129
- 0, 0, 0, 0, 17, 7, 7, 8.73, 0,
130
- 0, 0, 0, 0, 17, 7, 7, 8.19, 0,
131
- 0, 0, 0, 0, 17, 7, 7, 7.90, 0,
132
- 0, 0, 0, 0, 17, 7, 7, 8.19, 0,
133
- 0, 0, 0, 0, 19, 7, 6, 8.18, 0,
134
- 0, 0, 5, 2, 19, 7, 5, 7.10, 1
135
  ]
136
  }
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- # ===== Ví dụ mẫu cho mô hình "Dùng mô hình đơn giản" =====
139
- # ===== Ví dụ cho mô hình "Dùng hình đơn giản" cho Kinh tế =====
140
- sample_kinhte_example_simple = {
141
- "name": "Trần Thị B",
142
- "student_id": "11418092",
143
  "major": "Kinh tế",
144
  "semester_data": [
145
- 17, 7, 7.64, # Học kỳ 1: Số tín chỉ đạt, Số tín chỉ nợ, GPA
146
- 17, 7, 7.19, # Học kỳ 2: Số tín chỉ đạt, Số tín chỉ nợ, GPA
147
- 17, 7, 6.94, # Học kỳ 3: Số tín chỉ đạt, Số tín chỉ nợ, GPA
148
- 17, 7, 7.19, # Học kỳ 4: Số tín chỉ đạt, Số tín chỉ nợ, GPA
149
- 19, 7, 7.65, # Học kỳ 5: Số tín chỉ đạt, Số tín chỉ nợ, GPA
150
- 19, 7, 7.49 # Học kỳ 6: Số tín chỉ đạt, Số tín chỉ nợ, GPA
151
  ]
152
  }
153
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- # ===== Ví dụ cho mô hình "Dùng mô hình đơn giản" cho CNTT =====
156
- # ===== Ví dụ cho mô hình "Dùng mô hình đơn giản" cho CNTT =====
157
  sample_cntt_example_simple = {
158
- "name": "Nguyễn Văn B",
159
  "student_id": "10117368",
160
  "major": "Công nghệ thông tin",
161
  "semester_data": [
@@ -168,118 +275,143 @@ sample_cntt_example_simple = {
168
  ]
169
  }
170
 
171
- # ===== Ví dụ cho mô hình "Dùng mô hình đơn giản" cho Kinh tế =====
172
- sample_kinhte_example_full = {
173
- "name": "Trần Thị C",
174
- "student_id": "11418093",
175
  "major": "Kinh tế",
176
  "semester_data": [
177
- 0, 0, 0, 0, 16, 6, 6, 7.00, 1,
178
- 0, 0, 0, 0, 18, 7, 7, 8.20, 1,
179
- 0, 0, 0, 0, 17, 7, 7, 7.80, 1,
180
- 0, 0, 0, 0, 17, 7, 6, 7.90, 1,
181
- 0, 0, 0, 0, 19, 8, 5, 8.10, 0,
182
- 0, 0, 8, 2, 19, 7, 6, 7.30, 1
183
  ]
184
  }
185
 
 
186
  # ===== Thông tin cá nhân =====
187
- if sample_option == "Dùng ví dụ mẫu ngành Công nghệ thông tin":
188
- name = st.text_input("👤 Họ tên", value=sample_cntt_example_full["name"])
189
- student_id = st.text_input("🎓 Mã sinh viên", value=sample_cntt_example_full["student_id"])
190
- major = st.selectbox("📚 Ngành học", ["Công nghệ thông tin", "Kinh tế"], index=0)
191
- elif sample_option == "Dùng ví dụ mẫu ngành Kinh tế":
192
- name = st.text_input("👤 Họ tên", value=sample_kinhte_example_simple["name"])
193
- student_id = st.text_input("🎓 Mã sinh viên", value=sample_kinhte_example_simple["student_id"])
194
- major = st.selectbox("📚 Ngành học", ["Công nghệ thông tin", "Kinh tế"], index=1)
195
- else:
196
- name = st.text_input("👤 Họ tên")
197
- student_id = st.text_input("🎓 Mã sinh viên")
198
- major = st.selectbox("📚 Ngành học", ["Công nghệ thông tin", "Kinh tế"])
 
 
 
 
 
 
 
 
 
 
199
 
200
  st.write("---")
201
 
202
  # ===== Nhập thông tin học kỳ =====
203
- def input_semester(semester_label, default_values=None):
 
 
204
  with st.expander(f"📖 {semester_label}", expanded=True):
205
  col1, col2 = st.columns(2)
206
  with col1:
207
- somon0thi = st.number_input("Số môn không thi", 0, value=default_values[0] if default_values else 0, key=f"sm0_{semester_label}")
208
- sotc0thi = st.number_input("Số tín chỉ không thi", 0, value=default_values[1] if default_values else 0, key=f"tc0_{semester_label}")
209
- sotcno = st.number_input("Số tín chỉ nợ", 0, value=default_values[2] if default_values else 0, key=f"tcno_{semester_label}")
210
- mhno = st.number_input("Số môn không đạt", 0, value=default_values[3] if default_values else 0, key=f"mhno_{semester_label}")
211
- try:
212
- default_tc_qua = default_values[4] - default_values[2] if default_values and len(default_values) >= 5 else 0
213
- except:
214
- default_tc_qua = 0
215
- sotc_qua = st.number_input("Số tín chỉ qua môn", 0, value=default_tc_qua, key=f"tcqua_{semester_label}")
216
  with col2:
217
- TCHK = st.number_input("Tổng tín chỉ học kỳ", 0, value=default_values[4] if default_values else 0, key=f"tchk_{semester_label}")
218
- smhk = st.number_input("Số môn học kỳ", 0, value=default_values[5] if default_values else 0, key=f"smhk_{semester_label}")
219
- mhpass = st.number_input("Số môn đạt", 0, value=default_values[6] if default_values else 0, key=f"mhpass_{semester_label}")
220
- TBCHK = st.number_input("GPA", 0.0, 10.0, value=default_values[7] if default_values else 0.0, step=0.01, key=f"gpa_{semester_label}")
221
- xep_loai_selected = st.selectbox("Xếp loại", list(range(7)), index=default_values[8] if default_values else 0, key=f"xeploai_{semester_label}")
222
- return [somon0thi, sotc0thi, sotcno, mhno, TCHK, smhk, mhpass, TBCHK, xep_loai_selected, sotc_qua]
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
  def input_important_features(semester_label, default_values=None):
 
225
  with st.expander(f"📘 {semester_label}", expanded=True):
226
  col1, col2 = st.columns(2)
227
  with col1:
228
- sotc_qua = st.number_input("Số tín chỉ đạt", 0, value=default_values[0] if default_values else 0, key=f"tcqua_imp_{semester_label}")
229
- sotcno = st.number_input("Số tín chỉ nợ", 0, value=default_values[1] if default_values else 0, key=f"tcno_imp_{semester_label}")
230
  with col2:
231
- TBCHK = st.number_input("Điểm trung bình", 0.0, 10.0, value=default_values[2] if default_values else 0.0, step=0.01, key=f"gpa_imp_{semester_label}")
232
  return [sotc_qua, sotcno, TBCHK]
233
 
234
  # ===== Giao diện theo mô hình =====
 
235
  data = []
236
  semesters = ["HỌC KỲ I", "HỌC KỲ II", "HỌC KỲ III", "HỌC KỲ IV", "HỌC KỲ V", "HỌC KỲ VI"]
237
 
238
- # Mô hình 1: "Dùng toàn bộ dữ liệu"
 
239
  if model_type == "Dùng toàn bộ đặc trưng":
240
  st.subheader("🔢 Nhập thông tin học kỳ chi tiết")
241
  for idx, sem in enumerate(semesters):
242
- example_data = sample_cntt_example_full if sample_option == "Dùng dụ mẫu ngành Công nghệ thông tin" else sample_kinhte_example_full if sample_option == "Dùng dụ mẫu ngành Kinh tế" else None
243
- default_values = example_data["semester_data"][idx*9:(idx+1)*9] if example_data else None
244
- data += input_semester(sem, default_values)
245
  nganh = 0 if major == "Công nghệ thông tin" else 1
246
- final_features = np.array(data + [nganh]).reshape(1, -1)
 
 
247
 
248
- # hình 2: "Dùng hình đơn giản"
249
- else:
250
-
251
- # Ensure the example defaults to "Không ví dụ" when model type is 2
252
-
253
  st.subheader("✨ Nhập thông tin rút gọn")
254
- # Allow selecting an example
255
  for idx, sem in enumerate(semesters):
256
- example_data = sample_cntt_example_simple if sample_option == "Dùng dụ mẫu ngành Công nghệ thông tin" else sample_kinhte_example_simple if sample_option == "Dùng dụ mẫu ngành Kinh tế" else None
257
- default_values = example_data["semester_data"][idx*3:(idx+1)*3] if example_data else None # Fix this line to slice 3 values per semester (Số tín chỉ đạt, Số tín chỉ nợ, GPA)
258
  data += input_important_features(sem, default_values)
259
- final_features = np.array(data).reshape(1, -1)
260
-
261
- # ===== Predict =====
262
- if st.button("🎯 DỰ BÁO"):
263
- if model_type == "Dùng toàn bộ đặc trưng":
264
- prediction = model_full.predict(final_features)
265
- sheet_name = "Trang tính1" # For full data model, save to Trang tính1
266
 
267
- else:
268
- prediction = model_important.predict(final_features)
269
- sheet_name = "Trang tính2" # For full data model, save to Trang tính1
270
 
 
271
 
272
- if prediction[0] == 1:
273
- st.success(f"🎉 Chúc mừng bạn {name} - {major}! Bạn có khả năng tốt nghiệp đúng hạn!")
274
- st.balloons()
275
- else:
276
- st.error(f"⚠️ Bạn {name} - {major} cần cố gắng hơn! nguy cơ trễ hạn.")
277
- st.snow()
278
-
279
- # # Debugging: List all sheet names
280
- # sheet_id = "1i7bDNvLVLXN93_e-FN0JLzpg1jb64Z_aEuyPjIfwbdQ" # Use your actual Spreadsheet ID
281
- # sheet_names = list_sheet_names(sheet_id)
282
- # st.write("Available sheet names:", sheet_names)
283
-
284
- # Ghi log lên Google Sheets
285
- save_to_gsheet(name, student_id, major, prediction, data,sheet_name) # Pass data to save function
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import json
8
 
9
  # ===== Load models =====
10
+
11
+ # model_full = joblib.load("random_forest_model_full.pkl")
12
+ # model_important = joblib.load("random_forest_model_importainfeature.pkl")
13
+
14
+ # Dummy models for demonstration if files are not present
15
+ try:
16
+ model_full = joblib.load("random_forest_model_full.pkl")
17
+ except FileNotFoundError:
18
+ st.warning("Model file random_forest_model_full.pkl not found. Using dummy model.")
19
+ class DummyModel:
20
+ def predict(self, features):
21
+ # Simulate a prediction (e.g., always predict 1)
22
+ # You might want to return a random prediction for more realism
23
+ return np.array([1])
24
+ model_full = DummyModel()
25
+
26
+ try:
27
+ model_important = joblib.load("random_forest_model_importainfeature.pkl")
28
+ except FileNotFoundError:
29
+ st.warning("Model file random_forest_model_importainfeature.pkl not found. Using dummy model.")
30
+ class DummyModel:
31
+ def predict(self, features):
32
+ # Simulate a prediction (e.g., always predict 1)
33
+ # You might want to return a random prediction for more realism
34
+ return np.array([1])
35
+ model_important = DummyModel()
36
+
37
 
38
  # ===== Setup page =====
39
+
40
  st.set_page_config(page_title="Dự đoán tốt nghiệp đúng hạn", page_icon="🎓", layout="wide")
41
 
42
  # ===== Custom CSS =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ st.markdown(""" <style>
45
+ html, body, [class*="css"] {
46
+ font-family: 'Poppins', sans-serif;
47
+ }
48
+ .block-container {
49
+ padding: 2rem 2rem 2rem 2rem;
50
+ }
51
+ .stButton button {
52
+ background: linear-gradient(90deg, #4F8BF9 0%, #8E2DE2 100%);
53
+ border: none;
54
+ padding: 0.75em 1.5em;
55
+ border-radius: 8px;
56
+ color: white;
57
+ font-size: 18px;
58
+ font-weight: bold;
59
+ }
60
+
61
+ /* CSS để làm ngắn st.number_input và st.selectbox */
62
+ .stNumberInput, .stSelectbox {
63
+ width: 180px; /* Điều chỉnh giá trị này (pixel) để thay đổi độ rộng mong muốn */
64
+ /* Có thể dùng max-width thay cho width nếu muốn giới hạn độ rộng tối đa */
65
+ /* max-width: 200px; */
66
+ }
67
+
68
+ /* Tùy chọn: Đảm bảo input và select bên trong lấp đầy chiều rộng container */
69
+ .stNumberInput input, .stSelectbox select {
70
+ width: 100%;
71
+ box-sizing: border-box; /* Đảm bảo padding và border không làm tràn */
72
+ }
73
+
74
+ /* Điều chỉnh thêm khoảng cách giữa label và input nếu cần */
75
+ .stNumberInput label, .stSelectbox label {
76
+ margin-bottom: 0.2rem;
77
+ }
78
+
79
+
80
+ </style>
81
+ """, unsafe_allow_html=True)
82
 
83
  # ===== GSheet integration =====
84
+
85
+ # Make sure credentials.json is present or handle its absence
86
+ try:
87
+ with open("credentials.json") as f:
88
+ gsheet_credentials = json.load(f)
89
+ except FileNotFoundError:
90
+ st.error("Google Sheets credentials.json not found. Sheet logging disabled.")
91
+ gsheet_credentials = None
92
+
93
  @st.cache_resource
94
+ def get_gsheet_client(creds_json):
95
+ if creds_json is None:
96
+ return None
97
+ try:
98
+ scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
99
+ creds = ServiceAccountCredentials.from_json_keyfile_dict(creds_json, scope)
100
+ client = gspread.authorize(creds)
101
+ return client
102
+ except Exception as e:
103
+ st.error(f"Error connecting to Google Sheets: {e}")
104
+ return None
105
+
106
 
107
  # ===== Save to Google Sheets =====
108
+
109
  def save_to_gsheet(name, student_id, major, prediction, semester_data, sheet_name="Trang tính1"):
110
+ client = get_gsheet_client(gsheet_credentials)
111
+ if client is None:
112
+ return # Do not proceed if client is not available
113
+
114
  # Get the current date and time
115
  now = datetime.now()
116
  current_time = now.strftime("%Y-%m-%d %H:%M:%S")
117
+
118
  # Ensure prediction is in the expected format (0 or 1)
119
  if isinstance(prediction, np.ndarray):
120
  prediction = int(prediction[0]) # Convert to int if it's ndarray
 
121
  elif isinstance(prediction, np.int64):
122
  prediction = int(prediction) # Convert np.int64 to int
123
+ elif not isinstance(prediction, int):
124
+ # Handle cases where prediction might be a list or other format
125
+ try:
126
+ prediction = int(prediction[0]) if isinstance(prediction, list) else int(prediction)
127
+ except:
128
+ prediction = -1 # Indicate error or unknown prediction format
129
+
130
+
131
  # Flatten semester data to individual columns for each semester
132
+ # Ensure all data types are suitable for GSheets (strings or numbers)
133
+ semester_data_flat = [str(val) if val is not None else "" for val in semester_data]
134
+
135
+
136
  # Create a data row to insert into the sheet
137
  data_row = [name, student_id, major, prediction, current_time] + semester_data_flat
138
 
139
+ try:
140
+ # Use the Spreadsheet ID (replace with your actual ID)
141
+ sheet_id = "1i7bDNvLVLXN93_e-FN0JLzpg1jb64Z_aEuyPjIfwbdQ" # Use your actual ID here
142
+ sheet = client.open_by_key(sheet_id).worksheet(sheet_name)
143
+
144
+ # Get all values from the sheet to check if the first row is empty (i.e., headers not created yet)
145
+ # Be cautious with get_all_values on very large sheets
146
+ all_values = sheet.get_all_values()
147
+
148
+ # Define headers based on the model type and expected data structure
149
+ if sheet_name == "Trang tính1": # Full model
150
+ headers = ["Họ tên", "MSV", "Khoa", "Dự báo", "Thời gian"] + [
151
+ f"Số môn không thi - HK{i+1}" for i in range(6)] + [
152
+ f"Số tín chỉ không thi - HK{i+1}" for i in range(6)] + [
153
+ f"Số tín chỉ nợ - HK{i+1}" for i in range(6)] + [
154
+ f"Số môn không đạt - HK{i+1}" for i in range(6)] + [
155
+ f"Tổng tín chỉ học kỳ - HK{i+1}" for i in range(6)] + [ # TCHK
156
+ f"Số môn học kỳ - HK{i+1}" for i in range(6)] + [ # SMHK
157
+ f"Số môn đạt - HK{i+1}" for i in range(6)] + [ # MH Pass
158
+ f"GPA - HK{i+1}" for i in range(6)] + [
159
+ f"Xếp loại - HK{i+1}" for i in range(6)] + [
160
+ f"Số tín chỉ qua môn (calc) - HK{i+1}" for i in range(6)] # sotc_qua
161
+
162
+ elif sheet_name == "Trang tính2": # Important features model
163
+ headers = ["Họ tên", "MSV", "Khoa", "Dự báo", "Thời gian"] + [
164
+ f"Số tín chỉ đạt - HK{i+1}" for i in range(6)] + [ # sotc_qua
165
+ f"Số tín chỉ nợ - HK{i+1}" for i in range(6)] + [ # sotcno
166
+ f"GPA - HK{i+1}" for i in range(6)] # TBCHK
167
+ else: # Default or unknown sheet
168
+ headers = ["Họ và tên", "MSV", "Khoa", "Dự báo", "Thời gian"] + [f"Data_{i+1}" for i in range(len(semester_data_flat))]
169
+
170
+
171
+ # If the sheet is empty or if headers are missing/mismatched column count, insert headers
172
+ # Check column count + 5 base columns (Name, ID, Major, Pred, Time)
173
+ expected_cols = len(semester_data_flat) + 5
174
+ if not all_values or len(all_values[0]) != expected_cols or all_values[0][:5] != headers[:5]:
175
+ sheet.append_row(headers) # Create headers if missing or wrong
176
+
177
+
178
+ # Append data to the sheet
179
+ sheet.append_row(data_row)
180
+ # st.success(f"Đã lưu kết quả dự báo vào Google Sheet '{sheet_name}'.") # Optional confirmation
181
+ except Exception as e:
182
+ st.error(f"Lỗi khi lưu dữ liệu vào Google Sheet: {e}")
183
 
184
 
185
  # ===== HEADER =====
186
+
187
  st.markdown("<h1 style='text-align: center; color: #003366;'>🎓 DỰ ĐOÁN KHẢ NĂNG TỐT NGHIỆP ĐÚNG HẠN</h1>", unsafe_allow_html=True)
188
  st.markdown("<h4 style='text-align: center; color: #666;'>Áp dụng cho sinh viên năm 3</h4>", unsafe_allow_html=True)
189
  st.write("---")
190
 
191
  # ===== SIDEBAR: chọn mô hình =====
192
+
193
  model_type = st.sidebar.selectbox("🧠 Chọn mô hình dự báo:", ["Dùng toàn bộ đặc trưng", "Dùng đặc trưng quan trọng"])
194
 
195
  # ===== Giao diện nhập ví dụ =====
196
+
197
  st.subheader("🔢 Chọn ví dụ mẫu hoặc nhập thông tin cá nhân")
198
 
199
+ # Điều chỉnh options cho phù hợp với các ví dụ sẵn
200
+ sample_options = ["Không ví dụ"]
201
+ if model_type == "Dùng toàn bộ đặc trưng":
202
+ sample_options.append("Dùng ví dụ mẫu CNTT (toàn bộ)")
203
+ sample_options.append("Dùng ví dụ mẫu Kinh tế (toàn bộ)")
204
+ else: # Dùng đặc trưng quan trọng
205
+ sample_options.append("Dùng ví dụ mẫu CNTT (rút gọn)")
206
+ sample_options.append("Dùng ví dụ mẫu Kinh tế (rút gọn)")
207
+
208
+
209
+ sample_option = st.selectbox("📝 Chọn ví dụ:", sample_options)
210
+
211
+ # ===== Ví dụ mẫu cho mô hình "Dùng toàn bộ đặc trưng" =====
212
 
 
213
  sample_cntt_example_full = {
214
+ "name": "Nguyễn Văn A (Mẫu Full)",
215
  "student_id": "10117367",
216
  "major": "Công nghệ thông tin",
217
  "semester_data": [
218
+ 0, 0, 0, 0, 17, 7, 7, 8.73, 0, # HK1: somon0thi, sotc0thi, sotcno, mhno, TCHK, smhk, mhpass, TBCHK, xep_loai, sotc_qua (added later)
219
+ 0, 0, 0, 0, 17, 7, 7, 8.19, 0, # HK2
220
+ 0, 0, 0, 0, 17, 7, 7, 7.90, 0, # HK3
221
+ 0, 0, 0, 0, 17, 7, 7, 8.19, 0, # HK4
222
+ 0, 0, 0, 0, 19, 7, 6, 8.18, 0, # HK5
223
+ 0, 0, 5, 2, 19, 7, 5, 7.10, 1 # HK6
224
  ]
225
  }
226
+ # Thêm sotc_qua cho ví dụ Full (TCHK - sotcno)
227
+ for i in range(6):
228
+ tchk_idx = i * 9 + 4
229
+ sotcno_idx = i * 9 + 2
230
+ sotc_qua_val = sample_cntt_example_full["semester_data"][tchk_idx] - sample_cntt_example_full["semester_data"][sotcno_idx]
231
+ # Insert sotc_qua at the end of each semester's data in the list
232
+ sample_cntt_example_full["semester_data"].insert((i+1)*9 + i, sotc_qua_val) # Insert at (idx+1)*9 + current insertion count
233
+
234
+ # Re-calculate indices based on new structure
235
+ # After inserting 6 values, the length becomes 9*6 + 6 = 60
236
+ # HK1: 0-9, HK2: 10-19, ..., HK6: 50-59
237
+ # Each block is 10 values now: somon0thi, sotc0thi, sotcno, mhno, TCHK, smhk, mhpass, TBCHK, xep_loai, sotc_qua
238
 
239
+ sample_kinhte_example_full = {
240
+ "name": "Trần Thị C (Mẫu Full)",
241
+ "student_id": "11418093",
 
 
242
  "major": "Kinh tế",
243
  "semester_data": [
244
+ 0, 0, 0, 0, 16, 6, 6, 7.00, 1, # HK1
245
+ 0, 0, 0, 0, 18, 7, 7, 8.20, 1, # HK2
246
+ 0, 0, 0, 0, 17, 7, 7, 7.80, 1, # HK3
247
+ 0, 0, 0, 0, 17, 7, 6, 7.90, 1, # HK4
248
+ 0, 0, 0, 0, 19, 8, 5, 8.10, 0, # HK5
249
+ 0, 0, 8, 2, 19, 7, 6, 7.30, 1 # HK6
250
  ]
251
  }
252
 
253
+ # Thêm sotc_qua cho ví dụ Full Kinh tế (TCHK - sotcno)
254
+ for i in range(6):
255
+ tchk_idx = i * 9 + 4
256
+ sotcno_idx = i * 9 + 2
257
+ sotc_qua_val = sample_kinhte_example_full["semester_data"][tchk_idx] - sample_kinhte_example_full["semester_data"][sotcno_idx]
258
+ # Insert sotc_qua at the end of each semester's data in the list
259
+ sample_kinhte_example_full["semester_data"].insert((i+1)*9 + i, sotc_qua_val)
260
+
261
+
262
+ # ===== Ví dụ mẫu cho mô hình "Dùng đặc trưng quan trọng" (Simplified) =====
263
 
 
 
264
  sample_cntt_example_simple = {
265
+ "name": "Nguyễn Văn B (Mẫu Rút gọn)",
266
  "student_id": "10117368",
267
  "major": "Công nghệ thông tin",
268
  "semester_data": [
 
275
  ]
276
  }
277
 
278
+ sample_kinhte_example_simple = {
279
+ "name": "Trần Thị B (Mẫu Rút gọn)",
280
+ "student_id": "11418092",
 
281
  "major": "Kinh tế",
282
  "semester_data": [
283
+ 17, 7, 7.64, # Học kỳ 1: Số tín chỉ đạt, Số tín chỉ nợ, GPA
284
+ 17, 7, 7.19, # Học kỳ 2: Số tín chỉ đạt, Số tín chỉ nợ, GPA
285
+ 17, 7, 6.94, # Học kỳ 3: Số tín chỉ đạt, Số tín chỉ nợ, GPA
286
+ 17, 7, 7.19, # Học kỳ 4: Số tín chỉ đạt, Số tín chỉ nợ, GPA
287
+ 19, 7, 7.65, # Học kỳ 5: Số tín chỉ đạt, Số tín chỉ nợ, GPA
288
+ 19, 7, 7.49 # Học kỳ 6: Số tín chỉ đạt, Số tín chỉ nợ, GPA
289
  ]
290
  }
291
 
292
+
293
  # ===== Thông tin cá nhân =====
294
+
295
+ # Chọn dụ mẫu phù hợp với mô hình đã chọn
296
+ selected_example = None
297
+ if sample_option == "Dùng dụ mẫu CNTT (toàn bộ)" and model_type == "Dùng toàn bộ đặc trưng":
298
+ selected_example = sample_cntt_example_full
299
+ elif sample_option == "Dùng ví dụ mẫu Kinh tế (toàn bộ)" and model_type == "Dùng toàn bộ đặc trưng":
300
+ selected_example = sample_kinhte_example_full
301
+ elif sample_option == "Dùng dụ mẫu CNTT (rút gọn)" and model_type == "Dùng đặc trưng quan trọng":
302
+ selected_example = sample_cntt_example_simple
303
+ elif sample_option == "Dùng ví dụ mẫu Kinh tế (rút gọn)" and model_type == "Dùng đặc trưng quan trọng":
304
+ selected_example = sample_kinhte_example_simple
305
+
306
+
307
+ default_name = selected_example["name"] if selected_example else ""
308
+ default_student_id = selected_example["student_id"] if selected_example else ""
309
+ default_major_index = 0 if selected_example and selected_example["major"] == "Công nghệ thông tin" else (1 if selected_example and selected_example["major"] == "Kinh tế" else 0)
310
+
311
+
312
+ name = st.text_input("👤 Họ và tên", value=default_name)
313
+ student_id = st.text_input("🎓 Mã sinh viên", value=default_student_id)
314
+ major = st.selectbox("📚 Ngành học", ["Công nghệ thông tin", "Kinh tế"], index=default_major_index)
315
+
316
 
317
  st.write("---")
318
 
319
  # ===== Nhập thông tin học kỳ =====
320
+
321
+ def input_semester_full(semester_label, default_values=None):
322
+ # Default values structure: [somon0thi, sotc0thi, sotcno, mhno, TCHK, smhk, mhpass, TBCHK, xep_loai, sotc_qua]
323
  with st.expander(f"📖 {semester_label}", expanded=True):
324
  col1, col2 = st.columns(2)
325
  with col1:
326
+ # Use get() with a default value (0 or 0.0) to handle None or shorter lists
327
+ somon0thi = st.number_input("Số môn không thi", min_value=0, value=default_values[0] if default_values and len(default_values)>0 and default_values[0] is not None else 0, key=f"sm0_{semester_label}")
328
+ sotc0thi = st.number_input("Số tín chỉ không thi", min_value=0, value=default_values[1] if default_values and len(default_values)>1 and default_values[1] is not None else 0, key=f"tc0_{semester_label}")
329
+ sotcno = st.number_input("Số tín chỉ nợ", min_value=0, value=default_values[2] if default_values and len(default_values)>2 and default_values[2] is not None else 0, key=f"tcno_{semester_label}")
330
+ mhno = st.number_input("Số môn không đạt", min_value=0, value=default_values[3] if default_values and len(default_values)>3 and default_values[3] is not None else 0, key=f"mhno_{semester_label}")
331
+ sotc_qua = st.number_input("Số tín chỉ qua môn (TCHK - Số TC nợ)", min_value=0, value=default_values[9] if default_values and len(default_values)>9 and default_values[9] is not None else 0, key=f"tcqua_{semester_label}") # Use the pre-calculated or default
 
 
 
332
  with col2:
333
+ TCHK = st.number_input("Tổng tín chỉ học kỳ", min_value=0, value=default_values[4] if default_values and len(default_values)>4 and default_values[4] is not None else 0, key=f"tchk_{semester_label}")
334
+ smhk = st.number_input("Số môn học kỳ", min_value=0, value=default_values[5] if default_values and len(default_values)>5 and default_values[5] is not None else 0, key=f"smhk_{semester_label}")
335
+ mhpass = st.number_input("Số môn đạt", min_value=0, value=default_values[6] if default_values and len(default_values)>6 and default_values[6] is not None else 0, key=f"mhpass_{semester_label}")
336
+ TBCHK = st.number_input("GPA", min_value=0.0, max_value=10.0, value=float(default_values[7]) if default_values and len(default_values)>7 and default_values[7] is not None else 0.0, step=0.01, key=f"gpa_{semester_label}")
337
+ # Ensure the default index is valid for the selectbox options (0 to 6)
338
+ default_xeploai_index = int(default_values[8]) if default_values and len(default_values)>8 and default_values[8] is not None and 0 <= int(default_values[8]) <= 6 else 0
339
+ xep_loai_selected = st.selectbox("Xếp loại", list(range(7)), index=default_xeploai_index, key=f"xeploai_{semester_label}")
340
+
341
+ # Return data in the order expected by the full model (9 features per semester + sotc_qua)
342
+ # [somon0thi, sotc0thi, sotcno, mhno, TCHK, smhk, mhpass, TBCHK, xep_loai, sotc_qua]
343
+ # The order for the model features must match the training data!
344
+ # Let's assume the training data columns per semester were:
345
+ # ['So mon khong thi HK1', 'So tin chi khong thi HK1', 'So tin chi no HK1',
346
+ # 'So mon khong dat HK1', 'So tin chi qua mon HK1', 'Tong tin chi hoc ky HK1',
347
+ # 'So mon hoc ky HK1', 'So mon dat HK1', 'GPA HK1', 'Xep loai HK1']
348
+ # So we need to return: somon0thi, sotc0thi, sotcno, mhno, sotc_qua, TCHK, smhk, mhpass, TBCHK, xep_loai
349
+ return [somon0thi, sotc0thi, sotcno, mhno, sotc_qua, TCHK, smhk, mhpass, TBCHK, xep_loai_selected]
350
+
351
 
352
  def input_important_features(semester_label, default_values=None):
353
+ # Default values structure: [sotc_qua, sotcno, TBCHK]
354
  with st.expander(f"📘 {semester_label}", expanded=True):
355
  col1, col2 = st.columns(2)
356
  with col1:
357
+ sotc_qua = st.number_input("Số tín chỉ đạt", min_value=0, value=default_values[0] if default_values and len(default_values)>0 and default_values[0] is not None else 0, key=f"tcqua_imp_{semester_label}")
358
+ sotcno = st.number_input("Số tín chỉ nợ", min_value=0, value=default_values[1] if default_values and len(default_values)>1 and default_values[1] is not None else 0, key=f"tcno_imp_{semester_label}")
359
  with col2:
360
+ TBCHK = st.number_input("Điểm trung bình", min_value=0.0, max_value=10.0, value=float(default_values[2]) if default_values and len(default_values)>2 and default_values[2] is not None else 0.0, step=0.01, key=f"gpa_imp_{semester_label}")
361
  return [sotc_qua, sotcno, TBCHK]
362
 
363
  # ===== Giao diện theo mô hình =====
364
+
365
  data = []
366
  semesters = ["HỌC KỲ I", "HỌC KỲ II", "HỌC KỲ III", "HỌC KỲ IV", "HỌC KỲ V", "HỌC KỲ VI"]
367
 
368
+ # Mô hình 1: "Dùng toàn bộ đặc trưng"
369
+
370
  if model_type == "Dùng toàn bộ đặc trưng":
371
  st.subheader("🔢 Nhập thông tin học kỳ chi tiết")
372
  for idx, sem in enumerate(semesters):
373
+ default_values = selected_example["semester_data"][idx*10:(idx+1)*10] if selected_example and "semester_data" in selected_example else None # Slice 10 values for full model
374
+ data += input_semester_full(sem, default_values)
 
375
  nganh = 0 if major == "Công nghệ thông tin" else 1
376
+ final_features = np.array(data + [nganh]).reshape(1, -1) # Add nganh feature at the end
377
+
378
+ # Mô hình 2: "Dùng đặc trưng quan trọng"
379
 
380
+ else: # model_type == "Dùng đặc trưng quan trọng"
 
 
 
 
381
  st.subheader("✨ Nhập thông tin rút gọn")
 
382
  for idx, sem in enumerate(semesters):
383
+ default_values = selected_example["semester_data"][idx*3:(idx+1)*3] if selected_example and "semester_data" in selected_example else None # Slice 3 values for simple model
 
384
  data += input_important_features(sem, default_values)
385
+ final_features = np.array(data).reshape(1, -1) # Simple model doesn't include 'nganh' based on your original code structure
 
 
 
 
 
 
386
 
 
 
 
387
 
388
+ # ===== Predict =====
389
 
390
+ if st.button("🎯 DỰ BÁO"):
391
+ try:
392
+ if model_type == "Dùng toàn bộ đặc trưng":
393
+ prediction = model_full.predict(final_features)
394
+ sheet_name = "Trang tính1" # For full data model, save to Trang tính1
395
+ else:
396
+ prediction = model_important.predict(final_features)
397
+ sheet_name = "Trang tính2" # For simple model, save to Trang tính2
398
+
399
+ if prediction[0] == 1:
400
+ st.success(f"🎉 Chúc mừng bạn {name} - {major}! Bạn có khả năng tốt nghiệp đúng hạn!")
401
+ st.balloons()
402
+ else:
403
+ st.error(f"⚠️ Bạn {name} - {major} cần cố gắng hơn! nguy cơ trễ hạn.")
404
+ st.snow()
405
+
406
+ # Ghi log lên Google Sheets
407
+ # Pass the *actual* data used for the model to the save function
408
+ # For the full model, this is 'data' which is 60 elements + nganh = 61. But Gsheet wants the 60.
409
+ # For the simple model, this is 'data' which is 18 elements.
410
+ data_to_save = data + [nganh] if model_type == "Dùng toàn bộ đặc trưng" else data
411
+ save_to_gsheet(name, student_id, major, prediction, data_to_save, sheet_name)
412
+
413
+ except Exception as e:
414
+ st.error(f"Đã xảy ra lỗi trong quá trình dự báo hoặc lưu trữ: {e}")
415
+ # Optional: Print detailed error to console for debugging
416
+ # import traceback
417
+ # st.text(traceback.format_exc())