Commit Graph

14 Commits

Author SHA1 Message Date
a2nr 7696a04d72 feat(progress): export laporan per bab/materi (poin 6)
Tambah filter ?bab= dan ?materi= pada export CSV, plus ikon unduh di kartu bab dan baris materi.
2026-10-27 16:08:55 +07:00
a2nr 2a5fdf8400 feat(progress): rekap distribusi bobot jawaban (poin 5)
Hitung ulang distribusi point N: count dari jawaban, tampilkan di ringkasan siswa, tooltip guru, dan CSV.
2026-10-27 16:08:48 +07:00
a2nr 81cb9c69ca feat(quiz): taksonomi key quiz per jenis + MCQ berbobot (partial credit)
Fitur B — setiap jenis soal kini punya key sendiri sehingga model
penilaiannya eksplisit, tidak ditebak dari isi konten:

- ---QUIZ_MCQ---      pilihan ganda biner (tepat satu [x]; larang [N])
- ---QUIZ_WEIGHTED--- pilihan ganda berbobot ([N] >= 1, minimal satu [ ],
                      bobot tertinggi unik = kunci; poin parsial)
- ---FLASHCARD---     kartu hafalan tanpa penilaian (larang opsi)
- legacy ---QUIZ_FLASHCARD--- / ---INITIAL_QUIZ--- tetap dibaca tanpa
  perubahan perilaku (regex opsi legacy sengaja tidak berubah — baris
  '- [10]' di konten lama BUKAN opsi); dihapus dari docs/template
- ::diagnostic tetap kategori di dalam QUIZ_MCQ & tetap masuk skor resmi

Model skor: rata-rata persentase per jenis yang ada, Math.round,
lulus >= 75. statusString: "X/Y" bila hanya MCQ/legacy, "P/100" bila ada
weighted, "completed" bila hanya flashcard (auto-lulus). Satu sesi/
attempt untuk semua jenis; soal flat urut dokumen.

Parser (backend & frontend mirror, ekstraksi multi-blok per key urut
dokumen via loop marker-start paling awal; id q-N deterministik):
- lesson_service.py: regex [ xX]?[0-9]* utk key baru, _iter_option_lines
  kind-aware, _validate_quiz_options per kind, _parse_flashcards(kind,
  id_offset), _extract_quiz_sections + guard loop maju (marker end
  hilang tidak lagi infinite loop), get_lesson_quiz_total -> 100 bila
  ada weighted (round-trip CSV "P/100")
- parseFlashcards.ts / index.ts / detectActiveTabs.ts / extractSections.ts:
  mirror perilaku; heading soal <h3> kini fence-aware (fix fixture stale)

Payload attempt baru: quiz_type, earned_points, question_max_points —
opsional di validasi backend supaya klien lama tetap diterima; poin
di-recompute dari konten saat resume/review.

UI: QuizTab skor via statusString + alert berbasis percent, badge
weighted (✓ 10 / 🟡 7/10 / ✗ 0) di review, preview guru badge bobot +
"Berbobot (total N)", laporan guru badge MCQ/Bobot + tooltip, CSV
progress 2 kolom baru per lesson (_quiz_mcq, _quiz_weighted), template
editor konten QUIZ_MCQ/Quiz Bobot/Flashcard.

Tooling & docs: scripts/convert_quiz_keys.py (konversi legacy -> key
baru per run jenis, idempoten, verifikasi parse identik; examples/
content dikonversi), scanner mendukung semua key, docs/07 ditulis ulang,
docs/15 direvisi ke desain final, README/documentation/docs 02/04/09
diperbarui.

Tes: backend 444 lulus (+33: validasi ketat per key, urutan dokumen,
multi-blok, zero regression, field selalu di-emit), frontend 249 lulus
(+13 parser key baru, +11 model skor), parity fixture diregenerasi,
svelte-check 0 error, vite build OK.
2026-10-25 20:16:56 +07:00
a2nr 087d338477 feat(quiz): izinkan guru melanjutkan attempt terminated dari jawaban tersimpan
Kebijakan termination tetap strict, tapi guru pegang override untuk kendala
tak terduga (false-positive blur dari dialog OS, browser crash, refresh
tak sengaja). Selain reset (siswa mengulang dari nol), guru kini bisa
memberi izin resume — siswa melanjutkan dari jawaban yang sudah tersimpan.

- Tambah kolom quiz_attempts.resumed_at (migrasi 0006) sebagai penanda izin
  guru melanjutkan attempt terminated
- Tambah endpoint teacher-only POST /resume-quiz-attempt — kontrak keamanan
  sama dengan /reset-progress (guru cukup student_id anonim, tanpa token siswa)
- Syarat: hanya attempt ber-status terminated yang bisa di-resume; submitted
  ditolak karena kuis sudah selesai normal
- has_quiz_attempt meloloskan attempt ber-izin sehingga gate one-attempt
  terbuka selama sesi lanjutan
- finalize_quiz_attempt menimpa row attempt sekali (status/skor/answers baru)
  lalu clear resumed_at; retry berikutnya kembali ke jalur idempotent normal
- Sinyal ke frontend siswa lewat quiz_resumable pada data lesson; laporan guru
  menampilkan badge "Dilanjutkan" dan tombol "Lanjutkan" untuk attempt
  terminated
- Frontend: restoreQuizSession() merekonstruksi sesi dari jawaban tersimpan per
  question_id, is_correct dihitung ulang dari source sebagai guard bila
  konten soal berubah; option_id tak dikenal dianggap belum dijawab
