SDD-010 · Luật chọn câu, smoke test, độ phủ lab và vùng metadata
Một phần của SDD-010.
12. Luật chọn câu cho một Experience (SRC-141, SRC-143)
Vấn đề đã sửa
Đo ngày 2026-08-15: 251/252 bài có dưới 12 câu hỏi, trung bình 8,5 câu/bài. Luật cũ cho Luyện tập lấy LIMIT 12 nên learner thấy gần như toàn bộ kho ngay lượt đầu, rồi "Đo giữa"/"Đo cuối" lại lấy 6 câu từ đúng kho đó. Bài đo hoá ra chỉ đo xem learner có nhớ mấy câu vừa làm cách đó vài phút không — mà Mastery, Readiness và Learning Plan đều ăn con số này.
Cùng lúc, Học vượt chọn câu bằng ORDER BY role, difficulty DESC LIMIT 10 — tất định. Trượt rồi bấm "Làm lại từ đầu" là gặp lại đúng 10 câu cũ; thử vài lần là thuộc đáp án và "vượt" cả unit mà không hề vững.
Luật mới
Chính sách nằm ở workers/api/src/modules/learning/selection.ts — hàm thuần, 17 test, không trốn trong ORDER BY. Route kéo cả kho của node về (chỉ ~8,5 câu nên rất rẻ) rồi áp luật:
| Luật | Nội dung | Vì sao |
|---|---|---|
| Để dành câu đo | ~35% kho (tối thiểu 2 câu), ưu tiên câu discriminator/boundary, thiếu thì lấy câu khó nhất | Luyện tập không bao giờ được đụng vào phần này |
| Ngưỡng tách | Kho dưới 6 câu thì không để dành gì cả | Thà cho luyện đủ còn hơn cắt cả hai bên đều thiếu — nhưng khi đó API trả pool_warning nói thẳng "kết quả đo chỉ mang tính tham khảo" |
| Cỡ một lượt luyện | Đúng 10 câu nếu kho cho phép, chưa đủ thì lấy hết phần dùng được (xem §12b) | Nhịp cố định — learner biết trước bao giờ xong |
| Luyện thêm (vòng ≥2) | Ưu tiên câu chưa gặp trong 30 ngày, hết mới quay lại câu cũ | Luyện lại không phải là gặp lại y hệt |
| Bài đo / Học vượt | Lấy câu để dành trước, rồi câu chưa gặp, rồi câu đã gặp; luôn xáo theo session_id | Thi lại ra đề khác — bịt lỗ học thuộc 10 câu |
| Chờ giữa hai lần Học vượt | Trượt rồi phải chờ 3 tiếng (SKIP_COOLDOWN_MS) | Đề xáo vẫn chưa đủ khi kho mỏng: chờ để lần sau là trí nhớ thật, không phải trí nhớ ngắn hạn |
Xáo dùng seed tất định (LCG lấy bit cao) chứ không Math.random: cùng một session_id thì tái lập được y hệt khi cần điều tra sự cố.
Hệ quả phải theo dõi
Luật này chỉ chữa được phần chia; gốc vẫn là kho câu mỏng. docs/reference/item-coverage.md (sinh bằng node scripts/audit-items.mjs) liệt kê từng bài đang dưới ngưỡng và cần soạn thêm bao nhiêu câu.
12b. Luật 10 câu mỗi Learning Experience (SRC-153)
Mỗi Learning Experience nhắm đúng 10 câu. Lý do không phải là tối ưu sư phạm mà là quyết định bắt đầu: biết trước "10 câu là xong" thì đứa trẻ mới dám mở bài ra làm. Trước đây số câu nhảy 4, 6, 8 tuỳ kho mà learner không thấy lý do nào cả.
Hai điều không được phá để đạt đủ 10:
- Không lấn vào phần kho để dành cho bài đo. Lấn thì bài đo quay lại đo trí nhớ của phiên luyện vừa xong — đúng lỗi §12 vừa sửa.
- Không lặp câu trong cùng một phiên cho đủ số. Lặp là dạy đáp án, không dạy khái niệm.
Kho chưa đủ thì phiên ngắn hơn 10 và pool_warning nói thẳng con số thật ("Bài này hiện có 6 câu luyện (chuẩn là 10) — kho câu đang được soạn thêm"), hiển thị ngay đầu phiên.
Điều kiện kho: cần 16 câu/bài để có 10 câu luyện (16 − 6 câu để dành = 10). Hiện chuẩn là 10 câu/bài → chỉ ra 6 câu luyện. Toàn hệ 274 bài × 16 = 4.384 câu, đang có 2.314 → còn thiếu ~2.070 câu; 50 bài dưới 6 câu là ưu tiên trước (item-coverage).
Lấy mẫu câu "vì sao": 10 câu × 3 lớp đo là 30 lượt bấm — trẻ nhỏ ngán rồi bấm bừa, mà bấm bừa thì lớp đo thứ 3 thành dữ liệu rác. Nên chỉ hỏi "vì sao con chọn" ở 3 câu đầu · câu learner tự nhận là chưa chắc (tự tin ≤ 2) · câu đang gặp lại sau khi từng sai. Ba chỗ đó là nơi câu trả lời có giá trị nhất; các câu còn lại đi thẳng tới kết quả. Cắt khoảng 40% thao tác mà giữ nguyên tín hiệu hiệu chuẩn.
13. Smoke test giao diện (SRC-141)
Trang learn nằm sau đăng nhập Google nên trong suốt các đợt làm trước, không ai mở được giao diện để kiểm trước khi deploy — mọi kiểm chứng dừng ở "typecheck + build sạch", và hai lỗi ngày 2026-08-15 lọt ra production đều thuộc loại mở trang là thấy ngay.
apps/learn/e2e/ dựng bản build thật rồi chặn toàn bộ request /v1/** và trả dữ liệu giả (fixtures.ts) — không cần tài khoản, không đụng dữ liệu thật, chạy trong CI trước bước deploy. 5 luồng đang canh:
- Trang gốc hiện các school, nút Lighthouse nằm trên navbar (SRC-120).
- Breadcrumb đủ chặng và không còn thanh tab (SRC-129) —
.n12-tabphải bằng 0. - Phòng Lab dựng được đường đi Unit, không còn bộ đếm "0/9" (SRC-116).
- Làm bài: chọn đáp án → Tiếp → hiện lớp đo độ tự tin (SRC-069).
- Skip/Exit nằm bên phải và Skip đi thẳng câu sau, không chấm (SRC-117).
⚠️ Fixture phải giữ đúng hình dạng response thật. Đổi response của
/labs,/practice/start,/cockpitmà quên sửafixtures.tsthì test vẫn xanh còn production thì gãy.
14. Độ phủ lab: mỗi Unit đang hiện có đúng MỘT lab live (SRC-407, SRC-439)
REQ-LAB-08 đặt trần "tối đa một lab live mỗi Unit" (idx_labs_one_per_unit). Chương trình phủ lab (SRC-399 mở màn, kết ở SRC-439) đặt nốt sàn đối xứng: mọi Unit learner nhìn thấy phải có một lab — vì lab là chỗ chạm đầu tiên của Unit, Unit không lab là một ô mở ra không có gì để nghịch. Độ phủ hiện tại: 11/11 môn đang hiện, 0 Unit thiếu lab (Toán SRC-421 · Tiếng Anh SRC-422 · Vật lí SRC-427 · Sinh SRC-428 · Hoá SRC-429 · Ngữ văn SRC-430 · GenAI/AP/IELTS/SAT SRC-434 · Tin học SRC-436 · đợt chốt SRC-439).
Luật của chương trình, rút từ chính các đợt:
- Khuôn duy nhất là picker đã chốt ở SRC-388/390 (đoán bằng cách bấm → bấm từng bước xem điều gì lộ ra → câu kiểm), để mọi lab của một môn đọc lên như một bộ và không phải sửa LabPlayer. Nội dung nhắm quan niệm sai bền nhất của Unit; với môn ôn thi/kỹ năng (GenAI · AP · IELTS · SAT) lab nhắm cách chấm và bẫy hay gặp thay vì nội dung học thuật.
- Nhãn nút phải nói ra việc mà cú bấm làm (SRC-400, SRC-421): cấm nhãn đánh số kiểu "Lựa chọn 1"/"Bước 2" (20/39 lab đợt đầu mắc — learner bấm mù), cấm hai nút trùng nhãn, câu kiểm phải có đáp án đúng. Cổng canh nằm ngay trong
scripts/seed-b21-labs.mjs, chặn trước khi ghi — lab vào production dưới dạng dữ liệu, khuôn lệch là màn trắng mà không test nào đỏ. - Seed idempotent và không cướp chỗ (SRC-427):
ON CONFLICT DO UPDATEchứ khôngINSERT OR REPLACE(REPLACE trong SQLite là xoá-rồi-chèn, làm treo khoá ngoạilab_content.lab_id— lỗi chỉ lộ ở lần chạy THỨ HAI); Unit đã có lab live của người khác thì bỏ qua và in ra, script không có tư cách quyết định lab nào tốt hơn.
15. Vùng metadata của lab — ba người đọc, hai vùng khác nhau (SRC-511, SRC-532, SRC-544)
Chủ dự án 2026-08-22, sau khi mở một lab Toán: "xem xong, tôi không hiểu nó xuất hiện ra để làm gì." Đó là lỗi thật của thiết kế: mọi lab vào thẳng câu hỏi, không chỗ nào nói vì sao lab này tồn tại. Cột objective_vi có sẵn nhưng chỉ phủ 34/161 lab Toán, và nó trả lời "lab dạy gì" — không trả lời "vì sao có lab này".
15.1 Vùng "Về lab này" — cho learner và mentor (SRC-511)
Tách theo NGƯỜI ĐỌC, không gộp thành một đoạn văn. Migration 0068 thêm ba cột nội dung + một cột thời lượng:
| Cột | Trả lời câu hỏi của ai | Nội dung |
|---|---|---|
why_vi | mentor: vì sao có lab này | lý do lab tồn tại trong đường học |
notice_vi | learner: làm xong thì nhận ra gì | điều learner sẽ thấy |
mentor_vi | phụ huynh: nên hỏi lại con câu gì | một câu hỏi để nói chuyện sau lab |
minutes | cả ba | thời lượng dự kiến |
Gộp ba câu thành một đoạn thì ai đọc cũng phải lọc qua phần không dành cho mình, và người bỏ cuộc trước nhất là learner.
Vùng meta ĐÓNG SẴN, sau nút "Lab này để làm gì?". Learner tới lab để nghịch; mở sẵn thì chính cái lab bị đẩy xuống dưới mép màn hình — cùng nguyên tắc ngân sách chữ ở §10c.
15.2 Footer QC — dữ liệu thô cho người rà, đứng cuối cùng (SRC-532, SRC-544)
Chủ dự án 2026-08-24 yêu cầu: với riêng tài khoản dac2205@gmail.com, dưới cùng mỗi màn hiển thị mọi metadata có thể của Experience.
Vị trí là một luật, không phải tuỳ chọn: footer QcExpMeta đứng CUỐI CÙNG mỗi màn Experience. Nó là dữ liệu thô cho người rà, nên không được chen vào trải nghiệm của learner thường.
- Hai con số nổi lên đầu (thứ chủ dự án theo dõi thường xuyên): Lớp G/12 và Level B21 x/12.
- Quy đổi thang bắt buộc: D1 lưu level ở thang 0–100 (quy đổi
midpoint/12*100trongseed-b21-tree.mjs), nhưng Building 21 dùng thang 1–12, nên hiển thị phải đổi ngược về 1–12; giá trịlevel(0-100)vẫn in trong phần dump để đối chiếu. Hiện thang thô là bắt người đọc tự quy đổi mỗi lần nhìn. - Cổng quyền theo màn: màn practice dùng cờ
qcdo server cấp theo email (cùng cổng với vùng kiểm định SRC-454); màn lab không có phiên practice nên dùngownerdoApptruyền xuống. Chấp nhận được vì footer chỉ hiển thị dữ liệu công khai — quyền thật vẫn do server canh, client không tự nới được gì. - Bố cục: lưới 3 cột, mỗi trường một CÂU GIẢI NGHĨA đứng ngay dưới giá trị (SRC-544). Footer này là chỗ chủ dự án học cách hệ vận hành, không chỉ là chỗ dump. Bảng giải nghĩa phủ 20 trường — ví dụ:
strandkèm ghi chú SH/DS/HH là strand ẩn;blockslà số node đang bị chặn vì cần node này làm nền (số càng lớn thì vá chỗ này càng lời);mastery 0.00kèmnot_assessednghĩa là chưa đo, không phải kém. - Trường không có trong bảng giải nghĩa vẫn hiện giá trị thô — hơn là giấu. Giấu thì người rà không biết là hệ không có dữ liệu hay tài liệu chưa theo kịp.