Alur Question Bank → Participant Question (Overview)
Tabel-tabel yang terlibat
| Tabel | Isi | Baru di V2? |
|---|---|---|
exam_packages | "Bank" soal bernama untuk satu kombinasi (level, exam type) — memiliki status: active/draft/inactive. Bisa ada lebih dari satu yang active sekaligus untuk kombinasi level + type yang sama | Sudah ada sebelumnya |
exam_questions | Question bank — seluruh soal yang tersedia, dapat digunakan ulang lintas schedule | Sudah ada sebelumnya, ditambahkan flag is_poolable |
exam_question_history | Snapshot versi dari redaksi soal pada suatu titik waktu | Sudah ada sebelumnya |
schedule_exam_packages | "Ini adalah kumpulan soal yang telah dikunci untuk schedule X, level Y, exam type Z, versi #N" | Baru |
schedule_exam_questions | Daftar soal aktual di dalam satu baris schedule_exam_packages | Baru |
participant_exam_questions | Salinan lembar soal ujian milik satu participant | Sudah ada sebelumnya, ditambahkan relasi ke schedule_exam_packages |
Kedua tabel baru tersebut adalah inti dari perubahan yang terjadi. Bagian lainnya — question bank, riwayat versi soal, lembar soal participant — sudah ada sebelumnya; V2 hanya menyisipkan satu tahap tambahan berupa "kumpulan soal yang terkunci" di antara keduanya.
V1: Bank → Participant, Langsung
Pada V1 tidak ada tabel perantara antara question bank dan participant. Proses pengundian soal terjadi secara langsung di memori, pada saat request berlangsung, dan hasilnya langsung ditulis ke participant_exam_questions. Proses pengundian itu sendiri tidak meninggalkan jejak/baris data di mana pun.
Prasyarat
Sebelum seorang participant dapat memulai ujian, hal-hal berikut harus sudah tersedia:
- Question bank telah terisi —
exam_questionsmemiliki jumlah soal yang cukup untuk exam package terkait, guna memenuhi seluruh kuota tiap kompetensi. Jika suatu kuota tidak dapat terpenuhi, proses pengundian tetap berjalan dengan hasil kurang tanpa peringatan sebelumnya. - Competency pool telah dikonfigurasi — kuota diatur per kompetensi (misalnya "3 soal dari Kompetensi A, 2 soal dari Kompetensi B, 5 soal tanpa kompetensi") pada exam config untuk level dan exam type terkait.
- Exam package berstatus aktif — harus tersedia
exam_packageyang aktif untuk level dan exam type milik participant, jika tidak maka permintaan memulai ujian akan langsung ditolak. - Participant memiliki access code yang valid — diterbitkan untuk schedule terkait, diperiksa pada setiap upaya memulai ujian.
- Participant sudah terdaftar pada schedule —
certification_schedule_iddanscheme_level_version_idmiliknya harus sudah benar, karena pencarian kompetensi pada proses pengundian bergantung pada level version tersebut.
Tidak ada satu pun dari hal di atas yang direview atau dikunci lebih dahulu oleh admin — semuanya hanya berupa data yang harus sudah tersedia. Proses pengundian baru benar-benar terjadi belakangan, secara langsung (live).
┌─────────────────────────────┐
│ Participant klik "Start │
│ Exam" (mengirim access code)│
└─────────────┬───────────────┘
▼
┌────────────────────────────┐
│ Access code valid? │
└──────┬─────────────┬───────┘
tidak │ │ ya
▼ ▼
┌───────────┐ ┌─────────────v────────────────┐
│ Tolak, │ │ Muat kuota kompetensi │
│ tampilkan │ │ milik participant ini │
│ error │ │ (mis. 3 dari Komp A, │
└───────────┘ │ 2 dari Komp B, 5 tanpa komp) │
└───────────────┬──────────────┘
▼
┌───────────────────────────────┐
│ Untuk tiap kuota kompetensi: │
│ query exam_questions WHERE │
│ competency = X │
│ ORDER BY RANDOM() │
│ LIMIT quota │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ Gabungkan + acak seluruh soal │
│ terpilih menjadi satu daftar │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ Untuk tiap soal terpilih: │
│ ambil redaksi terbaru │
│ (exam_question_history) │
│ → INSERT ke │
│ participant_exam_questions │
└───────────────┬───────────────┘
▼
┌─────────────────────────────────┐
│ Participant melihat lembar soal │
│ miliknya. Tidak ada jejak dari │
│ proses pengundian ini yang │
│ tersimpan di tempat lain. │
└─────────────────────────────────┘Kekurangan yang ditinggalkan: karena tidak ada apa pun yang disimpan antara "question bank" dan "lembar soal participant", tidak ada catatan yang bisa diquery belakangan untuk menjawab "soal apa saja yang digunakan pada schedule ini?" Dua participant yang menjalankan alur ini beberapa detik berbeda pun masing-masing mengundi secara independen — untuk mengetahui apakah soal mereka sama, harus memeriksa lembar soal tiap participant satu per satu secara manual.
V2: Bank → Locked Set → Participant; dalam Dua Tahap Terpisah
Prasyarat
Karena V2 memecah alur menjadi dua tahap, masing-masing tahap memiliki prasyaratnya sendiri.
Sebelum admin dapat melakukan pooling pada suatu schedule (Tahap 1):
- Question bank telah terisi, sama seperti V1 — tersedia cukup soal yang poolable (
is_poolable = true) untuk memenuhi kuota. - Tersedia
exam_packageberstatus Active untuk level + exam type terkait. Admin dapat menentukan satu secara eksplisit, atau membiarkannya kosong — jika ada lebih dari satuexam_packageberstatusactiveuntuk kombinasi tersebut, salah satunya dipilih secara acak dengan peluang yang sama (tanpa pembobotan). Jika admin menentukan package tertentu namun statusnya bukanactive, permintaan ditolak. - Exam config sudah tersedia untuk level + exam type terkait, lengkap dengan
pool_modeyang telah ditentukan (specific,flat, ataumix) — ini bukan sekadar metadata opsional, karena menentukan strategi pengundian mana yang dijalankan. - Competency pool telah dikonfigurasi sesuai
pool_mode(misalnyaspecificmengharuskan setiap pool menargetkan satu kompetensi;flatmengharuskan tidak ada satu pun yang menargetkan kompetensi). - Kompetensi sudah ditandai pada soal melalui tabel pivot, bukan lagi melalui kolom langsung yang lama — jika tidak, proses pengundian yang difilter berdasarkan kompetensi akan menghasilkan data kosong.
- Admin memiliki akses penjadwalan untuk memicu proses pooling pada schedule tersebut — ini merupakan aksi eksplisit oleh admin, bukan sesuatu yang dapat dipicu oleh participant.
Sebelum participant dapat memulai ujian (Tahap 2):
- Sudah harus ada locked set yang aktif — artinya Tahap 1 sudah pernah dijalankan minimal satu kali dan menghasilkan baris
schedule_exam_packagesdenganis_active = trueuntuk kombinasi (schedule, level, exam type) milik participant tersebut. Jika belum ada yang melakukan pooling, participant tidak dapat memulai ujian — tidak ada mekanisme fallback ke pengundian langsung (live). - Participant memiliki access code / session token yang valid, pemeriksaan yang sama seperti pada V1.
- Participant sudah terdaftar pada schedule, dengan level version yang benar, sama seperti pada V1.
Perbedaan utama dari V1: prasyarat Tahap 2 kini mencakup "seseorang sudah menjalankan Tahap 1 lebih dahulu." Participant tidak pernah menjadi pemicu proses pengundian itu sendiri.
Tahap 1 — Admin mengunci sebuah set (schedule_exam_packages + schedule_exam_questions)
Aksi admin ("pool this schedule") menjalankan jenis proses pengundian berbasis kompetensi yang sama seperti yang digunakan V1 — namun alih-alih langsung diberikan ke participant, hasilnya disimpan sebagai catatan tersendiri:
┌─────────────────────────────────────┐
│ Admin klik "Pool this schedule" │
│ untuk (schedule, level, exam type), │
│ opsional menentukan exam_package │
│ (bank) tertentu │
└──────────────────┬──────────────────┘
▼
┌─────────────────────────────────────────┐
│ Resolve exam_package (bank): │
│ - ditentukan -> harus berstatus │
│ active, jika tidak ditolak (422) │
│ - kosong -> pilih secara acak │
│ (peluang sama) di antara bank │
│ berstatus active untuk kombinasi ini │
└────────────────────┬────────────────────┘
▼
┌────────────────────────────────────┐
│ Cari pool_number tertinggi │
│ yang sudah ada untuk kombinasi ini │
└──────────────────┬─────────────────┘
▼
┌───────────────────────────────────┐
│ Buat baris schedule_exam_packages │
│ baru: pool_number + 1 │
│ is_active = true hanya jika ini │
│ pool pertama untuk kombinasi ini │
│ (pool berikutnya tetap inactive │
│ sampai admin mengaktifkannya) │
└─────────────────┬─────────────────┘
▼
┌────────────────────────────────────┐
│ Pool mode? │
└───┬─────────────┬──────────────┬───┘
specific │ flat│ mix │
▼ ▼ ▼
┌───────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ Undi per │ │ Undi langsung │ │ Undi per pool │
│ kompetensi, │ │ dari seluruh │ │ kompetensi lebih │
│ sesuai kuota │ │ package, tanpa │ │ dahulu, lalu sisanya │
│ │ │ filter │ │ diambil dari pool │
│ │ │ kompetensi │ │ tanpa kompetensi │
└──────┬────────┘ └────────┬───────┘ └──────────┬───────────┘
└───────────────────┴────────────────────┘
▼
┌─────────────────────────────────┐
│ Untuk tiap soal terpilih: │
│ ambil snapshot │
│ exam_question_history terkini │
│ → INSERT ke │
│ schedule_exam_questions │
└─────────────────┬───────────────┘
▼
┌─────────────────────────────────┐
│ Set kini telah terkunci dan │
│ terlihat oleh admin. Belum ada │
│ participant yang memulai ujian. │
└─────────────────────────────────┘schedule_exam_packagespada dasarnya adalah "folder berlabel": "schedule X, level Y, exam type Z, pool #N, aktif: ya/tidak."schedule_exam_questionsadalah daftar soal di dalam folder tersebut — masing-masing merujuk ke snapshotexam_question_historytertentu, sehingga meskipun redaksi soal diedit setelahnya, folder ini tetap menunjukkan soal apa yang sebenarnya digunakan.- Hanya ada satu pool yang berstatus "aktif" untuk tiap kombinasi (schedule, level, exam type) pada satu waktu. Melakukan pooling ulang tidak menghapus folder lama — melainkan membuat pool #N+1 baru, namun hanya pool pertama untuk suatu kombinasi yang otomatis aktif; hasil pooling ulang tetap inactive (pool aktif sebelumnya tidak berubah) sampai admin mengaktifkannya secara eksplisit.
Tahap 2 — Participant memulai ujian, dan hanya menyalin dari locked set
┌─────────────────────────────────┐
│ Participant (atau assessor) │
│ memulai ujian │
└─────────────────┬───────────────┘
▼
┌───────────────────────────────────────┐
│ Apakah participant ini sudah │
│ memiliki session untuk exam type ini? │
└────────┬─────────────────┬────────────┘
ya │ │ tidak
▼ ▼
┌────────────────────┐ ┌───────────────────────────────┐
│ Lanjutkan session │ │ Cari baris schedule_exam_ │
│ tersebut, lewati │ │ packages yang AKTIF │
│ langkah-langkah │ │ untuk (schedule, level, type) │
│ berikutnya │ └───────────────┬───────────────┘
└────────────────────┘ ▼
┌──────────────────────────────┐
│ Muat seluruh baris │
│ schedule_exam_questions di │
│ bawah package tersebut │
└───────────────┬──────────────┘
▼
┌──────────────────────────────┐
│ Exam type = theory? │
└───────┬───────────────────┬──┘
ya │ tidak │
▼ ▼
┌───────────────────┐ ┌─────────────────────┐
│ Acak urutan untuk │ │ Pertahankan urutan │
│ participant ini │ │ sesuai pool apa │
│ │ │ adanya (assessor │
│ │ │ mengerjakan secara │
│ │ │ berurutan) │
└──────────┬────────┘ └─────────────┬───────┘
└─────────────┬──────────┘
▼
┌────────────────────────────────┐
│ Buat participant_exam_session, │
│ terhubung ke baris │
│ schedule_exam_packages ini │
└───────────────┬────────────────┘
▼
┌─────────────────────────────────────┐
│ Salin tiap exam_question_history_id │
│ → INSERT ke │
│ participant_exam_questions │
└───────────────┬─────────────────────┘
▼
┌────────────────────────────────────┐
│ Participant melihat lembar │
│ soal miliknya. Tidak ada │
│ pengundian yang terjadi di sini — │
│ hanya urutan yang ditentukan. │
└────────────────────────────────────┘Tidak ada soal yang diundi "baru" pada tahap ini — set soal sudah tetap (fixed). Satu-satunya hal yang dapat berbeda per participant adalah urutan kemunculan soal (exam theory mengacak urutan; exam yang dipandu assessor seperti oral/observation tidak diacak, karena assessor mengerjakannya secara berurutan sesuai pool).
participant_exam_questions kini juga menyimpan informasi folder schedule_exam_packages asalnya — sehingga lembar soal milik participant mana pun dapat ditelusuri kembali ke locked set yang tepat menjadi sumbernya.
Tabel Perbandingan
| V1 | V2 | |
|---|---|---|
| Siapa yang memicu proses pengundian | Participant, saat memulai ujian | Admin, jauh sebelumnya |
| Berapa kali proses pengundian (randomness) berjalan | Satu kali per participant | Satu kali per schedule (saat pooling), kemudian hanya proses salin per participant |
| Apakah ada tabel yang merepresentasikan "set soal yang digunakan untuk schedule ini"? | Tidak ada | Ada — schedule_exam_packages / schedule_exam_questions |
| Bisakah proses pengundian dijalankan ulang tanpa memengaruhi participant yang sudah memulai? | Tidak relevan — tidak ada "set" yang bisa diulang | Bisa — pooling ulang membuat versi baru; lembar soal participant yang sudah ada tetap merujuk ke set aslinya |
| Apa yang menentukan kompetensi suatu soal | Kolom langsung pada soal (hanya berlaku untuk satu scoring mode) atau tabel penghubung terpisah (untuk scoring mode lain) — tidak konsisten | Satu tabel penghubung (pivot) tunggal, digunakan dengan cara yang sama untuk setiap scoring mode |
Lokasi tiap komponen (untuk orientasi, bukan implementasi)
Bank & versioning exam_questions, exam_question_history
Locked set admin (BARU) schedule_exam_packages, schedule_exam_questions
Lembar soal participant participant_exam_questionsKode apa pun yang membaca atau menulis ke schedule_exam_packages / schedule_exam_questions adalah bagian dari V2. Kode yang masih melakukan query langsung ke exam_questions untuk menyusun lembar soal participant — tanpa melalui tahap locked set — merupakan perilaku V1.