# 03 — Temuan & Batasan Sistem Penilaian

Perilaku-perilaku berikut adalah konsekuensi nyata dari implementasi saat ini.
Beberapa merupakan pilihan desain yang wajar, satu di antaranya perlu keputusan
dari pemilik produk.

---

## 1. Bobot Soal Essay Ikut Menjadi Penyebut, tetapi Tidak Pernah Menghasilkan Poin

### Apa yang terjadi

Pada perulangan penilaian, bobot soal ditambahkan ke `totalBobot` **sebelum**
soal essay dilewati:

```ts
for (const s of soalList) {
  totalBobot += s.bobot          // ← bobot essay ikut masuk penyebut
  if (s.tipe === 'essay') continue
  const fraction = gradeAnswer(s, answersMap.get(s.id) ?? null)
  bobotBenar += s.bobot * fraction
  ...
}
```

Akibatnya, pada tryout yang memuat soal essay, nilai tertinggi yang mungkin
dicapai siswa **tidak pernah bisa mencapai 100**:

```
nilai_maksimum = ( Σ bobot_non_essay / Σ bobot_seluruh_soal ) × 100
```

### Dampak terukur

Pemeriksaan pada basis data pengembangan lokal menemukan enam tryout yang
terdampak. Kolom terakhir adalah nilai tertinggi yang secara matematis masih
mungkin diraih siswa sempurna:

| Jenjang | Tryout | Jumlah Soal | Total Bobot | Bobot Essay | Nilai Maksimum |
| --- | --- | ---: | ---: | ---: | ---: |
| SD | PENJUMLAHAM | 3 | 10 | 5 | **50,00** |
| SD | TRY OUT PENJUMLAHAN | 3 | 10 | 5 | **50,00** |
| SD | Soal Try Out Test | 9 | 31 | 14 | **54,84** |
| SD | TEST | 14 | 46 | 16 | **65,22** |
| SMP | TRY OUT MATH | 3 | 10 | 5 | **50,00** |
| SMP | BANK SOAL TEST ATALA | 3 | 10 | 5 | **50,00** |

> Angka di atas berasal dari basis data pengembangan lokal (`db_sd`, `db_smp`,
> `db_sma`), bukan dari basis data produksi. Kueri yang sama perlu dijalankan di
> produksi untuk mengetahui dampak sebenarnya di lapangan.

Gejala ini menjadi tajam karena soal essay pada data yang ada berbobot **5
sampai 8**, sementara soal pilihan ganda umumnya berbobot **1 sampai 5**. Satu
soal essay dapat mengunci separuh nilai sebuah tryout.

### Tidak ada jalan koreksi

Komentar pada kode menyebut essay *"graded manually by the teacher"*, tetapi:

- tidak ada antarmuka koreksi essay di mana pun pada aplikasi;
- tidak ada kolom pada tabel `hasil` untuk menampung nilai essay hasil koreksi;
- tidak ada endpoint untuk menghitung ulang nilai sebuah sesi.

Dengan demikian nilai yang tersimpan saat siswa menekan **Selesai** bersifat
final, dan potongan bobot essay tidak pernah bisa dikembalikan lewat aplikasi.

### Tiga pilihan penanganan

| Pilihan | Perubahan | Konsekuensi |
| --- | --- | --- |
| **A. Keluarkan essay dari penyebut** | Pindahkan `totalBobot += s.bobot` ke bawah baris `continue` | Nilai menjadi persentase dari bagian yang memang dinilai otomatis; skala kembali 0–100. Perubahan kode paling kecil. Nilai lama tidak ikut berubah karena tidak ada hitung ulang. |
| **B. Bangun fitur koreksi essay** | Tambah kolom nilai manual per jawaban, antarmuka koreksi guru, dan endpoint hitung ulang | Paling benar secara asesmen, tetapi lingkup pekerjaannya paling besar. |
| **C. Larang essay pada tryout ternilai** | Validasi di editor soal | Paling sederhana, tetapi menghilangkan kemampuan yang sudah ada. |

Selama belum diputuskan, jalan pintas yang aman bagi guru adalah **tidak
memasukkan soal essay** ke dalam tryout yang nilainya dipakai untuk peringkat.

---

## 2. Nilai Tidak Pernah Dihitung Ulang

Penilaian hanya berjalan pada `POST /sesi/:sesiId/selesai`. Bila kunci jawaban
sebuah soal diperbaiki setelah sebagian siswa mengerjakan, siswa yang sudah
selesai tetap memakai nilai lama, sedangkan yang mengerjakan sesudahnya memakai
kunci baru — sehingga satu tryout dapat memuat dua standar penilaian sekaligus.

**Saran operasional:** perbaikan kunci jawaban sebaiknya dilakukan saat tryout
masih berstatus draf, atau setelah tryout ditutup dan seluruh peserta selesai.

---

## 3. Sesi Diskualifikasi Tetap Dinilai

Bila sistem anti-curang mendeteksi pelanggaran proktor, sesi ditutup dengan
status `timeout`, bukan `selesai`:

```ts
const disqualified = req.body?.disqualified === true
const finalStatus = disqualified ? 'timeout' : 'selesai'
```

Meskipun demikian, nilai tetap dihitung dan disimpan ke tabel `hasil` seperti
biasa, dan catatan audit diberi penanda `[diskualifikasi: pelanggaran proktor]`.
Sesi semacam ini **tetap muncul di papan peringkat** selama `selesai_at`
terisi. Bila kebijakan bimbel menghendaki peserta diskualifikasi dikeluarkan
dari peringkat, penyaringan itu harus ditambahkan secara khusus.

---

## 4. Batas Skala Nilai

Kolom `hasil.nilai` bertipe `NUMERIC(5,2)` — mampu menampung sampai `999.99`.
Karena rumus selalu menghasilkan rentang 0–100, batas ini tidak akan pernah
tercapai. Nilai dibulatkan ke **dua angka di belakang koma** saat disimpan
(`nilai.toFixed(2)`).

---

## 5. Bobot Bersifat Relatif per Tryout

Karena penyebutnya adalah total bobot pada tryout tersebut, **bobot tidak dapat
dibandingkan antar tryout**. Soal berbobot 5 di tryout yang total bobotnya 10
menyumbang 50% nilai; soal berbobot 5 yang sama di tryout dengan total bobot 100
hanya menyumbang 5%.

Hal ini juga berarti menambahkan soal baru ke sebuah tryout akan **menurunkan
porsi** seluruh soal yang sudah ada.

---

## 6. Tidak Ada Penalti Menebak

Jawaban salah dan jawaban kosong sama-sama bernilai `0`. Tidak ada pengurangan
nilai, sehingga secara strategi siswa selalu diuntungkan bila menebak. Ini
konsisten dengan model penilaian AKM dan tampaknya memang disengaja, tetapi
perlu diketahui bila suatu saat sistem perlu menghitung skor terkalibrasi.

---

## 7. Penilaian Seragam di Tiga Jenjang

Berkas `jawaban.routes.ts` dan `benarSalahTabel.ts` pada `sd-service`,
`smp-service`, dan `sma-service` **identik baris demi baris** (diverifikasi
dengan `diff`, hasil 0 baris perbedaan). Setiap perubahan aturan penilaian wajib
diterapkan ke ketiga service secara bersamaan agar tidak timbul perbedaan nilai
antar jenjang.
