Gap Analysis - Integrasi Pola B
Audit gap antara implementasi aplikasi saat ini vs visi integrasi Sejoli Tryout pola 1-AJAX-per-soal.
Gap Analysis: Integrasi Pola B
Status: Audit teknis — bukan dokumen user-facing. Tujuan: Single source of truth untuk "apa yang kurang" di aplikasi agar sesuai visi integrasi. Sumber temuan: Verifikasi langsung dari
backend/app/routers/*.py+backend/app/services/*.pyper 2026-07-25.
Konteks: Visi Integrasi Pola B
Setelah integrasi penuh dengan Sejoli Tryout, alur ujian harus pakai Pola B (1 AJAX call per soal) — baik untuk mode fixed maupun adaptive. Siswa tetap di UI Sejoli (tidak redirect/iframe app), Sejoli call API app setiap klik "Next".
sequenceDiagram
autonumber
participant Siswa
participant Sejoli as Sejoli Tryout UI
participant App as yellow-bank-soal API
Siswa->>Sejoli: Buka halaman ujian
Sejoli->>App: POST /session/ start attempt
App-->>Sejoli: session_id
loop Setiap soal selama tryout
Siswa->>Sejoli: Klik Next
Sejoli->>App: POST /session/id/next_item
Note over App: Algoritma pilih soal:
App->>App: Mode fixed? Ambil slot berikutnya
App->>App: Mode adaptive? Hitung theta, pilih b terdekat
App->>App: Cek varian level sedang/mudah/sulit
App-->>Sejoli: 1 soal terpilih
Sejoli-->>Siswa: Render soal itu saja
Siswa->>Sejoli: Jawab
Sejoli->>App: POST /session/id/answer
Note over App: Update theta, cek terminasi
end
Siswa->>Sejoli: Selesai / waktu habis
Sejoli->>App: POST /session/id/complete
App-->>Sejoli: NM, NN, theta, hasil
Sejoli-->>Siswa: Tampilkan hasil
Prinsip kunci yang harus dipenuhi:
- Sumber soal utama = App DB (bukan Sejoli). Sejoli hanya fallback saat app unavailable.
- Soal dipilih per-request, bahkan di mode
fixed(untuk variasi level Sedang/Mudah/Sulit per siswa). - Tidak ada pre-bundle tryout di awal sesi — yang di-preload cuma konfigurasi & pool soal tersedia.
- App = backend headless; Sejoli = full UI (render, navigasi, timer, localStorage).
- Theta diupdate real-time setiap jawaban (kritis untuk mode adaptive).
Yang Sudah Siap (Tidak Perlu Diubah)
Gap Prioritas P0 (Blok Visi Pola B)
Gap 1: Endpoint /session/{id}/next_item tidak ada
Impact: Tanpa ini, Sejoli tidak bisa minta soal berikutnya dari app. Visi Pola B mustahil.
Yang ada sekarang:
- Service
cat_selection.get_next_item()sudah lengkap di service layer - Tapi tidak ada router yang expose ini sebagai HTTP endpoint
Yang harus ditambah:
Schema response baru (schemas/session.py):
Gap 2: Endpoint /session/{id}/answer tidak ada
Impact: Tanpa ini, theta tidak bisa di-update real-time. Mode adaptive rusak total (app tidak tahu theta siswa berkembang).
Yang ada sekarang:
complete_sessionmenerimauser_answers: List[UserAnswerInput](batch) di akhir ujian- Theta dihitung dari nol di akhir (post-hoc), bukan real-time
Yang harus ditambah:
Schema baru:
Gap 3: Refactor complete_session — dari batch ke finalisasi
Impact: Saat ini complete_session terima array jawaban. Setelah Gap 1+2 jadi, ini redundant dan bikin race condition (sumber truth dobel: jawaban di UserAnswer vs di request body).
Yang harus diubah:
Behavior baru complete_session:
- Validasi sesi exists, not yet completed
- Cek minimal 1 jawaban tersimpan (
SELECT COUNT(*) FROM user_answers WHERE session_id = ?) - Hitung skor final dari
UserAnswer(bukan dari request body) - Kalau mode
irt/hybrid: theta sudah ter-update real-time, tinggal ambil nilai terakhir - Update Session: status=
completed, NM, NN, theta, completed_at - Update
TryoutStats(inkremental) - Return skor final
Backward compatibility: butuh feature flag atau versioning (/v1/session/{id}/complete = batch, /v2/session/{id}/complete = finalisasi). Atau breaking change dengan migrasi plugin Sejoli serentak.
Gap Prioritas P1 (Kualitas & Robustness)
Gap 4: Attempt Lifecycle State Machine
Sekarang Session cuma punya status implisit: "created" (ada record) vs "completed" (ada completed_at).
Yang dibutuhkan: state eksplisit untuk enforce urutan operasi.
Aturan transisi:
Validasi endpoint:
next_itempada sessioncompleted→ 400 "Session already completed"answerpada sessioncreated(belum next_item) → 400 "No active item"answerpada sessioncompleted→ 400 "Session already completed"
Gap 5: Idempotency next_item
Skenario masalah: siswa klik Next 2x cepat (double-click, network lag). Tanpa proteksi, app kasih 2 soal berbeda → siswa bingung, jawaban tidak sinkron.
Solusi:
Field Session.current_item_id dari Gap 4 dipakai untuk tracking ini.
Gap 6: Validasi Urutan answer ↔ next_item
Skenario masalah: siswa submit answer untuk item_id=42, padahal last next_item kasih item_id=99. Bisa karena bug frontend, replay attack, atau siswa buka 2 tab.
Solusi:
Gap Prioritas P2 (Nice-to-have / Hardening)
Gap 7: Quota Entitlement Enforcement (Opsional)
Sekarang: create_session catat entitlement metadata tapi tidak enforce quota.
Pertanyaan desain: siapa yang enforce?
- Opsi A: App cek
attempt_number <= attempt_limitsebelum buat session → sumber truth di app - Opsi B: Sejoli enforce sebelum call
create_session→ app trust Sejoli (sesuai kontrak awal)
Rekomendasi: Opsi B (sesuai SEJOLI_TRYOUT_INTEGRATION.md — "Sejoli tetap pemilik keputusan 'boleh mulai lagi'"). Tapi tambah audit log di app untuk tracing.
Gap 8: Rate Limiting per-Sesi
Cegah abuse: siswa/script spam next_item atau answer untuk scrape soal atau brute-force jawaban.
Implementasi: Redis-based limiter, mis.:
next_item: max 1 request per 2 detik per sessionanswer: max 1 request per 500ms per session
Gap 9: Resumption Session (Resume)
Skenario: siswa refresh tab di tengah ujian, atau koneksi putus reconnect.
Sekarang: data tersimpan di localStorage Sejoli, kalau hilang → data hilang.
Yang dibutuhkan:
- Endpoint
GET /session/{id}/state— return soal terakhir yang aktif + jawaban yang sudah tersimpan - Sejoli bisa resume dari state server-side, bukan dari localStorage
- Lebih reliable untuk ujian panjang (>50 soal)
Gap 10: Audit Trail
Untuk compliance & debugging:
- Log setiap
next_itemcall (siapa, kapan, soal mana yang dipilih, algoritma kenapa pilih itu) - Log setiap
answer(respons time, theta sebelum/sesudah) - Log terminasi (alasan: user vs timeout vs auto-SE-threshold)
Estimasi Effort
Rough estimate (1 developer senior, FTE):
Total P0+P1: ~10-14 hari kerja Total semua: ~15-20 hari kerja
Test Plan
Setelah implementasi, verifikasi dengan skenario:
Skenario A: Mode fixed (smoke test)
- Create session → dapat session_id
- Loop 10x:
next_item→answer - Verify: 10 UserAnswer tersimpan, slot urut 1-10
complete→ dapat NM, NN
Skenario B: Mode adaptive (CAT real)
- Create session dengan tryout yang sudah terkalibrasi IRT
- Loop:
next_item→answer, simpan theta tiap iterasi - Verify: theta konvergen (perubahan makin kecil)
- Verify: soal terpilih punya
bmendekati theta terbaru - Auto-terminasi setelah
SE < 0.5dann >= 15
Skenario C: Edge cases
next_itemdi session yang sudah complete → 400answertanpanext_itemsebelumnya → 400- Double-click
next_item→ return item yang sama (idempotent) - Submit
answeruntuk item_id beda → 409 Conflict - Network putus di tengah, refresh → bisa resume
Referensi
- PRD section 3.6 Item Selection — requirement asli pemilihan soal
- SEJOLI_TRYOUT_INTEGRATION.md — kontrak awal integrasi
backend/app/services/cat_selection.py— service yang siap dipakaibackend/app/routers/sessions.py— router yang perlu di-extend- Dokumentasi: Integrasi → Sejoli Tryout — visi alur integrasi
- Dokumentasi: Modul → CAT Selection — detail algoritma pemilihan
Last updated Jul 25, 2026