Model Penilaian
Ringkasan Satu Menit
Nilai siswa bukan persentase jumlah soal yang benar, melainkan
persentase bobot yang berhasil dikumpulkan. Inilah yang membuat soal
dengan bobot berbeda dapat hidup berdampingan dalam satu tryout.
Rumus Nilai
nilai = ( Σ bobotsoal × fraksibenar ) ÷ ( Σ bobotsoal ) × 100
bobot
Bilangan bulat positif per soal, bawaan 1. Ditetapkan guru di editor soal.
fraksi benar
Angka 0–1. Dua tipe soal dapat menghasilkan pecahan (nilai parsial).
skala hasil
Selalu 0–100, disimpan 2 angka desimal pada kolom hasil.nilai.
Diverifikasi Terhadap Kode
jawaban.routes.ts — fungsi gradeAnswer() dan rumus nilai pada POST /sesi/:sesiId/selesai
benarSalahTabel.ts — hitungFraksiBenarSalahTabel() untuk nilai parsial
schema.sql — kolom soal.bobot dan tabel hasil
- Kode penilaian identik di sd/smp/sma-service (uji
diff: 0 baris beda)
Model Penilaian
Rumus Inti & Arti Bobot
jawaban.routes.ts
let totalBobot = 0
let bobotBenar = 0
let totalBenar = 0
for (const s of soalList) {
totalBobot += s.bobot
if (s.tipe === 'essay') continue // dinilai manual
const fraction = gradeAnswer(s, answersMap.get(s.id) ?? null)
bobotBenar += s.bobot * fraction
if (fraction === 1) totalBenar += 1
}
const nilai = totalBobot > 0 ? (bobotBenar / totalBobot) * 100 : 0
Ketentuan Kolom bobot
| Aspek | Ketentuan |
| Tipe data | INTEGER NOT NULL DEFAULT 1 |
| Validasi API | z.number().int().positive() — wajib bulat & > 0 |
| Diisi di mana | Editor soal Guru & Admin Soal, kolom Bobot (minimum 1) |
| Impor Word | Baris Bobot: 5 di dalam blok soal .docx |
| Bila kosong | Otomatis bernilai 1 |
Bobot bersifat relatif, bukan absolut
Bobot 10 tidak berarti "10 poin dari 100", melainkan
"sepuluh kali lebih berharga daripada soal berbobot 1 di tryout yang sama".
Karena penyebutnya total bobot seluruh soal, menaikkan bobot satu soal otomatis
memperkecil porsi semua soal lain — dan menambah soal baru menurunkan porsi soal
yang sudah ada.
Model Penilaian
Contoh Perhitungan Lengkap
Sebuah tryout berisi 5 soal dengan bobot berbeda-beda.
| No |
Tipe |
Bobot |
Jawaban |
Fraksi |
Poin |
| 1 | Pilihan ganda | 1 | Benar | 1 | 1 |
| 2 | Pilihan ganda | 1 | Salah | 0 | 0 |
| 3 | Isian singkat | 2 | Benar | 1 | 2 |
| 4 | Menjodohkan (benar 3 dari 4) | 3 | Sebagian | 0,75 | 2,25 |
| 5 | Tabel pernyataan (parsial) | 3 | Sebagian | 0,5 | 1,5 |
| Total | 10 | | | 6,75 |
nilai = 6,75 ÷ 10 × 100 = 67,50
Siswa hanya menjawab 2 soal sepenuhnya benar dari 5 soal, tetapi
nilainya 67,50 — bukan 40. Nilai parsial pada soal nomor 4 dan 5 ikut
diperhitungkan.
Model Penilaian
Nilai vs Jumlah Benar
Tabel hasil menyimpan dua besaran yang mudah tertukar.
| Kolom | Arti | Contoh Sebelumnya |
| nilai | Persentase bobot terkumpul (0–100) | 67.50 |
| total_benar | Cacah soal dengan fraksi tepat 1 | 2 |
| total_soal | Cacah seluruh soal dalam tryout | 5 |
Keduanya benar, dan memang bisa terlihat janggal
Pada halaman hasil, siswa dapat melihat "Benar 2/5" bersanding
dengan nilai 67,50. Yang satu menghitung soal, yang lain
menghitung bobot. Soal bernilai parsial tidak pernah dihitung sebagai "benar",
tetapi tetap menyumbang poin.
Urutan Papan Peringkat
- 1
nilai tertinggi lebih dulu
- 2bila seri — waktu pengerjaan tersingkat menang (
selesai_at − mulai_at)
- 3bila masih seri — yang lebih dahulu dihitung (
dihitung_at) menang
Hanya sesi yang benar-benar diselesaikan (selesai_at IS NOT NULL) yang masuk papan peringkat.
Aturan per Tipe
Enam Tipe Soal
Seluruh penilaian otomatis dipusatkan pada satu fungsi, gradeAnswer(), yang mengembalikan fraksi 0–1.
| Tipe |
Fraksi |
Parsial |
Aturan |
| pilihan_ganda | 0 atau 1 | — | opsi_id siswa harus sama dengan opsi is_benar. Pengacakan opsi tidak berpengaruh karena yang dibandingkan id, bukan huruf. |
| pg_kompleks | 0 atau 1 | — | Himpunan pilihan harus persis sama dengan kunci. Benar 3 dari 4 tetap bernilai 0. |
| isian_singkat | 0 atau 1 | — | Dinormalisasi (huruf kecil, spasi dirapikan). Guru boleh menulis beberapa kunci alternatif, satu per baris. |
| menjodohkan | 0 … 1 | | pasangan_benar ÷ total_pasangan. Benar 3 dari 4 → fraksi 0,75. |
| benar_salah_tabel | 0 … 1 | | Mode PARSIAL (bawaan) menimbang bobot tiap baris; mode FULL menuntut semua baris benar. |
| essay | selalu 0 | — | Dilewati untuk dinilai manual — namun bobotnya tetap masuk penyebut. |
Soal yang dilewati
Soal tanpa jawaban diperlakukan sama dengan jawaban salah: fraksi 0.
Tidak ada penalti negatif dan tidak ada koreksi tebakan, sehingga siswa tidak pernah
dirugikan karena mencoba menjawab.
Aturan per Tipe
Nilai Parsial & Bobot Bertingkat
Tipe benar_salah_tabel adalah satu-satunya yang punya bobot di dua tingkat sekaligus.
Mode PARSIAL (bawaan)
fraksi = Σ bobot_baris_benar ÷ Σ seluruh_bobot_baris
Mode FULL
fraksi = 1 bila seluruh baris benar, selain itu 0
Bobot baris tidak menambah bobot soal
Ini bagian yang paling sering disalahpahami. Bobot baris hanya menentukan
porsi antar baris di dalam soal itu; yang masuk ke penyebut nilai akhir
hanyalah bobot soal.
Soal berbobot 4, berisi 3 baris dengan bobot baris 1, 1, dan 2. Siswa benar pada baris ke-1 dan ke-3.
fraksi = (1 + 2) ÷ (1 + 1 + 2) = 3 ÷ 4 = 0,75
poin = bobot_soal × fraksi = 4 × 0,75 = 3
Bila bobot soal diubah menjadi 10 sementara bobot baris tetap, poinnya menjadi
10 × 0,75 = 7,5. Total bobot baris berapa pun tidak
mengubah kontribusi soal terhadap penyebut.
Label Pilihan Kustom (TRN-51)
Sejak TRN-51 label tiap baris tidak harus "Benar/Salah" — boleh diganti menjadi
Fact/Opinion, True/False,
Sesuai/Tidak Sesuai, dan seterusnya. Perbandingan jawaban
dilakukan melalui canonicalPilihan() yang mengabaikan beda huruf
besar/kecil serta spasi berlebih. Bila jawaban siswa maupun kunci sama-sama tidak cocok
dengan label baris (akibat suntingan manual pada data), sistem membandingkan keduanya
secara langsung agar soal yang konsisten dengan dirinya sendiri tetap dapat dinilai.
Aturan per Tipe
Kapan Nilai Dihitung
Hanya sekali, di server, pada endpoint POST /sesi/:sesiId/selesai.
1
Kunci jawaban dibaca dari database
Melalui loadSoalForScoring() pada service jenjang bersangkutan. Kunci tidak pernah dikirim ke browser siswa selama ujian berlangsung.
2
Jawaban tersimpan diambil
Seluruh baris pada tabel jawaban untuk sesi tersebut.
3
Nilai dihitung & disimpan
Ke tabel hasil, di dalam satu transaksi bersama pembaruan status sesi.
4
Catatan audit dibuat
Aksi EXAM_SESSION_SUBMIT berisi nilai akhir siswa.
Tidak ada endpoint hitung ulang
Bila kunci jawaban diperbaiki setelah siswa mengerjakan, nilai yang sudah tersimpan
tidak ikut berubah. Perbaikan kunci sebaiknya dilakukan saat tryout masih berstatus
draf, atau setelah seluruh peserta selesai.
Temuan
Bobot Essay Mengunci Nilai Maksimum
Perlu keputusan pemilik produk. Bagian ini menjelaskan sebab, dampak terukur, dan tiga pilihan penanganan.
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
nilai_maksimum = ( Σ bobot_non_essay ÷ Σ bobot_seluruh_soal ) × 100
Pada tryout yang memuat soal essay, nilai tertinggi yang mungkin dicapai siswa
tidak pernah bisa mencapai 100 — berapa pun bagusnya jawabannya.
Dampak Terukur
| Jenjang |
Tryout |
Soal |
Total Bobot |
Bobot Essay |
Nilai Maks |
| 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, bukan 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–8,
sementara pilihan ganda umumnya 1–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.
Tiga Pilihan Penanganan
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 — namun 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.