# Hợp đồng bộ đề 3.1 – tài liệu tham khảo cho AI soạn câu hỏi > Tệp này dành cho mô hình AI (ChatGPT, Gemini, Claude, Copilot…). Người dùng đính kèm tệp này cùng tài liệu nội dung (sách, nội quy, bài giảng) và yêu cầu ra đề. Hãy đọc hết và **tuân thủ tuyệt đối**: app “Vở luyện tập” chỉ nhập JSON đúng hợp đồng này. Bản 3.1 thêm câu tự luận, câu chùm (đọc hiểu), chia phần và media (ảnh, âm thanh, video); file 3.0 cũ vẫn hợp lệ. Mọi định dạng khác (bảng, markdown, JSON thiếu trường) đều bị từ chối. ## 1. Đầu ra - Trả về **một object JSON duy nhất**, không lời dẫn, không chú thích (`//` không hợp lệ trong JSON), không dấu phẩy thừa. - Có thể bọc trong một khối ```json … ``` (app tự bỏ khung). - Nếu câu trả lời dài có nguy cơ bị cắt, hãy chia: mỗi lần trả một file 3.1 hoàn chỉnh chứa một phần câu hỏi (cùng `id` bộ đề, `id` câu không trùng), hoặc ra ít câu hơn. ```json { "schema": "bo-de/3.1", "generator": { "name": "tên AI hoặc công cụ" }, "sets": [ { …bộ đề… } ], "collections": [ { …tuỳ chọn… } ] } ``` ## 2. Bộ đề (set) | Trường | Bắt buộc | Ý nghĩa | |---|---|---| | `id` | có | mã bộ đề: chữ thường không dấu, số, `-`, `_` (tối đa 64), ví dụ `toan5-phan-so-01`. Giữ nguyên khi sửa/bổ sung bộ đề này | | `title` | có | tên hiển thị (≤ 200 ký tự) | | `lang` | có | mã ngôn ngữ, ví dụ `"vi"`, `"en"` | | `items` | có | 1–500 câu hỏi | | `version` | không | số nguyên ≥ 1; **tăng lên 1** mỗi lần trả lại bộ đề đã sửa | | `description` | không | mô tả ngắn | | `tags` | không | ≤ 20 tag, mỗi tag chữ thường không dấu, số, `-`, `_` | | `scoring` | không | cách chấm mặc định cho cả bộ: `all_or_nothing` (mặc định), `partial` | | `pass_score` | không | điểm đạt, phần trăm 0–100 | | `time_limit_sec` | không | giới hạn thời gian (10–86400 giây) | | `shuffle` | không | `{"items": true, "options": false}` – đặt `options: false` nếu thứ tự phương án có ý nghĩa | | `provenance` | không | `{"source", "license", "attribution", "url"}` – nguồn tài liệu | | `sections` | không | chia đề thành phần (3.1), xem mục 4b | | `ext` | không | mở rộng theo hồ sơ (`k12`, `org`), xem mục 6 | Không thêm trường nào ngoài danh sách (app báo lỗi “trường không thuộc chuẩn”). ## 3. Trường chung của mỗi câu (item) | Trường | Bắt buộc | Ý nghĩa | |---|---|---| | `id` | nên có | mã câu, duy nhất trong bộ (cùng quy tắc như id bộ đề), ví dụ `ps01` | | `type` | có | `single`, `multi`, `tf`, `short`, `order`, `match`, `fill`, `essay` (tự luận), `group` (câu chùm) | | `prompt` | có | nội dung câu hỏi (≤ 2000 ký tự) | | `hints` | không | mảng 1–5 gợi ý **từng nấc**, từ nhẹ đến sát đáp án; KHÔNG nêu thẳng đáp án | | `explain` | không, nên có | lời giải / cách làm | | `difficulty` | không | 1 (rất dễ) – 5 (rất khó), mặc định 3 | | `variant_group` | không | mã nhóm biến thể: các câu cùng mô hình, khác số liệu; mỗi lượt app chỉ dùng một câu trong nhóm. Các câu cùng nhóm phải cùng `type`, cùng `points` | | `points` | không | trọng số câu (> 0, ≤ 100), mặc định 1 | | `scoring` | không | `all_or_nothing`, `partial`, hoặc `thpt_tf` (chỉ cho `tf` 4 mệnh đề) | | `graded` | không | `false` = câu khảo sát, không có đáp án đúng | | `tags` | không | tag của câu (dùng để rút câu theo chủ đề con) | | `media` | không | 1–4 ảnh / âm thanh / video kèm câu hỏi (3.1), xem mục 4c | | `section` | khi bộ có `sections` | `id` của phần chứa câu này | | `ext` | không | mở rộng (`k12`, `org`) | ## 4. Bảy dạng câu (và hai dạng mới của 3.1 ở mục 4a) **Quy tắc chung:** đáp án đúng đánh dấu **ngay trên phương án** bằng `"correct": true`. KHÔNG dùng trường `answer`, KHÔNG dùng `hint` (số ít). Phương án là object `{"text": …}`, không phải chuỗi. App tự trộn thứ tự phương án, nên **không** dùng phương án kiểu “Cả A và B”, “Tất cả đều đúng”. ### single – một đáp án đúng 2–8 phương án (nên 4), **đúng một** phương án `"correct": true`. Mỗi phương án sai nên có `feedback` giải thích vì sao sai. ```json {"id": "q1", "type": "single", "difficulty": 2, "prompt": "\\(\\frac{1}{2} + \\frac{1}{3} = ?\\)", "options": [ {"text": "\\(\\frac{5}{6}\\)", "correct": true}, {"text": "\\(\\frac{2}{5}\\)", "feedback": "Không cộng tử với tử, mẫu với mẫu; hãy quy đồng trước."}, {"text": "\\(\\frac{2}{6}\\)", "feedback": "Đã quy đồng mẫu nhưng quên nhân tử số."}, {"text": "\\(\\frac{1}{6}\\)", "feedback": "Đây là phép nhân, không phải phép cộng."}], "hints": ["Hai phân số khác mẫu: quy đồng trước.", "Mẫu số chung nhỏ nhất của 2 và 3 là 6."], "explain": "\\(\\frac{3}{6} + \\frac{2}{6} = \\frac{5}{6}\\)."} ``` ### multi – nhiều đáp án đúng 2–10 phương án, **ít nhất một** `"correct": true`. Tuỳ chọn `min_select`, `max_select` (`max_select` không nhỏ hơn số đáp án đúng). `"scoring": "partial"` = điểm theo tỉ lệ max(0, (đúng − sai) ÷ số đáp án đúng). ```json {"id": "q2", "type": "multi", "scoring": "partial", "prompt": "Những số nào là số nguyên tố?", "options": [{"text": "2", "correct": true}, {"text": "4"}, {"text": "5", "correct": true}, {"text": "9"}]} ``` ### tf – đúng/sai `statements`: 1–8 mệnh đề, mỗi mệnh đề `{"text", "correct": true|false}`. Dạng thi THPT: **đúng 4 mệnh đề** a)–d) và `"scoring": "thpt_tf"` (đúng 1 ý 0,1 điểm; 2 ý 0,25; 3 ý 0,5; 4 ý 1). Câu một nhận định: một mệnh đề. ```json {"id": "q3", "type": "tf", "scoring": "thpt_tf", "prompt": "Cho hai phân số 3/5 và 5/8.", "statements": [ {"text": "a) Mẫu số chung nhỏ nhất là 40.", "correct": true}, {"text": "b) 3/5 = 24/40.", "correct": true}, {"text": "c) 3/5 > 5/8.", "correct": false, "feedback": "24/40 < 25/40."}, {"text": "d) Tổng hai phân số lớn hơn 1.", "correct": true}]} ``` ### short – trả lời ngắn Có `accept` (mảng 1–20 cách viết đúng) và/hoặc `numeric` (`{"value": số, "tolerance": sai số, "unit": "cm"}`). App so chữ không phân biệt hoa thường, gộp khoảng trắng, **giữ dấu tiếng Việt** (bỏ dấu khi có `"match": {"ignore_diacritics": true}`; phân biệt hoa thường với `"case_sensitive": true`). Đáp án số: viết `value` là số JSON (3.5), học sinh gõ 3,5 hay 3.5 đều được; không dùng dấu phân cách hàng nghìn. ```json {"id": "q4", "type": "short", "prompt": "Viết 7/2 dưới dạng số thập phân.", "numeric": {"value": 3.5}} {"id": "q5", "type": "short", "prompt": "Thủ đô của Việt Nam là thành phố nào?", "accept": ["Hà Nội", "Thành phố Hà Nội"]} ``` ### order – sắp xếp `items`: 2–10 mục **khác nhau**, viết theo **thứ tự đúng** (app tự xáo). `"scoring": "partial"` = điểm theo tỉ lệ vị trí đúng. ```json {"id": "q6", "type": "order", "prompt": "Sắp xếp các phân số từ bé đến lớn.", "items": ["1/3", "1/2", "2/3", "5/6"]} ``` ### match – ghép đôi `pairs`: 2–10 cặp **đúng** `{"left", "right"}`; tuỳ chọn `distractors` (≤ 5 mục thừa bên phải). Cột trái không trùng nhau; cột phải (kể cả mục thừa) không trùng nhau. Nên dùng cho nội dung chữ ngắn. ```json {"id": "q7", "type": "match", "scoring": "partial", "prompt": "Nối thành phố với món ăn nổi tiếng.", "pairs": [{"left": "Huế", "right": "Bún bò"}, {"left": "Hà Nội", "right": "Phở"}], "distractors": ["Hủ tiếu"]} ``` ### fill – điền khuyết `text` có chỗ trống `{{1}}`, `{{2}}`… đánh số **liên tục từ 1, mỗi số một lần**; `blanks` có đúng bấy nhiêu mục theo thứ tự. Mỗi chỗ trống dùng **hoặc** `accept` (gõ chữ) **hoặc** `choices` (chọn; đúng một `"correct": true`). `prompt` vẫn bắt buộc (lời yêu cầu). ```json {"id": "q8", "type": "fill", "prompt": "Điền vào chỗ trống.", "text": "Đỉnh núi cao nhất Việt Nam là {{1}}, thuộc dãy {{2}}.", "blanks": [{"accept": ["Fansipan", "Phan Xi Păng"]}, {"choices": [{"text": "Hoàng Liên Sơn", "correct": true}, {"text": "Trường Sơn"}]}]} ``` ### Câu khảo sát Thêm `"graded": false` (dạng `single`, `multi` hoặc `short`), không đánh dấu `correct`. App không chấm điểm câu này. ## 4a. Hai dạng câu mới của 3.1 ### essay – tự luận Học sinh viết bài; app **không chấm tự động**. Học sinh xem đáp án mẫu rồi tự chấm theo tiêu chí (mặc định), hoặc bố mẹ chấm (`"review": "manual"`). Câu tự chấm không cộng vào điểm lượt. ```json {"id": "tl1", "type": "essay", "points": 3, "prompt": "Viết 2–3 câu kể về một lần em dùng phân số.", "rubric": [{"criterion": "Kể đúng tình huống chia phần bằng nhau", "points": 1}, {"criterion": "Nêu được phân số cụ thể", "points": 1}, {"criterion": "Câu văn rõ ràng", "points": 1}], "sample_answer": "Mẹ chia bánh thành 6 phần bằng nhau, em ăn 1 phần, tức là 1/6 chiếc bánh.", "max_chars": 600} ``` - `sample_answer` (nên có, ≤ 5000), `rubric` 1–10 tiêu chí `{criterion, points}`, `review`: `self` (mặc định) | `manual`, `max_chars` 1–20000. ### group – câu chùm / đọc hiểu Một đoạn dẫn dùng chung cho 1–10 câu con. Câu con là câu bình thường (mọi dạng trừ `group`), **mỗi câu con có `id` riêng** trong nhóm. ```json {"id": "dh1", "type": "group", "prompt": "Đọc đoạn văn rồi trả lời.", "context": "Mẹ cắt chiếc bánh thành 8 miếng bằng nhau. An ăn 3 miếng, Bình ăn 2 miếng.", "sequential": true, "parts": [ {"id": "a", "type": "single", "prompt": "An ăn bao nhiêu phần chiếc bánh?", "options": [{"text": "\\(\\frac{3}{8}\\)", "correct": true}, {"text": "\\(\\frac{3}{5}\\)"}]}, {"id": "b", "type": "short", "prompt": "Còn lại mấy miếng?", "numeric": {"value": 3}}]} ``` - `context` ≤ 8000 ký tự. Câu con không có `section`, `variant_group` (đặt ở câu chùm). `points` của câu chùm (nếu có) chia cho câu con theo tỉ lệ `points` của câu con. ## 4b. Chia phần (sections, 3.1) Đề nhiều phần (ví dụ Trắc nghiệm / Tự luận), mỗi phần có thể có thời gian riêng: ```json "time_limit_sec": 1800, "sections": [{"id": "p1", "title": "Phần 1: Trắc nghiệm", "time_limit_sec": 900}, {"id": "p2", "title": "Phần 2: Tự luận", "description": "Trình bày rõ ràng."}], "items": [{"id": "c1", "section": "p1", "…": "…"}, {"id": "tl1", "section": "p2", "…": "…"}] ``` - Có `sections` thì **mọi câu** phải có `section` hợp lệ. Tổng thời gian các phần không vượt `time_limit_sec` của bộ. App cho làm cả đề theo thứ tự phần; hết giờ phần nào thì khoá phần đó. ## 4c. Media: ảnh, âm thanh, video (3.1) Chỉ dùng **URL https công khai** (app không nhận tệp tải lên, không nhận `http://`, `data:`). Một media là: ```json {"type": "image", "url": "https://…/hinh.png", "alt": "mô tả ảnh (bắt buộc với ảnh)", "caption": "nguồn / chú thích"} {"type": "audio", "url": "https://…/bai-nghe.mp3", "start": 5, "end": 20} {"type": "video", "url": "https://www.youtube.com/watch?v=XXXXXXXXXXX", "start": 30, "end": 75} ``` - Đặt ở câu (`"media": [ … ]`, 1–4), ở **phương án** của `single`/`multi` (`{"media": {…}, "correct": true}`, có thể bỏ `text` khi phương án là ảnh) và ở **mệnh đề** của `tf`. - Video: link YouTube (`youtube.com/watch?v=`, `youtu.be/`, `youtube.com/shorts/`) hoặc link thẳng tới tệp `.mp4`/`.webm`. Âm thanh: link thẳng tới `.mp3`/`.m4a`/`.ogg`/`.wav`. `start`/`end` tính bằng giây. - Chỉ dùng link **có thật** từ tài liệu người dùng đưa hoặc nguồn bạn chắc chắn tồn tại; không bịa URL. Nội dung phải phù hợp với trẻ em. Không có link phù hợp thì **không thêm media**. ## 5. Viết nội dung - **Công thức toán**: LaTeX trong `\( … \)` (giữa dòng) hoặc `\[ … \]` (riêng dòng). Trong chuỗi JSON dấu `\` phải viết thành `\\`, ví dụ `"\\(\\frac{3}{4}\\)"`. KHÔNG dùng `$…$`, KHÔNG dùng HTML (``, `
`). - Định dạng: `**đậm**`, `*nghiêng*`, xuống dòng bằng `\n`. - Chỉ hỏi những gì có trong tài liệu được cung cấp; không bịa số liệu, tên riêng, điều khoản. - Phương án sai phải là **lỗi tư duy thật** của người học (không phải số ngẫu nhiên); độ dài các phương án tương đương nhau để không lộ đáp án. - Biến thể: muốn luyện lại không học vẹt, tạo 2–5 câu cùng mô hình, khác số liệu, cùng `variant_group`; mỗi câu vẫn có `id` và đáp án riêng. ## 6. Hồ sơ mở rộng (ext) ### `ext.k12` – học sinh phổ thông Trên **bộ đề**: ```json "ext": {"k12": { "subject": "toan", "grade": 5, "topic": "Ôn tập phân số", "curriculum": "ket-noi-tri-thuc", "source": "SGK Toán 5, tập 1", "skills": [{"code": "rut-gon", "name": "Rút gọn phân số"}], "misconceptions": [{"code": "cong-tu-mau", "name": "Cộng tử với tử, mẫu với mẫu"}]}} ``` - `subject`: `toan`, `tieng-viet`, `ngu-van`, `tieng-anh`, `khoa-hoc`, `khtn`, `lich-su-dia-li`, `ly`, `hoa`, `sinh`, `su`, `dia`, `tin`, `cong-nghe`, `dao-duc`, `gdcd`…; `grade`: 1–12; `curriculum`: `ket-noi-tri-thuc`, `chan-troi-sang-tao`, `canh-dieu`… - Trên **câu**: `"ext": {"k12": {"skill": "", "level": 1–4, "form": "compute", "trap": "bẫy hay gặp"}}` – `level`: 1 nhận biết, 2 thông hiểu, 3 vận dụng, 4 vận dụng cao. - Trên **phương án / mệnh đề sai**: `"ext": {"k12": {"misconception": ""}}`. Dùng lại cùng mã cho cùng một lỗi trong cả bộ: app thống kê “lỗi tư duy hay gặp” theo mã này. ### `ext.org` – tổ chức, doanh nghiệp Bộ đề: `"pass_score": 80, "ext": {"org": {"policy_code": "NQ-01", "policy_version": "2026.1", "effective_date": "2026-01-01", "required": true}}`; câu: `"ext": {"org": {"clause": "Điều 3.1"}}`. ## 7. Collection – bộ sưu tập / lộ trình (tuỳ chọn) ```json "collections": [{ "id": "lo-trinh-phan-so", "title": "Lộ trình Phân số", "ordered": true, "pass_score": 80, "entries": [ {"set": "toan5-phan-so-01"}, {"set": "toan5-phan-so-02", "requires_previous": true, "pass_score": 90}, {"title": "Ôn tổng hợp", "draw": {"from": ["toan5-phan-so-01", "toan5-phan-so-02"], "count": 10, "types": ["single", "tf"], "difficulty": {"min": 2, "max": 4}, "tags": ["phan-so"]}, "requires_previous": true}]}] ``` - Mỗi mục dùng **hoặc** `set` (có thể kèm `version` để ghim phiên bản) **hoặc** `draw` (rút ngẫu nhiên `count` câu). - `requires_previous` chỉ có tác dụng khi `ordered: true`. Điểm đạt: mục > collection > bộ đề. - Mục `draw` phải đủ câu khớp điều kiện (mỗi nhóm biến thể tính là một). ## 8. Tự kiểm tra trước khi trả 1. JSON hợp lệ, root có `"schema": "bo-de/3.1"` và `sets`. 2. Mọi `id` (bộ, câu, nhóm, tag) chữ thường không dấu; `id` câu không trùng trong bộ. 3. `single`: đúng một `correct: true`. `multi`: ít nhất một. Không có `answer`, `hint`. 4. `tf` + `thpt_tf`: đúng 4 mệnh đề. 5. `fill`: số chỗ trống `{{n}}` khớp số `blanks`, đánh số 1…n. 6. `short`: có `accept` hoặc `numeric`. 7. Không có hai phương án giống nhau; không có “Tất cả đều đúng”. 8. Đã **giải lại từng câu** để chắc đáp án đúng; mỗi phương án sai có `feedback`. 9. LaTeX trong `\( \)` với dấu `\` nhân đôi; không HTML. 10. `essay` có `sample_answer`; `group` có câu con mang `id` riêng, không lồng `group`; bộ có `sections` thì mọi câu có `section`. 11. Media chỉ URL https có thật; ảnh có `alt`. ## 9. Sửa lỗi Khi nhập, app kiểm tra và báo lỗi theo vị trí, ví dụ: `LỖI: Bộ 1 › câu 3 › options: dạng một đáp án nhưng có nhiều hơn một phương án "correct": true`. Nếu người dùng gửi lại các dòng bắt đầu bằng `LỖI:`, hãy sửa **đúng các vị trí đó**, giữ nguyên phần còn lại, tăng `version`, và trả lại **toàn bộ JSON**. ## 10. Ví dụ đầy đủ Xem các tệp ví dụ cùng thư mục: `vi-du-k12.json` (Toán lớp 5), `vi-du-gia-dinh.json` (đố vui), `vi-du-to-chuc.json` (nội quy công ty, có collection). Đề mẫu 3.1 dùng đủ tự luận, câu chùm, chia phần và media: `vi-du-3-1-tong-hop.json`. ## 11. JSON Schema (draft 2020-12, chuẩn máy đọc) ```json {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://vo-luyen-tap.example/schema/bo-de-3.1.json","title":"Hợp đồng bộ đề 3.1","description":"Định dạng mở cho bộ câu hỏi và collection. Bản 3.1 thêm: câu tự luận (essay), câu chùm (group), chia phần (sections), media (ảnh, âm thanh, video qua URL https). File 3.0 vẫn hợp lệ.","type":"object","required":["schema","sets"],"additionalProperties":false,"properties":{"schema":{"type":"string","pattern":"^bo-de/3\\.[0-9]+$"},"generator":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"$ref":"#/$defs/shortText"},"version":{"$ref":"#/$defs/shortText"}}},"sets":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/$defs/set"}},"collections":{"type":"array","maxItems":20,"items":{"$ref":"#/$defs/collection"}}},"$defs":{"id":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,63}$"},"shortText":{"type":"string","minLength":1,"maxLength":200},"text":{"type":"string","minLength":1,"maxLength":2000},"optionText":{"type":"string","minLength":1,"maxLength":500},"tags":{"type":"array","maxItems":20,"uniqueItems":true,"items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,39}$"}},"percent":{"type":"number","minimum":0,"maximum":100},"ext":{"type":"object","propertyNames":{"pattern":"^[a-z][a-z0-9_-]{0,31}$"},"additionalProperties":{"type":"object"}},"scoring":{"enum":["all_or_nothing","partial","thpt_tf"]},"set":{"type":"object","required":["id","title","lang","items"],"additionalProperties":false,"properties":{"id":{"$ref":"#/$defs/id"},"title":{"$ref":"#/$defs/shortText"},"lang":{"type":"string","pattern":"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$"},"description":{"$ref":"#/$defs/text"},"version":{"type":"integer","minimum":1},"tags":{"$ref":"#/$defs/tags"},"scoring":{"$ref":"#/$defs/scoring"},"pass_score":{"$ref":"#/$defs/percent"},"time_limit_sec":{"type":"integer","minimum":10,"maximum":86400},"shuffle":{"type":"object","additionalProperties":false,"properties":{"items":{"type":"boolean"},"options":{"type":"boolean"}}},"provenance":{"type":"object","additionalProperties":false,"properties":{"source":{"$ref":"#/$defs/shortText"},"license":{"$ref":"#/$defs/shortText"},"attribution":{"$ref":"#/$defs/shortText"},"url":{"type":"string","format":"uri","maxLength":500}}},"ext":{"$ref":"#/$defs/ext"},"items":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/$defs/item"}},"sections":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"object","required":["id","title"],"additionalProperties":false,"properties":{"id":{"$ref":"#/$defs/id"},"title":{"$ref":"#/$defs/shortText"},"description":{"$ref":"#/$defs/text"},"time_limit_sec":{"type":"integer","minimum":10,"maximum":86400},"pass_score":{"$ref":"#/$defs/percent"}}}}}},"choice":{"type":"object","required":["text"],"additionalProperties":false,"properties":{"text":{"$ref":"#/$defs/optionText"},"correct":{"type":"boolean"},"feedback":{"$ref":"#/$defs/text"},"ext":{"$ref":"#/$defs/ext"}}},"correctChoice":{"type":"object","required":["correct"],"properties":{"correct":{"const":true}}},"acceptList":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string","minLength":1,"maxLength":200}},"matchRules":{"type":"object","additionalProperties":false,"properties":{"case_sensitive":{"type":"boolean"},"ignore_diacritics":{"type":"boolean"}}},"item":{"type":"object","required":["type","prompt"],"properties":{"id":{"$ref":"#/$defs/id"},"type":{"enum":["single","multi","tf","match","order","fill","short","essay","group"]},"prompt":{"$ref":"#/$defs/text"},"hints":{"type":"array","minItems":1,"maxItems":5,"items":{"$ref":"#/$defs/text"},"description":"Gợi ý từng nấc, hiện lần lượt mỗi lần bấm."},"explain":{"$ref":"#/$defs/text"},"difficulty":{"type":"integer","minimum":1,"maximum":5},"variant_group":{"$ref":"#/$defs/id","description":"Các câu cùng nhóm là biến thể tương đương; mỗi lượt chỉ dùng một câu."},"points":{"type":"number","exclusiveMinimum":0,"maximum":100},"scoring":{"$ref":"#/$defs/scoring"},"graded":{"type":"boolean"},"tags":{"$ref":"#/$defs/tags"},"ext":{"$ref":"#/$defs/ext"},"media":{"$ref":"#/$defs/mediaList"},"section":{"$ref":"#/$defs/id","description":"id của một phần trong sections của bộ (chỉ câu cấp bộ, không dùng cho câu con của group)."}},"allOf":[{"if":{"properties":{"type":{"const":"single"}}},"then":{"$ref":"#/$defs/t_single"}},{"if":{"properties":{"type":{"const":"multi"}}},"then":{"$ref":"#/$defs/t_multi"}},{"if":{"properties":{"type":{"const":"tf"}}},"then":{"$ref":"#/$defs/t_tf"}},{"if":{"properties":{"type":{"const":"match"}}},"then":{"$ref":"#/$defs/t_match"}},{"if":{"properties":{"type":{"const":"order"}}},"then":{"$ref":"#/$defs/t_order"}},{"if":{"properties":{"type":{"const":"fill"}}},"then":{"$ref":"#/$defs/t_fill"}},{"if":{"properties":{"type":{"const":"short"}}},"then":{"$ref":"#/$defs/t_short"}},{"if":{"properties":{"type":{"const":"essay"}}},"then":{"$ref":"#/$defs/t_essay"}},{"if":{"properties":{"type":{"const":"group"}}},"then":{"$ref":"#/$defs/t_group"}},{"if":{"required":["scoring"],"properties":{"scoring":{"const":"thpt_tf"}}},"then":{"properties":{"type":{"const":"tf"},"statements":{"minItems":4,"maxItems":4}}}}],"unevaluatedProperties":false},"t_single":{"required":["options"],"properties":{"options":{"type":"array","minItems":2,"maxItems":8,"items":{"$ref":"#/$defs/choiceM"}}},"if":{"required":["graded"],"properties":{"graded":{"const":false}}},"then":true,"else":{"properties":{"options":{"contains":{"$ref":"#/$defs/correctChoice"},"minContains":1,"maxContains":1}}}},"t_multi":{"required":["options"],"properties":{"options":{"type":"array","minItems":2,"maxItems":10,"items":{"$ref":"#/$defs/choiceM"}},"min_select":{"type":"integer","minimum":0},"max_select":{"type":"integer","minimum":1}},"if":{"required":["graded"],"properties":{"graded":{"const":false}}},"then":true,"else":{"properties":{"options":{"contains":{"$ref":"#/$defs/correctChoice"},"minContains":1}}}},"t_tf":{"required":["statements"],"properties":{"statements":{"type":"array","minItems":1,"maxItems":8,"items":{"type":"object","required":["text","correct"],"additionalProperties":false,"properties":{"text":{"$ref":"#/$defs/optionText"},"correct":{"type":"boolean"},"feedback":{"$ref":"#/$defs/text"},"ext":{"$ref":"#/$defs/ext"},"media":{"$ref":"#/$defs/media"}}}}}},"t_match":{"required":["pairs"],"properties":{"pairs":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"object","required":["left","right"],"additionalProperties":false,"properties":{"left":{"$ref":"#/$defs/optionText"},"right":{"$ref":"#/$defs/optionText"}}}},"distractors":{"type":"array","maxItems":5,"items":{"$ref":"#/$defs/optionText"}}}},"t_order":{"required":["items"],"properties":{"items":{"type":"array","minItems":2,"maxItems":10,"uniqueItems":true,"items":{"$ref":"#/$defs/optionText"}}}},"t_fill":{"required":["text","blanks"],"properties":{"text":{"type":"string","minLength":1,"maxLength":2000,"pattern":"\\{\\{[0-9]+\\}\\}"},"blanks":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","additionalProperties":false,"properties":{"accept":{"$ref":"#/$defs/acceptList"},"choices":{"type":"array","minItems":2,"maxItems":8,"items":{"$ref":"#/$defs/choice"},"contains":{"$ref":"#/$defs/correctChoice"},"minContains":1,"maxContains":1},"match":{"$ref":"#/$defs/matchRules"}},"oneOf":[{"required":["accept"]},{"required":["choices"]}]}}}},"t_short":{"properties":{"accept":{"$ref":"#/$defs/acceptList"},"numeric":{"type":"object","required":["value"],"additionalProperties":false,"properties":{"value":{"type":"number"},"tolerance":{"type":"number","minimum":0},"unit":{"type":"string","maxLength":20}}},"match":{"$ref":"#/$defs/matchRules"}},"if":{"required":["graded"],"properties":{"graded":{"const":false}}},"then":true,"else":{"anyOf":[{"required":["accept"]},{"required":["numeric"]}]}},"collection":{"type":"object","required":["id","title","entries"],"additionalProperties":false,"properties":{"id":{"$ref":"#/$defs/id"},"title":{"$ref":"#/$defs/shortText"},"description":{"$ref":"#/$defs/text"},"ordered":{"type":"boolean"},"pass_score":{"$ref":"#/$defs/percent"},"tags":{"$ref":"#/$defs/tags"},"ext":{"$ref":"#/$defs/ext"},"entries":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/$defs/entry"}}}},"entry":{"type":"object","properties":{"title":{"$ref":"#/$defs/shortText"},"pass_score":{"$ref":"#/$defs/percent"},"requires_previous":{"type":"boolean"},"set":{"$ref":"#/$defs/id"},"version":{"type":"integer","minimum":1},"draw":{"type":"object","required":["from","count"],"additionalProperties":false,"properties":{"from":{"type":"array","minItems":1,"maxItems":50,"uniqueItems":true,"items":{"$ref":"#/$defs/id"}},"count":{"type":"integer","minimum":1,"maximum":200},"tags":{"$ref":"#/$defs/tags"},"types":{"type":"array","uniqueItems":true,"items":{"enum":["single","multi","tf","match","order","fill","short","essay","group"]}},"difficulty":{"type":"object","additionalProperties":false,"properties":{"min":{"type":"integer","minimum":1,"maximum":5},"max":{"type":"integer","minimum":1,"maximum":5}}}}}},"oneOf":[{"required":["set"],"not":{"required":["draw"]}},{"required":["draw"],"not":{"anyOf":[{"required":["set"]},{"required":["version"]}]}}],"additionalProperties":false},"media":{"type":"object","required":["type","url"],"additionalProperties":false,"description":"Ảnh / âm thanh / video qua URL https. Video YouTube dùng link youtube.com/watch?v=…, youtu.be/… hoặc youtube.com/shorts/…; còn lại là link thẳng tới tệp.","properties":{"type":{"enum":["image","audio","video"]},"url":{"type":"string","pattern":"^https://[^\\s\"'<>]+$","maxLength":1000},"alt":{"type":"string","minLength":1,"maxLength":300},"caption":{"type":"string","minLength":1,"maxLength":200},"start":{"type":"integer","minimum":0,"maximum":86400},"end":{"type":"integer","minimum":1,"maximum":86400}},"if":{"properties":{"type":{"const":"image"}}},"then":{"required":["alt"]}},"mediaList":{"type":"array","minItems":1,"maxItems":4,"items":{"$ref":"#/$defs/media"}},"choiceM":{"type":"object","additionalProperties":false,"properties":{"text":{"$ref":"#/$defs/optionText"},"media":{"$ref":"#/$defs/media"},"correct":{"type":"boolean"},"feedback":{"$ref":"#/$defs/text"},"ext":{"$ref":"#/$defs/ext"}},"anyOf":[{"required":["text"]},{"required":["media"]}]},"t_essay":{"properties":{"sample_answer":{"type":"string","minLength":1,"maxLength":5000},"rubric":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","required":["criterion","points"],"additionalProperties":false,"properties":{"criterion":{"$ref":"#/$defs/shortText"},"points":{"type":"number","exclusiveMinimum":0,"maximum":100}}}},"review":{"enum":["self","manual"]},"max_chars":{"type":"integer","minimum":1,"maximum":20000}}},"part":{"allOf":[{"$ref":"#/$defs/item"},{"not":{"required":["type"],"properties":{"type":{"const":"group"}}}}]},"t_group":{"required":["parts"],"properties":{"context":{"type":"string","minLength":1,"maxLength":8000},"parts":{"type":"array","minItems":1,"maxItems":10,"items":{"$ref":"#/$defs/part"}},"sequential":{"type":"boolean"}}}}} ```