diff --git a/app/main.py b/app/main.py index edd24bb..530ff89 100644 --- a/app/main.py +++ b/app/main.py @@ -703,6 +703,61 @@ def _tabel_komponen(grid: list[dict], komponen_daftar: list[dict], return out +def _rincian_bobot(kode: str) -> tuple[list[dict], dict[str, list[dict]]]: + """Rincian pembagian bobot komponen (RPS bagian G) ke tiap Sub-CPMK — + bobot_komponen ÷ jumlah Sub-CPMK komponen itu, dijumlah per Sub-CPMK bila + diukur >1 komponen. Murni dari konfigurasi RPS (tak bergantung nilai + mahasiswa) — dipakai untuk menjelaskan metodologi bobot di halaman nilai.""" + rps = db.ambil_rps(kode) + per_komponen: list[dict] = [] + per_sub: dict[str, list[dict]] = {} + if not rps: + return per_komponen, per_sub + for k in rps["data"].get("komponen", []): + subs = k.get("subs") or [] + if not subs: + continue + try: + bobot = float(k.get("bobot") or 0) + except (TypeError, ValueError): + bobot = 0 + if not bobot: + continue + poin = bobot / len(subs) + per_komponen.append({"nama": k["nama"], "bobot": bobot, + "n_subs": len(subs), "poin": poin}) + for s in subs: + per_sub.setdefault(s, []).append({"nama": k["nama"], "poin": poin}) + return per_komponen, per_sub + + +def _tabel_poin(grid: list[dict], bobot_sub_rincian: dict[str, list[dict]]) -> list[dict]: + """Nilai per mahasiswa dalam skala POIN (bukan 0-100) — tiap Sub-CPMK + ditimbang sesuai kontribusi poinnya ke total 100 (lihat _rincian_bobot). + Poin diperoleh = skor/100 × poin maks Sub-CPMK itu. Kolom Total adalah + jumlah poin yang SUDAH didapat mahasiswa dari Sub-CPMK yang sudah + ternilai — bukan nilai akhir yang direskalakan ke bobot yang ternilai + saja, jadi Total ini bisa lebih kecil dari Nilai Akhir bila masih ada + Sub-CPMK yang belum dinilai.""" + poin_maks = {s: sum(c["poin"] for c in kontribusi) + for s, kontribusi in bobot_sub_rincian.items()} + out = [] + for m in grid: + skor = m.get("skor") or {} + poin, total = {}, 0.0 + for s, maks in poin_maks.items(): + v = skor.get(s) + if v is None: + poin[s] = None + else: + dapat = v / 100 * maks + poin[s] = {"dapat": dapat, "maks": maks} + total += dapat + out.append({"nim": m["nim"], "nama": m["nama"], "kelas": m.get("kelas"), + "poin": poin, "total": total}) + return out + + def _konteks_nilai(request, kode, pesan="", peringatan=None): peta = db.peta_mk(kode) tersimpan = db.ambil_dok("nilai", kode) @@ -721,13 +776,44 @@ def _konteks_nilai(request, kode, pesan="", peringatan=None): grid = _grid_mahasiswa(tersimpan, terdaftar, kelas_dosen) komponen_daftar = _komponen_daftar(kode) sesi_semua = (tersimpan["data"].get("sesi") or {}) if tersimpan else {} + bobot_komponen, bobot_sub_rincian = _rincian_bobot(kode) + # Nilai Akhir dihitung hanya dari Sub-CPMK yang sudah ternilai (lihat + # nilai_akhir_mahasiswa) — kalau belum semua Sub-CPMK MK ini ternilai, + # angkanya cuma perkiraan sementara dan bisa berubah; ditandai bintang + # di template supaya tak disalahsangka sudah final. + total_subs = len(peta.get("subs") or []) + tidak_lengkap = set() + if total_subs: + for m in grid: + skor = m.get("skor") or {} + n_terisi = sum(1 for s in peta["subs"] if skor.get(s["kode"]) is not None) + if n_terisi < total_subs: + tidak_lengkap.add(m["nim"]) + # Sub-CPMK yang skor tersimpannya beda dari hasil hitung ulang sesi — + # berarti pernah "dikatrol" manual lewat grid Nilai per Mahasiswa. Nilai + # itu akan tertimpa balik kalau sesi/komponen yang mengukur Sub-CPMK + # tsb disimpan lagi (lihat aksi="komponen" di simpan_nilai), jadi + # ditandai di template supaya dosen tak kaget kalau koreksinya hilang. + dikatrol = set() + if komponen_daftar: + skor_hitung_sesi = hitung.skor_dari_sesi(komponen_daftar, sesi_semua) + for m in grid: + terhitung = skor_hitung_sesi.get(m["nim"]) or {} + tersimpan_skor = m.get("skor") or {} + for sub_kode, v_terhitung in terhitung.items(): + v_tersimpan = tersimpan_skor.get(sub_kode) + if v_tersimpan is not None and abs(v_tersimpan - v_terhitung) > 0.05: + dikatrol.add((m["nim"], sub_kode)) return render( request, "form_nilai.html", {"peta": peta, "tersimpan": tersimpan, "hasil": hasil, "ambang": ambang, "akhir": akhir, "tahun": db.tahun_aktif(), "pesan": pesan, - "grid": grid, "kelas_dosen": kelas_dosen, + "grid": grid, "kelas_dosen": kelas_dosen, "tidak_lengkap": tidak_lengkap, + "dikatrol": dikatrol, "komponen_daftar": komponen_daftar, "tabel_komponen": _tabel_komponen(grid, komponen_daftar, sesi_semua) if komponen_daftar else [], + "bobot_komponen": bobot_komponen, "bobot_sub_rincian": bobot_sub_rincian, + "tabel_poin": _tabel_poin(grid, bobot_sub_rincian) if bobot_sub_rincian else [], "peringatan": peringatan or []}, ) diff --git a/app/templates/form_nilai.html b/app/templates/form_nilai.html index 5e869b2..009036e 100644 --- a/app/templates/form_nilai.html +++ b/app/templates/form_nilai.html @@ -99,12 +99,15 @@ min="0" max="100" step="any" value="{{ '%g'|format(r.nilai[k.i]) if r.nilai[k.i] is not none else '' }}"> {% endfor %} -
* Sebagian Sub-CPMK mahasiswa ini belum ternilai — Nilai Akhir dihitung hanya dari yang sudah ternilai dan bisa berubah setelah sisanya diisi.
+ {% endif %} + {% if tidak_lengkap %} +* Sebagian Sub-CPMK mahasiswa ini belum ternilai — Nilai Akhir dihitung hanya dari yang sudah ternilai dan bisa berubah setelah sisanya diisi.
+ {% endif %} + {% if dikatrol %} +✎ = nilai sudah dikoreksi manual di sini (beda dari hasil hitung sesi/tugas komponennya) — hati-hati, koreksi ini akan tertimpa balik kalau sesi/komponen yang mengukur Sub-CPMK itu diisi/disimpan lagi.
+ {% endif %}