| |
| |
| |
|
|
| import random |
| from typing import List, Dict, Any |
|
|
| |
| from .recommendation_builder import generate_recommendation_paragraph |
|
|
| |
| |
| |
| |
| FEATURE_LABEL_MAP = { |
| |
| "IPK_Terakhir": "IPK Semester Terakhir", |
| "IPS_Terakhir": "IPS Semester Terakhir", |
| "Total_SKS": "Total SKS Diambil", |
| "IPS_Tertinggi": "Capaian IPS Tertinggi", |
| "IPS_Terendah": "Capaian IPS Terendah", |
| "Rentang_IPS": "Stabilitas Nilai (Rentang IPS)", |
| "Jumlah_MK_Gagal": "Jumlah Mata Kuliah Gagal", |
| "Total_SKS_Gagal": "Total SKS Gagal/Hangus", |
| |
| |
| "Tren_IPS_Slope": "Tren Perubahan Nilai", |
| "Perubahan_Kinerja_Terakhir": "Perubahan Kinerja Terakhir", |
| "IPK_Ternormalisasi_SKS": "Rasio Efisiensi IPK per SKS", |
| |
| |
| "Tren_Menaik": "Pola Tren Menaik", |
| "Tren_Menurun": "Pola Tren Menurun", |
| "Tren_Stabil": "Pola Tren Stabil" |
| } |
|
|
| |
| |
| |
| EXPLANATION_TEMPLATES = { |
| "pembuka": { |
| "Resiko Tinggi": [ |
| "β οΈ Perhatian Serius Diperlukan: Sistem mendeteksi indikator risiko tinggi pada profil akademik Anda. Berikut adalah faktor krusial yang memicunya:", |
| "π¨ Peringatan Dini: Berdasarkan pola data historis, performa Anda saat ini berada dalam zona 'Resiko Tinggi'.", |
| ], |
| "Resiko Sedang": [ |
| "β οΈ Waspada: Profil Anda menunjukkan tanda-tanda 'Resiko Sedang'. Belum kritis, namun perlu perbaikan segera.", |
| "π‘ Perlu Evaluasi: Sistem mendeteksi adanya ketidakstabilan yang memicu status 'Resiko Sedang'.", |
| ], |
| "Resiko Rendah": [ |
| "β
Cukup Aman: Profil akademik Anda tergolong 'Resiko Rendah', namun tetap ada beberapa catatan kecil:", |
| "π Progres Baik: Secara umum performa Anda stabil di zona aman. Sistem menyoroti beberapa hal minor:", |
| ], |
| "Aman": [ |
| "π Sangat Baik: Selamat! Rekam jejak akademik Anda sangat solid sehingga dikategorikan 'Aman'.", |
| "π Performa Unggul: Sistem tidak mendeteksi masalah berarti. Prediksi 'Aman' didukung pondasi yang kuat.", |
| ], |
| "default": "π Berikut adalah faktor-faktor analisis sistem untuk kategori '{prediction_val}':" |
| }, |
| "fitur": { |
| |
| "IPS_Terakhir": { |
| "rendah_parah": "π Penurunan Drastis: IPS semester terakhir ({value:.2f}) sangat rendah.", |
| "rendah": "β οΈ Penurunan: IPS semester terakhir ({value:.2f}) berada di bawah ambang batas ideal.", |
| "cukup": "β
Cukup: IPS semester terakhir ({value:.2f}) tercatat di atas ambang batas kritis model.", |
| "baik": "π Baik: Capaian IPS semester terakhir ({value:.2f}) memberikan kontribusi positif.", |
| "tinggi": "π Sangat Solid: IPS semester terakhir Anda ({value:.2f}) sangat baik." |
| }, |
| "IPK_Terakhir": { |
| "rendah_parah": "π Zona Bahaya: IPK kumulatif ({value:.2f}) berada di bawah 2.00.", |
| "rendah": "ποΈ Pondasi Rapuh: IPK kumulatif ({value:.2f}) terdeteksi di zona yang memerlukan perbaikan.", |
| "cukup": "π‘οΈ Cukup Aman: IPK kumulatif ({value:.2f}) telah lolos ambang batas kritis model.", |
| "baik": "ποΈ Pondasi Kokoh: IPK kumulatif ({value:.2f}) Anda tergolong baik.", |
| "tinggi": "π Prestasi: IPK kumulatif ({value:.2f}) Anda sangat solid." |
| }, |
| "Jumlah_MK_Gagal": { |
| "rendah": "β¨ Rekam Jejak Bersih: Anda memiliki sedikit/tanpa mata kuliah gagal (Total: {value}).", |
| "tinggi_sedikit": "π Beban Mengulang: Terdapat {value} mata kuliah gagal yang perlu diwaspadai.", |
| "tinggi_banyak": "π¨ Beban Berat: Terdapat {value} mata kuliah gagal. Tumpukan beban ini meningkatkan risiko." |
| }, |
| "Tren_IPS_Slope": { |
| "rendah": "π Tren Menurun: Grafik performa Anda cenderung melandai/turun.", |
| "tinggi_sedikit": "π Tren Membaik: Grafik nilai Anda menunjukkan sedikit tren kenaikan (slope positif).", |
| "tinggi_kuat": "π Tren Menanjak: Grafik nilai Anda menunjukkan tren kenaikan yang kuat." |
| }, |
| "Rentang_IPS": { |
| "rendah": "βοΈ Performa Stabil: Fluktuasi nilai Anda kecil ({value:.2f}), menunjukkan konsistensi.", |
| "tinggi": "π’ Nilai Fluktuatif: Terdeteksi rentang nilai yang lebar ({value:.2f}) (tidak stabil)." |
| }, |
| "Total_SKS_Gagal": { |
| "rendah": "β
Minim SKS Hangus: Total SKS dari mata kuliah gagal sangat minim ({value}).", |
| "tinggi": "β οΈ SKS Terbuang: Total SKS gagal ({value}) cukup besar dan membebani rasio kelulusan." |
| }, |
| "Total_SKS": { |
| "rendah": "β³ Progres Lambat: Total SKS ({value}) masih tertinggal dari target.", |
| "tinggi": "π On-Track: Tabungan SKS Anda ({value}) sudah cukup banyak." |
| }, |
| |
| "Tren_Menaik": { "ya": "π Grafik Positif: Pola data dikategorikan sebagai tren 'Menaik'." }, |
| "Tren_Menurun": { "ya": "π Peringatan Penurunan: Pola data dikategorikan sebagai tren 'Menurun'." }, |
| "Tren_Stabil": { "ya": "β‘οΈ Stagnan/Stabil: Pola nilai Anda cenderung datar (Stabil)." }, |
| |
| "default": { |
| "rendah": "πΉ Nilai {feature_name} tercatat {value:.2f}, di bawah acuan ({threshold:.2f}).", |
| "tinggi": "πΈ Nilai {feature_name} tercatat {value:.2f}, di atas acuan ({threshold:.2f})." |
| } |
| } |
| } |
|
|
| |
| |
| |
| def _get_explanation_text(rule: Dict[str, Any]) -> str: |
| """Memilih template FAKTOR yang paling sesuai berdasarkan NILAI ASLI.""" |
| |
| raw_feature = rule["feature"] |
| condition = rule["condition"] |
| value = rule["value"] |
| threshold = rule["threshold"] |
| |
| |
| |
| readable_feature_name = FEATURE_LABEL_MAP.get(raw_feature, raw_feature.replace("_", " ")) |
|
|
| templates = EXPLANATION_TEMPLATES["fitur"] |
| |
| |
| if raw_feature in ["Tren_Menaik", "Tren_Menurun", "Tren_Stabil"]: |
| key = "ya" if condition == "tinggi" else "tidak" |
| |
| return templates.get(raw_feature, {}).get(key) |
|
|
| |
| chosen_template_key = "" |
| if raw_feature == "IPS_Terakhir": |
| if condition == "tinggi": |
| if value < 2.75: chosen_template_key = "cukup" |
| elif value < 3.25: chosen_template_key = "baik" |
| else: chosen_template_key = "tinggi" |
| else: |
| if value < 2.0: chosen_template_key = "rendah_parah" |
| else: chosen_template_key = "rendah" |
| |
| elif raw_feature == "IPK_Terakhir": |
| if condition == "tinggi": |
| if value < 2.75: chosen_template_key = "cukup" |
| elif value < 3.25: chosen_template_key = "baik" |
| else: chosen_template_key = "tinggi" |
| else: |
| if value < 2.0: chosen_template_key = "rendah_parah" |
| else: chosen_template_key = "rendah" |
|
|
| elif raw_feature == "Jumlah_MK_Gagal": |
| if condition == "rendah": |
| chosen_template_key = "rendah" |
| else: |
| if value <= 3: chosen_template_key = "tinggi_sedikit" |
| else: chosen_template_key = "tinggi_banyak" |
|
|
| elif raw_feature == "Tren_IPS_Slope": |
| if condition == "tinggi": |
| if value < 0.1: chosen_template_key = "tinggi_sedikit" |
| else: chosen_template_key = "tinggi_kuat" |
| else: |
| chosen_template_key = "rendah" |
| |
| |
| if not chosen_template_key: |
| if raw_feature in templates: |
| |
| chosen_template_key = condition |
| else: |
| |
| |
| return templates["default"][condition].format( |
| feature_name=readable_feature_name, |
| value=value, |
| threshold=threshold |
| ) |
|
|
| |
| template_str = templates.get(raw_feature, {}).get(chosen_template_key) |
| |
| |
| if not template_str: |
| |
| template_str = templates.get(raw_feature, {}).get(condition) |
| |
| if not template_str: |
| |
| return templates["default"][condition].format( |
| feature_name=readable_feature_name, |
| value=value, |
| threshold=threshold |
| ) |
|
|
| |
| if isinstance(template_str, list): |
| template_str = random.choice(template_str) |
|
|
| |
| if not template_str: |
| return None |
|
|
| |
| return template_str.format( |
| feature_name=readable_feature_name, |
| value=value, |
| threshold=threshold |
| ) |
|
|
|
|
| |
| |
| |
| def build_full_response(structured_rules: List[Dict[str, Any]], prediction_val: str) -> Dict[str, Any]: |
| """ |
| Merakit respons lengkap: Poin Faktor + Paragraf Rekomendasi |
| """ |
| try: |
| |
| opening_templates = EXPLANATION_TEMPLATES["pembuka"].get(prediction_val) |
| |
| if not opening_templates: |
| |
| default_template = EXPLANATION_TEMPLATES["pembuka"].get("default", "Analisis Faktor:") |
| opening_line = default_template.format(prediction_val=prediction_val) |
| elif isinstance(opening_templates, list): |
| opening_line = random.choice(opening_templates) |
| else: |
| opening_line = opening_templates |
| |
| factors_list = [] |
| features_explained = set() |
| |
| for rule in reversed(structured_rules): |
| feature = rule["feature"] |
| if feature in features_explained: continue |
| features_explained.add(feature) |
| |
| |
| chosen_template = _get_explanation_text(rule) |
| |
| if chosen_template: |
| factors_list.append(chosen_template) |
| |
| |
| recommendation_text = generate_recommendation_paragraph(prediction_val, structured_rules) |
| |
| |
| return { |
| "opening_line": opening_line, |
| "factors": factors_list, |
| "recommendation": recommendation_text |
| } |
| |
| except Exception as e: |
| return { |
| "opening_line": f"β οΈ Maaf, terjadi kesalahan saat menyusun penjelasan: {str(e)}", |
| "factors": [], |
| "recommendation": "Gagal memuat rekomendasi personal." |
| } |