Lewati ke isi

SPMB Tests · CBT · Ranking · Penerimaan · Pengumuman — Implementation Plan

Status: Implementation plan (belum dieksekusi). Companion ke audit di docs/qa/spmb-tests-cbt-ranking-audit.md.

Repos: scola-fe-v2 (FE) + scola-odoo-module (BE). Branch utama: develop. Phase yang menyentuh BE menulis di scola-odoo-module/scola_admission*, scola_cbt, atau overlay scola_admission_assessment.

Strategi roll-out: phase modular, bisa dikirim sebagai PR terpisah. Phase 0 (Quick Fix) harus mendarat sebelum periode SPMB live berikutnya untuk hindari blocker user-facing.


0. Convention & Common Requirements

0.1 Phase template

Setiap phase wajib menyertakan: - Owner repo (FE / BE / both) - Estimasi effort (S = ≤1 hari, M = 2-5 hari, L = >1 minggu) - Acceptance criteria (checklist konkret) - Test gates (vitest contract, Playwright e2e, Odoo unit test) - Roll-out plan (feature flag jika perlu, migrasi data, dependency module) - Definition of Done (lint diff lulus, capability diff hijau, type-check, test green, build ok, docs updated)

0.2 Test gates baseline (semua phase)

Per AGENTS.md & docs/ai-guidelines/development-guide.md §10: - lint:diff - check:capability-diff - type-check - vitest --changed + contract tests - test:contract - build - E2E smoke (jika auth/routing berubah)

0.3 Phase Dependency Graph

Phase 0 (route fix) ──► Phase 1 (test schedule contract) ──► Phase 2 (CBT runner)
                                                                 │
Phase 7 (role bridges) ────────────────────────────────────────► │
                                                                 │
Phase 8 (regulatory) ──────────────────────────────────────────► │
                                                                 ▼
                                                        Phase 3 (score breakdown)
                                                                 │
                                                                 ▼
                                                        Phase 4 (announcement & idempotency)
                                                                 │
                                                                 ▼
                                                        Phase 5 (ranking audit)
                                                                 │
                                                                 ▼
                                                        Phase 6 (waitlist/refund)

Phase 7 & 8 dapat paralel dengan Phase 1-2 (tidak mengubah API contract pendaftar).


Phase 0 — Quick Fix Broken Routes (FE-only)

Owner: FE · Effort: S · Resolves: F-001, F-002, F-003, F-011

Scope

  1. Tambah named route di src/router/spmbRoutes.js:
  2. SPMBCBTStart/spmb/admission/:id/cbt-start (atau path existing yang konsisten)
  3. SPMBCBTRunner/spmb/admission/:id/cbt/:scheduleId/run (skeleton placeholder dengan info "Fitur akan tersedia saat hari-H tes")
  4. Ganti redirect /spmb/dashboard (dead route) → /spmb/admission/:id di:
  5. SPMBDaftarUlang.vue:582
  6. SPMBCBTStart.vue:15
  7. Tambah test tests/unit/router/spmbRoutesContract.spec.js:
  8. Pastikan semua name-based router.push dari komponen Public/SPMB*.vue punya named route corresponding.
  9. Pastikan tidak ada hardcode /spmb/dashboard.

Acceptance

  • [ ] Klik "Mulai CBT" dari SPMBTestSchedule.vue tidak menghasilkan router warning.
  • [ ] Setelah confirmEnrollment sukses, redirect ke /spmb/admission/:id dengan banner "Daftar ulang berhasil".
  • [ ] Vitest spmbRoutesContract.spec.js hijau.

Roll-out

  • PR tunggal ke develop. Tidak butuh feature flag.
  • Tag commit feat(spmb): add SPMBCBTStart/Runner route stubs and fix /spmb/dashboard redirect.

Phase 1 — Test Schedule Contract Hardening (FE+BE)

Owner: FE+BE · Effort: M · Resolves: F-005, F-006, F-007, F-014, F-013

