Gcompro / graduation_logic.py
tututz's picture
Upload 9 files
410b443 verified
# ======================================================================
# --- graduation_logic.py ---
# ======================================================================
import networkx as nx
from typing import Dict, Any, List
def _predict_naive(current_semester: int, total_sks_passed: int, last_gpa: float) -> Dict[str, Any]:
"""
Logika perhitungan matematis dasar (Naive) berdasarkan SKS dan IPK.
Menggunakan teks deskripsi custom sesuai permintaan user.
"""
TARGET_SKS = 144
TARGET_SEMESTER = 8
MAX_SKS_REGULAR = 24 # Batas absolut reguler (biasanya IP >= 3.00)
LIMIT_SKS_LOW_GPA = 20 # Batas jika IP < 3.00
SAFE_THRESHOLD = 18 # Batas aman/santai
# 1. Hitung Sisa
sks_needed = TARGET_SKS - total_sks_passed
semesters_left = (TARGET_SEMESTER - current_semester) + 1
# Stats dasar untuk dikembalikan
stats = {
"sks_needed": sks_needed,
"semesters_left": semesters_left,
"required_pace": 0,
"student_capacity": int(MAX_SKS_REGULAR if last_gpa >= 3.00 else LIMIT_SKS_LOW_GPA)
}
# --- LOGIC: Sudah Semester Akhir / Lewat ---
if semesters_left <= 0:
if sks_needed <= 0:
return {
"status": "Lulus",
"color": "green",
"description": "Selamat! Anda telah menyelesaikan kebutuhan SKS minimal.",
"stats": stats
}
else:
stats["required_pace"] = sks_needed
return {
"status": "Terlambat",
"color": "red",
"description": f"Saat ini semester {current_semester} dan SKS belum terpenuhi. Target lulus 8 semester sudah terlewat.",
"stats": stats
}
# 2. Hitung Kecepatan yang Dibutuhkan (Required Pace)
required_sks_per_sem = sks_needed / semesters_left
stats["required_pace"] = round(required_sks_per_sem, 2)
# 3. Evaluasi Status (Teks dari User)
student_capacity = stats["student_capacity"]
if required_sks_per_sem > MAX_SKS_REGULAR:
# KASUS: Mustahil reguler
return {
"status": "๐Ÿ”ด Terlambat",
"color": "red",
"description": f"Target lulus semester 8 tidak memungkinkan. Anda butuh rata-rata {required_sks_per_sem:.1f} SKS yang perlu dipenuhi setiap semester selanjutnya, melebihi batas reguler yaitu 24 SKS.",
"stats": stats
}
elif required_sks_per_sem <= SAFE_THRESHOLD:
# KASUS: Aman
return {
"status": "๐ŸŸข Aman",
"color": "green",
"description": f"Posisi aman. Beban ringan, sisa (~{required_sks_per_sem:.1f} SKS yang perlu dipenuhi tiap semester. Pertahankan performa tiap semester!",
"stats": stats
}
elif required_sks_per_sem <= student_capacity:
# KASUS: Padat tapi Masih Mungkin
status_text = "๐ŸŸก Jadwal Relatif Padat"
return {
"status": status_text,
"color": "yellow",
"description": f"Diperkirakan anda butuh ~{required_sks_per_sem:.1f} SKS yang perlu dipenuhi untuk semester-semester selanjutnya. Kapasitas SKS Anda ({student_capacity}) Sudah cukup mendukung untuk mengejar target ini.",
"stats": stats
}
else:
# KASUS: Terhambat IPK
return {
"status": "๐ŸŸ  Rawan Terlambat",
"color": "orange",
"description": f"Hati-hati! Anda butuh {required_sks_per_sem:.1f} SKS tiap semester agar lulus tepat waktu, tapi IPK saat ini membatasi jatah cuma {student_capacity} SKS.",
"stats": stats
}
def predict_graduation_status(
current_semester: int,
total_sks_passed: int,
last_gpa: float,
graph_G: nx.DiGraph = None, # Optional: Graph object (pass by reference)
passed_courses: List[str] = None # Optional: List kode MK lulus
) -> Dict[str, Any]:
"""
Fungsi utama: Menjalankan logika Naive, lalu melakukan Override jika
ditemukan masalah struktural pada graf (rantai prasyarat).
"""
# 1. Jalankan Prediksi Naive (Matematis)
result = _predict_naive(current_semester, total_sks_passed, last_gpa)
# Jika data graf tidak lengkap atau status sudah Critical/Lulus, kembalikan hasil naive
if graph_G is None or passed_courses is None:
return result
if result["color"] == "red" or result["status"] == "Lulus":
return result
# 2. LOGIKA OVERRIDE: Cek Rantai Prasyarat (Critical Path)
try:
# A. Identifikasi MK yang BELUM lulus
all_courses = set(graph_G.nodes())
passed_set = set(passed_courses)
unpassed_courses = list(all_courses - passed_set)
if not unpassed_courses:
return result
# B. Buat Subgraph (Hanya berisi matkul sisa & relasinya)
subgraph_remaining = graph_G.subgraph(unpassed_courses)
# C. Hitung Longest Path (Rantai Terpanjang) di subgraph
# dag_longest_path mengembalikan list node, misal ['A', 'B', 'C'] -> Panjang 3
if nx.is_directed_acyclic_graph(subgraph_remaining):
longest_chain_path = nx.dag_longest_path(subgraph_remaining)
min_semesters_needed_structural = len(longest_chain_path)
semesters_left = result["stats"]["semesters_left"]
# D. Bandingkan dengan Sisa Waktu
if min_semesters_needed_structural > semesters_left:
result["status"] = "๐Ÿ”ด Terlambat (Struktural)"
result["color"] = "red"
result["description"] = (
f"SKS tersisa cukup, namun terdeteksi rantai prasyarat panjang yang terdiri"
f"({min_semesters_needed_structural} Mata Kuliah Beruntun) dan tidak bisa diambil sekaligus dalam sisa waktu."
)
# Tambahkan info debug ke stats
result["stats"]["structural_issue"] = True
result["stats"]["longest_chain_len"] = min_semesters_needed_structural
result["stats"]["longest_chain_path"] = longest_chain_path
except Exception as e:
print(f"Graph Analysis Warning: {e}")
# Jika error graf, fallback ke hasil naive
return result
return result