Alur & Algoritma
Diagram alur lengkap CTT, IRT, CAT, Hybrid, Dynamic Normalization, kalibrasi IRT, dan AI question generation.
Alur Kerja Algoritma
Alur CTT (Classical Test Theory)
Mengikuti rumus Excel klien secara eksak.
flowchart TD
A["Kumpulkan jawaban semua peserta"] --> B["Hitung p per soal: p = Sigma Benar / Total Peserta"]
B --> C["Hitung Bobot per soal: Bobot = 1 - p"]
C --> D["Kategori: p di bawah 0.3 Sulit, 0.3-0.7 Sedang, di atas 0.7 Mudah"]
D --> E["Hitung Total_Bobot_Siswa per siswa"]
E --> F["Hitung Total_Bobot_Max tryout"]
F --> G["NM = Total_Bobot_Siswa / Total_Bobot_Max x 1000"]
G --> H{"Mode normalisasi?"}
H -->|Static| I["Pakai static_rataan & static_sb"]
H -->|Dynamic| J["Hitung Rataan & SB real-time dari TryoutStats"]
I --> K["NN = 500 + 100 kali NM-Rataan dibagi SB"]
J --> K
K --> L["Clip NN ke 0-1000"]
L --> M["Simpan ke Session & UserAnswer"]
Pembaruan statistik bertahap (update_tryout_stats):
setiap selesai sesi, participant_count, total_nm_sum, dan total_nm_sq_sum diperbarui secara inkremental — tanpa recompute penuh.
Alur IRT (Item Response Theory)
Model Rasch 1PL (1 parameter logistik).
flowchart TD
A["Ambil respon siswa: u_1, u_2, ..., u_n"] --> B["Ambil b soal: b_1, b_2, ..., b_n"]
B --> C["Inisialisasi theta = 0"]
C --> D["Maksimalkan Log-Likelihood: LL = Sigma u_i log P + 1-u_i log 1-P"]
D --> E{"Konvergen / max iter?"}
E -->|Belum| D
E -->|Ya| F["Klip theta ke -3, +3"]
F --> G["SE = 1 / sqrt Sigma I theta"]
G --> H["NN = 500 + theta / 3 x 500"]
H --> I["Simpan theta, theta_se ke Session"]
Rumus kunci:
Alur CAT (Computer Adaptive Testing)
Pemilihan soal adaptif mengikuti kemampuan siswa.
sequenceDiagram
autonumber
participant Siswa
participant API as FastAPI /session/adaptive
participant CAT as cat_selection.py
participant IRT as irt_calibration.py
participant DB as Database
Siswa->>API: POST /adaptive/start
API->>DB: Buat Session (theta=0, theta_se=3.0)
API-->>Siswa: session_id + soal pertama
loop Tiap soal
Siswa->>API: POST /adaptive/respond (jawaban)
API->>IRT: update_theta_after_response
IRT->>IRT: estimate_theta_mle + SE
IRT-->>API: theta_baru, se_baru
API->>CAT: get_next_item_adaptive(theta)
CAT->>DB: Filter soal terkalibrasi, b ~= theta, belum dijawab
CAT->>IRT: calculate_item_information
CAT->>CAT: Skor = |b - theta| - 0.1 x info
CAT-->>API: Item terbaik
API->>CAT: should_terminate?
alt SE di bawah 0.5 DAN n minimal 15
API-->>Siswa: POST /adaptive/complete
else lanjut
API-->>Siswa: soal berikutnya
end
end
Siswa->>API: POST /adaptive/complete
API-->>Siswa: theta, SE, NN hasil
Aturan pemilihan soal (get_next_item_adaptive):
- Hanya soal dengan
calibrated = truedanirt_btidak null. - Filter: belum dijawab di sesi ini.
- Filter: user belum menjawab
(slot, level)yang sama lintas sesi (reuse awareness). - Skor kandidat:
|b − θ| − 0.1 × I(θ)→ pilih skor terkecil.
Aturan terminasi (should_terminate):
items_answered ≥ max_items, atauSE < DEFAULT_SE_THRESHOLD (0.5)danitems_answered ≥ 15, atau- Tidak ada soal tersisa.
Alur Hybrid Mode
Kombinasi fixed + adaptive, dipisah oleh hybrid_transition_slot.
flowchart TD
A["Mulai sesi hybrid"] --> B{"Slot saat ini?"}
B -->|"Slot kurang dari hybrid_transition_slot"| C["Mode Fixed: urutkan sesuai slot"]
C --> D["Skor CTT per soal"]
D --> E["Update theta real-time"]
E --> B
B -->|"Slot mulai hybrid_transition_slot"| F["Mode Adaptive: b ~= theta"]
F --> G["Pilih soal terkalibrasi"]
G --> H["Update theta + SE"]
H --> I{"Terminasi?"}
I -->|Tidak| F
I -->|Ya| J["Hitung skor akhir CTT+IRT"]
J --> K["Skor primer/sekunder"]
Mode skor hybrid (scoring_mode = hybrid): skor CTT dan IRT dihitung paralel per respon. Admin memilih mana yang jadi primary.
Alur Dynamic Normalization
flowchart TD
A["Sesi selesai, NM terhitung"] --> B{"normalization_mode?"}
B -->|Static| C["Pakai static_rataan, static_sb"]
B -->|Dynamic| D{"participant_count mencapai min_sample_for_dynamic?"}
D -->|Belum| E["Fallback ke static + tampilkan readiness"]
D -->|Sudah| F["Hitung Rataan dari total_nm_sum"]
F --> G["Hitung SB dari total_nm_sum & _sq_sum"]
G --> H["NN = 500 + 100 x NM-Rataan / SB"]
C --> H
E --> H
H --> I["Clip 0-1000"]
Alur Kalibrasi IRT
flowchart TD
A["Trigger: POST /tryout/id/calibrate"] --> B["Kumpulkan matriks respon siswa x soal"]
B --> C{"Respon per soal mencapai min_calibration_sample?"}
C -->|Belum| D["Tandai belum siap kalibrasi"]
C -->|Sudah| E["estimate_b: EM/joint MLE, inisialisasi theta=0 b=0"]
E --> F{"Konvergen / max iter?"}
F -->|Belum| E
F -->|Ya| G["Klip b ke -3, +3"]
G --> H["Hitung SE per soal lewat Fisher information"]
H --> I["Update Item: irt_b, irt_se, calibrated=true, calibration_sample_size"]
I --> J["Soal siap untuk CAT"]
Konversi awal saat belum ada data cukup: b ≈ −ln((1 − p) / p).
Alur AI Question Generation
sequenceDiagram
autonumber
participant Admin
participant API as FastAPI /ai/generate
participant AI as ai_generation.py
participant Cache as Database
participant OR as OpenRouter API
Admin->>API: Request generate (tryout, slot, level)
API->>AI: generate_question
AI->>Cache: check_cache_reuse
alt Cache hit & belum dijawab user
Cache-->>AI: Soal reusable
AI-->>API: Variant dari cache
else Cache miss
AI->>OR: 1 request = 1 soal (Qwen/Llama/Mistral)
OR-->>AI: JSON stem+options+answer
AI->>AI: Parse & validasi struktur
AI->>Cache: save_ai_question
Cache-->>API: Item baru (generated_by=ai, basis_item_id)
end
API-->>Admin: Preview (playground, tanpa simpan) ATAU Item tersimpan
Aturan:
- Basis soal = level Sedang. Variasi dihasilkan untuk level Mudah & Sulit.
- 1 request = 1 soal (bukan batch).
- Reuse dicegah per user: jika user sudah pernah menjawab
(slot, level)tertentu, cache miss untuk user tersebut. - Toggle global
AI_generation_enabled: saatfalse, hanya reuse soal DB yang ada.
Alur Pengujian Siswa (End-to-End)
Status: planned — visi setelah integrasi Sejoli Tryout penuh (Pola B). Endpoint
next_itemdananswerbelum ada di app, lihat Gap Analysis.
sequenceDiagram
autonumber
participant Siswa
participant Sejoli as Sejoli Tryout UI
participant App as yellow-bank-soal API
Siswa->>Sejoli: Login WP + akses tryout
Sejoli->>Sejoli: Validasi entitlement & attempt quota
Sejoli->>App: POST /api/v1/wordpress/verify_session
Note over Sejoli,App: body wp_user_id, token, website_id
App-->>Sejoli: App access token
Sejoli->>App: POST /api/v1/session/
Note over Sejoli,App: body session_id, wp_user_id, tryout_id, scoring_mode, entitlement
App->>App: Buat Session + snapshot identitas
App-->>Sejoli: session_id + konfigurasi tryout
Sejoli-->>Siswa: Render UI ujian (timer, layout)
loop Tiap soal (fixed / adaptive / hybrid)
Siswa->>Sejoli: Klik Next
Sejoli->>App: POST /session/id/next_item
Note over App: Algoritma pilih soal + varian level
App-->>Sejoli: 1 soal terpilih
Sejoli-->>Siswa: Render soal itu
Siswa->>Sejoli: Jawab
Sejoli->>App: POST /session/id/answer
App->>App: Update theta & SE real-time (jika IRT/hybrid)
end
Siswa->>Sejoli: Selesai atau waktu habis
Sejoli->>App: POST /session/id/complete
App->>App: Hitung NM, NN (dan theta -> NN)
App-->>Sejoli: Hasil skor
Sejoli-->>Siswa: Tampilkan hasil + umpan balik
Note over App: App TIDAK mengurangi quota
Last updated Jul 25, 2026