LMS-BGN/docs/retrospectives/epic-2-retrospective-2025-1...

213 lines
10 KiB
Markdown

# Epic 2 Retrospektif — Ujian & Sertifikat (LMS-BGN)
Tanggal: 2025-11-12
Cakupan: Siklus sesi ujian, penilaian, ringkasan, daftar ujian, pengiriman ulang sertifikat
## Ringkasan Hasil
- Endpoint backend untuk Epic 2 telah dibuat dan diverifikasi dengan pengujian.
- Contoh REST Client dan cURL tersedia untuk QA manual.
- Kontrak mengikuti `docs/api/openapi.yaml` dan diselaraskan dengan kebutuhan UI inti.
- Integrasi frontend sengaja ditunda sesuai instruksi; fokus pada pelurusan dokumentasi.
## Cakupan yang Diimplementasikan
- `POST /api/exam-session` — membuat sesi ujian (validasi dasar).
- `POST /api/exam-session/{sessionId}/score` — penilaian batch dengan `idempotencyKey`.
- `GET /api/exam-session/{sessionId}/summary` — ringkasan hasil teragregasi.
- `GET /api/exams` — daftar ujian terpaginasikan dengan filter teks `q`.
- `POST /api/certificates/{certificateId}/resend` — pengiriman ulang email (diterima + throttling).
## Cakupan OpenAPI & Kuis Interaktif
- `ExamSessionCreateRequest`: saat ini memuat `examId` dan `userId`. Mode interaktif ditentukan dari metadata ujian di sisi server (lihat `ExamListItem.isInteractive`). Tidak ada skema bernama `InteractiveExam` terpisah pada spesifikasi sekarang.
- `ExamScoreRequest`: memuat `mode` (`batch|stream`), `idempotencyKey`, dan `answers[]` dengan bentuk sederhana `{ questionId, choiceIndex }`. Bentuk ini belum mencakup tipe interaktif (hotspot, puzzle, scenario) dari `sampleInteractiveExamData`; perlu perluasan skema di fase selanjutnya.
- `ExamSummaryResponse`: bentuk respons diselaraskan dengan kebutuhan halaman ringkasan (judul, skor, total, jawaban per soal) dan sesuai contoh di spesifikasi.
- `ExamListItem`: memuat `isInteractive: boolean` sehingga UI dapat membedakan kuis interaktif di listing.
- Konsekuensi: Integrasi penuh fitur interaktif memerlukan penambahan skema dan payload yang lebih kaya (mis. struktur hotspot, puzzle, scenario) ke OpenAPI sebelum wiring frontend.
## Kualitas & Metrik
- Pengujian: 17 lulus, 0 gagal (Jest + Supertest).
- Durasi suite: ~1.8s lokal.
- Artefak QA:
- REST Client: `docs/qa/assessments/backend-endpoints.http`
- cURL: `docs/qa/assessments/curl-examples.md`
## Hal yang Berjalan Baik
- Bootstrap backend cepat dengan Express; pemisahan concern jelas.
- Idempotensi & throttling dasar untuk route sensitif.
- Pengujian menyentuh happy-path dan kasus error inti.
- Dokumentasi dan contoh memudahkan verifikasi manual.
## Tantangan
- Integrasi frontend belum dihubungkan: `src/app/exam-session/page.tsx`, `exam-summary`, dan `exams` masih butuh wiring API (ditunda sesuai instruksi).
- Data masih in-memory; belum ada persistence atau auth sehingga alur belum realistis.
- Kesenjangan pemetaan `sampleInteractiveExamData` ke payload OpenAPI saat ini (belum ada skema interaktif rinci).
## Risiko & Mitigasi
- Drift kontrak vs ekspektasi frontend — Mitigasi: patuhi `docs/api/openapi.yaml`, perluas skema untuk interaktif, dan sesuaikan pengujian.
- Deliverability email & audit — Mitigasi: rencanakan konfigurasi SMTP, audit log, dan retry/backoff.
- Tidak ada persistence — Mitigasi: definisikan skema minimal, tambah layer DB ringan, pindahkan state idempotensi dari memori ke storage.
- Keamanan & rate limiting — Mitigasi: tambahkan auth checks dan rate-limit untuk route ujian & sertifikat.
## Tindak Lanjut (Tanpa Implementasi Langsung)
- Perluas OpenAPI untuk mendukung tipe interaktif (hotspot, puzzle, scenario) dan bentuk jawaban yang relevan.
- Definisikan pemetaan dari `sampleInteractiveExamData` ke payload backend (create/score/summary) dan tambahkan contoh di dokumentasi.
- Tambah persistence minimal untuk sesi & jawaban; pertimbangkan auth dasar.
- Siapkan rencana wiring frontend ke backend untuk sprint berikutnya, sesuai skema yang diperluas.
- CI: jalankan `npm test` pada push/PR.
- Observabilitas: logging terstruktur & audit trail untuk alur ujian/sertifikat.
## Referensi
- Konteks: `docs/epic-2-context.md`
- OpenAPI: `docs/api/openapi.yaml`
- QA: `docs/qa/assessments/backend-endpoints.http`, `docs/qa/assessments/curl-examples.md`
- Frontend (belum terhubung): `src/app/exam-session/page.tsx`, `src/app/exam-summary/page.tsx`, `src/app/exams/page.tsx`
## Contoh Payload — Interaktif (Draft)
Catatan: Contoh berikut bertujuan memudahkan diskusi skema. Beberapa field bertanda "proposal" tidak ada dalam spesifikasi saat ini dan akan diajukan di perluasan OpenAPI berikutnya.
### Create (proposal ekstensi)
Request:
```json
{
"examId": "interactive-dapur-mbg-001",
"userId": "user-123",
"isInteractive": true,
"idempotencyKey": "create-5a1d9e2c-9f12-4d3a-b3a1-7c8e0b4f6a11",
"clientContext": {
"device": "web",
"appVersion": "0.1.0",
"locale": "id-ID"
}
}
```
Response (sesuai spesifikasi saat ini):
```json
{
"sessionId": "sess-001",
"status": "created",
"startTime": "2025-11-12T08:00:00Z"
}
```
### Score (proposal dukungan tipe interaktif)
Request:
```json
{
"mode": "batch",
"idempotencyKey": "score-8d3bc2f0-26a3-4fd0-9b4c-5e2b7b1f9c77",
"answers": [
{ "questionId": "1", "type": "enhanced_mcq", "choiceIndex": 0, "confidence": 4 },
{ "questionId": "video-scenario-1", "type": "video_scenario", "scenarioAnswers": [
{ "stepId": "step1", "selectedOptionId": "opt4" },
{ "stepId": "step2", "selectedOptionId": "opt4" }
]},
{ "questionId": "hotspot-scenario-1", "type": "image_hotspot", "selectedHotspots": ["hotspot1", "hotspot2", "hotspot4"] },
{ "questionId": "media-gallery-1", "type": "media_gallery", "viewedItems": ["video1", "image1", "video2", "image2", "document1", "image3"] },
{ "questionId": "3", "type": "puzzle", "matches": [
{ "pieceId": "ccp", "targetId": "def1" },
{ "pieceId": "haccp", "targetId": "def2" },
{ "pieceId": "sanitasi", "targetId": "def3" },
{ "pieceId": "kontaminasi", "targetId": "def4" }
]},
{ "questionId": "6", "type": "scenario", "selectedOptionId": "option2" }
]
}
```
Response (contoh ringkas):
```json
{
"totalScore": 85,
"totalQuestions": 10,
"correctAnswers": 8,
"scorePercent": 85,
"perQuestion": [
{ "questionId": "1", "isCorrect": true, "earnedPoints": 10, "maxPoints": 10 },
{ "questionId": "video-scenario-1", "isCorrect": true, "earnedPoints": 20, "maxPoints": 20 }
]
}
```
### Summary (kompatibel + proposal detail)
Response (kompatibel):
```json
{
"examId": "interactive-dapur-mbg-001",
"examTitle": "Kuis Interaktif Dapur MBG",
"totalQuestions": 10,
"correctAnswers": 8,
"score": 85,
"timeSpent": "25 menit 30 detik",
"completedAt": "2025-11-12T08:30:00Z",
"answers": [
{
"questionId": "1",
"question": "Apa langkah pertama dalam memastikan keamanan pangan di Dapur MBG?",
"userAnswer": "Melakukan analisis bahaya",
"correctAnswer": "Melakukan analisis bahaya",
"isCorrect": true,
"type": "enhanced_mcq"
},
{
"questionId": "video-scenario-1",
"question": "Analisis Situasi Dapur: Identifikasi Masalah Keamanan Pangan",
"userAnswer": "step1: opt4; step2: opt4",
"correctAnswer": "step1: opt4; step2: opt4",
"isCorrect": true,
"type": "video_scenario"
},
{
"questionId": "hotspot-scenario-1",
"question": "Identifikasi Area Bermasalah dalam Tata Letak Dapur",
"userAnswer": "hotspot1,hotspot2,hotspot4",
"correctAnswer": "hotspot1,hotspot2,hotspot3,hotspot4,hotspot5",
"isCorrect": false,
"type": "image_hotspot"
},
{
"questionId": "3",
"question": "Cocokkan istilah keamanan pangan dengan definisinya",
"userAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
"correctAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
"isCorrect": true,
"type": "puzzle"
}
]
}
```
Catatan kompatibilitas:
- Agar tetap kompatibel, `userAnswer` untuk tipe interaktif diringkas sebagai string; perluasan dapat menambahkan `details` opsional berisi struktur lengkap per tipe (proposal).
## Catatan Implementasi (Update)
- Artefak QA telah disinkronkan: payload union untuk `ExamScoreRequest` tersedia di `docs/qa/assessments/backend-endpoints.http`, termasuk blok uji MCQ-only untuk hasil skor positif.
- Backend kini memparse dan menilai tipe interaktif (video_scenario, image_hotspot, media_gallery, puzzle, scenario) dengan skema sederhana, termasuk partial scoring per tipe; bentuk `{ questionId, choiceIndex }` tetap didukung untuk MCQ.
- Respons `GET /api/exam-session/{sessionId}/summary` sekarang menyertakan `answers[].details` per tipe (langkah video, hotspot terpilih vs kunci, progres gallery, langkah puzzle, dan opsi scenario) sehingga kompatibel + kaya detail sesuai contoh draft.
- Pemeriksaan Authorization (Bearer) diterapkan server-side untuk endpoint ujian: create session, score, dan summary. QA contoh sudah menyertakan header `Authorization: Bearer test-token`.
### NFR & Observabilitas (Ringkas)
- Logging terstruktur dengan korelasi `requestId`/`sessionId` untuk create/score/summary/resend
- Metrics: `score_latency_ms`, `summary_latency_ms`, `error_rate`, `resend_throttle_hits`
- Alerting sederhana untuk lonjakan error rata-rata p95
- Audit trail untuk `resend` (recipient, status, attempt)
### Dependencies (Versi Target)
- Node.js `18.x`, Express `4.x`, Jest `29.x`, Supertest `6.x`, Nodemailer `6.x`
### Acceptance Criteria Atomik + Pemetaan Test
- POST `/api/exam-session` membuat sesi valid → tes integrasi create (Bearer wajib)
- POST `/api/exam-session/{sessionId}/score` menghitung skor (MCQ+interaktif) & idempotent → tes integrasi score + idempotency
- GET `/api/exam-session/{sessionId}/summary` menampilkan agregat & `answers[].details` → tes integrasi summary
- POST `/api/certificates/{certificateId}/resend` throttling + audit → tes integrasi resend
### Traceability Matrix (Ringkas)
| AC ID | Bagian | Komponen | Story | Tes |
|------|--------|----------|-------|-----|
| R-AC1 | Create Session | `server.js` route create | `docs/stories/2-1-exam-session-create-api.md` | `backend/tests/server.test.js` (create session) |
| R-AC2 | Scoring & Idempotency | scorer util + route score | `docs/stories/2-2-exam-scoring-api.md` | `backend/tests/server.test.js` (score + idempotency) |
| R-AC3 | Summary kompatibel | summary builder + route summary | `docs/stories/2-3-exam-summary-read-api.md` | `backend/tests/server.test.js` (summary) |
| R-AC4 | Resend throttled + audit | resend handler | TBD: `docs/stories/2-4-certificate-resend-api.md` | `backend/tests/server.test.js` (resend) |