Spaces:
Sleeping
Sleeping
UI
Browse files
app.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import logging
|
| 2 |
from typing import Optional, Tuple
|
| 3 |
|
|
@@ -180,76 +181,138 @@ def generate_predictions(
|
|
| 180 |
).mean() ** 0.5
|
| 181 |
total_actual = courses_with_actual["actual_enrollment"].sum()
|
| 182 |
total_predicted = courses_with_actual["predicted_enrollment"].sum()
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
"""
|
| 215 |
else:
|
| 216 |
-
summary = f"""
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
"""
|
| 234 |
else:
|
| 235 |
-
summary = f"""
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
-
|
| 239 |
-
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
"""
|
| 254 |
|
| 255 |
# Prepare all predictions display
|
|
@@ -265,26 +328,26 @@ Data semester ada, tetapi tidak ditemukan mata kuliah pilihan yang cocok untuk p
|
|
| 265 |
]
|
| 266 |
].copy()
|
| 267 |
all_predictions_display.columns = [
|
| 268 |
-
"
|
| 269 |
-
"
|
| 270 |
-
"
|
| 271 |
-
"
|
| 272 |
"Status",
|
| 273 |
"Confidence",
|
| 274 |
"Strategy",
|
| 275 |
]
|
| 276 |
-
all_predictions_display["
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
all_predictions_display["
|
| 280 |
|
| 281 |
# Map status to plain text
|
| 282 |
all_predictions_display["Status"] = all_predictions_display["Status"].map(
|
| 283 |
-
{"BUKA": "
|
| 284 |
)
|
| 285 |
|
| 286 |
all_predictions_display = all_predictions_display.sort_values(
|
| 287 |
-
"
|
| 288 |
)
|
| 289 |
|
| 290 |
# Prepare comparison table if actual data exists
|
|
@@ -339,27 +402,25 @@ Data semester ada, tetapi tidak ditemukan mata kuliah pilihan yang cocok untuk p
|
|
| 339 |
].copy()
|
| 340 |
|
| 341 |
comparison_display.columns = [
|
| 342 |
-
"
|
| 343 |
-
"
|
| 344 |
-
"
|
| 345 |
-
"
|
| 346 |
"Error",
|
| 347 |
"Abs Error",
|
| 348 |
-
"
|
| 349 |
"Strategy",
|
| 350 |
]
|
| 351 |
|
| 352 |
-
comparison_display["
|
| 353 |
-
comparison_display["
|
| 354 |
-
1
|
| 355 |
-
)
|
| 356 |
comparison_display["Error"] = comparison_display["Error"].round(1)
|
| 357 |
comparison_display["Abs Error"] = comparison_display["Abs Error"].round(
|
| 358 |
1
|
| 359 |
)
|
| 360 |
-
comparison_display["
|
| 361 |
-
|
| 362 |
-
|
| 363 |
|
| 364 |
comparison_display = comparison_display.sort_values(
|
| 365 |
"Abs Error", ascending=False
|
|
@@ -404,25 +465,26 @@ def get_data_info() -> str:
|
|
| 404 |
elective_courses = courses[courses["kategori_mk"] == "P"]
|
| 405 |
|
| 406 |
info = f"""
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
|
|
|
| 426 |
"""
|
| 427 |
return info
|
| 428 |
|
|
@@ -439,16 +501,14 @@ if not init_success:
|
|
| 439 |
|
| 440 |
# Create Gradio Interface
|
| 441 |
with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
| 442 |
-
#
|
| 443 |
gr.Markdown("# Course Enrollment Predictor")
|
| 444 |
|
| 445 |
-
with gr.Row(
|
| 446 |
# Left panel - Controls
|
| 447 |
-
with gr.Column(scale=1, min_width=
|
| 448 |
-
gr.Markdown("#### Semester Target")
|
| 449 |
-
|
| 450 |
year_input = gr.Number(
|
| 451 |
-
label="
|
| 452 |
value=2025,
|
| 453 |
precision=0,
|
| 454 |
minimum=2020,
|
|
@@ -467,19 +527,28 @@ with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
|
| 467 |
size="lg",
|
| 468 |
)
|
| 469 |
|
|
|
|
|
|
|
| 470 |
# Data info section
|
| 471 |
with gr.Accordion("Dataset Info", open=False):
|
| 472 |
-
data_info_output = gr.
|
| 473 |
demo.load(fn=get_data_info, inputs=[], outputs=data_info_output)
|
| 474 |
|
| 475 |
# Right panel - Results
|
| 476 |
with gr.Column(scale=3):
|
| 477 |
-
summary_output = gr.
|
| 478 |
-
value="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
)
|
| 480 |
|
|
|
|
|
|
|
| 481 |
# Predictions table
|
| 482 |
-
gr.Markdown("
|
| 483 |
all_predictions_output = gr.Dataframe(
|
| 484 |
label="",
|
| 485 |
wrap=True,
|
|
@@ -487,9 +556,9 @@ with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
|
| 487 |
)
|
| 488 |
|
| 489 |
# Comparison section
|
| 490 |
-
with gr.Accordion("
|
| 491 |
comparison_info = gr.Markdown(
|
| 492 |
-
value="
|
| 493 |
)
|
| 494 |
comparison_output = gr.Dataframe(
|
| 495 |
label="",
|
|
@@ -512,7 +581,7 @@ with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
|
| 512 |
all_predictions,
|
| 513 |
gr.update(open=True),
|
| 514 |
gr.update(
|
| 515 |
-
value=f"
|
| 516 |
),
|
| 517 |
gr.update(value=comparison),
|
| 518 |
)
|
|
@@ -523,7 +592,7 @@ with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
|
| 523 |
all_predictions,
|
| 524 |
gr.update(open=False),
|
| 525 |
gr.update(
|
| 526 |
-
value="
|
| 527 |
),
|
| 528 |
gr.update(value=None),
|
| 529 |
)
|
|
|
|
| 1 |
+
# Version: 3.1 - Dark theme UI with white text
|
| 2 |
import logging
|
| 3 |
from typing import Optional, Tuple
|
| 4 |
|
|
|
|
| 181 |
).mean() ** 0.5
|
| 182 |
total_actual = courses_with_actual["actual_enrollment"].sum()
|
| 183 |
total_predicted = courses_with_actual["predicted_enrollment"].sum()
|
| 184 |
+
accuracy_pct = (
|
| 185 |
+
1 - abs(total_predicted - total_actual) / total_actual
|
| 186 |
+
) * 100
|
| 187 |
|
| 188 |
+
diff_color = (
|
| 189 |
+
"#4ade80" if total_predicted - total_actual >= 0 else "#f87171"
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
summary = f"""
|
| 193 |
+
<div style="padding: 24px;">
|
| 194 |
+
<div style="margin-bottom: 24px;">
|
| 195 |
+
<h2 style="margin: 0 0 8px 0; color: #fff; font-size: 24px; font-weight: 600;">{year} Semester {semester_name}</h2>
|
| 196 |
+
<p style="color: #9ca3af; margin: 0; font-size: 14px;">Validasi prediksi terhadap data aktual</p>
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px;">
|
| 200 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #4ade80;">
|
| 201 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Akurasi</div>
|
| 202 |
+
<div style="font-size: 28px; font-weight: 700; color: #4ade80;">{accuracy_pct:.1f}%</div>
|
| 203 |
+
</div>
|
| 204 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #60a5fa;">
|
| 205 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MAE</div>
|
| 206 |
+
<div style="font-size: 28px; font-weight: 700; color: #60a5fa;">{comparison_mae:.2f}</div>
|
| 207 |
+
</div>
|
| 208 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #a78bfa;">
|
| 209 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">RMSE</div>
|
| 210 |
+
<div style="font-size: 28px; font-weight: 700; color: #a78bfa;">{comparison_rmse:.2f}</div>
|
| 211 |
+
</div>
|
| 212 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #fb923c;">
|
| 213 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MK Divalidasi</div>
|
| 214 |
+
<div style="font-size: 28px; font-weight: 700; color: #fb923c;">{len(courses_with_actual)}</div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
| 219 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px;">
|
| 220 |
+
<h4 style="margin: 0 0 16px 0; color: #fff; font-size: 14px; font-weight: 600;">Ringkasan Enrollment</h4>
|
| 221 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #334155;">
|
| 222 |
+
<span style="color: #9ca3af;">Total Aktual</span>
|
| 223 |
+
<span style="font-weight: 600; color: #fff;">{int(total_actual)}</span>
|
| 224 |
+
</div>
|
| 225 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #334155;">
|
| 226 |
+
<span style="color: #9ca3af;">Total Prediksi</span>
|
| 227 |
+
<span style="font-weight: 600; color: #fff;">{int(total_predicted)}</span>
|
| 228 |
+
</div>
|
| 229 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0;">
|
| 230 |
+
<span style="color: #9ca3af;">Selisih</span>
|
| 231 |
+
<span style="font-weight: 600; color: {diff_color};">{int(total_predicted - total_actual):+d}</span>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px;">
|
| 236 |
+
<h4 style="margin: 0 0 16px 0; color: #fff; font-size: 14px; font-weight: 600;">Rekomendasi</h4>
|
| 237 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #334155;">
|
| 238 |
+
<span style="color: #9ca3af;">MK Dibuka</span>
|
| 239 |
+
<span style="font-weight: 600; color: #fff;">{total_to_open}</span>
|
| 240 |
+
</div>
|
| 241 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0; border-bottom: 1px solid #334155;">
|
| 242 |
+
<span style="color: #9ca3af;">Total Kuota</span>
|
| 243 |
+
<span style="font-weight: 600; color: #fff;">{total_seats}</span>
|
| 244 |
+
</div>
|
| 245 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0;">
|
| 246 |
+
<span style="color: #9ca3af;">Backtest MAE</span>
|
| 247 |
+
<span style="font-weight: 600; color: #fff;">{metrics["mae"]:.2f}</span>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
"""
|
| 253 |
else:
|
| 254 |
+
summary = f"""
|
| 255 |
+
<div style="padding: 24px;">
|
| 256 |
+
<div style="margin-bottom: 24px;">
|
| 257 |
+
<h2 style="margin: 0 0 8px 0; color: #fff; font-size: 24px; font-weight: 600;">{year} Semester {semester_name}</h2>
|
| 258 |
+
<p style="color: #9ca3af; margin: 0; font-size: 14px;">Data semester ada, tetapi tidak ditemukan MK pilihan yang cocok</p>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px;">
|
| 262 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #60a5fa;">
|
| 263 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MAE (Backtest)</div>
|
| 264 |
+
<div style="font-size: 28px; font-weight: 700; color: #60a5fa;">{metrics["mae"]:.2f}</div>
|
| 265 |
+
</div>
|
| 266 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #a78bfa;">
|
| 267 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">RMSE (Backtest)</div>
|
| 268 |
+
<div style="font-size: 28px; font-weight: 700; color: #a78bfa;">{metrics["rmse"]:.2f}</div>
|
| 269 |
+
</div>
|
| 270 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #4ade80;">
|
| 271 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MK Dibuka</div>
|
| 272 |
+
<div style="font-size: 28px; font-weight: 700; color: #4ade80;">{total_to_open}</div>
|
| 273 |
+
</div>
|
| 274 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #fb923c;">
|
| 275 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Total Kuota</div>
|
| 276 |
+
<div style="font-size: 28px; font-weight: 700; color: #fb923c;">{total_seats}</div>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
"""
|
| 281 |
else:
|
| 282 |
+
summary = f"""
|
| 283 |
+
<div style="padding: 24px;">
|
| 284 |
+
<div style="margin-bottom: 24px;">
|
| 285 |
+
<h2 style="margin: 0 0 8px 0; color: #fff; font-size: 24px; font-weight: 600;">{year} Semester {semester_name}</h2>
|
| 286 |
+
<p style="color: #9ca3af; margin: 0; font-size: 14px;">Prediksi masa depan berdasarkan tren historis</p>
|
| 287 |
+
</div>
|
| 288 |
+
|
| 289 |
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px;">
|
| 290 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #60a5fa;">
|
| 291 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MAE (Backtest)</div>
|
| 292 |
+
<div style="font-size: 28px; font-weight: 700; color: #60a5fa;">{metrics["mae"]:.2f}</div>
|
| 293 |
+
</div>
|
| 294 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #a78bfa;">
|
| 295 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">RMSE (Backtest)</div>
|
| 296 |
+
<div style="font-size: 28px; font-weight: 700; color: #a78bfa;">{metrics["rmse"]:.2f}</div>
|
| 297 |
+
</div>
|
| 298 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #4ade80;">
|
| 299 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">MK Dibuka</div>
|
| 300 |
+
<div style="font-size: 28px; font-weight: 700; color: #4ade80;">{total_to_open}</div>
|
| 301 |
+
</div>
|
| 302 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; border-left: 4px solid #fb923c;">
|
| 303 |
+
<div style="font-size: 12px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">Total Kuota</div>
|
| 304 |
+
<div style="font-size: 28px; font-weight: 700; color: #fb923c;">{total_seats}</div>
|
| 305 |
+
</div>
|
| 306 |
+
</div>
|
| 307 |
+
|
| 308 |
+
<div style="background: #1e293b; padding: 20px; border-radius: 12px; max-width: 300px;">
|
| 309 |
+
<h4 style="margin: 0 0 16px 0; color: #fff; font-size: 14px; font-weight: 600;">Estimasi Total</h4>
|
| 310 |
+
<div style="display: flex; justify-content: space-between; padding: 12px 0;">
|
| 311 |
+
<span style="color: #9ca3af;">Total Mahasiswa</span>
|
| 312 |
+
<span style="font-weight: 600; color: #fff;">{int(predictions["predicted_enrollment"].sum())}</span>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
"""
|
| 317 |
|
| 318 |
# Prepare all predictions display
|
|
|
|
| 328 |
]
|
| 329 |
].copy()
|
| 330 |
all_predictions_display.columns = [
|
| 331 |
+
"Kode MK",
|
| 332 |
+
"Nama MK",
|
| 333 |
+
"Prediksi",
|
| 334 |
+
"Kuota",
|
| 335 |
"Status",
|
| 336 |
"Confidence",
|
| 337 |
"Strategy",
|
| 338 |
]
|
| 339 |
+
all_predictions_display["Prediksi"] = all_predictions_display["Prediksi"].round(
|
| 340 |
+
1
|
| 341 |
+
)
|
| 342 |
+
all_predictions_display["Kuota"] = all_predictions_display["Kuota"].astype(int)
|
| 343 |
|
| 344 |
# Map status to plain text
|
| 345 |
all_predictions_display["Status"] = all_predictions_display["Status"].map(
|
| 346 |
+
{"BUKA": "BUKA", "TUTUP": "TUTUP"}
|
| 347 |
)
|
| 348 |
|
| 349 |
all_predictions_display = all_predictions_display.sort_values(
|
| 350 |
+
"Prediksi", ascending=False
|
| 351 |
)
|
| 352 |
|
| 353 |
# Prepare comparison table if actual data exists
|
|
|
|
| 402 |
].copy()
|
| 403 |
|
| 404 |
comparison_display.columns = [
|
| 405 |
+
"Kode MK",
|
| 406 |
+
"Nama MK",
|
| 407 |
+
"Aktual",
|
| 408 |
+
"Prediksi",
|
| 409 |
"Error",
|
| 410 |
"Abs Error",
|
| 411 |
+
"Akurasi %",
|
| 412 |
"Strategy",
|
| 413 |
]
|
| 414 |
|
| 415 |
+
comparison_display["Aktual"] = comparison_display["Aktual"].astype(int)
|
| 416 |
+
comparison_display["Prediksi"] = comparison_display["Prediksi"].round(1)
|
|
|
|
|
|
|
| 417 |
comparison_display["Error"] = comparison_display["Error"].round(1)
|
| 418 |
comparison_display["Abs Error"] = comparison_display["Abs Error"].round(
|
| 419 |
1
|
| 420 |
)
|
| 421 |
+
comparison_display["Akurasi %"] = comparison_display["Akurasi %"].round(
|
| 422 |
+
1
|
| 423 |
+
)
|
| 424 |
|
| 425 |
comparison_display = comparison_display.sort_values(
|
| 426 |
"Abs Error", ascending=False
|
|
|
|
| 465 |
elective_courses = courses[courses["kategori_mk"] == "P"]
|
| 466 |
|
| 467 |
info = f"""
|
| 468 |
+
<div style="padding: 8px 0;">
|
| 469 |
+
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;">
|
| 470 |
+
<div style="background: #1e293b; padding: 16px; border-radius: 8px; text-align: center;">
|
| 471 |
+
<div style="font-size: 11px; color: #9ca3af; margin-bottom: 6px;">Total MK</div>
|
| 472 |
+
<div style="font-size: 20px; font-weight: 700; color: #fff;">{len(courses)}</div>
|
| 473 |
+
</div>
|
| 474 |
+
<div style="background: #1e293b; padding: 16px; border-radius: 8px; text-align: center;">
|
| 475 |
+
<div style="font-size: 11px; color: #9ca3af; margin-bottom: 6px;">MK Pilihan</div>
|
| 476 |
+
<div style="font-size: 20px; font-weight: 700; color: #4ade80;">{len(elective_courses)}</div>
|
| 477 |
+
</div>
|
| 478 |
+
<div style="background: #1e293b; padding: 16px; border-radius: 8px; text-align: center;">
|
| 479 |
+
<div style="font-size: 11px; color: #9ca3af; margin-bottom: 6px;">MK Wajib</div>
|
| 480 |
+
<div style="font-size: 20px; font-weight: 700; color: #fff;">{len(courses) - len(elective_courses)}</div>
|
| 481 |
+
</div>
|
| 482 |
+
<div style="background: #1e293b; padding: 16px; border-radius: 8px; text-align: center;">
|
| 483 |
+
<div style="font-size: 11px; color: #9ca3af; margin-bottom: 6px;">Tahun Data</div>
|
| 484 |
+
<div style="font-size: 20px; font-weight: 700; color: #60a5fa;">{students["thn"].min()}-{students["thn"].max()}</div>
|
| 485 |
+
</div>
|
| 486 |
+
</div>
|
| 487 |
+
</div>
|
| 488 |
"""
|
| 489 |
return info
|
| 490 |
|
|
|
|
| 501 |
|
| 502 |
# Create Gradio Interface
|
| 503 |
with gr.Blocks(title="SKS Enrollment Predictor") as demo:
|
| 504 |
+
# Header
|
| 505 |
gr.Markdown("# Course Enrollment Predictor")
|
| 506 |
|
| 507 |
+
with gr.Row():
|
| 508 |
# Left panel - Controls
|
| 509 |
+
with gr.Column(scale=1, min_width=300):
|
|
|
|
|
|
|
| 510 |
year_input = gr.Number(
|
| 511 |
+
label="Tahun",
|
| 512 |
value=2025,
|
| 513 |
precision=0,
|
| 514 |
minimum=2020,
|
|
|
|
| 527 |
size="lg",
|
| 528 |
)
|
| 529 |
|
| 530 |
+
gr.Markdown("---")
|
| 531 |
+
|
| 532 |
# Data info section
|
| 533 |
with gr.Accordion("Dataset Info", open=False):
|
| 534 |
+
data_info_output = gr.HTML()
|
| 535 |
demo.load(fn=get_data_info, inputs=[], outputs=data_info_output)
|
| 536 |
|
| 537 |
# Right panel - Results
|
| 538 |
with gr.Column(scale=3):
|
| 539 |
+
summary_output = gr.HTML(
|
| 540 |
+
value="""
|
| 541 |
+
<div style="padding: 60px 40px; text-align: center; background: #1e293b; border-radius: 12px;">
|
| 542 |
+
<h3 style="color: #fff; margin: 0 0 8px 0; font-size: 18px; font-weight: 600;">Pilih tahun dan semester</h3>
|
| 543 |
+
<p style="color: #9ca3af; margin: 0; font-size: 14px;">Klik Generate Predictions untuk melihat hasil</p>
|
| 544 |
+
</div>
|
| 545 |
+
"""
|
| 546 |
)
|
| 547 |
|
| 548 |
+
gr.Markdown("---")
|
| 549 |
+
|
| 550 |
# Predictions table
|
| 551 |
+
gr.Markdown("### Daftar Prediksi Mata Kuliah")
|
| 552 |
all_predictions_output = gr.Dataframe(
|
| 553 |
label="",
|
| 554 |
wrap=True,
|
|
|
|
| 556 |
)
|
| 557 |
|
| 558 |
# Comparison section
|
| 559 |
+
with gr.Accordion("Detail Validasi", open=False) as comparison_accordion:
|
| 560 |
comparison_info = gr.Markdown(
|
| 561 |
+
value="Data validasi muncul ketika data aktual tersedia",
|
| 562 |
)
|
| 563 |
comparison_output = gr.Dataframe(
|
| 564 |
label="",
|
|
|
|
| 581 |
all_predictions,
|
| 582 |
gr.update(open=True),
|
| 583 |
gr.update(
|
| 584 |
+
value=f"Validasi terhadap {len(comparison)} mata kuliah",
|
| 585 |
),
|
| 586 |
gr.update(value=comparison),
|
| 587 |
)
|
|
|
|
| 592 |
all_predictions,
|
| 593 |
gr.update(open=False),
|
| 594 |
gr.update(
|
| 595 |
+
value="Tidak ada data validasi untuk prediksi masa depan",
|
| 596 |
),
|
| 597 |
gr.update(value=None),
|
| 598 |
)
|