Contest FLEXIBLE: mỗi học viên có đồng hồ đếm ngược riêng
Contest FLEXIBLE có một khoảng thời gian mở. Học viên bấm Start lúc nào cũng được trong khoảng đó, và đồng hồ của riêng họ bắt đầu chạy. Contest FIXED giữ nguyên như cũ.
SP-762 · 29/09/2026
Trạng thái ngày 29/09/2026
Giai đoạn
Đã phát hành. PR đã merge vào main ngày 28/09/2026 và nằm trong tag production v0.0.49 của ngày 28/09/2026. GitHub release của tag này đăng ngày 29/09/2026.
PR
#497 Cả tính năng nằm trong một PR: backend, editor của instructor, các trang của học viên, standings và bản dịch English, Vietnamese. Không có PR nào chồng lên nó.
Ticket
SP-762. Trạng thái Jira ngày 29/09/2026 là Testing.
Sau bản đầu
Trong lúc review, PR có thêm hai bản sửa. Một, trang contest bị mất đăng nhập sau khi reload hoặc mở link trực tiếp. Lỗi này cũng có trên main trước PR. Hai, các ô lịch trong editor bị lệch hàng khi một ô báo lỗi. Các ảnh bị ảnh hưởng đã chụp lại và hai phần của video đã quay lại.
Beta
Chưa có ghi nhận. Mục "Beta walkthrough" trong test plan của PR chưa được tick.
Ảnh và video
Toàn bộ ảnh và video đến từ một local stack với mock data. Tên và số liệu không có thật.
Vì sao cần tính năng này
Với contest FIXED, mọi học viên thi cùng một giờ. Contest kết thúc sau một khoảng cố định kể từ giờ bắt đầu, ví dụ 2 giờ. Ai vào trễ thì mất phần thời gian đó. Ai không rảnh đúng giờ thì không thi được.
Contest FLEXIBLE mở một khoảng thời gian dài hơn, ví dụ từ 06:00 đến 21:00. Mỗi học viên tự chọn lúc bấm Start và nhận đủ thời lượng, ví dụ 2 giờ, tính từ lúc đó.
Thay đổi chính, trong một hình
Bước 1
Chọn mode
Instructor chọn FIXED hoặc FLEXIBLE trong editor, rồi đặt lịch: giờ mở đăng ký, Starts, Ends và Duration.
→
Bước 2
Học viên bấm Start
Trong khoảng từ Starts đến Ends, học viên bấm Start. Trước đó, problems bị khóa.
→
Bước 3
Đồng hồ riêng chạy
Server lưu giờ Start. Time left giữ nguyên sau khi reload, đổi thiết bị hoặc đổi múi giờ.
→
Bước 4
Hết giờ cá nhân
Submit bị khóa. Standings mở khi contest đóng. Hòa điểm thì ai dùng ít thời gian hơn xếp trên.
Video demo
Khoảng 2 phút 10 giây, không có tiếng. Chấm xanh là con trỏ chuột. Video ghép từ ba lần quay, mỗi lần một tài khoản: instructor, học viên Alice và học viên Sarah.
Trong video
0:00 đến 0:07. Instructor xem danh sách contest. Chuột chỉ vào nhãn FLEXIBLE và FIXED.
0:08 đến 0:21. Instructor tạo contest mới. Ở card Schedule, mode Fixed có một dòng giải thích. Bấm Flexible thì ô Ends xuất hiện.
0:22 đến 0:31. Instructor mở contest Autumn Data Challenge 2026. Dòng "Timing is locked" hiện vì đã có học viên bấm Start.
0:33 đến 0:48. Alice xem danh sách contest có badge Flexible và Registered, rồi mở contest. Nút Start contest hiện và problems bị khóa.
0:49 đến 1:02. Alice bấm Start. Hộp thoại xác nhận hiện khoảng 3,5 giây, rồi Alice bấm Start now. Time left bắt đầu từ khoảng 01:59:5x và problems hiện ra.
1:02 đến 1:18. Alice mở problem ESC-50. Header có Time left và Submit đang bật. Khi quay lại contest, Time left là 01:59:38 ở giây thứ 76.
1:18 đến 1:29. Alice reload trang thật. Màn hình loading hiện ngắn, rồi trang trở lại như cũ. Alice vẫn đăng nhập, Time left chạy tiếp, 01:59:35 ở giây 79 và 01:59:27 ở giây 87, và problems vẫn hiện.
1:30 đến 1:38. Sarah mở danh sách contest, rồi contest Autumn Data Challenge 2026. Banner "Your time is up" hiện và chỉ còn một nút "Final standings".
1:38 đến 1:45. Sarah mở problem ESC-50. Submit bị khóa, tooltip ghi "Your time is up."
1:45 đến 2:10. Contest Summer Practice Round, trang Final standings. Chuột chỉ vào cột Time used: Linh 01:04:00 và An 01:21:00, cùng 170.00. Video dừng ở dòng giải thích cách xử lý hòa điểm.
Lưu ý. Số trên đồng hồ trong video và trong ảnh khác nhau, vì chụp ở các lần khác nhau. Alice bấm Start trong video lúc 01:43 sáng 28/09/2026 giờ VN. Ảnh trước Start, hộp thoại, sau Start và trang problem đến từ lần chụp đầu, khi Alice bấm Start lúc khoảng 01:17 đến 01:19. Hai ảnh sau reload và mở link trực tiếp, cùng phần 2 và 3 của video, đến từ lần chụp sau bản sửa đăng nhập. Phần editor của video, từ 0:00 đến 0:31, đến từ lần chụp đầu. Nó không có trạng thái lỗi, nên không cần quay lại sau bản sửa lịch.
Cách dùng
Dành cho instructor
Chọn Timing khi tạo hoặc sửa contest
Mở card Schedule. Chọn Fixed hoặc Flexible. Mỗi mode có một dòng giải thích ngay dưới tên.
Card Schedule khi tạo contest mới. Fixed đang chọn và Duration là 120 phút. Fixed: contest kết thúc 2h sau giờ Starts. Flexible: mỗi học viên có 2h kể từ lúc bấm Start.
Đặt lịch cho FLEXIBLE
Với Flexible, editor có thêm ô Ends. Điền Registration opens, Starts, Ends và Duration. Quy tắc: Registration opens không sau Starts, Starts trước Ends, và Duration phải nằm gọn trong khoảng từ Starts đến Ends. Khoảng đó gọi là window.
Contest nháp Winter Warm-up. Registration opens 01/10/2026 09:00 AM. Window từ 05/10/2026 08:00 AM đến 12:00 PM, Duration 90 phút.
Sửa nếu editor báo lỗi
Nếu Duration dài hơn window, editor báo lỗi ngay dưới ô Duration và không lưu. Các ô khác vẫn nằm trên cùng một hàng.
Winter Warm-up, window 4h từ 08:00 AM đến 12:00 PM, Duration nhập 300 phút. Lỗi đỏ: "The duration must fit between the start and end times."
Xem mode trong danh sách contest
Trong Contest Management, mode nằm dưới cột Window.
Năm contest. Winter Warm-up là DRAFT và FLEXIBLE. Weekly Sprint #14 là FIXED. Autumn Data Challenge 2026 là FLEXIBLE và có 5 người đăng ký. Summer Practice Round là FLEXIBLE và có 8 người đăng ký. SP-574 QA Private Contest là contest cũ, FIXED.
Theo dõi ai đã bấm Start
Mở contest, chọn tab Registered. Cột Started at cho biết giờ mỗi học viên bấm Start. Ô hiện "–" nghĩa là chưa Start.
Autumn Data Challenge 2026, 5 người đăng ký. Sarah Start lúc 27/09/2026 10:53 PM, Bob lúc 28/09/2026 12:43 AM, Minh lúc 28/09/2026 01:08 AM. Alice và Linh hiện "–", nghĩa là chưa Start.
Lịch bị khóa khi đã có học viên Start
Sau khi có học viên bấm Start, editor khóa mode, Starts, Ends và Duration, và hiện một dòng nhắc. Registration opens vẫn sửa được.
Dòng vàng: "Timing is locked because students have already started. You can still change when registration opens." Mode, Starts 27/09/2026 08:00 PM, Ends 28/09/2026 08:00 PM và Duration 120 bị mờ. Registration opens 25/09/2026 09:00 AM vẫn sửa được.
Dành cho học viên
Tìm contest trong danh sách
Contest FLEXIBLE có badge Flexible và một dòng "Any time between ..." thay cho giờ bắt đầu cố định.
Autumn Data Challenge 2026 có badge Flexible và Registered, thời lượng 2h, trạng thái Running, "Closes in 18:17:00". Weekly Sprint #14 không có badge Flexible vì là contest FIXED, còn "04:17:00 left". Summer Practice Round là Flexible, 1h 30m, đã Ended.
Mở contest và chưa bấm Start
Trang contest có nút Start contest. Problems bị khóa cho đến khi học viên bấm Start.
Alice trước khi Start. Nút Start contest ở góc phải, "Closes in 18:42:03", bốn ô ngày giờ, trong đó Your time là 2h. Khung Problems ghi "Problems are locked" và "Problems appear after you press Start."
Bấm Start và xác nhận
Hộp thoại nhắc rằng đồng hồ không dừng lại được. Đồng hồ cũng dừng khi contest đóng. Bấm Not yet để quay lại, hoặc Start now để bắt đầu.
Hộp thoại "Start the contest now?": "Your countdown of 2h starts now and cannot be paused. It also stops when the contest closes at 28/09/2026, 08:00 PM."
Làm bài với Time left
Sau khi Start, Time left đếm ngược và problems hiện ra. Nút Start contest biến mất.
Ngay sau khi Start. Time left 01:59:55, danh sách 2 problems: ESC-50 và Capital Bike Share Hourly Demand.
Khi hết giờ cá nhân
Trang contest báo hết giờ, dù contest vẫn Running. Học viên vẫn xem được problems nhưng không nộp được nữa. Standings chỉ mở khi contest đóng.
Sarah, khi contest vẫn Running. Banner "Your time is up": "The final standings appear after the contest closes at 28/09/2026, 08:00 PM." Danh sách problems vẫn hiện.
Xem standings khi contest đóng
Standings của contest FLEXIBLE có thêm cột Time used. Khi hai học viên hòa điểm, ai dùng ít thời gian hơn xếp trên.
Summer Practice Round. Dòng ghi chú: "Ties are broken by the time each student used, counted from when they pressed Start." Linh Tran và An Le cùng 170.00. Linh dùng 01:04:00 nên hạng 2, An dùng 01:21:00 nên hạng 3. Dòng của Sarah được tô màu và có nhãn YOU.
Đọc màn hình
Chỗ trên màn hình
Ý nghĩa
Ví dụ
Badge Running, Ended
Contest đang mở hoặc đã đóng.
Running
Badge Flexible
Contest thuộc mode FLEXIBLE. Contest FIXED không có badge này.
Flexible
Closes in
Thời gian còn lại đến khi contest đóng. Hiện trước khi học viên bấm Start.
18:42:03
Time left
Thời gian còn lại của riêng học viên. Hiện sau khi Start.
01:59:55
Start from, Closes
Đầu và cuối của window. Học viên chỉ bấm Start được trong khoảng này.
27/09/2026, 08:00 PM đến 28/09/2026, 08:00 PM
Your time
Thời lượng mỗi học viên nhận sau khi bấm Start.
2h
Started at
Cột trong tab Registered của instructor. Giờ học viên bấm Start. Ô hiện "–" là chưa Start.
27/09/2026, 10:53 PM
Time used
Cột trong standings. Thời gian học viên đã dùng, tính từ lúc Start.
01:04:00
Hạn chót cá nhân. Hạn chót của một học viên là mốc nào đến trước: giờ Start cộng thời lượng, hoặc giờ contest đóng. Ví dụ, nếu contest đóng lúc 08:00 PM, học viên bấm Start lúc 07:30 PM chỉ có 30 phút, không phải 2 giờ. Ví dụ này theo quy tắc trong PR và dòng trong hộp thoại Start, không có trong ảnh chụp.
Submit khi còn giờ và khi hết giờ
Còn giờ. Problem ESC-50 của Alice: Running, Time left 01:59:16 ở header, Submit bật. Bấm vào ảnh để mở cỡ đầy đủ.Hết giờ. Cùng problem ESC-50, tài khoản Sarah: Submit bị mờ, tooltip "Your time is up.", header không còn đồng hồ. Bấm vào ảnh để mở cỡ đầy đủ.
Badge: RunningEndedFlexibleRegistered
Giao diện tiếng Việt
Alice đang làm bài, giao diện tiếng Việt. Các nhãn là "Đang diễn ra", "Linh hoạt" và "Thời gian còn lại 01:57:51". Giờ hiển thị theo dạng 24 giờ. Dòng mô tả contest vẫn bằng tiếng Anh vì đó là nội dung của contest, không phải bản dịch. Bản dịch cần người bản ngữ đọc lại.
Vì sao tin được
Giờ Start nằm trên server. Time left giống nhau sau khi reload, đăng nhập lại, đổi thiết bị hoặc đổi múi giờ. Bộ ảnh chỉ kiểm tra reload và mở link trực tiếp.
Đồng hồ đi theo giờ server. Mỗi contest trả về giờ server. Đồng hồ tính từ giờ đó, nên đồng hồ trên máy học viên không quyết định Time left.
Server chặn nộp bài, không chỉ giao diện. Trước khi Start hoặc sau hạn chót cá nhân, server từ chối và trả 409. Xem ảnh API bên dưới.
Problems ẩn cho đến khi Start. Theo PR, dataset và leaderboard theo problem cũng ẩn. Khi window kết thúc, mọi thứ mở cho tất cả. Bộ ảnh chỉ có ảnh problems bị khóa.
Điểm private không lộ sớm. Quy tắc này không đổi. Điểm private ẩn đến giờ contest đóng, kể cả với học viên xong sớm. Không có ảnh cho mục này.
Lịch không đổi được khi đã có học viên Start. Server trả 409 nếu ai đó sửa Starts, Ends, Duration hoặc mode. Registration opens vẫn sửa được.
Bấm Start nhiều lần không tạo ra hai giờ Start. Theo PR, Start an toàn khi bấm lặp lại và khi nhiều yêu cầu đến cùng lúc. Bộ ảnh không thử trường hợp này.
Đã chạy test. Theo test plan của PR: backend lint, 1,885 unit test và 2,084 database test; frontend lint, 464 test và build; migration lên và xuống trên một database thử.
Kiểm tra bằng tay. Sarah bấm Start lúc 22:53 ngày 27/09/2026. Thời lượng 2h, nên 22:53 cộng 2h là 00:53 ngày 28/09/2026. Contest đóng lúc 20:00 ngày 28/09, muộn hơn, nên hạn chót là 00:53. Bảng database ghi đúng 00:53:00. Lúc chụp là 01:23:20, đã quá hạn, nên trạng thái là "time is up". Với standings: Linh dùng 3840 giây, chia 60 được 64 phút, tức 01:04:00. An dùng 4860 giây, tức 81 phút, hay 01:21:00. Cả hai khớp với cột Time used.
Đồng hồ sau khi reload và mở link trực tiếp
Alice mở contest bằng URL rồi reload. Vẫn đăng nhập, Time left 01:59:05. Alice bấm Start khoảng 55 giây trước đó.Trang problem ESC-50 mở thẳng bằng link, không đi qua ứng dụng. Running, Time left 01:58:59, Submit bật. Bấm vào ảnh để mở cỡ đầy đủ.
Dữ liệu lưu và phản hồi của API
Bảng registrations của Autumn Data Challenge 2026, đọc từ database lúc 01:23:20 ngày 28/09/2026 giờ VN. Sarah Start 22:53, hạn chót 00:53, time is up. Bob Start 00:43, hạn chót 02:43. Minh Start 01:08, hạn chót 03:08. Cả hai đang làm. Alice và Linh chưa Start.Bảng standings của Summer Practice Round. Cột elapsed_s là số giây dùng: Linh 3840, An 4860, cùng 170.00. Cột last_best_vn là giờ trên đồng hồ của lần nộp tốt nhất: Linh 15:09, An 09:51. Nếu xếp theo giờ trên đồng hồ, An sẽ trên Linh. Xếp theo thời gian đã dùng, Linh trên An.Bốn lệnh gọi API. Ba lệnh trả 409: nộp bài khi hết giờ ("Your contest time has ended"), nộp bài trước Start ("Start the contest first") và sửa Ends khi đã có học viên Start ("Contest timing is locked because students have already started"). Lệnh thứ tư sửa Registration opens và trả 200, timing_locked là true. Bấm vào ảnh để mở cỡ đầy đủ.
Giới hạn đã biết
Chưa kiểm tra trên beta. Ảnh và video chỉ cho thấy code chạy với mock data trên máy local. Mục "Beta walkthrough" trong test plan của PR chưa tick. Trước khi dùng cho một contest quan trọng, hãy chạy thử một contest FLEXIBLE với một instructor và một học viên.
Một phần dữ liệu demo được nạp bằng SQL. Bob, Minh, Linh, An và vài học viên khác không đăng nhập được trên stack demo, nên giờ Start và điểm của họ được nạp thẳng vào database. Bài nộp thật cần upload file và một judge chấm, nên điểm trong standings là dữ liệu nạp sẵn. Thứ hạng thì do API tính. Alice và Sarah đăng ký và bấm Start qua giao diện và API thật. Giờ Start của Sarah sau đó được lùi về quá khứ bằng SQL, để cô ấy hết giờ ngay lúc chụp. Ảnh chứng minh cách hiển thị và cách xếp hạng, không chứng minh việc chấm điểm.
Bộ ảnh chưa thử vài điều PR nêu. Đó là đăng nhập lại, đổi thiết bị, đổi múi giờ, bấm Start nhiều lần, ẩn dataset và leaderboard trước Start, và ẩn điểm private. Các mục này dựa vào mô tả của PR.
Nút "Final standings" hiện quá sớm. Khi contest còn Running, header vẫn có nút này, như trong ảnh của Alice và Sarah, nhưng trang đó chỉ dùng được sau khi contest đóng. Lỗi nhỏ, đã có trước PR này và chưa sửa. Tài liệu capture không nêu ticket cho lỗi này. Cần ticket nếu muốn sửa.
Hướng dẫn "How to participate" chỉ nói về contest FIXED. Nút này vẫn có trên trang contest FLEXIBLE, nên học viên có thể hiểu sai cách tính giờ. Hãy cập nhật nội dung hướng dẫn, hoặc giải thích riêng cho học viên.
Bản dịch tiếng Việt cần người bản ngữ đọc lại. PR ghi rõ điều này.
Lịch bị khóa sau khi có học viên Start. Đây là quyết định thiết kế. Sau đó chỉ sửa được Registration opens. Hãy kiểm tra kỹ lịch trước khi học viên có thể Start.
Lỗi hiển thị nhỏ. Trong editor tiếng Anh, Baseline và Target của problem hiện dấu phẩy thập phân, ví dụ "0,5" và "0,95". Có thể đã có từ trước PR này. Không ảnh hưởng đến tính giờ.
Các quyết định
Câu hỏi
Lựa chọn
Có những mode nào?
Hai mode: FIXED và FLEXIBLE. Mặc định là FIXED, và contest FIXED chạy đúng như trước.
Hạn chót cá nhân tính thế nào?
Mốc nào đến trước: giờ Start cộng thời lượng, hoặc giờ contest đóng.
Nộp bài trước Start hoặc sau hạn chót?
Server từ chối và trả 409.
Khi nào học viên thấy problems?
Sau khi bấm Start. Khi window kết thúc, mọi thứ mở cho tất cả.
Điểm private?
Vẫn ẩn đến giờ contest đóng, kể cả với học viên xong sớm.
Hòa điểm trong contest FLEXIBLE?
Ai dùng ít thời gian hơn, tính từ lúc Start, xếp trên. Standings có thêm cột Time used.
Sửa lịch sau khi có học viên Start?
Không. Starts, Ends, Duration và mode bị khóa. Registration opens vẫn sửa được.
Đồng hồ theo giờ nào?
Giờ server, không phải giờ trên máy học viên.
Bấm Start trên contest đang ẩn, nháp hoặc đã archive?
Trả 404, giống các endpoint khác của học viên, để không lộ rằng contest có tồn tại.
Ngôn ngữ?
English và Vietnamese.
Cập nhật database?
Thêm cột mới với giá trị mặc định FIXED. Theo PR, bản app cũ vẫn chạy được.
Việc cần làm sau phát hành
Đã xong. PR #497 merge vào main ngày 28/09/2026. PR #491 (SP-763) cũng thêm migration và sửa cùng khu vực contest. PR đó merge sau #497, và cả hai cùng nằm trong v0.0.49.
Xác nhận migration của PR đã chạy trên production. Tôi chưa kiểm tra bước này.
Chạy thử một contest FLEXIBLE thật: instructor tạo contest, một học viên bấm Start rồi reload, và một học viên hết giờ. Đây là mục cuối chưa tick trong test plan của PR.
Cập nhật hướng dẫn "How to participate" cho FLEXIBLE. Nhờ một người bản ngữ đọc lại bản tiếng Việt.
Đưa SP-762 từ Testing sang Done. Code đã nằm trong tag production v0.0.49.