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:

text
P(theta, b) = 1 / (1 + e^-(theta - b))
I(theta)    = P * (1 - P)            # Fisher information per item
SE(theta)   = 1 / sqrt(Sum I(theta)) # standard error estimasi theta
b_awal      = -ln((1 - p) / p)       # konversi dari CTT

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 = true dan irt_b tidak 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, atau
  • SE < DEFAULT_SE_THRESHOLD (0.5) dan items_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: saat false, hanya reuse soal DB yang ada.

Alur Pengujian Siswa (End-to-End)

Status: planned — visi setelah integrasi Sejoli Tryout penuh (Pola B). Endpoint next_item dan answer belum 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