Tambah tabel nilai skala poin, penanda Nilai Akhir belum lengkap, dan peringatan katrol manual

Halaman Nilai kini punya tabel per-mahasiswa dalam skala poin (bukan 0-100)
supaya terlihat berapa poin riil yang sudah didapat dari total 100 per
Sub-CPMK. Nilai Akhir juga ditandai * kalau sebagian Sub-CPMK belum
ternilai (angkanya sementara), dan sel di grid Sub-CPMK ditandai ✎ kalau
skornya pernah dikoreksi manual dan berisiko tertimpa balik saat sesi
komponen terkait disimpan ulang.
This commit is contained in:
Power BI Dev
2026-07-27 07:16:44 +07:00
parent d6469a01dc
commit b85c75bb31
2 changed files with 203 additions and 4 deletions
+87 -1
View File
@@ -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 []},
)