Status: ✅ Resolved (Apr 30, 2026). BE hardened, FE strict (no fallbacks), contract tests green.

Scope BE — Done

  1. ✅ Hardened get_my_test_schedules — all fields have safe defaults (score→float, state→'draft', attendance_status→'pending', test_type→getattr, cbt_launch always present).
  2. ✅ Hardened get_my_test_results — score/weighted_score→float, test_type fields→getattr with safe defaults.
  3. ✅ Unit tests: test_my_test_schedules_shape, test_my_test_schedules_safe_defaults_when_empty, test_my_test_results_shape, test_my_test_results_safe_defaults_when_empty.

Scope FE — Done

  1. ✅ Created spmbTests.service.js (dedicated service for test schedule + results).
  2. ✅ Created tests/unit/services/spmbTests.contract.spec.js — strict type assertions + safe-defaults fixtures (8 tests).
  3. ✅ Created composables/useSpmbTestSchedule.js — consolidated formatDate, formatTime, attendanceConfig, scheduleStateConfig.
  4. ✅ Removed backward-compat delegates from spmb.service.js.
  5. ✅ Updated SPMBTestSchedule.vue, SPMBCBTStart.vue, SPMBAdmissionDetail.vue — no optional chaining/fallbacks on contract-guaranteed fields.

Acceptance

  • [x] Response /api/SPMB/my/test-schedules schema documented in docs/qa/qa-test-contract-matrix.md.
  • [x] Vitest contract test green for both endpoints (8 tests).
  • [x] Odoo unit test passes (4 new tests).
  • [x] No FE consumer breaks.

Roll-out

  • 2 PR (BE first, then FE).
  • BE PR aman tanpa FE update (backward compatible — hanya menambah field).

Phase 2 — CBT Launch & Runner (FE+BE, paling besar)

Owner: FE+BE · Effort: L · Resolves: F-004, F-013, RK-1, RK-4

Status: ✅ Resolved (Apr 30, 2026). Sebagian besar scope sudah ter-implementasi oleh infrastruktur CBT generik (scola_cbt module). Cleanup pass menghapus dead code dan menambah contract test.

Scope BE — Resolved via scola_cbt module

