{"openapi":"3.1.0","info":{"title":"Ngân hàng đề THPT","description":"Kho câu hỏi số hoá thành mã Python. Mỗi câu là một khuôn sinh ra vô số biến thể từ một con số seed: cùng cặp (mã câu hỏi, seed) thì vĩnh viễn ra đúng một đề. Kho gồm nhiều môn; yêu cầu lấy câu nói rõ môn ở trường `subject`.","version":"0.3.0"},"paths":{"/health":{"get":{"tags":["Hệ thống"],"summary":"Kiểm tra máy chủ và kho câu hỏi","description":"Trạng thái máy chủ, số khuôn trong kho và vân tay của kho.\n\nVân tay là mã băm nội dung toàn bộ tệp khuôn. Máy chủ đối chiếu vân tay này\nvới chỉ mục lúc khởi động — lệch thì trên máy chủ thật sẽ từ chối chạy, để\nkhông bao giờ phục vụ bằng một chỉ mục cũ.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Get"}}}}}}},"/subjects":{"get":{"tags":["Danh mục"],"summary":"Cây môn, chương và chủ đề","description":"Toàn bộ danh mục chương trình, kèm kho có gì ở từng chủ đề.\n\nMỗi nút chủ đề mang theo số khuôn, các loại câu và các mức độ mà kho ra được\n— gọi một lần là dựng được cây chọn chương và biết ngay chỗ nào còn trống.\n\nRiêng `type_levels` nói rõ từng loại câu ra được những mức nào. Một chủ đề\ncó thể ra trắc nghiệm mức vận dụng trong khi câu trả lời ngắn của nó chỉ tới\nthông hiểu, nên lấy `levels` và `types` ghép chéo nhau là hỏi phải chỗ trống.\n\nMã chủ đề lấy ở đây chính là thứ truyền vào `/questions`.","operationId":"subjects_subjects_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Subjects Subjects Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/questions":{"post":{"tags":["Câu hỏi"],"summary":"Lấy câu hỏi theo yêu cầu","description":"Lấy câu hỏi theo ma trận loại câu × mức độ, trong một phạm vi chủ đề.\n\nYêu cầu nói ba điều, tách bạch nhau:\n\n**Phạm vi** — `topics` liệt kê chương và chủ đề được phép xuất hiện, nhận cả\nmẫu khớp nên một dòng viết được cả lớp: `g12.*`, cả chương:\n`g12.integrals.*`, hoặc đúng một bài. `exclude` và `skills` lọc thêm. Bỏ\ntrống thì lấy toàn kho.\n\n**Ma trận** — `types` nói mỗi loại câu lấy bao nhiêu và chia theo mức thế\nnào. Khai `levels` thì `count` là tổng của chúng, khai lệch thì báo lỗi chứ\nkhông đoán. Bỏ `levels` đi thì loại ấy lấy `count` câu ở mức nào cũng được.\n\n**Hạn ngạch** — `limits` giới hạn số câu **tối đa** mỗi phạm vi được góp.\nKhoá là tiền tố mã chủ đề: `g11` cho cả lớp, `g12.integrals` cho một chương.\nPhạm vi không nêu tên thì không bị giới hạn. Khi các hạn ngạch cộng lại đúng\nbằng tổng số câu thì chúng thành hạn ngạch đúng chứ không còn là trần.\n\nHai ràng buộc sau được giải **cùng lúc**, không phải bốc dần: nếu mức nhận\nbiết chỉ lớp 10 mới có mà lớp 10 lại bị giới hạn hai câu, hệ thống vẫn xếp\nđủ thay vì đi vào ngõ cụt. Câu được trải đều trên các chủ đề trong phạm vi,\ntrừ khi hạn ngạch buộc dồn.\n\nBỏ trống `seed` thì máy chủ tự bốc một seed và trả về trong kết quả — mỗi lần\ngọi ra một đề khác. Truyền lại đúng seed ấy cùng yêu cầu cũ thì vĩnh viễn ra\nlại nguyên bộ câu, nên chỉ cần lưu con số ấy là dựng lại được. Vị trí đáp án\nđúng của phần trắc nghiệm được phân bổ đều bốn vị trí.\n\nKết quả chia sẵn thành `sections` theo loại câu, xếp đúng thứ tự các phần của\nmột đề thi: trắc nghiệm, đúng–sai, trả lời ngắn, tự luận. Chỉ những loại có\ncâu mới xuất hiện.\n\nTrường `summary` đối chiếu xin gì nhận nấy theo từng loại, từng mức và từng\nhạn ngạch. Khi không gom đủ, `warnings` nói rõ thiếu ở ô nào và thiếu vì kho\nkhông có hay vì chính hạn ngạch bên gọi đặt ra — không bao giờ lặng lẽ trả ít\nhơn.","operationId":"questions_questions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuestionsPayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Questions Questions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/questions/re-render":{"post":{"tags":["Câu hỏi"],"summary":"Dựng lại câu hỏi","description":"Dựng lại câu hỏi từ mã khuôn, seed, loại câu và mức độ.\n\nTruyền đúng bộ đã lưu thì ra lại nguyên văn câu cũ. Truyền thêm `level` khác\nthì ra câu khác của cùng khuôn ở mức ấy — dùng khi muốn giữ dạng bài nhưng\nđổi độ khó.\n\nCâu nào hỏng thì báo ở `warnings` và bỏ qua, những câu còn lại vẫn trả về.","operationId":"re_render_questions_re_render_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenderPayload"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Re Render Questions Re Render Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"QuestionsPayload":{"properties":{"types":{"additionalProperties":{"$ref":"#/components/schemas/TypeQuota"},"type":"object","title":"Types","description":"Chỉ tiêu theo từng loại câu. Khoá thuộc single_choice | true_false | short_answer | essay","examples":[{"short_answer":{"count":6,"levels":{"nb":0,"th":0,"vd":3,"vdc":3}},"single_choice":{"count":12,"levels":{"nb":2,"th":2,"vd":5,"vdc":3}},"true_false":{"count":4,"levels":{"nb":0,"th":1,"vd":2,"vdc":1}}}]},"topics":{"items":{"type":"string"},"type":"array","title":"Topics","description":"Chương và chủ đề được phép xuất hiện, nhận cả mẫu khớp: `g12.*`, `g12.integrals.*`, hoặc đúng một mã chủ đề. Bỏ trống thì lấy toàn kho","examples":[["g10.*","g11.*","g12.*"]]},"exclude":{"items":{"type":"string"},"type":"array","title":"Exclude","description":"Chương và chủ đề loại trừ, cùng cú pháp với `topics`"},"skills":{"items":{"type":"string"},"type":"array","title":"Skills","description":"Khoá kỹ năng, nhận cả mẫu khớp. Bỏ trống thì không lọc"},"limits":{"additionalProperties":{"type":"integer"},"type":"object","title":"Limits","description":"Số câu **tối đa** mỗi phạm vi được góp. Khoá là tiền tố mã chủ đề: `g11` cho cả lớp, `g12.integrals` cho một chương, hoặc mã chủ đề đủ. Phạm vi không nêu tên thì không bị giới hạn. Khi các hạn ngạch cộng lại đúng bằng tổng số câu thì chúng thành hạn ngạch đúng chứ không còn là trần","examples":[{"g10":2,"g11":4,"g12":16}]},"seed":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Seed","description":"Bỏ trống thì máy chủ tự bốc một seed và trả về trong kết quả. Truyền lại đúng seed ấy cùng yêu cầu cũ thì ra lại nguyên bộ câu"},"include_answer":{"type":"boolean","title":"Include Answer","description":"Tắt thì bỏ hẳn đáp án và lời giải","default":true},"subject":{"type":"string","title":"Subject","description":"Mã môn học","default":"math"}},"type":"object","required":["types"],"title":"QuestionsPayload","description":"Yêu cầu lấy câu hỏi.","example":{"include_answer":true,"limits":{"g10":2,"g11":4,"g12":16},"topics":["g10.*","g11.*","g12.*"],"types":{"short_answer":{"count":6,"levels":{"nb":0,"th":0,"vd":3,"vdc":3}},"single_choice":{"count":12,"levels":{"nb":2,"th":2,"vd":5,"vdc":3}},"true_false":{"count":4,"levels":{"nb":0,"th":1,"vd":2,"vdc":1}}}}},"RenderItem":{"properties":{"id":{"type":"string","title":"Id","description":"Mã khuôn câu hỏi"},"seed":{"type":"integer","minimum":0.0,"title":"Seed"},"type":{"type":"string","title":"Type","description":"Loại câu: single_choice | true_false | short_answer | essay"},"level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Level","description":"Mức độ. Bỏ trống thì dựng đúng câu cũ; truyền mức khác thì ra câu khác cùng khuôn ở mức ấy. Loại câu không ra được mức xin thì báo lỗi chứ không trả về mức khác"}},"type":"object","required":["id","seed","type"],"title":"RenderItem","description":"Một câu cần dựng lại."},"RenderPayload":{"properties":{"questions":{"items":{"$ref":"#/components/schemas/RenderItem"},"type":"array","maxItems":500,"minItems":1,"title":"Questions"},"include_answer":{"type":"boolean","title":"Include Answer","default":true}},"type":"object","required":["questions"],"title":"RenderPayload","description":"Yêu cầu dựng lại nhiều câu từ mã và seed.","example":{"include_answer":true,"questions":[{"id":"g12.integrals.computing-integrals.monomial-integral","seed":84213,"type":"single_choice"},{"id":"g12.integrals.computing-integrals.monomial-integral","level":"VD","seed":84213,"type":"single_choice"}]}},"TypeQuota":{"properties":{"count":{"anyOf":[{"type":"integer","maximum":200.0,"minimum":0.0},{"type":"null"}],"title":"Count","description":"Số câu của loại này. Bỏ trống thì lấy bằng tổng của `levels`"},"levels":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Levels","description":"Chia theo mức độ, khoá nhận cả chữ hoa lẫn chữ thường: nb | th | vd | vdc. Bỏ trống thì không ràng buộc mức","examples":[{"nb":2,"th":2,"vd":5,"vdc":3}]}},"type":"object","title":"TypeQuota","description":"Chỉ tiêu của một loại câu: tổng số câu và cách chia theo mức độ."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"APIKeyHeader":{"type":"apiKey","description":"Khoá API do bên quản trị cấp. Cũng nhận qua header `Authorization: Bearer <khoá>`.","in":"header","name":"X-API-Key"}}}}