7.1 KiB
7.1 KiB
Design Notes — Pemetaan sampleInteractiveExamData ke OpenAPI
Tanggal: 2025-11-12
Tujuan: Menyelaraskan struktur data kuis interaktif dari frontend (sampleInteractiveExamData) dengan kontrak backend (OpenAPI) untuk create/score/summary, agar diskusi skema dan wiring berikutnya berjalan mulus.
Ruang Lingkup & Asumsi
- Frontend: sumber data contoh berada di
src/app/exam-session/page.tsx(tipe soal: enhanced MCQ, video scenario, image hotspot, media gallery, puzzle, scenario). - Backend: spesifikasi saat ini di
docs/api/openapi.yamlmendukung penilaian sederhana; perluasan diperlukan untuk tipe interaktif. - Kompatibilitas: halaman ringkasan (
exam-summary) saat ini menampilkanuserAnswersebagai string; detail interaktif akan ditambahkan secara opsional.
Tipe Soal & Pemetaan Payload
- enhanced_mcq
- Struktur frontend:
{ id, content, options[], correctAnswers[], hints?, explanation?, points?, confidenceEnabled? } - Score payload:
{ questionId, type: "enhanced_mcq", choiceIndex, confidence? } - Summary payload (kompatibel):
userAnswersebagai teks;details(opsional){ choiceIndex, confidence } - Catatan:
confidenceuntuk analitik, tidak memengaruhi skor kecuali aturan penilaian menggunakannya.
- video_scenario
- Struktur frontend:
{ id, title, steps: [{ stepId, prompt, options[] }], scoringRules? } - Score payload:
{ questionId, type: "video_scenario", scenarioAnswers: [{ stepId, selectedOptionId }] } - Summary payload:
userAnswerdiringkas;details(opsional){ steps: [{ stepId, selectedOptionId, isCorrect, earnedPoints }] } - Catatan: skor dihitung agregat per-step mengikuti
scoringRules.
- image_hotspot
- Struktur frontend:
{ id, imageUrl, hotspots: [{ hotspotId, label }], scoringRules? } - Score payload:
{ questionId, type: "image_hotspot", selectedHotspots: [hotspotId] } - Summary payload:
userAnswerdiringkas;details(opsional){ selectedHotspots, correctHotspots } - Catatan: titik hotspot adalah ID referensial (koordinat didefinisikan di konten soal).
- media_gallery
- Struktur frontend:
{ id, items: [{ id, type: "video|image|document", title }], trackingEnabled: true, scoringRules? } - Score payload:
{ questionId, type: "media_gallery", viewedItems: [itemId] } - Summary payload:
details(opsional){ viewedItems, requiredItems?, completionRate } - Catatan: biasanya non-graded; dapat diberi syarat minimal keterlihatan (mis. harus melihat
document1).
- puzzle (matching)
- Struktur frontend:
{ id, pairs: [{ pieceId, targetId }], points?, scoringRules? } - Score payload:
{ questionId, type: "puzzle", matches: [{ pieceId, targetId }] } - Summary payload:
details(opsional){ matches, correctPairs, earnedPoints } - Catatan: penilaian bisa per-pasangan (parsial) atau all-or-nothing.
- scenario (single-step)
- Struktur frontend:
{ id, prompt, options[], correctOptionId, points? } - Score payload:
{ questionId, type: "scenario", selectedOptionId } - Summary payload:
details(opsional){ selectedOptionId, isCorrect }
Proposal Perluasan OpenAPI
- ExamSessionCreateRequest
- Tambah (opsional)
isInteractive: booleandanclientContext: { device, appVersion, locale }. - Idempotensi create (opsional):
idempotencyKey.
- ExamScoreRequest
answers[]menjadi union berdasarkantype:
{
"questionId": "string",
"type": "enhanced_mcq | video_scenario | image_hotspot | media_gallery | puzzle | scenario",
"choiceIndex": 0,
"confidence": 4,
"scenarioAnswers": [{ "stepId": "string", "selectedOptionId": "string" }],
"selectedHotspots": ["string"],
"viewedItems": ["string"],
"matches": [{ "pieceId": "string", "targetId": "string" }],
"selectedOptionId": "string"
}
- Wajib:
questionId,type. Field lain kondisional sesuai tipe. - Tetap dukung bentuk sederhana untuk MCQ sebagai compatibility path.
- ExamSummaryResponse.answers[]
- Tambah
typedandetails(opsional) sebagai union detail per tipe:
{
"questionId": "string",
"question": "string",
"userAnswer": "string",
"correctAnswer": "string",
"isCorrect": true,
"type": "...",
"details": {
"enhanced_mcq": { "choiceIndex": 0, "confidence": 4 },
"video_scenario": { "steps": [{ "stepId": "s1", "selectedOptionId": "opt4", "isCorrect": true, "earnedPoints": 10 }] },
"image_hotspot": { "selectedHotspots": ["hotspot1"], "correctHotspots": ["hotspot1","hotspot2"] },
"media_gallery": { "viewedItems": ["video1","image1"], "requiredItems": ["document1"], "completionRate": 0.8 },
"puzzle": { "matches": [{ "pieceId": "ccp", "targetId": "def1" }], "correctPairs": 4, "earnedPoints": 10 },
"scenario": { "selectedOptionId": "option2", "isCorrect": true }
}
}
Contoh Payload Ringkas per Tipe
- enhanced_mcq:
{ "questionId": "1", "type": "enhanced_mcq", "choiceIndex": 0, "confidence": 4 }
- video_scenario:
{ "questionId": "video-scenario-1", "type": "video_scenario", "scenarioAnswers": [ { "stepId": "step1", "selectedOptionId": "opt4" }, { "stepId": "step2", "selectedOptionId": "opt4" } ] }
- image_hotspot:
{ "questionId": "hotspot-scenario-1", "type": "image_hotspot", "selectedHotspots": ["hotspot1","hotspot2","hotspot4"] }
- media_gallery:
{ "questionId": "media-gallery-1", "type": "media_gallery", "viewedItems": ["video1","image1","video2","image2","document1","image3"] }
- puzzle:
{ "questionId": "3", "type": "puzzle", "matches": [ { "pieceId": "ccp", "targetId": "def1" }, { "pieceId": "haccp", "targetId": "def2" } ] }
- scenario:
{ "questionId": "6", "type": "scenario", "selectedOptionId": "option2" }
Kompatibilitas & Migrasi
- Backward:
userAnswertetap string untuk semua tipe;detailsopsional. - Forward: UI interaktif dapat menampilkan rincian dari
detailsjika tersedia. - Penilaian: jika backend belum mendukung tipe interaktif, treat sebagai MCQ atau non-graded dengan zero-points.
Rencana Wiring Frontend (Ringkas)
- Create: panggil
POST /api/exam-sessiondenganexamId,userId(opsionalisInteractive,clientContext). SimpansessionId. - Score: bentuk
answers[]union dari stateExamContext; kirimidempotencyKeyunik. - Summary: ambil
GET /api/exam-session/{sessionId}/summary; render sesuai compatibility +detailsbila tersedia.
Catatan Idempotensi
idempotencyKeypada score menjamin hasil konsisten untuk payload yang sama.- Backend menyimpan fingerprint jawaban untuk kunci yang sama dalam rentang waktu tertentu.
Referensi
- OpenAPI:
docs/api/openapi.yaml - Retrospektif:
docs/retrospectives/epic-2-retrospective-2025-11-12.md - Frontend contoh:
src/app/exam-session/page.tsx,src/app/exam-summary/page.tsx
Diagram Pemetaan (Sederhana)
flowchart LR
UI[UI Interaktif] --> EC[ExamContext State]
EC --> MP[Mapper: build answers[]]
MP --> ASR[ExamScoreRequest.answers[]]
ASR --> API[POST /api/exam-session/{sessionId}/score]
- Sumber:
ExamContextmenyimpan jawaban interaktif perquestionId. - Mapper: membentuk union
answers[]berdasarkan tipe soal. - Payload: dikirim sebagai
ExamScoreRequest(modebatch) denganidempotencyKey. - Hasil: backend menghitung skor dan menyediakan ringkasan melalui
ExamSummaryResponse.