SDD-038 · Kho bài Đọc và Viết
Một phần của SDD-038 Cổng IELTS. Phần đầu là đặc tả đang áp dụng, mỗi mục giữ số § cũ; diễn biến và lý do các vòng quyết định nằm ở mục Lịch sử quyết định cuối trang.
25. Dap an thoi luon dung dau, va file JSON noi dung bi go han (SRC-882, 2026-09-19)
25a. Sap lai thu tu lua chon cua 128 cau
16 bai nghe soan dau tien co 123 tren 128 cau dat dap an dung o lua chon thu nhat (muc 23). Hai dot sau da rai san luc sinh seed; dot dau thi chua. Nay ca 128 cau duoc sap lai: 33 / 31 / 28 / 31.
Cai gia da chap nhan, noi ra cho ro: bang ielts_practice_attempts ghi CHI SO lua chon chu khong ghi noi dung, nen nhung lan tra loi truoc ngay hom nay khong con doi chieu duoc voi de. Diem dung/sai da luu van dung; chi rieng viec "learner da chon phuong an nao" la mat nghia. Chu du an quyet dinh danh doi dieu do de de thoi du doan duoc.
Sap TAT DINH chu khong xoay. Xoay di k cho phu thuoc vao thu tu hien tai, nen chay lai lan hai la xoay tiep - ma workflow seed khong co nut hoan tac va chay nham hai lan la chuyen se xay ra (QG-004). Thu tu moi vi the suy tu chinh NOI DUNG tung lua chon: sap theo khoa bam cua chuoi lua chon cong ma cau. Chay bao nhieu lan cung ra dung mot thu tu.
Doc tu D1, khong doc tu file. scripts/gen-listening-answer-order.mjs lay bai qua duong doc cong khai cua API. Mot ban sao trong repo se lech voi ban dang chay dung vao ngay co nguoi sua mot cau o man admin, va khi ay no se lang le ghi de ban sua ay.
25b. File JSON noi dung da go han
Da go: 12 file JSON, va script sinh seed cua hai dot ay - no chi chay duoc khi con JSON nen khong con ly do ton tai. Ca hai deu tim lai duoc trong lich su git neu can doi chieu.
Giu lai hai file .sql da nap (seed-ielts-listening-batch2.sql, batch3.sql) va file sap lai thu tu. Chung khong phai ban thu hai cua noi dung ma la SO GHI viec da chay: luat seed-data noi ro file nap phai nam trong repo, vi "chay roi ma khong co trong repo thi lan sau khong ai dung lai duoc". Chung khong bao gio duoc doc lai de dung lai noi dung.
Soan dot sau o dau: man admin (apps/admin, muc 18), hoac mot script doc tu D1 roi sinh SQL nhu gen-listening-answer-order.mjs. Khong soan trong repo nua.
27. Bai doc cung thoi dan dap an ve lua chon dau (SRC-899, 2026-09-20)
Chi dao chu du an: "sua luon 16 bai cu di" - noi sau khi 16 bai nghe da sua xong, nen hieu la sua not phan con lai. Do lai thi lo ra con so: 52 bai doc co 1336 tren 1422 cau mot dap an dat dap an dung o lua chon thu nhat, tuc 94 phan tram - te hon bai nghe nhieu (123/128 nhung chi tren 128 cau). Sau khi sap lai: 375 / 361 / 349 / 337.
Phep sap nam TRONG SQL, khac lan sua bai nghe. Lan truoc mot script Node doc bai qua API roi sinh san tung cau UPDATE. Nay khong lam the duoc: duong doc noi dung da vao sau cong dang nhap (SRC-885), va token wrangler tren may soan da het han - may soan khong con duong nao doc D1.
Doi lai duoc mot thu dang gia hon ca su tien loi: cau UPDATE sinh san mang theo CA NOI DUNG bon lua chon, nen neu co nguoi vua sua mot cau o man admin thi lan nap ay ghi de ban sua. Ban SQL chi dao THU TU cua chinh nhung lua chon dang co, nen noi dung moi nhat duoc giu nguyen du no la ban nao. Tu nay moi lan sua hang loat noi dung nen theo loi ay.
Van TAT DINH chu khong xoay, cung ly do voi muc 25a: khoa sap suy tu noi dung tung lua chon (length(value) * 131 + unicode(value), lay du 1000), nen chay lai bao nhieu lan cung ra mot thu tu. Khoa nay khong phai ham bam tot, va khong can tot - viec duy nhat no phai lam la khong tuong quan voi thu tu nguoi soan go vao.
Kiem TRUOC khi chay tren production. Cau SQL nay phuc tap hon moi file seed truoc do, nen no duoc chay thu tren SQLite cuc bo voi noi dung 52 bai doc THAT lay tu lich su git (commit 87f4c53e da xoa cac file JSON, nhung git show van doc duoc). Bon bat bien duoc kiem: dap an van tro vao dung chuoi lua chon cu; tap lua chon khong mat khong them; chay lan hai khong doi gi; va cau ngoai pham vi khong bi dung toi.
Pham vi: chi cau trac nghiem MOT dap an. Cau nhieu dap an (dang "Which THREE statements") dung ngoai vi dao chung can anh xa nhieu chi so cung luc, va chung chi chiem vai chuc cau.
Cai gia giong muc 25a va da duoc chap nhan lan thu hai: ielts_practice_attempts ghi CHI SO lua chon, nen nhung lan tra loi bai doc truoc hom nay khong con doi chieu duoc voi de.
32. Sub-skills của phần Viết, xếp theo bốn tiêu chí chấm (SRC-896, 2026-09-20)
| Cụm (tiêu chí chấm) | Sub-skills |
|---|---|
| Task Response / Task Achievement | Reading the task · Holding a position · Developing ideas · Supporting ideas · Addressing all parts · Describing key features |
| Coherence & Cohesion | Building a paragraph · Linking ideas · Logical progression · Paragraph organisation · Referencing · Cohesive devices |
| Lexical Resource | Choosing words · Vocabulary range · Precision · Appropriacy · Word formation · Spelling |
| Grammatical Range & Accuracy | Varying sentences · Sentence structures · Complex sentences · Grammatical accuracy · Punctuation |
a. Cụm là một cột trong CSDL, không phải một bảng tra trong mã
ielts_micro_skills.cluster (migration 0264). Để phép chia này trong một hằng số ở app thì cùng một danh sách sống ở hai nơi, và cái ở màn admin, nơi nội dung được sửa thật, sẽ là cái không có.
Cột rỗng với reading, listening, speaking: ba kỹ năng ấy không được chấm theo tiêu chí, nên gán cụm cho chúng là bịa ra một tầng không có thật. Thẻ Mastery gom nhóm khi cột có chữ và xếp phẳng khi không, nên vẫn chỉ có một bản thẻ cho cả bốn kỹ năng.
Vì sao chia cụm lại đáng làm: một danh sách phẳng hai mươi ba dòng đọc ra "hai mươi ba thứ phải giỏi". Cùng danh sách ấy cắt theo cụm thì đọc ra "bốn tiêu chí được chấm, mỗi tiêu chí gồm mấy thao tác" — đúng hình dạng thật của bài thi, và là câu trả lời cho câu learner hỏi ngay sau khi nhận điểm: mất điểm ở tiêu chí nào.
b. Bốn mục cũ nằm ngoài danh sách mới
describe-datađổi TÊN thành Describing key features (chữ của chính bản mô tả thang điểm Task 1), giữ nguyên mã. Mã là đoạn URL và là khoá đếm mastery: đổi mã đi thì link learner đã lưu gãy, và mọi câu họ đã làm dưới mã cũ biến khỏi thẻ.introduction,conclusion,concederút xuốngdraft(chỉ đạo chủ dự án 2026-09-20, khi được hỏi thẳng). Không xoá: lý thuyết và bài tập đã soạn xong vẫn nằm nguyên trong CSDL và bật lại ở màn admin bằng một cú bấm. Xoá dòng thì mất cả nội dung lẫn bằng chứng learner đã làm dưới mã ấy, mà cái được chỉ là vài dòng sạch hơn.
c. Mười sáu mục mới sinh ra đã có chỗ học và chỗ luyện, và đủ thang bốn chặng
Mỗi mục mới nạp đủ năm phần lý thuyết (là gì · các bước · ví dụ có giải · bẫy) và bài tập theo đúng thang bốn chặng của SRC-821:
| Chặng | Nội dung | Số câu mỗi năng lực |
|---|---|---|
| 1 | tiếng Việt, tình huống thẳng thớm | 2 |
| 2 | tiếng Việt, mỗi câu mang tên kỹ thuật | 2 |
| 3 | tiếng Anh, đoạn ngắn | 4 |
| 4 | tiếng Anh, đoạn dài gần đề thật | 2 |
Migration 0260 nạp chặng 3; 0262 nạp ba chặng còn lại. Hai lần vì lần đầu dừng ở chặng 3 và cổng microSkills/routes.test.ts đỏ ngay: thang bốn chặng là ràng buộc chứ không phải gợi ý, và với phần Viết nó còn đúng hơn phần Đọc, vì "triển khai một ý" hay "mạch ý tiến lên" là thao tác của người viết bất kỳ thứ tiếng nào. Gặp nó lần đầu bằng tiếng Anh là trộn hai việc khó vào một.
Một ngoại lệ có chủ ý: spelling, word-formation, punctuation giữ ngữ liệu tiếng Anh ở mọi chặng, vì thứ được luyện ở đó chính là mặt chữ tiếng Anh; phần hỏi và phần giải thích của chặng 1-2 vẫn tiếng Việt.
Một dòng hiện trên thẻ mastery mà bấm vào chỉ thấy No exercises published yet là lời mời dẫn vào phòng trống: learner bấm đúng chỗ họ yếu nhất rồi không có gì làm.
d. Thang năm bậc cho cả mười sáu mục (migration 0266)
Chỉ đạo chủ dự án 2026-09-20, vòng ba: "soạn tiếp thang năm bậc cho 16 mục mới". Mỗi mục nay có 40 câu, đúng khuôn mà scripts/check-micro-content.mjs gác:
| Bậc | Thành phần | Số câu |
|---|---|---|
| 1 | tiếng Việt, tình huống thẳng thớm | 5 |
| 2 | 5 cặp Việt-Anh, đoạn 30-62 từ | 10 |
| 3 | tiếng Anh, đoạn ngắn | 10 |
| 4 | 5 cặp Việt-Anh, đoạn 65-110 từ | 10 |
| 5 | tiếng Anh, đoạn 110-200 từ như đề thi | 5 |
Tổng: 640 câu, 80 bài tập. Nội dung soạn trong content/ielts-micro/writing/ (một file một năng lực, theo luật nhiều-phiên-song-song) và sinh ra SQL bằng scripts/micro-seed.mjs, cùng đường với phần Reading.
Một sửa trong chính trình sinh đi kèm bản này: mặc định task_type_id là <skill>.multiple-choice chỉ còn áp cho Reading. ielts_task_types hiện chỉ có bộ dạng của Reading (0264 nói rõ vì sao), và cột ấy là khoá ngoại, nên gán writing.multiple-choice không phải một nhãn sai mà là một dòng không nạp được: cả migration dừng ở lỗi FOREIGN KEY. Kỹ năng chưa có bộ dạng thì để NULL, đúng như cột đã cho phép từ đầu.
e. Còn mở
- Kho bài Writing chưa gắn thẻ sub-skill ở mức câu hỏi như Reading và Listening, nên mastery của phần Viết vẫn đếm từ bài tập năng lực là chính.
- Phần Viết chưa có chiều thứ hai theo dạng đề như Reading (SRC-895). Bốn tiêu chí chấm là một trục khác với dạng đề, nên nếu sau này thêm trục ấy thì nó là Task 1 và Task 2, không phải các dạng câu hỏi trắc nghiệm.
35. Reading có HAI chiều, và chúng cắt cùng một kho bài (SRC-895, 2026-09-20)
35a. Vì sao MỘT kho hai nhãn, không phải hai kho
Sơ đồ chủ dự án gửi đặt SKILLS và TASK TYPES cạnh nhau như hai nhánh song song, và cách đọc thẳng nhất là dựng cho mỗi nhánh một kho bài riêng. Chủ dự án đã bác cách ấy (2026-09-20), và lý do nằm ở chỗ hai chiều này không độc lập với nhau:
- Mọi bài Matching Headings đều đang luyện Main Idea. Đó là định nghĩa của dạng ấy, không phải một sự trùng hợp. Tách hai kho là soạn lại cùng một thao tác tư duy hai lần, rồi đo nó bằng hai cái thước không cộng được với nhau.
- Bảng "Where am I strong?" cộng theo năng lực. Một kho bài tập thứ hai không mang năng lực thì mỗi lần learner luyện Matching Headings là một lần thanh Main Idea đứng yên, trong khi learner biết rõ mình vừa luyện đúng cái đó. Sai lệch ấy không ai đọc được từ màn hình.
Nên một bài tập mang đúng một năng lực và đúng một dạng câu hỏi. Lọc theo chiều nào cũng cắt trên cùng một kho, và mọi lần làm bài đều rơi vào đúng một ô của cả hai thước đo.
Hệ quả trên giao diện, và nó là thứ phải giữ: thẻ bài ở trang năng lực mang nhãn DẠNG, thẻ bài ở trang dạng mang nhãn NĂNG LỰC. Chiều nào cũng bày ra nhãn của chiều kia trước khi learner bấm. Bỏ nhãn chéo ấy đi là hai chiều thành hai thế giới rời nhau, và learner sẽ làm lại đúng một bài tưởng là hai.
35b. Bài tập vẫn chỉ có MỘT địa chỉ
Hai nhánh URL cho hai lối vào — /ielts/skills/reading/<năng lực> và /ielts/tasks/reading/<dạng> — vì mỗi lối vào là chỗ learner sẽ quay lại và sẽ gửi link cho nhau.
Nhưng không có /ielts/tasks/reading/matching-headings/levels/2/1. Thẻ ở trang dạng trỏ thẳng về địa chỉ dưới nhánh skills. Cho một bài hai URL là để sổ hoạt động ghi hai ref khác nhau cho cùng một lần làm bài, rồi mọi con số đếm lượt sai đi một cách không ai đọc ra được từ màn hình. taskTypeRoutes.test.ts khoá quyết định này lại bằng một ca kiểm riêng.
35c. Mười hai dạng, và vì sao chúng chưa có kiểu tương tác thật
Bảng ielts_task_types (migration 0264) giữ mười hai dạng của Reading theo đúng danh sách chủ dự án gửi. Tên tiếng Anh là tên chính, chặt hơn luật chung của sân luyện một bậc: đây là dòng chữ in nguyên văn trên đề thi, và một learner chỉ quen "Ghép tiêu đề" thì lúc cầm đề sẽ phải dịch ngược trong khi đang tính giờ.
Bảy trong mười hai dạng — Matching Headings, bốn dạng Completion, Short Answer, Matching Sentence Endings — trong phòng thi đòi learner nhập chữ hoặc ghép cặp, chứ không chọn trong bốn ô. Đợt này chưa dựng hai kiểu tương tác ấy (quyết định chủ dự án 2026-09-20: "trắc nghiệm trước, kiểu thật ở đợt sau").
Điều đó không làm các bài mới thành trắc nghiệm chung chung: ngữ liệu và đề bài vẫn mang đúng hình dạng của dạng ấy — TFNG chỉ có ba lựa chọn True/False/Not Given, Matching Information hỏi "đoạn nào", Sentence Completion in sẵn giới hạn số từ và các lựa chọn đều là chữ lấy nguyên văn từ bài. Thứ còn thiếu là ô nhập, không phải thao tác tư duy. Việc còn mở: mở rộng lược đồ cho đáp án nhập chữ và đáp án ghép cặp, cùng phần chấm điểm của chúng.
35d. Evidence đổi tên thành Logical Argument, giữ nguyên mã
Sơ đồ chủ dự án ghi "Logical Argument" ở chỗ hệ đang chạy "Evidence". Đổi nhãn, giữ nguyên mã evidence:
learn.nemo12.com/ielts/skills/reading/evidenceđã là URL công khai, và luật CLAUDE.md là link người dùng đã lưu không được gãy.- Mã còn là tiền tố của id bài tập (
reading.evidence.l2.1) và củareftrong sổ hoạt động (exercise:reading.evidence.l2.1). Đổi mã là phải viết lại cả lịch sử làm bài của learner, và một lần viết lại như thế hỏng giữa chừng thì không có đường lùi.
Tên Việt đổi theo: "Bằng chứng" nói về thứ learner tìm, còn "Lập luận" nói về thứ learner xét — và chính cái sau mới là điều năng lực này dạy.
35e. Nội dung soạn theo trục nào thì xếp theo trục ấy
content/ielts-micro/reading/<năng lực>.json xoay theo năng lực; content/ielts-micro/reading-tasks/<dạng>.json xoay theo dạng, mỗi bài khai micro của mình. Hai thư mục cùng đổ vào một bảng qua scripts/micro-seed.mjs.
Hai cách xếp vì cách xếp phải theo trục mà người soạn đang nghĩ: soạn năm bài Matching Headings một lượt thì chúng phải nằm cạnh nhau để đọc được ra là năm bài ấy có lặp ý nhau không. Bắt xé chúng ra năm file năng lực khác nhau là bỏ đúng phép kiểm ấy.
Đợt này thêm 55 bài · 275 câu cho 11 dạng chưa có (Multiple Choice đã được 100 bài cũ phủ). Kết quả hai chiều: mười năng lực có 12–23 bài mỗi cái, mười hai dạng có ít nhất 5 bài mỗi cái — vượt mức sàn chủ dự án đặt ra cho cả hai chiều.
35f. Bốn chỗ hỏng im lặng mà bộ test gác
Cả bốn đều biểu hiện bằng một màn hình trông bình thường:
- Đường mới không thừa hưởng cổng đăng nhập. Cổng SRC-885 gác theo mẫu
micro-skills/*, màtask-typeskhông khớp mẫu ấy — hai đường mới mở toang vào đúng kho nội dung vừa khoá. Bộ test bắt được chỗ này trước khi nó kịp ra production. LEFT JOINsang bảng dạng, khôngINNER. Bài chưa gán dạng phải vẫn hiện; mộtINNER JOINlàm cả trang Listening trống trơn vào đúng ngày cột mới thêm mà nội dung chưa kịp gán.- Dạng rỗng phải trả
exercises: 0, không biến mất. Ô xám nói với learner "có chỗ này, chưa tới lượt"; một dạng vắng mặt thì không nói gì, và bản đồ kỳ thi thủng một lỗ. Vì thế số đếm dùng truy vấn con tương quan chứ không dùngJOIN ... GROUP BY, vốn sẽ lặng lẽ đánh rơi đúng những dòng ấy. - Hai chiều phải soi vào cùng một kho. Nếu một bài đếm được ở chiều này mà mất ở chiều kia thì mô hình một-kho đã hỏng, và hỏng theo cách chỉ lộ ra khi ai đó ngồi đếm tay.
38. Gắn thẻ năng lực nhỏ cho bài viết, ở mức câu hỏi (SRC-913, 2026-09-20)
a. Vì sao phần Viết không gắn thẻ được theo cách của phần Đọc
Với Reading và Listening, mỗi câu hỏi mang sẵn một thẻ năng lực và learner trả lời đúng hoặc sai, nên bằng chứng rơi ra từ chính phép chấm. Một đề Viết thì không có câu hỏi nào bên trong: nó có một bài văn, và bài văn ấy được chấm bằng bốn band tiêu chí. Dừng ở đó thì thẻ "Where am I strong?" của phần Viết mãi mãi chỉ đếm được bài tập năng lực, trong khi thứ learner bỏ nhiều công nhất lại là những bài viết thật.
Nên chỗ gắn thẻ là lúc chấm, không phải lúc soạn đề: một lượt đọc riêng trả lời câu hỏi "bài này cho thấy gì về từng thao tác viết", và kết quả đi vào đúng bảng mà hai kỹ năng kia đang dùng (ielts_subskill_results).
b. Một prompt riêng, không gộp vào hai prompt đã có
ielts.writing-subskill-tag@1. Hai prompt sẵn có viết cho hai người đọc (mentor và learner) và cả hai trả về lời văn; thứ cần ở đây là một bảng dữ liệu. Gộp là buộc một prompt làm hai việc, và tới lượt sửa thứ hai thì phần dữ liệu sẽ bị lời văn lấn át — bài học đã trả giá bốn lần trong PROMPT_HISTORY. Tách riêng còn cho phép phần chấm hỏng mà phần thẻ vẫn chạy, và ngược lại.
Danh mục 23 năng lực là ĐẦU VÀO, đọc từ D1, không viết cứng trong prompt: nội dung sửa được ở màn admin (SRC-896), nên một bản sao trong mã là một nguồn sự thật thứ hai mà không ai nhớ cập nhật.
c. Trần tám thẻ mỗi bài, và đó là ràng buộc chứ không phải con số cho đẹp
MIN_ITEMS của thang mastery là 8. Một bài viết được phép gắn hai mươi ba thẻ sẽ tự mình đẩy mọi năng lực qua ngưỡng xếp bậc chỉ sau ba lần nộp, và bậc ấy dựng trên phán đoán của model về một bài duy nhất. Prompt vì thế bị cấm nói về năng lực mà đề bài không đòi tới, và đường gọi loại bỏ mã lạ, mã trùng rồi cắt trần trước khi ghi (writingTags.ts, có test riêng).
d. Đếm chung một thanh với bài tập (chỉ đạo chủ dự án 2026-09-20)
Bằng chứng ở đây do model phán đoán, còn bằng chứng của bài tập là đúng sai khách quan. Chủ dự án chọn cộng chung: cùng một thao tác thì cùng một thước, và learner không phải cộng nhẩm hai con số trên một thẻ vốn đã có hai mươi ba dòng. Cột ref giữ nguồn (writing:<production_id>) để khi một con số trông sai thì còn truy ngược được về đúng bài đã sinh ra nó.
e. Cả hai đường nộp đều gắn thẻ
Bài chẩn đoán (ielts/routes.ts#finishScoring) và bài thang luyện (ieltsSkills/routes.ts#finishLadder) đều là bài viết thật. Chỉ gắn thẻ một đường là dựng lại đúng chỗ lệch mà SRC-849 đã phải đi sửa một lần rồi. Lỗi gắn thẻ bị nuốt ở cả hai đường: learner đã nộp bài và đã có nhận xét trong tay, một lượt gắn thẻ hỏng là việc của hệ thống chứ không phải của họ.
f. Phần Nói dùng chung đúng bộ máy ấy (SRC-914)
Hai kỹ năng sản sinh chỉ khác nhau ở đầu vào (chữ hay bản phiên âm) và ở danh mục năng lực; phép lọc, trần tám thẻ và cách dựng dòng bằng chứng thì giống hệt. Nên chúng đi chung một đường: prompt ielts.writing-subskill-tag@1 nghỉ hưu sau vài giờ, thay bằng ielts.production-subskill-tag@1 có nhánh theo kỹ năng, cùng cách mà ielts.production-feedback đã làm. Dựng một prompt thứ hai cho Nói thì hai bản sẽ lệch nhau ngay lượt sửa sau, và lệch im lặng.
Bản phiên âm giới hạn thẻ nào nói được, và giới hạn ấy nằm trong prompt. Phát âm, trọng âm, ngữ điệu đã bị lọc mất khi tiếng thành chữ, nên cấm gắn thẻ dựa vào chúng (cùng lệnh cấm mà SRC-860 đã mang vào đường chấm). Hai thứ vẫn còn trong chữ:
- Chỗ tự sửa giữa câu để lại nguyên vệt trong bản phiên âm, nên
self-correctgắn thẻ được bình thường. - Nhịp nói suy được từ
secondscộng số chữ, cộng các chỗ ngắc ngứ còn sót lại ("I think, er, the main reason"). Không có hai thứ ấy thì prompt được dặn bỏ qua năng lực này thay vì đoán.
Ba đường nộp đều gắn thẻ, không phải hai: bài chẩn đoán, bài thang luyện, và lượt nói buộc vào một năng lực (speakingTurn.ts). Đường thứ ba dễ bị bỏ sót vì nó đã mang sẵn một mã năng lực, nhưng mã ấy nói learner ĐỊNH luyện gì chứ không nói đoạn nói CHO THẤY gì: một lượt luyện "Extending an answer" bốn mươi giây vẫn để lộ việc thì bị trôi hay một chỗ tự sửa rất gọn.
g. Còn mở
- Mentor chưa sửa được thẻ. Khi mentor chốt một band khác model, phần thẻ vẫn giữ nguyên phán đoán cũ. Một đường cho mentor gỡ một thẻ sai là việc đáng làm, và nó cũng là dữ liệu để đo xem model gắn thẻ đúng tới đâu.
- Bảng bằng chứng không ghi prompt nào đã sinh ra thẻ.
ielts_productions.ai_prompt_refchỉ giữ prompt CHẤM;ielts_subskill_resultskhông có cột tương đương. Ngày nào đổi prompt gắn thẻ thì không tách được số liệu trước và sau, nên một cộtprompt_refở đó là việc nên làm trước lần đổi tiếp theo chứ không phải sau.
84. Reading: hai lối khoá lại, và bộ chọn lĩnh vực dọn về đúng chỗ (SRC-982, 22.09.2026)
Vì sao khoá lối "theo dạng đề". Hai chiều cắt cùng một kho bài, nhưng chúng không ngang giá với người mới: chia theo NĂNG LỰC nói cho learner biết mình yếu ở đâu, còn chia theo DẠNG ĐỀ chỉ có nghĩa khi đã biết mình yếu ở đâu rồi. Mở cả hai lối từ ngày đầu là mời họ đi lối thứ hai trước, rồi luyện hai mươi bài matching mà không hiểu vì sao mình sai.
Điều kiện mở: mọi năng lực nhỏ đã có bậc (level !== null), tức là đã làm đủ câu để hệ thống xếp được bậc. Cố ý KHÔNG đòi một bậc tối thiểu: "học xong" ở đây là đã đi qua, không phải đã giỏi, và một cổng đòi bậc cao sẽ khoá vĩnh viễn đúng những learner cần đổi cách luyện nhất.
Khoá hiện ra chứ không giấu, và nói rõ còn thiếu mấy ("3 of 10 sub-skills done"). Một lối đi biến mất thì learner không biết nó tồn tại; một lối đi xám kèm điều kiện thì nó thành một cái đích. Cổng đứng ở CẢ HAI chỗ - cái nút và chính trang TaskTypesPage - vì một lối khoá bằng cách bỏ nút đi thì vẫn mở với bất kỳ ai gõ URL hoặc còn giữ một đường dẫn đã lưu.
Word list khoá cùng điều kiện: học thuộc từ rời trước khi gặp chúng trong bài đọc là cách học ngược, và chủ dự án khoá cả hai lối trong cùng một chỉ đạo.
Bộ chọn lĩnh vực rời header về ngay trên danh sách cụm. Chip cũ theo learner qua mọi trang, kể cả những trang mà lĩnh vực không đổi được gì - ở đó nó chỉ là một nút gây tò mò dẫn ra khỏi việc đang làm. Bộ chọn mới có cả nhãn "All fields": bỏ lọc phải dễ y như đặt lọc, nếu không learner chọn nhầm một lĩnh vực là mắc kẹt trong đó mà không biết đường ra. Trang /ielts/domain vẫn còn cho những đường dẫn đã lưu.
91. Đáp án đúng không còn là đáp án dài nhất (SRC-990, 22.09.2026)
Chủ dự án chụp màn hình một bài Main Idea và chỉ ra một dấu hiệu chạy suốt kho bài: đáp án đúng gần như luôn là lựa chọn dài nhất. Đo lại trên toàn bộ content/ielts-micro/ thì đúng như vậy: 621/1000 câu có đáp án đúng là lựa chọn dài nhất một cách duy nhất, trong khi mức ngẫu nhiên của bộ bốn lựa chọn là 25%. Có những file gần như tuyệt đối: writing/organisation 100%, writing/referencing 100%, writing/support-ideas 100%, reading/main-idea 96%, reading/summary 95%.
Vì sao đây là hỏng chứ không phải chuyện nhỏ về hình thức. Một learner đếm chữ rồi chọn câu dài nhất sẽ đúng hai phần ba số câu mà không đọc đoạn văn. Bài đo khi ấy không còn đo kỹ năng đọc: nó đo khả năng nhận ra thói quen soạn đề. Tệ hơn, thói quen ấy KHÔNG có trong phòng thi IELTS thật, nên learner mang chiến thuật này tới ngày thi và mất điểm ở đúng chỗ họ tưởng mình mạnh. Một chỉ báo sai còn hại hơn không có chỉ báo nào.
Nguyên nhân là ở bản chất của đáp án đúng, không phải ở sự cẩu thả. Với Main Idea hay Summary, đáp án đúng phải ôm cả đoạn, nên nó tự nhiên dài hơn ba lựa chọn nhiễu vốn chỉ đỡ một chi tiết. Vì vậy cách sửa KHÔNG phải rút ngắn đáp án đúng - làm thế là phá chính cái đúng của nó - mà là viết lại các lựa chọn nhiễu cho đủ dài, đúng như đề IELTS thật vẫn làm: nhiễu là một câu hoàn chỉnh, đủ sức nặng, sai ở NỘI DUNG chứ không sai ở kích cỡ.
Đã viết lại khoảng 300 lựa chọn nhiễu trên 24 file. Mỗi lần sửa chỉ chạm vào nhiễu, không chạm vào đáp án đúng (script áp bản vá từ chối mọi thay đổi vào chỉ số answer), và nội dung sai của nhiễu được giữ nguyên - nó chỉ được viết thành câu đầy đủ hơn.
Cổng check-option-length-cue (scripts/check-option-length-cue.mjs, nằm trong npm run check:code) giữ chỗ này: mỗi file phải có tỉ lệ "đáp án đúng là lựa chọn dài nhất duy nhất" không quá 40%, và tỉ lệ "ngắn nhất duy nhất" cũng không quá 40% - vì một kho bài mà đáp án đúng không bao giờ dài nhất thì cũng là một dấu hiệu đoán được, chỉ đảo chiều. Cổng bỏ qua hai loại câu mà độ dài không mang tin: bộ lựa chọn ngắn dưới 24 ký tự (một con số, một từ, một liên từ như On the other hand) và file có dưới mười câu chấm được.
Hiện trạng sau khi sửa: 30% trên 689 câu chấm được, không file nào quá ngưỡng.
Nội dung chạy trên production nằm trong D1 chứ không phải trong repo, nên bản vá này đi kèm migration 0280_reload_ielts_micro_content_after_option_length_rebalance.sql, sinh bằng node scripts/micro-seed.mjs --all. Migration là INSERT OR REPLACE trên id cố định nên chạy lại bao nhiêu lần cũng được.
117. Sub-skill trước, task type sau (SRC-1050, 25.09.2026)
Chủ dự án: "Cần giúp learners hiểu rằng hệ thống đang ưu tiên để nâng sub-skills, chứ mình chưa quan tâm lắm tới task-types... Cần nói về việc này ở vài chỗ." Bốn chỗ, tiếng Việt (chữ giải thích, SRC-953): dưới "Where am I strong?" ở trang kỹ năng; dưới tên sub-skill; trang task type đang khoá (lý do đầy đủ); và cụm "Vì sao luyện sub-skill trước?" trong popover hướng dẫn.
120. Bù câu "Not Given" cho các bài T/F/NG và Y/N/NG (SRC-1056, 25.09.2026)
Trả món nợ đã ghi ở §113: lượt nạp kho 0294 không có câu mới nào đáp án Not Given, vì bộ kiểm độ dài chặn nhãn dài nhất. Migration 0298 thêm 2 câu Not Given cho mỗi bài trong 8 bài (reading.detail.l3.3, evidence.l3.3, evidence.l3.4, inference.l5.3, inference.l5.4, summary.l5.3, tone.l3.3, tone.l5.3): kho mỗi bài từ 1-2 lên 3-4 câu Not Given trên 10, gần tỉ lệ đề thật. Mỗi câu nói về một điều đoạn văn KHÔNG nhắc tới (không phải điều nó nói ngược lại), nên Not Given là đáp án duy nhất; why_vi nói rõ vì sao không phải True/False. Luật SRC-990 về độ dài đáp án không áp dụng cho nhãn cố định, vì nhãn giống nhau ở mọi câu.
138. Viết lại lựa chọn sai trong kho đọc/nghe (SRC-1126, 29.09.2026)
P0 nội dung của lượt audit 2 (§132): đáp án đúng thường là lựa chọn dài nhất, nên learner đoán được bằng độ dài. Chủ dự án cho làm ngày 29.09.2026.
- Nguồn: nội dung chỉ nằm trong D1 (
ielts_content_questions), không có file nguồn trong repo. Xuất bằngseed-data.yml+scripts/noop-readonly.sql(câuverifychỉ đọc): 2.560 câu trắc nghiệm (Reading 1.920, Listening 640); trong 2.230 câu có lựa chọn đủ dài để độ dài thành gợi ý, 1.990 câu có đáp án là lựa chọn dài nhất (Reading 89%, Listening 74%, thước đo cùng luật vớicheck-option-length-cue). - Cách sửa: chỉ viết lại LỰA CHỌN SAI thành một ý sai cụ thể về nội dung (sai con số, sai người, sai nguyên nhân, khái quát quá). Chữ và vị trí đáp án đúng giữ nguyên, nên lời giải
whyvẫn khớp. Mười agent viết song song; mỗi lô qua một script kiểm: đáp án không đổi, không còn dài nhất, không trùng lựa chọn, không gạch dài, và (sau khi một lô đầu làm sai) không có câu đệm tự tố cáo kiểu "which is not what the speaker says" hay đuôi câu lặp lại. - Cân tỉ lệ: sửa hết thì lựa chọn dài nhất gần như luôn SAI, tức một mẹo ngược. Giữ bản gốc cho 25% số câu, chọn ngẫu nhiên, để tỉ lệ đáp án là lựa chọn dài nhất về 25% (Reading 26%, Listening 22%), đúng mức đoán bừa với bốn lựa chọn. Còn một gợi ý yếu: vài lựa chọn sai mới dài hẳn hơn phần còn lại.
- Nạp:
scripts/seed-ielts-option-rebalance-2026-09-29.sql, 1.432 câu UPDATE, mỗi câu gác bằngAND options_json = <bản đã xuất>để không đè câu admin sửa sau ngày xuất và chạy lại không đổi gì.
Lịch sử quyết định
Từ mục 25. Dap an thoi luon dung dau, va file JSON noi dung bi go han (SRC-882, 2026-09-19)
Chi dao chu du an: "sua luon 16 bai cu di, va dua vao D1 Cloudflare database di, dung de o JSON nua".
Dot 2 va dot 3 giu ban da gui di o scripts/content/ielts-listening-batch*.json, lap luan luc ay la "ban DA NAP, cung loai voi moi scripts/seed-*.sql". Chu du an bac lap luan ay, va bac dung: hai ban cua cung mot noi dung thi mot ban se cu. Ngay co nguoi sua mot cau o man admin, file JSON thanh sai ma khong co dau hieu nao bao la no sai.
Từ mục 32. Sub-skills của phần Viết, xếp theo bốn tiêu chí chấm (SRC-896, 2026-09-20)
Chỉ đạo chủ dự án 2026-09-20, kèm ảnh chụp thẻ "Where am I strong?" của trang Writing: dùng đúng bộ sub-skills dưới đây, chia theo bốn cụm. Và ngay sau đó: "Điều chỉnh lại các tasks để mỗi sub-skills đều có cái để learners có thể học và luyện tập."
Từ mục 35. Reading có HAI chiều, và chúng cắt cùng một kho bài (SRC-895, 2026-09-20)
Chỉ đạo chủ dự án 2026-09-20: "Cần chia lại theo 2 chiều sau, nghĩa là learner có thể lọc theo cả sub-skills lẫn theo task types để luyện tập, và cần tạo đủ mỗi sub skill cần có 5 tasks, và mỗi task types cũng vậy." Kèm sơ đồ mười năng lực và mười hai dạng câu hỏi.
Từ mục 38. Gắn thẻ năng lực nhỏ cho bài viết, ở mức câu hỏi (SRC-913, 2026-09-20)
Chỉ đạo chủ dự án 2026-09-20: "gắn thẻ sub-skill cho kho bài Writing ở mức câu hỏi" — đóng đúng cái lỗ mà §32e vừa ghi ra.
Chỉ đạo chủ dự án 2026-09-20, ngay sau khi phần Viết xong: "gắn thẻ cho Speaking luôn".
Từ mục 84. Reading: hai lối khoá lại, và bộ chọn lĩnh vực dọn về đúng chỗ (SRC-982, 22.09.2026)
Chỉ đạo: "Không cho Learners vào Practice by Task Types. Cái đó sau khi học xong hết cả 10 phần trên thì mới được Unlock Practice By Task Type. Word List cũng bị locked lại. Tính năng chọn field hay domain, cần đưa vào bên trong của Reading Clusters."