- Frontend: firstUnansweredIndex() menentukan soal awal sesi lanjutan;
  resolveQuizTabView() memusatkan keputusan view agar transisi resume dapat
  diuji Vitest
- UI siswa: panel "Lanjutkan Kuis" + snackbar mode lanjutan (sekali muncul).
  Attempt di-refetch segar sebelum sesi dimulai agar izin yang sudah dicabut
  guru tidak memulai sesi yang jawabannya akan terbuang
- Test: 16 integrasi backend (services/tests/test_resume_quiz.py), 9 unit
  quiz-session, 10 unit quiz-view
- Docs: alur resume + keterbatasan di 12-quiz-integrity.md, ringkasan di
  07-quiz-authoring.md, kontrak endpoint di 02-backend-flask.md
2026-10-20 19:24:40 +07:00
a2nr f95365a6e3 feat(teacher): refactor progress into tree view and separate classroom management
- Redesign /teacher/progress into a folder-like tree view (sub-bab -> lesson -> completed students)
- Create /teacher/classroom for student management (import/export CSV, add, bulk delete)
- Enrich /progress-report.json API response with sub_bab, sub_bab_title, and sub_bab_list
- Add Classroom navigation item to teacher navbar menu
2026-09-15 04:51:35 +07:00
a2nr 9bfecfbcab feat(student-management): sync imported progress with composite model and add inline expected format to CSV headers
- Sync imported progress (completed, scored, done, reset) to composite progress model and recompute composite_percent
- Prevent caching of /progress-report.json with Cache-Control: no-store and frontend cache-busting timestamp
- Provide inline expected format guides in exported CSV lesson headers (e.g. [completed], [<earned>/<total>], [done:<1|0>:<earned>/N])
- Support robust slug extraction from formatted headers in CSV roundtrip import with full backward compatibility
- Add example composite lesson (exercise + quiz) in examples/content/dasar/composite_test.md
- Add comprehensive unit and integration tests covering progress synchronization, header formatting, and roundtrip workflows
2026-09-08 19:01:33 +07:00
a2nr 41c434f064 feat(progress): composite scoring, roundtrip CSV grammar, and report export
- Integrate diagnostic and evaluation MCQ scoring in quiz sessions
- Extend round-trip CSV grammar with `done:<ex>:<earned>/<total>` and `RESET`
- Fix completed count aggregation in repositories to include 'done' state
- Update progress report CSV export to output composite scores per lesson
- Add comprehensive test coverage for scoring, CSV round-trip, and exports
2026-08-30 04:12:23 +07:00
a2nr 467b146988 feat: unified lesson progress — composite score & /api/lesson-progress
- composite score: exercise 70% + quiz 30%, done ≥75% (env LESSON_*)
- endpoint /api/lesson-progress menggantikan /track-progress + /quiz-attempts
- state baru 'done'/'in_progress'; deprecated per-lesson min:N% -> global threshold
- progress_status emit composite; teacher report single composite column
- frontend: submitLessonProgress/lessonProgressBeacon/fetchLessonProgress, QuizTab fix, SW url
- migration 0005; test baru: composite, lesson-progress routes, composite flow
2026-08-29 00:22:10 +07:00
a2nr ac37db5c91 feat(storage): migrasi penyimpanan CSV ke PostgreSQL (SQLAlchemy + Alembic)
- facade storage: csv_backend + postgres_backend, dipilih via STORAGE_BACKEND
- token disimpan sebagai HMAC-SHA256 digest (TOKEN_PEPPER), tanpa plaintext
- schema alembic 0001: users, access_tokens, lessons, student_progress
- importer CSV idempotent + dry-run + sparse model (not_started tanpa row)
- lesson registry: sync dari home.md (lesson hilang di-deactivate, bukan dihapus)
- routes progress/auth: report & reset via DB, validasi via token hash
- elemes.sh: dbupgrade/dbstatus/dbimport/synclessons/dbverify/dbbackup/dbrestore/dbexport
- podman-compose: service postgres (PG18), tokens_siswa.csv mount :ro
- frontend: api client + progress page; tests kontrak dual-backend + isolasi DB
- load-test: skenario database (locustfile_db.py)
- docs: 01-11 termasuk 11-database-migration.md (arsitektur & operasional)
2026-08-08 04:57:08 +00:00
a2nr 5f150da651 fix: progress path fix 2026-05-12 14:49:18 +07:00
a2nr ea3556d05f feat: implement interactive quiz with secure content protection, scoring, and teacher reset 2026-05-09 13:04:42 +07:00
a2nr 2b6ec21633 perf: fix race conditions with file locking and optimize backend latency 2026-05-05 22:57:19 +07:00
a2nr a41711fb54 security: implement gVisor sandboxing and harden API endpoints
- Menambahkan service 'compiler-worker' terpisah untuk isolasi eksekusi kode C/Python.
   - Mengintegrasikan gVisor (runsc) pada worker untuk mencegah RCE pada level kernel.
   - Menggunakan Gunicorn (4 workers) pada compiler-worker untuk mendukung concurrency.
   - Menambahkan otentikasi token wajib pada endpoint /compile dan laporan progres.
   - Memperketat CORS policy menggunakan environment variable ORIGIN.
   - Menerapkan secure_filename pada rute pelajaran untuk mencegah Path Traversal.
   - Mengubah volume mounting backend utama menjadi Read-Only (:ro) untuk perlindungan data.
   - Memperbarui proposal.md dan .env.example dengan standar keamanan terbaru.
2026-04-21 15:13:16 +07:00
a2nr d2b6d62b28 refactor to make it better 2026-03-25 09:39:51 +07:00