Quality Engine
Cổng cuối cùng trước khi một câu hỏi tới tay đứa trẻ. Assessment Engine lo "đo có đáng tin không"; engine này lo "cái đem ra đo có xứng đáng không".
Thiết kế: SDD-003 §6–§10 · Code: modules/content/quality.ts · screening.ts · lifecycle.ts · review-queue.ts · Test: quality.test.ts (22 ca) · Rubric: CC-QAF-1.0 (kế thừa chuyenchon)
1. Bốn lớp, không lớp nào thay được lớp nào
1. Sàng lọc máy screening.ts 12 luật cấu trúc, tất định, rẻ
2. Chấm rubric quality.ts 8 chiều × ≥2 evaluator độc lập, mỗi chiều kèm bằng chứng
3. Cổng publish quality.ts 6 điều kiện SỐ, trả lý do bằng tiếng người
4. Vòng phản hồi review-queue.ts learner báo sai → hàng đợi người soát → item lùi khỏi publishedLớp 1 bắt lỗi hình thức (đáp án ngoài phạm vi, lựa chọn trùng). Lớp 2 bắt lỗi nội dung (sai kiến thức, đoán được đáp án mà không cần biết bài). Lớp 3 quyết định. Lớp 4 sửa những gì ba lớp trên bỏ sót — vì sẽ luôn có.
2. Lớp 1 — Sàng lọc máy (12 luật)
Chặn (blocking) — 9 luật. Sai cấu trúc thì không có điểm số nào cứu được:
| Mã | Nghĩa |
|---|---|
options_unparsable | options_json không parse được |
options_too_few | ít hơn 2 lựa chọn |
options_duplicate | hai lựa chọn trùng nhau |
option_empty | có lựa chọn rỗng — trên màn hình là một nút trắng không đọc được |
correct_index_out_of_range | correct_index ngoài phạm vi |
prompt_empty / prompt_too_short | đề bài rỗng hoặc dưới 10 ký tự |
prompt_duplicate_conflicting | trùng đề và trùng lựa chọn nhưng khác đáp án đúng → chắc chắn một bản sai |
node_missing | node_id trỏ tới skill node không tồn tại → không biết dạy/đo kỹ năng nào |
Cảnh báo (warn) — 3 luật: prompt_duplicate, difficulty_out_of_range, discriminator_missing_misconception.
Ba cái bẫy đã bịt (có test riêng)
| Bẫy | Luật đúng |
|---|---|
| Lựa chọn chỉ khác hoa/thường | Không phải trùng — chính chữ hoa có thể là nội dung được hỏi (chính tả, tên riêng) |
| Cùng đề, khác bộ lựa chọn | Là họ câu biến thể hợp lệ → chỉ warn |
| Cùng đề, cùng lựa chọn, khác đáp án | Blocking — chắc chắn một bản sai |
Ba bẫy này là lý do sàng lọc phải là code có test, không phải một câu prompt gửi cho LLM.
3. Lớp 2 — Rubric CC-QAF, 8 chiều
Đúng 8 chiều theo SDD-003 §6 — không thêm, không bớt, không đổi tên khoá:
| Chiều | Nhãn | Trọng số |
|---|---|---|
accuracy | Chính xác | 3 |
assessment_validity | Giá trị đo lường | 2 |
clarity | Rõ ràng | 1.5 |
pedagogy | Sư phạm | 1.5 |
alignment | Bám chương trình | 1 |
cognitive_demand | Mức tư duy | 1 |
accessibility | Tiếp cận được | 1 |
engagement | Cuốn hút | 0.5 |
Trọng số nói lên thứ tự giá trị: sai kiến thức hoặc không đo được gì thì mọi chiều khác vô nghĩa. Một câu cuốn hút mà sai kiến thức là câu tệ hơn một câu khô khan mà đúng.
Evidence-gated scoring — luật gốc của rubric
score > 0 && evidence.trim() === ""
→ score = 0, reason += "· điểm bị hạ về 0: chấm không kèm evidence"Điểm trần không lý do là cách nhanh nhất để "đạt chuẩn" mà nội dung vẫn sai.
Mỗi DimensionScore bắt buộc có reason (vì sao chấm mức đó) và evidence (trích từ chính item). Ví dụ evaluator tất định chấm pedagogy:
score: 0.9
reason: "Có ghi lỗi sai học sinh hay mắc → sai còn học được điều gì đó"
evidence: 'misconception: "Nhầm dấu khi chuyển vế"'Hai loại evaluator
Evaluator 1 — tất định (rule-engine@1). Không gọi AI, chạy được mọi lúc, kết quả tái lập được. Suy điểm từ những gì máy đo được: độ dài đề bài, có misconception không, role/difficulty, chênh lệch độ dài các lựa chọn.
Một luật đáng chú ý trong assessment_validity:
chênh lệch độ dài lựa chọn > 60 ký tự → 0.55
"Đáp án đúng dài hơn hẳn các phương án nhiễu — learner đoán được mà không cần biết bài"Evaluator 2 — ngữ nghĩa (LLM), qua AI Gateway. Đây là chỗ tinh tế nhất:
Chấm hai lần cùng một câu hỏi thì chỉ là cùng một ý kiến nói to hơn, không phải multi-evaluator.
Nên hai lượt chấm dùng hai góc nhìn khác nhau (AS-10.4.2):
| Góc nhìn | Soi gì |
|---|---|
teacher — giáo viên chấm đề | Đúng sai kiến thức, chất lượng phương án nhiễu, câu có đo đúng kỹ năng cần đo |
struggling_learner — học sinh yếu đọc thử | Có hiểu được không, có mẹo đoán đáp án mà không cần biết bài không, chỗ nào gây rối |
Góc nhìn thứ hai bắt được loại lỗi mà giáo viên hay bỏ sót: câu đúng về kiến thức nhưng đoán được.
SDD-003 §7.3 cấm hỏi chung "câu này có tốt không" — phải hỏi từng chiều kèm bằng chứng. Output AI qua zod schema trước khi vào profileFromRubricScores().
4. Lớp 3 — Cổng publish, 6 điều kiện bằng số
Ngưỡng khai tập trung, đổi thì đổi một chỗ (AS-06.3.3):
PUBLISH_MIN_QUALITY_SCORE 0.72 điểm tổng hợp
PUBLISH_MIN_DIMENSION_SCORE 0.50 không chiều nào được thủng sàn
PUBLISH_MIN_EVALUATORS 2 lượt đánh giá ĐỘC LẬP
PUBLISH_MAX_EVALUATOR_SPREAD 0.25 chênh điểm tổng giữa các evaluator
PUBLISH_MAX_DIMENSION_SPREAD 0.40 chênh trên MỘT chiều
REVIEW_QUEUE_QUALITY_SCORE 0.55 dưới mức này đẩy thẳng vào hàng đợi ngườiSáu điều kiện, thiếu bất kỳ cái nào là không qua:
- Sàng lọc máy không còn lỗi chặn — cờ chặn thắng mọi điểm số
- Đủ ≥2 evaluator độc lập
- Điểm tổng ≥ 0.72
- Các evaluator không lệch nhau quá 0.25 — lệch nhiều nghĩa là cần người soát, không phải lấy trung bình
- Không chiều nào lệch quá 0.40 giữa các evaluator
- Mỗi evaluator chấm đủ 8 chiều; và trung bình từng chiều ≥ 0.5
Điều kiện 6 bịt một lỗ thật: điểm tổng cao vẫn có thể che một chiều bằng 0. Câu sai kiến thức nhưng rõ ràng, cuốn hút, dễ đọc vẫn có thể vượt 0.72 nếu chỉ nhìn điểm tổng.
Trả lý do, không trả true/false
{
"ok": false,
"reasons": [
"Mới có 1 lượt đánh giá, cần tối thiểu 2 lượt độc lập",
"Chiều \"Giá trị đo lường\" chỉ 0.4 < sàn 0.5"
]
}Coral hiện thẳng những câu này cho người soạn. Một cổng chỉ nói "không được" mà không nói vì sao thì người soạn chỉ có thể đoán.
5. Vòng đời nội dung — không ai sửa thẳng vào items
candidate → đang chờ đủ điểm + đủ evaluator
published → bản đang nằm trong `items`, đang phục vụ learner
superseded → từng published, bị version mới thay
rejected → người soát bác bỏLuật gốc: bảng items là bản đang phục vụ learner, không ai sửa thẳng vào đó. Mọi thay đổi tạo một dòng mới trong item_versions ở trạng thái candidate; publish là hành động sao chép version đã đạt ngưỡng sang items; rollback là publish lại một version cũ.
Nhờ vậy luôn trả lời được: nội dung này version mấy, ai duyệt, lúc nào, dựa trên hồ sơ chất lượng nào.
SDD-003 §10 thiết kế 7 trạng thái (Draft → Review → Candidate → Evaluated → Approved → Published → Deprecated); schema 0032 rút về 4 — đủ để phân biệt những gì hệ thống thật sự cần hành xử khác nhau.
6. Lớp 4 — Vòng phản hồi từ learner
"Learner báo sai thì đi về đâu" — audit #001 chấm trượt đúng vì báo cáo rơi vào hư vô.
Hai nguồn đổ vào content_review_queue:
| Nguồn | Từ đâu |
|---|---|
| Người báo | content_reports (forum/interaction) + nút báo sai trong app learn |
| Máy sàng lọc | scripts/screen-items.mjs + screening.ts trong luồng sinh nội dung |
Một target chỉ nằm một dòng khi còn mở: 5 learner báo cùng một câu thì người soát thấy một việc, không phải năm việc. Lý do mới được nối thêm vào dòng đang mở.
Trạng thái: open → in_review → resolved / dismissed.
Hệ quả tức thì khi một item bị gắn cờ: nó lùi khỏi published và không tới tay đứa trẻ tiếp theo. Đứa trẻ đang làm dở thì vẫn làm hết bài, nhưng câu đó không ghi evidence (Learning §5).
Spot-check bởi người — 20%, chọn bằng hash
HUMAN_SPOT_CHECK_RATE = 0.2
isSpotCheckSelected(key) → FNV-1a hash % 1000 / 1000 < rateChọn bằng hash nên tái lập được: cùng một item luôn cho cùng kết luận, và đếm lại được "đã spot-check bao nhiêu %" từ chính id.
Máy chấm đạt ngưỡng vẫn không thay được việc người đọc thử một phần nội dung (AS-10.4.3).
7. Cách DÙNG
| Nơi | Endpoint |
|---|---|
| Sinh item có chấm chất lượng | POST /v1/coral/generate-items |
| Sinh cả bộ theo blueprint | POST /v1/coral/blueprints/{id}/generate |
| Hàng đợi rà soát | GET /v1/coral/review-queue · PATCH /v1/coral/review-queue/{id} |
| Xem prompt đang dùng | GET /v1/coral/prompts |
Giao diện: coral.nemo12.com. Rate limit ai-generate: 30 lượt/giờ/user (config §4).
quality_json lưu nguyên từng lượt chấm, không nuốt evidence — để sau này trả lời được "ai chấm, dựa vào cái gì".
8. Bất biến — vi phạm là bug
- Điểm > 0 mà không có evidence → hạ về 0, ghi lý do.
- Đủ 8 chiều hoặc không tính là hồ sơ hoàn chỉnh.
- ≥2 evaluator độc lập, và phải khác góc nhìn.
- Cờ chặn của sàng lọc máy thắng mọi điểm số.
- Evaluator lệch nhau nhiều → chặn, không lấy trung bình cho qua.
- Trung bình từng chiều phải ≥ sàn — điểm tổng cao không che được một chiều 0.
- Không ai sửa thẳng vào
items— luôn quaitem_versions. - Cổng publish trả lý do, không trả true/false trần.
- Báo cáo của learner không được rơi vào hư vô.
9. Kiểm chứng
quality.test.ts — 22 ca, gồm ba ca "bẫy" ở §2 và những ca khoá cổng publish:
- evaluator tất định chấm đủ 8 chiều, đúng rubric version
- mỗi chiều kèm evidence + reason, không chỉ điểm số trần
- chấm điểm dương mà evidence rỗng → hạ về 0
accuracyđược cân nặng nhất — sai kiến thức thì mọi chiều khác không cứu nổi- một evaluator là chưa đủ, dù điểm cao
- hai evaluator lệch nhau quá nhiều thì chặn dù điểm trung bình đẹp
- thiếu một chiều là hồ sơ chưa hoàn chỉnh → chặn
- cờ chặn của sàng lọc máy thắng mọi điểm số
Gate: QG-006.
10. Khoảng trống đã biết
| Việc | Trạng thái |
|---|---|
Evaluator learner_evidence — dùng kết quả làm bài thật để chấm lại chất lượng câu | ⚠️ kiểu đã khai trong evaluator_kind, chưa có luồng nào sinh |
Evaluator graph — kiểm tính nhất quán với skill graph | ⚠️ như trên |
| Kho câu mỏng (trung bình 8,5 câu/bài) | ⚠️ gốc là thiếu nội dung, không phải lỗi engine |
| Spot-check 20% có bảng theo dõi "đã soát bao nhiêu" | ⏳ hàm chọn đã có, chưa có báo cáo độ phủ |
| Quality profile cho lab và đề thi | ⏳ hiện rubric chạy trên items |
Dòng đầu đáng chú ý nhất: learner_evidence là evaluator mạnh nhất có thể có — 200 đứa trẻ làm một câu nói lên nhiều hơn hai lượt chấm. Dữ liệu đã có trong assessment_responses; chưa có luồng nối ngược về quality profile.
Trace
- REQ-KNW-07/08/09 (quality profile, multi-evaluator, lifecycle).
- Nguồn: SRC-004 (Knowledge & Quality), SRC-105 · Audit #001 (AS-06.2.x, AS-06.3.x, AS-10.4.x).
- Thiết kế: SDD-003 §6/§7/§8/§10/§13, SDD-013.
- Kiểm chứng: QG-006; AI governance: QG-010.
- Liên quan: Assessment Engine · Learning Engine · AI Registry · State Machines §9.