Spaces:
Sleeping
Sleeping
| # ====================================================================== | |
| # --- recommendation_builder.py (LOGIKA DIPERBAIKI) --- | |
| # ====================================================================== | |
| import random | |
| from typing import List, Dict, Any | |
| # --- SENTIMENT CONFIG --- | |
| SENTIMENT_MAP = { | |
| "IPS_Terakhir": {"rendah": -1, "tinggi": 1}, | |
| "IPK_Terakhir": {"rendah": -1, "tinggi": 1}, | |
| "Jumlah_MK_Gagal": {"rendah": 1, "tinggi": -1}, | |
| "Total_SKS": {"rendah": -1, "tinggi": 1}, | |
| "Tren_IPS_Slope": {"rendah": -1, "tinggi": 1}, | |
| "Rentang_IPS": {"rendah": 1, "tinggi": -1}, | |
| "Total_SKS_Gagal": {"rendah": 1, "tinggi": -1}, | |
| } | |
| # --- KLAUSA TEKS (FALLBACK) --- | |
| # Ini digunakan jika tidak ada 'Sanity Check' khusus | |
| CLAUSES_FALLBACK = { | |
| "IPS_Terakhir": { | |
| "rendah": "capaian IPS semester terakhir yang berada di bawah standar", | |
| "tinggi": "capaian IPS semester terakhir yang cukup baik", # Dibuat netral | |
| }, | |
| "IPK_Terakhir": { | |
| "rendah": "nilai IPK kumulatif yang masih relatif rendah", | |
| "tinggi": "rekam IPK kumulatif yang solid", | |
| }, | |
| "Jumlah_MK_Gagal": { | |
| "rendah": "rekam jejak mata kuliah yang cukup baik", | |
| "tinggi": "adanya beban mata kuliah gagal", | |
| }, | |
| "Total_SKS_Gagal": { | |
| "rendah": "minimnya SKS yang terbuang", | |
| "tinggi": "besarnya jumlah SKS yang harus diulang", | |
| }, | |
| "Tren_IPS_Slope": { | |
| "rendah": "tren performa yang menurun belakangan ini", | |
| "tinggi": "tren peningkatan nilai yang konsisten", | |
| }, | |
| "Total_SKS": { | |
| "rendah": "jumlah SKS yang masih di bawah target progres studi", | |
| "tinggi": "kemajuan pengambilan SKS yang sejalan dengan rencana studi", | |
| }, | |
| "Rentang_IPS": { | |
| "rendah": "konsistensi performa yang stabil", | |
| "tinggi": "fluktuasi performa yang tidak stabil" | |
| } | |
| } | |
| # --- KATA SAMBUNG --- | |
| CONNECTORS = { | |
| "same_bad": [". Masalah ini diperberat dengan ", ". Selain itu, terdeteksi juga ", ". Sayangnya, hal ini diikuti oleh "], | |
| "same_good": [". Hal ini didukung pula oleh ", ". Ditambah lagi dengan ", ". Serta adanya "], | |
| "contrast_bad_to_good": [". Namun kabar baiknya, ", ". Walaupun begitu, Anda memiliki ", ". Untungnya, hal ini diimbangi oleh "], | |
| "contrast_good_to_bad": [". Namun sayangnya, ", ". Meskipun demikian, perlu diwaspadai adanya ", ". Akan tetapi, sistem mencatat "], | |
| } | |
| # --- TEMPLATE STATIS (FORMATTING DIHAPUS) --- | |
| REKOMENDASI_BANK = { | |
| "Resiko Tinggi": [ | |
| ( | |
| "🚨 Tindakan Mendesak Diperlukan. Berdasarkan analisis sistem, Anda berada di kategori Resiko Tinggi. " | |
| "Segera lakukan evaluasi mendalam terhadap kebiasaan belajar, disiplin waktu, dan strategi akademik Anda. " | |
| "Prioritaskan perbaikan pada mata kuliah dengan nilai rendah, manfaatkan bimbingan dosen, " | |
| "dan pertimbangkan untuk mengurangi beban SKS sementara agar fokus pada peningkatan performa inti." | |
| ), | |
| ( | |
| "⚠️ Perhatian Serius. Performa akademik Anda menunjukkan tanda risiko tinggi. " | |
| "Usahakan untuk memperbaiki IPK dan IPS dengan memperkuat dasar konsep, " | |
| "bergabung dalam kelompok belajar, serta mencari mentor akademik. " | |
| "Manajemen waktu dan pola belajar teratur akan sangat membantu dalam mengembalikan performa Anda." | |
| ) | |
| ], | |
| "Resiko Sedang": [ | |
| ( | |
| "⚠️ Waspada & Antisipasi. Anda berada di zona Resiko Sedang. " | |
| "Hal ini menandakan performa Anda masih fluktuatif. " | |
| "Pertahankan aspek yang sudah baik, namun segera identifikasi area yang masih lemah. " | |
| "Disarankan untuk membuat jadwal belajar lebih terstruktur dan melakukan evaluasi kecil tiap minggu." | |
| ), | |
| ( | |
| "💡 Perlu Peningkatan. Kinerja akademik Anda stabil namun belum optimal. " | |
| "Fokuslah pada konsistensi nilai dan hindari penurunan mendadak di semester berikutnya. " | |
| "Coba tingkatkan interaksi dengan dosen dan teman sekelas untuk memperkuat pemahaman materi." | |
| ) | |
| ], | |
| "Resiko Rendah": [ | |
| ( | |
| "✅ Pertahankan Momentum. Anda berada di kategori Resiko Rendah. " | |
| "Performa Anda sudah cukup baik dan konsisten. " | |
| "Teruskan pola belajar yang efektif, namun jangan lengah terhadap materi yang sulit. " | |
| "Pertimbangkan untuk mengambil tantangan baru seperti proyek penelitian atau lomba akademik." | |
| ), | |
| ( | |
| "📈 Progres Positif. Anda menunjukkan performa yang solid. " | |
| "Gunakan kesempatan ini untuk memperkuat area yang masih lemah dan menjaga keseimbangan antara studi dan istirahat. " | |
| "Tetap evaluasi hasil belajar secara berkala untuk memastikan kestabilan performa." | |
| ) | |
| ], | |
| "Aman": [ | |
| ( | |
| "🌟 Luar Biasa! Anda berada di kategori Aman. " | |
| "Kinerja akademik Anda konsisten dan menunjukkan kedewasaan belajar yang tinggi. " | |
| "Pertahankan strategi belajar yang sudah terbukti efektif, " | |
| "dan jangan ragu berbagi pengalaman dengan rekan yang membutuhkan bantuan." | |
| ), | |
| ( | |
| "🏆 Prestasi Stabil. Sistem mendeteksi profil akademik Anda sangat kuat. " | |
| "Anda dapat mulai mengeksplorasi kegiatan tambahan seperti magang, penelitian, atau lomba akademik " | |
| "untuk memperluas wawasan dan pengalaman profesional." | |
| ) | |
| ], | |
| "default": ( | |
| "🔍 Evaluasi Umum. Hasil prediksi Anda menunjukkan area yang perlu diperhatikan. " | |
| "Tetap jaga semangat belajar dan lakukan refleksi berkala terhadap hasil akademik Anda." | |
| ) | |
| } | |
| # --- [FUNGSI BARU] LOGIC SANITY CHECK --- | |
| def _get_dynamic_clause(feature: str, condition: str, value: float) -> str: | |
| """ | |
| Logika Cerdas: Menyesuaikan kata sifat berdasarkan NILAI ASLI, | |
| bukan hanya label 'tinggi/rendah' dari decision tree. | |
| """ | |
| # --- LOGIKA OVERRIDE (SANITY CHECK) --- | |
| # 1. IPS Terakhir (Skala 0-4) | |
| if feature == "IPS_Terakhir": | |
| if condition == "tinggi": # DT bilang "tinggi" | |
| if value < 2.5: | |
| return "capaian IPS semester terakhir yang sedikit membaik namun masih rawan" | |
| elif value < 3.0: | |
| # Ini akan menangkap 2.62 dan 2.92 | |
| return "capaian IPS semester terakhir yang cukup aman" | |
| else: | |
| return "capaian IPS semester terakhir yang sangat memuaskan" | |
| else: # DT bilang "rendah" | |
| return "capaian IPS semester terakhir yang cukup rendah" | |
| # 2. IPK Terakhir (Skala 0-4) | |
| elif feature == "IPK_Terakhir": | |
| if condition == "tinggi": # DT bilang "tinggi" | |
| if value < 2.5: | |
| # Ini akan menangkap 2.15 | |
| return "IPK kumulatif yang baru saja lolos ambang batas kritis" | |
| elif value < 3.0: | |
| # Ini akan menangkap 2.78 | |
| return "IPK kumulatif yang tergolong cukup baik" | |
| else: | |
| return "IPK kumulatif yang sangat solid" | |
| else: # DT bilang "rendah" | |
| if value < 2.0: | |
| return "IPK kumulatif yang berada di zona bahaya" | |
| else: | |
| return "IPK kumulatif yang masih relatif rendah" | |
| # 3. MK Gagal | |
| elif feature == "Jumlah_MK_Gagal": | |
| if condition == "rendah": | |
| if value == 0: | |
| return "rekam jejak mata kuliah yang bersih tanpa kegagalan" | |
| else: | |
| return f"jumlah mata kuliah gagal yang masih dalam batas wajar ({int(value)} MK)" | |
| else: # DT bilang "tinggi" | |
| if value < 3: | |
| return f"adanya {int(value)} mata kuliah gagal yang perlu segera diulang" | |
| else: | |
| # Ini akan menangkap 4 Gagal | |
| return f"adanya beban {int(value)} mata kuliah gagal yang menumpuk" | |
| # 4. Tren | |
| elif feature == "Tren_IPS_Slope": | |
| if condition == "tinggi": | |
| if value < 0.1: # Jika naiknya sedikit (kasus 0.066) | |
| return "tren performa yang cukup membaik" | |
| else: | |
| return "tren peningkatan nilai yang konsisten" | |
| else: | |
| return "tren performa yang menurun belakangan ini" | |
| # Jika tidak ada aturan khusus, gunakan Fallback | |
| fallback = CLAUSES_FALLBACK.get(feature, {}).get(condition) | |
| if fallback: | |
| return fallback | |
| # Jika tidak ada fallback, return string kosong | |
| return "" | |
| def generate_recommendation_paragraph(prediction_val: str, structured_rules: List[Dict[str, Any]]) -> str: | |
| """ | |
| Menghasilkan rekomendasi personal: Template Statis + Jahitan Dinamis | |
| """ | |
| # 1️⃣ Pilih template dasar (statis) dari REKOMENDASI_BANK | |
| base_templates = REKOMENDASI_BANK.get(prediction_val) | |
| if not base_templates: | |
| base_templates = [REKOMENDASI_BANK["default"]] | |
| if isinstance(base_templates, list): | |
| base_text = random.choice(base_templates) | |
| else: | |
| base_text = base_templates # Handle 'default' yang bukan list | |
| # 2️⃣ Buat bagian dinamis (berdasarkan fitur dominan) | |
| active_clauses = [] | |
| features_seen = set() | |
| for rule in reversed(structured_rules): | |
| feature = rule["feature"] | |
| if feature in features_seen: continue | |
| features_seen.add(feature) | |
| condition = rule["condition"] | |
| value = rule["value"] # Ambil nilai asli | |
| # [DIUBAH] Panggil helper baru, bukan CLAUSES.get() | |
| text = _get_dynamic_clause(feature, condition, value) | |
| sentiment = SENTIMENT_MAP.get(feature, {}).get(condition, 0) | |
| if text: # Hanya tambahkan jika text tidak kosong | |
| active_clauses.append({"text": text, "sentiment": sentiment}) | |
| if not active_clauses: | |
| # Jika tidak ada fitur penting, kembalikan template statis saja | |
| return base_text | |
| # 3️⃣ Stitching: gabungkan klausa | |
| dynamic_part = " Dalam penjelasan yang lebih spesifik, profil Anda dipengaruhi oleh " | |
| current = active_clauses[0] | |
| dynamic_part += current["text"] | |
| last_sentiment = current["sentiment"] | |
| for i in range(1, len(active_clauses)): | |
| item = active_clauses[i] | |
| current_sentiment = item["sentiment"] | |
| if last_sentiment == -1 and current_sentiment == -1: | |
| connector = random.choice(CONNECTORS["same_bad"]) | |
| elif last_sentiment == 1 and current_sentiment == 1: | |
| connector = random.choice(CONNECTORS["same_good"]) | |
| elif last_sentiment == -1 and current_sentiment == 1: | |
| connector = random.choice(CONNECTORS["contrast_bad_to_good"]) | |
| elif last_sentiment == 1 and current_sentiment == -1: | |
| connector = random.choice(CONNECTORS["contrast_good_to_bad"]) | |
| else: | |
| connector = ". Selanjutnya, perhatikan juga " | |
| dynamic_part += connector + item["text"] | |
| last_sentiment = current_sentiment | |
| dynamic_part += "." | |
| # 4️⃣ Gabungkan template + hasil stitching | |
| # Format: [Paragraf Statis] + [Paragraf Dinamis] | |
| # \n\n digunakan untuk membuat paragraf baru | |
| final_text = f"{base_text}{dynamic_part}" | |
| return final_text |