API Session

Endpoint lifecycle sesi ujian - create, next_item (planned), answer (planned), complete.

API: Session

Source: backend/app/routers/sessions.py Schemas: backend/app/schemas/session.py

Lifecycle sesi ujian siswa. Sebagian endpoint di sini masih planned karena berkaitan langsung dengan transisi ke Pola B (1-AJAX-per-soal).

Diagram Lifecycle (Visi Pola B)

flowchart TD
    A["POST /session/ create"] --> B["status: created"]
    B --> C["POST /session/id/next_item"]
    C --> D["status: in_progress, current_item di-set"]
    D --> E["POST /session/id/answer"]
    E --> F{terminasi?}
    F -->|"tidak, masih ada soal"| C
    F -->|"ya, SE cukup atau max items"| G["POST /session/id/complete"]
    G --> H["status: completed"]
    H --> I["return NM, NN, theta"]

🚧 Endpoint next_item dan answer belum ada — lihat Gap Analysis Gap 1 & 2.

Endpoint Reference

POST /api/v1/session/ — Create Session

Implemented. Buat sesi baru untuk attempt siswa.

http
POST /api/v1/session/
Authorization: Bearer {token}
X-Website-ID: 1
Content-Type: application/json

{
  "session_id": "sejoli-132380-123-2",
  "wp_user_id": "123",
  "website_id": 1,
  "tryout_id": "132380",
  "scoring_mode": "ctt",
  "user_email": "[email protected]",
  "user_display_name": "Student Name",
  "user_login": "student123",
  "entitlement": {
    "id": "quota-abc",
    "order_id": "999",
    "product_id": "456",
    "attempt_number": 2,
    "attempt_limit": 3
  }
}

Response 201 Created:

json
{
  "session_id": "sejoli-132380-123-2",
  "wp_user_id": "123",
  "tryout_id": "132380",
  "status": "created",
  ...
}

Validasi:

  • wp_user_id harus match dengan token owner (untuk role student)
  • tryout_id harus exists di website tersebut
  • session_id harus unique (kalau ada → 409 Conflict)

Field entitlement: optional, hanya metadata untuk audit. App tidak enforce quota (lihat Integrasi Sejoli).

GET /api/v1/session/{session_id} — Get Session

Implemented. Ambil detail sesi (status, skor kalau sudah complete).

http
GET /api/v1/session/sejoli-132380-123-2
Authorization: Bearer {token}
X-Website-ID: 1

Response 200 OK:

json
{
  "session_id": "sejoli-132380-123-2",
  "status": "completed",
  "nm": 750,
  "nn": 580,
  "theta": 0.523,
  "theta_se": 0.42,
  "completed_at": "2026-07-25T10:00:00Z"
}

🚧 POST /api/v1/session/{session_id}/next_item — Get Next Question

Planned — lihat Gap Analysis Gap 1.

Endpoint kritis untuk Pola B. Sejoli call ini setiap kali siswa klik "Next" untuk dapat soal berikutnya.

http
POST /api/v1/session/sejoli-132380-123-2/next_item
Authorization: Bearer {token}
X-Website-ID: 1

Response 200 OK (proposed):

json
{
  "item": {
    "id": 42,
    "slot": 5,
    "level": "sedang",
    "stem": "Berapa hasil 2+2?",
    "options": {"A": "4", "B": "5", "C": "6", "D": "7"},
    "explanation": null
  },
  "selection_method": "adaptive",
  "slot": 5,
  "level": "sedang",
  "reason": "Adaptive selection - b=0.234 close to theta=0.180",
  "items_remaining": 12,
  "should_terminate": false,
  "session_status": "in_progress"
}

Response saat sesi selesai:

json
{
  "item": null,
  "should_terminate": true,
  "reason": "SE threshold met (0.42 < 0.5 after 18 items)",
  "session_status": "ready_to_complete"
}

Aturan:

  • Idempotent — double-click harus return item yang sama (Gap 5)
  • Return item: null + should_terminate: true kalau SE sudah cukup
  • Menerapkan algoritma cat_selection.get_next_item() (sudah ada di service layer)

🚧 POST /api/v1/session/{session_id}/answer — Submit Single Answer

Planned — lihat Gap Analysis Gap 2.

Submit 1 jawaban, simpan ke UserAnswer, update theta real-time.

http
POST /api/v1/session/sejoli-132380-123-2/answer
Authorization: Bearer {token}
X-Website-ID: 1
Content-Type: application/json

{
  "item_id": 42,
  "response": "A",
  "time_spent_ms": 25000
}

Response 200 OK (proposed):

json
{
  "is_correct": true,
  "bobot_earned": 0.65,
  "theta": 0.234,
  "theta_se": 0.41,
  "items_answered": 5,
  "items_remaining": 12
}

Aturan:

  • item_id harus match current_item_id di session (Gap 6)
  • Jika mode irt/hybrid: update theta real-time via irt_calibration.update_theta_after_response()
  • Jika mode ctt: theta dan theta_se return null

⚠️ POST /api/v1/session/{session_id}/complete — Complete Session

Implemented (Pola A) — needs refactor for Pola B — lihat Gap Analysis Gap 3.

Pola A (saat ini)

Menerima batch jawaban di akhir ujian:

http
POST /api/v1/session/sejoli-132380-123-2/complete
Content-Type: application/json

{
  "end_time": "2026-07-25T10:00:00Z",
  "user_answers": [
    {"item_id": 1, "response": "A"},
    {"item_id": 2, "response": "C"},
    ...
  ]
}

Cocok untuk integrasi Sejoli saat ini (Pola A — pre-render semua soal).

Pola B (target setelah refactor)

Hanya menerima signal "selesai", tidak terima jawaban (karena sudah tersimpan via /answer):

http
POST /api/v1/session/sejoli-132380-123-2/complete
Content-Type: application/json

{
  "end_time": "2026-07-25T10:00:00Z",
  "reason": "user_submit"
}

Behavior baru:

  1. Validasi minimal 1 jawaban tersimpan di UserAnswer
  2. Hitung skor final dari UserAnswer (bukan dari request body)
  3. Untuk mode irt/hybrid: theta sudah real-time, tinggal ambil nilai terakhir
  4. Update Session status → completed
  5. Update TryoutStats inkremental

Backward compatibility: butuh feature flag atau versioning selama transisi.

State Machine Session

🚧 Planned — lihat Gap Analysis Gap 4.

StatusArtiTransisi Keluar
createdSession dibuat, belum ada soalin_progress via first next_item
in_progressSedang berlangsung, ada current_itemin_progress via answer + next_item
completedSelesai, skor final(terminal)
abandonedDitinggalkan (timeout lama)(terminal)

Validasi Endpoint (Planned)

EndpointStatus yang validError kalau salah
next_itemcreated, in_progress400 "Session already completed"
answerin_progress + ada current_item400 "No active item" / 409 "Wrong item_id"
completein_progress400 "Session already completed" / 400 "No answers yet"

Bacaan Lanjutan

Last updated Jul 25, 2026