| |
| |
| |
| import random |
| from typing import List, Dict, Any |
|
|
| |
| 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}, |
| } |
|
|
| |
| |
| CLAUSES_FALLBACK = { |
| "IPS_Terakhir": { |
| "rendah": "capaian IPS semester terakhir yang berada di bawah standar", |
| "tinggi": "capaian IPS semester terakhir yang cukup baik", |
| }, |
| "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" |
| } |
| } |
|
|
| |
| 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 "], |
| } |
|
|
| |
| 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." |
| ) |
| } |
|
|
| |
| 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. |
| """ |
| |
| |
| |
| |
| if feature == "IPS_Terakhir": |
| if condition == "tinggi": |
| if value < 2.5: |
| return "capaian IPS semester terakhir yang sedikit membaik namun masih rawan" |
| elif value < 3.0: |
| |
| return "capaian IPS semester terakhir yang cukup aman" |
| else: |
| return "capaian IPS semester terakhir yang sangat memuaskan" |
| else: |
| return "capaian IPS semester terakhir yang cukup rendah" |
|
|
| |
| elif feature == "IPK_Terakhir": |
| if condition == "tinggi": |
| if value < 2.5: |
| |
| return "IPK kumulatif yang baru saja lolos ambang batas kritis" |
| elif value < 3.0: |
| |
| return "IPK kumulatif yang tergolong cukup baik" |
| else: |
| return "IPK kumulatif yang sangat solid" |
| else: |
| if value < 2.0: |
| return "IPK kumulatif yang berada di zona bahaya" |
| else: |
| return "IPK kumulatif yang masih relatif rendah" |
|
|
| |
| 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: |
| if value < 3: |
| return f"adanya {int(value)} mata kuliah gagal yang perlu segera diulang" |
| else: |
| |
| return f"adanya beban {int(value)} mata kuliah gagal yang menumpuk" |
| |
| |
| elif feature == "Tren_IPS_Slope": |
| if condition == "tinggi": |
| if value < 0.1: |
| return "tren performa yang cukup membaik" |
| else: |
| return "tren peningkatan nilai yang konsisten" |
| else: |
| return "tren performa yang menurun belakangan ini" |
|
|
| |
| fallback = CLAUSES_FALLBACK.get(feature, {}).get(condition) |
| if fallback: |
| return fallback |
| |
| |
| return "" |
|
|
|
|
| def generate_recommendation_paragraph(prediction_val: str, structured_rules: List[Dict[str, Any]]) -> str: |
| """ |
| Menghasilkan rekomendasi personal: Template Statis + Jahitan Dinamis |
| """ |
| |
| |
| 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 |
|
|
| |
| 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"] |
| |
| |
| text = _get_dynamic_clause(feature, condition, value) |
| |
| sentiment = SENTIMENT_MAP.get(feature, {}).get(condition, 0) |
| |
| if text: |
| active_clauses.append({"text": text, "sentiment": sentiment}) |
|
|
| if not active_clauses: |
| |
| return base_text |
|
|
| |
| 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 += "." |
|
|
| |
| |
| |
| final_text = f"{base_text}{dynamic_part}" |
| return final_text |