Tambah ringkasan nilai per komponen, rincian tugas ala RTM, auto-save RPS
- Halaman Nilai: tabel "Nilai per Komponen" (rata-rata skor mentah tiap sesi, bisa diedit langsung) berdampingan dengan grid Sub-CPMK yang tetap presisi; kolom persentase ternilai di Hasil Ketercapaian. - Komponen Tugas: sesi bisa menyasar subset Sub-CPMK (RPS tetap batasannya), lengkap dengan field ala RTM (bentuk tugas, deskripsi, metode, luaran, indikator/bobot, jadwal) dan tombol isi-otomatis per bentuk tugas. - RPS: auto-save berkala sebagai draft, tombol ke atas, validasi total bobot komponen 100% saat ditandai selesai, komponen berbobot kosong disembunyikan dari halaman Nilai. - Admin: tampilkan kelas yang tersedia dari data mahasiswa saat mengatur penugasan dosen per kelas. - Perbaikan step="any" pada input nilai (mencegah validasi HTML5 menolak nilai desimal hasil roll-up). - Tambah METODOLOGI_ASESMEN_KOMPONEN.md: dokumentasi metodologi Sub-CPMK vs komponen, termasuk skenario tugas menyasar sebagian Sub-CPMK. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+25
-20
@@ -98,26 +98,20 @@ def skor_dari_sesi(komponen_daftar: list[dict], sesi: dict) -> dict[str, dict[st
|
||||
antar-komponen bila satu Sub-CPMK diukur oleh >1 komponen (mis. Kuis dan
|
||||
Presentasi sama-sama menilai satu Sub-CPMK) — bobot mengikuti alokasi
|
||||
yang sama dengan bobot_sub_dari_rps (bobot komponen dibagi rata ke subnya).
|
||||
|
||||
Tiap sesi boleh punya "subs" sendiri (subset Sub-CPMK komponen itu — dipakai
|
||||
komponen Observasi/Tugas untuk rincian ala RTM per tugas bernama); sesi
|
||||
tanpa "subs" (atau kosong) dianggap menyasar semua Sub-CPMK komponen itu
|
||||
(perilaku lama). Dengan begitu satu komponen boleh diisi lewat beberapa
|
||||
sesi yang masing-masing hanya menyasar sebagian Sub-CPMK, tanpa
|
||||
menyamaratakan nilainya ke Sub-CPMK lain yang tak disasar sesi itu.
|
||||
"""
|
||||
komponen_by_i = {k["i"]: k for k in komponen_daftar}
|
||||
per_komponen_avg: dict[int, dict[str, float]] = {}
|
||||
for i_str, daftar_sesi in (sesi or {}).items():
|
||||
try:
|
||||
i = int(i_str)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if i not in komponen_by_i or not daftar_sesi:
|
||||
continue
|
||||
per_nim: dict[str, list[float]] = {}
|
||||
for s in daftar_sesi:
|
||||
for nim, v in (s.get("nilai") or {}).items():
|
||||
if v is not None:
|
||||
per_nim.setdefault(nim, []).append(float(v))
|
||||
per_komponen_avg[i] = {nim: _rata(xs) for nim, xs in per_nim.items()}
|
||||
|
||||
kontribusi_per_sub: dict[str, list[tuple[float, dict]]] = {}
|
||||
for i, avg_map in per_komponen_avg.items():
|
||||
k = komponen_by_i[i]
|
||||
for i, k in komponen_by_i.items():
|
||||
daftar_sesi = (sesi or {}).get(i) or []
|
||||
if not daftar_sesi:
|
||||
continue
|
||||
try:
|
||||
bobot = float(k.get("bobot") or 0)
|
||||
except (TypeError, ValueError):
|
||||
@@ -125,6 +119,15 @@ def skor_dari_sesi(komponen_daftar: list[dict], sesi: dict) -> dict[str, dict[st
|
||||
n_subs = len(k["subs"]) or 1
|
||||
w = bobot / n_subs if bobot else 1.0
|
||||
for sub in k["subs"]:
|
||||
relevan = [s for s in daftar_sesi if sub in (s.get("subs") or k["subs"])]
|
||||
if not relevan:
|
||||
continue
|
||||
per_nim: dict[str, list[float]] = {}
|
||||
for s in relevan:
|
||||
for nim, v in (s.get("nilai") or {}).items():
|
||||
if v is not None:
|
||||
per_nim.setdefault(nim, []).append(float(v))
|
||||
avg_map = {nim: _rata(xs) for nim, xs in per_nim.items()}
|
||||
kontribusi_per_sub.setdefault(sub, []).append((w, avg_map))
|
||||
|
||||
out: dict[str, dict[str, float]] = {}
|
||||
@@ -192,9 +195,11 @@ def rollup_mk(peta: dict, nilai_data: dict, ambang: float) -> dict:
|
||||
|
||||
cpl: dict[str, dict] = {}
|
||||
for cp in peta["cpls"]:
|
||||
skor = _rata([v["skor"] for v in cpmk.values()
|
||||
if cp["kode"] in v["cpl"] and v["skor"] is not None])
|
||||
cpl[cp["kode"]] = {"skor": skor}
|
||||
pendukung = [v for v in cpmk.values() if cp["kode"] in v["cpl"]]
|
||||
ternilai = [v for v in pendukung if v["skor"] is not None]
|
||||
skor = _rata([v["skor"] for v in ternilai])
|
||||
cpl[cp["kode"]] = {"skor": skor, "n_cpmk": len(pendukung),
|
||||
"n_cpmk_ternilai": len(ternilai)}
|
||||
|
||||
return {"sub": sub, "cpmk": cpmk, "cpl": cpl,
|
||||
"mode": nilai_data.get("mode", "agregat")}
|
||||
|
||||
Reference in New Issue
Block a user