Plan awal meminta endpoint /api/SPMB/my/cbt/* terpisah, namun infrastruktur generik sudah mencukupi:

Planned Endpoint Actual Implementation Status
POST /api/SPMB/my/cbt/start POST /scola_cbt/runner/start (supports exam_context=admission, skip token)
POST /api/SPMB/my/cbt/save-answer POST /scola_cbt/runner/answer + POST /scola_cbt/runner/sync
POST /api/SPMB/my/cbt/heartbeat POST /scola_cbt/runner/state (returns remaining_seconds)
POST /api/SPMB/my/cbt/submit POST /scola_cbt/runner/submit
Audit log per event POST /scola_cbt/runner/integrity
Server-side time clock remaining_seconds from attempt.start_time + duration
cbt_launch.enabled logic _build_cbt_launch_payload in spmb_api.py (hardened Phase 1)

Scope FE — Resolved via shared CBTRunner.vue

Planned Actual Implementation Status
SPMBCBTRunner.vue baru Re-use ExamManagement/Student/CBTRunner.vue (746 LOC) via route SPMBCBTRunner
Fullscreen + anti-cheat CBTRunner.vue: visibilitychange, blur, contextmenu, copy/paste, fullscreen API
Auto-save + heartbeat useExamState.js: autosave on change + periodic sync + state check every 10s
spmbCbt.service.js Re-use services/cbt/cbtRunnerService.js (start/save/sync/submit/integrity)
useCbtRunner.js composable Re-use composables/cbt/useExamState.js (461 LOC, full state machine)
Consolidate SPMBCBTStart.vue Now entry-list only, uses useSpmbTestSchedule composable, no duplication

Cleanup Pass (Apr 30, 2026)

  1. Removed dead launchCbt from spmbTests.service.js — called /api/SPMB/my/cbt/start which never existed on BE, and was never called by any component.
  2. Added cbtContext: "admission" to SPMBCBTRunner route meta — enables CBTRunner.vue to correctly redirect to SPMBCBTStart after submission.
  3. Consolidated SPMBCBTStart.vue — now uses useSpmbTestSchedule composable instead of direct formatTestDate import.
  4. Added spmbCbtFlow.contract.spec.js — 10 tests covering route wiring, cbt_launch shape sufficiency, service/composable interface, and dead code prevention.

QA Gates

  • ✅ Contract test: tests/unit/services/spmbCbtFlow.contract.spec.js (10 tests)
  • ✅ Route contract: tests/menu/spmbRoutesContract.spec.js (SPMBCBTRunner verified)
  • ⬜ Playwright E2E: tests/e2e/spmb/cbt-runner.spec.ts (deferred to staging integration)

Acceptance

  • [x] Route wiring: SPMBCBTRunner properly connected with admission context.
  • [x] Contract tests green for SPMB→CBT flow.
  • [x] No dead endpoints or unused service methods.
  • [ ] Pendaftar bisa kerjakan CBT end-to-end di staging (requires staging deployment).
  • [ ] Auto-save tidak hilang saat refresh (requires staging verification).

Roll-out

  • No new feature flag needed — re-uses existing scola_cbt infrastructure.
  • Single FE PR: cleanup + contract tests + route meta fix.

Phase 3 — Score Transparency & Manual Score Input (FE+BE)

Owner: FE+BE · Effort: M · Resolves: F-008, F-015, F-016, V-Gap-4, V-Gap-5

Status: 🟡 Pendaftar Score Transparency done (Apr 30, 2026). Admin manual scoring deferred.

Scope BE — Score Breakdown Done

  1. ✅ Added _build_score_breakdown(admission) helper in spmb_api.py:
  2. Mirrors _compute_ranking_score logic from admission.py
  3. Returns per-pathway breakdown: rapor, achievement, test[], distance_m, formula_text, final, rank_in_pathway
  4. Safe defaults: never None for required fields, nullable for pathway-specific fields
  5. Supports: domisili, prestasi, afirmasi, mutasi, and fallback pathways
  6. ✅ Wired into get_admission_detail response — score_breakdown populated only when state ∈ (admission, reject, done), otherwise null
  7. ✅ Unit tests: test_score_breakdown_prestasi_pathway, test_score_breakdown_domisili_pathway, test_score_breakdown_safe_defaults_no_pathway (3 new tests)

Deferred: - ⬜ Admin endpoint POST /api/SPMB/test-slots/<id>/score (manual V2/V3 scoring) - ⬜ Admin endpoint POST /api/SPMB/test-slots/<id>/rubric-score - ⬜ Model scola.admission.test.rubric & scola.admission.test.rubric.score

Scope FE — Score Transparency Done

  1. ✅ Created SPMBScoreBreakdown.vue component:
  2. Hero card with final score + rank
  3. Formula explanation banner
  4. Component cards grid: rapor (prestasi), achievement (prestasi), distance (domisili)
  5. Test components table with weighted scores
  6. Empty state for no data
  7. ✅ Added dynamic "Penilaian" tab in SPMBAdmissionDetail.vue:
  8. Tab appears only when score_breakdown is present (states: admission/reject/done)
  9. Passes breakdown and pathway_name to SPMBScoreBreakdown
  10. ✅ Contract test: spmbScoreBreakdown.contract.spec.js (5 tests: prestasi shape, domisili shape, empty defaults, formula_text, test component shape)

Deferred: - ⬜ Admin manual scoring page /admin/spmb/test/:scheduleId/manual-scoring - ⬜ Rubric form UI

Acceptance

  • [x] Pendaftar lihat breakdown skor + formula setelah pengumuman (via score_breakdown in detail response).
  • [x] Contract tests green (5 FE + 3 BE tests).
  • [ ] Admin bisa input skor manual (deferred to admin phase).

Roll-out

  • 2 PR (BE _build_score_breakdown + FE SPMBScoreBreakdown.vue).
  • Admin scoring deferred — can ship pendaftar transparency independently.

Phase 4 — Scheduled Announcement & Idempotency (FE+BE)

Owner: FE+BE · Effort: M · Resolves: F-009, F-010, F-012, F-021, RK-5, RK-9

Status: ✅ Resolved (Apr 30, 2026). All core items implemented. Enrollment deadline reminder deferred.

Scope BE — Done

  1. ✅ Added announcement_at (Datetime) + announcement_released (Boolean, default=False) to admission_register.py.
  2. ✅ State transition guard in admission.py::write():
  3. Blocks state changes to admission/reject/pending when announcement_at is in future and announcement_released is False.
  4. Raises UserError with scheduled date.
  5. Cron bypass via context['spmb_cron_release'].
  6. ✅ Cron cron_spmb_release_announcement in admission_register.py:
  7. Runs every 5 minutes via data/cron_announcement.xml.
  8. Finds registers with announcement_at <= now AND announcement_released = False.
  9. Sets announcement_released = True and sends portal notifications (accepted/rejected/waitlisted).
  10. ✅ Idempotency guard in confirm_enrollment (spmb_api.py):
  11. If admission already state='done' with student_id, returns success without re-enrolling.
  12. announcement_at + announcement_released included in both get_admission_detail and get_public_status responses.
  13. ✅ Pre-announcement masking in get_public_status: state masked to confirm, scores zeroed when pre-announcement.

Deferred: - ⬜ Enrollment deadline reminder cron (H-3, H-1, H-day) — can add in dedicated notification phase.

Scope FE — Done

  1. SPMBAnnouncementBanner.vue: Pre-announcement countdown with live timer, date formatting, state masking (accepted/rejected/pending banners hidden during countdown).
  2. SPMBStatusCheck.vue: Pre-announcement detection with generic "Pengumuman akan dirilis [tanggal]" message, result section hidden.
  3. SPMBAdmissionDetail.vue: Passes announcement-at + announcement-released props to SPMBAnnouncementBanner.
  4. ✅ Contract test: spmbAnnouncement.contract.spec.js (5 tests: field types, pre-announcement masking, post-announcement reveal, null defaults, isPreAnnouncement logic).

Acceptance

  • [x] Admin tidak bisa manual leak hasil sebelum announcement_at (UserError guard).
  • [x] Cron release berjalan ≤ 5 menit setelah announcement_at.
  • [x] Notif portal terkirim ke semua admission saat release (3 notification types).
  • [x] Double-click confirm_enrollment tidak membuat invoice/student dobel (idempotency).
  • [x] Contract tests green (5 FE + BE assertion in public_status test).

Roll-out

  • 2 PR (BE first — fields + cron + guard, FE follow — banner + status check).
  • Migration: announcement_released defaults False; existing registers without announcement_at are unaffected (treated as already released).

Phase 5 — Ranking Transparency, Audit, & Validasi Data (FE+BE)

Owner: FE+BE · Effort: M · Resolves: F-018, F-019, F-020, V-Gap-3, Reg-4

Status: ✅ Resolved (Apr 30, 2026). Core audit + validation items implemented. PDF watermark deferred.

Scope BE — Done

  1. ✅ Audit trail scola.admission.ranking.log model created:
  2. Fields: register_id, triggered_by, triggered_at, top_n_snapshot (JSON), pathway_summary.
  3. _create_log() method builds top-N snapshot per pathway.
  4. Hooked into action_calculate_all_ranks on op.admission.register.
  5. Security rules added for admin/staff groups.
  6. last_recalc_at + last_recalc_by + ranking_log_ids fields on op.admission.register.
  7. ✅ Fixed else-branch in _compute_ranking_score (F-018): fallback uses rapor×10 + achievement(max 500) + test_score instead of test-only.
  8. ✅ Updated _build_score_breakdown fallback formula text + rapor/achievement components.
  9. ✅ Submit validations via _validate_submit_data():
  10. kk_issue_date < 1 year → warning
  11. achievement.year < 3 years ago → hard error (blocks submit)
  12. rapor_grade_ids < 5 for prestasi pathway → warning
  13. ✅ Submit API returns warnings array in response.
  14. _register_to_dict includes last_recalc_at, last_recalc_by, announcement_at, announcement_released.

Deferred: - ⬜ Signed PDF metadata + watermark (can add when PDF export is prioritized). - ⬜ Admin audit-log page /admin/spmb/ranking/:registerId/audit-log (can add as standalone admin feature). - ⬜ tie_breaker_rule field (Phase 8 — jenjang-aware tie-breaker).

Scope FE — Done

  1. AdmissionApplicationList.vue: Confirmation modal on "Hitung Ranking" via showConfirmDialog pattern + last_recalc_at display with timestamp + user name.
  2. SPMBScoreBreakdown.vue: Already displays formula_text per pathway (serves as tie-breaker explanation).
  3. ✅ Contract test: spmbRankingAudit.contract.spec.js (7 tests: register recalc fields, ranking log shape, snapshot structure, submit warning/error types, unique codes).

Acceptance

  • [x] Setiap "Hitung Ranking" terlog di scola.admission.ranking.log.
  • [x] Pendaftar lihat formula penilaian di portal (via formula_text in score_breakdown).
  • [x] Validasi KK/achievement/rapor aktif (warning + error pada submit).
  • [x] Contract tests green (7 FE + BE assertions in public_status test).
  • [ ] PDF ranking ber-watermark + signer info (deferred).

Phase 6 — Waitlist, Refund & Rujukan (BE-heavy, FE light)

Owner: FE+BE · Effort: M · Resolves: F-010, Reg-8

Status: ✅ Resolved (Apr 30, 2026). Core waitlist, refund, and export features implemented. Also fixed 3 pre-existing BE test failures.

Pre-existing BE Test Fixes

  • FakeAdmissionModel.with_context() — missing method caused get_active_registers_public test to fail.
  • FakeAdmission.test_result_ids / rapor_grade_ids / first_name / middle_name / last_name — missing attrs returned False via __getattr__, broke _build_score_breakdown iteration.
  • scola_core.group_scola_pendaftar ref + token factory with name/_render_otp_email_body + mail.mail model — register_account test now matches current controller flow (direct mail.mail.create().send() instead of template.send_mail()).
  • Result: 13/13 BE tests pass.

Scope BE — Done

  1. waitlist_position computed field on op.admission — position among pending admissions in same register+pathway, ordered by ranking_score desc.
  2. action_promote_from_waitlist(pathway_id=None) on op.admission.register — promotes top pending candidate per pathway where quota available, with message_post audit trail.
  3. action_refund() on op.admission — creates credit note via _reverse_moves for rejected/cancelled admissions with paid invoice.
  4. ✅ API: POST /api/SPMB/registers/<id>/promote-waitlist — admin endpoint, returns promoted list.
  5. ✅ API: POST /api/SPMB/registers/<id>/export-rejected — admin endpoint, returns JSON with candidate referral data (NISN, NIK, school info, parent contact).
  6. waitlist_position exposed in get_admission_detail, get_public_status, get_enrollment_info API responses.

Scope FE — Done

  1. SPMBAnnouncementBanner.vue: Added waitlistPosition prop — shows position badge (#N) and conditional text for position > 0.
  2. SPMBAdmissionDetail.vue: Passes waitlist_position to banner.
  3. AdmissionApplicationList.vue: "Promote Cadangan" button with confirmation dialog + "Export Ditolak" button (JSON download).
  4. ✅ Service: promoteFromWaitlist() and exportRejectedCandidates() in spmbAdminAdmissions.service.js.
  5. ✅ Contract test: spmbWaitlistExport.contract.spec.js (9 tests: waitlist position, promote shape, export rejected shape).

Acceptance

  • [x] Pendaftar waitlist lihat posisi di portal (badge + text).
  • [x] Admin bisa promote cadangan dengan satu klik (per jalur atau semua).
  • [x] Export rujukan tersedia (JSON download).
  • [x] Refund credit note stub ready for rejected/cancelled admissions.
  • [x] 65 FE tests pass, 13 BE tests pass, 0 lint errors.

Phase 7 — Role Bridges & SPMB Committee (BE-heavy)

Owner: BE+FE · Effort: M · Resolves: R-001, R-002, R-003, R-004, R-005, Reg-7

Status: ✅ Resolved (May 1, 2026). CBT role bridges, SPMB committee group, DTKS stub, and FE capability wiring all done.

Scope BE — Done

  1. security/spmb_cbt_bridge.xml — bridges:
  2. admin_staffcbt_exam_creator + cbt_proctor
  3. head_admincbt_manager
  4. teachercbt_grader
  5. group_scola_spmb_committee — new group in scola_admission_security.xml with op_admission_admin + cbt_proctor + cbt_grader implied. Allows dedicated SPMB-only staff access.
  6. scola_cbt added to scola_admission depends in __manifest__.py.
  7. ✅ DTKS stub: dtks_verified, dtks_verified_at, dtks_verified_by fields on op.admission. Admin API endpoint POST /api/SPMB/admissions/<id>/verify-dtks for manual verify/unverify.

Scope FE — Done

  1. ✅ Capabilities added to admissionsCapabilities.js (canonical format):
  2. admissions.cbt_author.manage, admissions.cbt_proctor.manage, admissions.cbt_grade.manage, admissions.cbt.manage, admissions.dtks.manage
  3. ✅ Role mappings in operationalRoles.js:
  4. admin_staff: cbt_author.manage, cbt_proctor.manage, cbt_grade.manage, dtks.manage
  5. head_admin: cbt.manage (additional)
  6. teacher: cbt_grade.manage
  7. ✅ Route: /admin/spmb/proctor/:scheduleId?SPMBProctorDashboard (reuses ProctorDashboard.vue, gated by admissions.cbt_proctor.manage + scola_cbt featureFlag).

Acceptance

  • [x] Admin SPMB gets CBT exam_creator + proctor permissions via bridge XML.
  • [x] Proctor route /admin/spmb/proctor/:scheduleId wired with correct capability.
  • [x] SPMB Committee group available for granular assignment.
  • [x] DTKS manual verify endpoint available for afirmasi admissions.
  • [x] 479 FE tests pass (0 failures), 13 BE tests pass, 0 lint errors.

Phase 8 — Regulatory Compliance Permendikdasmen 3/2025 (BE-heavy)

Owner: BE+FE · Effort: L · Resolves: V-Gap-1, V-Gap-2, F-017, Reg-1, Reg-2, Reg-3, Reg-5, Reg-6

Scope BE

  1. Field selection_method di op.admission.register:
  2. selection_method (Selection: cbt_only, pbt_manual, interview, hybrid, admin_only, portfolio)
  3. domicile_method (Selection: admin_area, radius, custom)
  4. admin_area_ids (M2M res.country.state atau model baru scola.admin.area)
  5. is_paid_admission (Boolean, default False)
  6. tie_breaker_rule (Text, computed berdasarkan education_level × pathway)
  7. Tie-breaker jenjang-aware di action_calculate_ranks:
  8. SD domisili: (usia desc, distance asc, ranking_score desc)
  9. SMP domisili: (distance asc, usia desc, ranking_score desc)
  10. SMA domisili: (total_test_score desc, distance asc, usia desc, ranking_score desc)TKA wajib
  11. Afirmasi (semua jenjang): (distance asc, ranking_score desc)
  12. Prestasi: (ranking_score desc, distance asc)
  13. Validasi kuota minimum saat create/write op.admission.register:
  14. SD: domisili ≥70%, afirmasi ≥15%
  15. SMP: domisili ≥40%, afirmasi ≥20%, prestasi ≥25%
  16. SMA: domisili ≥30%, afirmasi ≥30%, prestasi ≥30%
  17. Mutasi semua jenjang: maks 5%
  18. Hanya warning (UserError vs Logger) — admin bisa override dengan context flag.
  19. Conditional invoice flow (Stage 6): kalau is_paid_admission = False, skip invoice creation & lewatkan langsung ke Stage 7.
  20. Endpoint publik GET /api/SPMB/public/quota-monitor?register_id= → response live quota per pathway.

Scope FE

  1. Form admin register: tambah field selection_method, domicile_method, admin_area_ids, is_paid_admission, announcement_at.
  2. SPMBAdmissionDetail.vue & SPMBTestSchedule.vue: hide tab "Tes Masuk" kalau register.selection_method ∈ {admin_only, portfolio} (V5/V6) — tampilkan info "Seleksi tidak memerlukan tes masuk" alih-alih "Belum ada jadwal" (F-017).
  3. Halaman publik /spmb/public/quota-monitor — live grid (auto-refresh 30s) sisa kuota per pathway × register.
  4. Pendaftar SPMBDaftarUlang.vue: kondisional invoice section (kalau is_paid_admission=False, skip).

Acceptance

  • [x] Admin set selection_method saat create register.
  • [x] Tie-breaker SMA otomatis pakai TKA.
  • [x] Public quota monitor live.
  • [x] Sekolah negeri (is_paid_admission=False) skip invoice flow tanpa error.
  • [x] FE pendaftar adaptif terhadap V5/V6 (no-test pathways).

Roll-out

  • 3 PR (BE schema, BE compute logic, FE UI).
  • Migration data: existing register tanpa selection_method → default cbt_only atau hybrid berdasarkan ada/tidaknya schedule.

Backlog (post-Phase 8)

ID Item Trigger
BL-1 Integrasi Dapodik (Reg-9) Saat Scola dipakai instansi pemda
BL-2 Terminologi PPDB → SPMB lazy migration (Reg-10) Anytime (cleanup PR)
BL-3 UTBK-style proctoring webcam + screen recording Tier 2 anti-cheat
BL-4 Multi-school SPMB cluster (1 platform, N sekolah) Demand pemda multi-satuan
BL-5 Mobile native CBT runner (lockdown browser) Kalau anti-cheat web tidak cukup

Effort Summary

Phase Owner Effort Priority
0 FE S P0 (must-have before next SPMB live)
1 FE+BE M P1
2 FE+BE L P1 (paralel dengan Phase 1)
3 FE+BE M P2
4 FE+BE M P2
5 FE+BE M P3
6 FE+BE M P3
7 BE+FE M P2 (paralel Phase 1-2)
8 BE+FE L P2 jika target pemda; P3 jika hanya swasta

Total: ~6-10 minggu (1 dev FE + 1 dev BE) untuk Phase 0-4. Phase 5-8 bisa dijadwalkan terpisah.


Verifikasi & Sign-off

Sebelum merge tiap phase: 1. PR description sebut phase ID + acceptance items. 2. Reviewer cross-check audit doc untuk gap yang di-resolve. 3. Update docs/qa/spmb-tests-cbt-ranking-audit.md § Gap Matrix → tandai "✅ resolved in Phase X (PR #YYY)". 4. Update docs/qa/qa-test-contract-matrix.md jika ada kontrak baru. 5. Smoke test E2E (Playwright) di staging sebelum prod.

Referensi