Skip to content

Recommendation Engine ​

"Mở app ra thì hôm nay làm gì?" — biến 154 bài học thành một việc nên làm ngay, kèm lý do.

Thiết kế: SDD-002 §10 · Code: modules/knowledge/routes.ts → buildCockpit() · Workflow: WF-05


1. Chưa phải một module riêng ​

Đọc trước khi đi tìm file

Không có modules/recommendation/. Recommendation Engine hôm nay chạy inline trong buildCockpit() — cùng hàm với Readiness, trong modules/knowledge/routes.ts.

Nó cũng không sinh model: không có recommendation trong MODEL_KINDS, không có version, không có snapshot. Mỗi lần learner mở màn hình là tính lại từ đầu.

Vì sao hiện tại như vậy: recommendation là hàm thuần của trạng thái. Cùng mastery + cùng goal + cùng ngày → cùng kết quả. Không có gì để lưu mà đọc lại không suy ra được.

Cái mất đi vì không lưu: không trả lời được "hôm thứ Ba hệ thống đã khuyên con làm gì, và vì sao?". SDD-002 §10 yêu cầu lưu reason + input_model_version + algorithm_version — xem §7 khoảng trống.


2. Công thức: SDD nói một đằng, code làm một nẻo ​

Đây là khác biệt lớn nhất giữa thiết kế và hiện trạng, nên nói thẳng.

SDD-002 §10 thiết kế:

Priority = ExpectedImpact × GoalRelevance × Urgency × PrerequisiteImportance
         × ProbabilityOfSuccess ÷ Cost   (+ ConfidenceNeed khi thiếu data)

Code đang chạy:

ts
priority = round2(gap.weight × gap.deficit)
Thừa số thiết kếHiện trạng
ExpectedImpact✅ chính là weight × deficit
GoalRelevance✅ ngầm — gap chỉ lấy từ blueprint mục tiêu
PrerequisiteImportance⚠️ có, nhưng dưới dạng đổi loại hành động (§3) chứ không nhân vào điểm
Urgency⚠️ chỉ với review (từ Retention) và exam_practice
ProbabilityOfSuccess❌ chưa có
Cost❌ chưa có (estimated_minutes chỉ có ở review)
ConfidenceNeed⚠️ có, dưới dạng loại assess

Nói cách khác: v1 giữ đúng phần cốt lõi (tác động × liên quan mục tiêu) và bỏ phần chưa đo được. ProbabilityOfSuccess cần mô hình dự đoán chưa có; Cost cần ước lượng thời gian mỗi node mà curriculum chưa gắn đủ.

Ghi ra đây vì đọc SDD rồi đi tìm công thức 6 thừa số trong code sẽ không thấy — và tưởng là bug.


3. Thuật toán thật — 5 bước ​

Bước 1 — Chọn blueprint mục tiêu (có fallback) ​

learner_goals active của môn        → dùng
không có → blueprint kind='conditional' mặc định của môn
không có nữa → KHÔNG có recommendation

Fallback là lý do learner chưa khai mục tiêu vẫn thấy việc để làm. Khác với Readiness Model (không fallback, chỉ ghi sự thật).

Bước 2 — Từ top 6 gap, chọn loại hành động ​

Đây là chỗ PrerequisiteImportance sống — không phải bằng cách nhân điểm mà bằng cách đổi việc:

gap.node có prereq mastery < 0.5      → fix_prerequisite   (đề xuất HỌC PREREQ, không phải node gap)
gap.node chưa từng đo (!mastery.has)  → assess             (làm bài chẩn đoán ngắn)
gap.severity === "critical"           → learn
còn lại                                → practice

Đề xuất prerequisite thay vì node gap là quyết định sư phạm quan trọng nhất của engine. Nếu học sinh yếu "Hàm số" vì chưa vững "Hệ số góc", luyện thêm bài Hàm số là lãng phí thời gian và làm em ấy tin rằng mình dốt. Engine trả về node prereq với lý do nói rõ nó đang chặn cái gì.

Mỗi hành động mang reason viết bằng tiếng người, nói bằng tên bài:

"Cần vững "Hệ số góc" trước — đang chặn "Hàm số & đồ thị""
"Chưa đủ bằng chứng về "Phân tích đa thức" — làm bài chẩn đoán ngắn"
"Củng cố "Hằng đẳng thức" (mastery 35%, cần 80%)"

Bước 3 — Dedupe, cắt còn 4 ​

Sắp theo priority giảm dần → giữ node_id đầu tiên gặp → lấy 4. Cùng một node xuất hiện từ nhiều gap thì chỉ vào danh sách một lần, với priority cao nhất.

Bước 4 — Chèn Review từ Retention ​

ts
const reviews = await topReviewCandidates(env, learnerId, subjectId, 2);
priority = r.urgency === "CRITICAL" ? Math.max(r.priority, 4) : r.priority

Hai quyết định về thang điểm chung (SDD-017 §8):

  • Review dùng cùng thang với gap action (weight × deficit, trần ~3.2) để được xếp cạnh chứ không đè bẹp Learn/Repair. Nếu review dùng thang riêng cao hơn, mọi buổi học sẽ biến thành ôn tập.
  • CRITICAL có sàn 4 để nổi lên trên khi thật sự gấp — sắp quên một thứ đang chặn nhiều bài và sắp thi thì phải thắng.

Retention lỗi → catch + log retention_cockpit_degraded, cockpit vẫn trả về bình thường (QG-009). Mất phần ôn còn hơn mất cả màn hình học.

Sau đó sắp lại và cắt còn 5.

Bước 5 — Gần thi thì luyện đề lên đầu ​

ts
if (mode.mode === "exam_prep") nextActions.unshift({ type: "exam_practice", priority: 999 });

priority: 999 = luôn đứng đầu, không cạnh tranh. Đây không phải điểm số mà là một lệnh ghi đè có chủ đích: còn ≤30 ngày tới kỳ thi thì việc đúng là luyện đề, bất kể gap map nói gì.

Xem study mode để biết exam_prep được xác định thế nào.


4. Loại hành động — thật vs thiết kế ​

LoạiSDD-002 §10Đang sinh
fix_prerequisite✅✅
assess (Diagnostic)✅✅
learn✅✅
practice✅✅
review✅✅ (từ Retention)
exam_practice (Prepare Exam)✅✅
Skip (học vượt)✅❌ chưa sinh trong cockpit — có ở Phòng Lab dưới dạng Assessment Experience
Ask Mentor✅❌
Rest✅❌

Rest đáng chú ý nhất trong ba cái còn thiếu. Một hệ thống chỉ biết nói "học tiếp" thì không bao giờ nói được "hôm nay nghỉ đi" — trong khi Planning Engine đã biết cắt giờ khi có lời khai ốm/stress (learning-plan §4). Hai bên chưa nối.


5. Bốn engine đề xuất khác trong hệ thống ​

"Recommendation" xuất hiện ở nhiều nơi với nghĩa khác nhau. Bảng này để không ai đi tìm nhầm chỗ:

EngineTrả lờiCho aiỞ đâu
Cockpit next actions (trang này)"Hôm nay học bài nào?"Learnerknowledge/routes.ts
Parent Recommendation (WF-12)"Tuần này bố mẹ nên làm gì — và không nên làm gì?"Phụ huynhparent/routes.ts
Need Signal"Bài này có cần học không?"Learnerlearning/routes.ts
Whale recommendations"Học bổng/câu chuyện nào đáng xem?"Learnerwhale/routes.ts
Orca fit"Con có nên thi cuộc này không?"Learner + phụ huynhorca/routes.ts

Whale — gợi ý tò mò, rule-based ​

3–5 gợi ý, mỗi cái bắt buộc có reason:

2 học bổng nổi bật     → "Học bổng đáng biết sớm — em đang lớp 8, còn thời gian chuẩn bị hồ sơ"
2 câu chuyện khớp school → "Hành trình có thi chuyên giống con đường em đang đi"
1 cơ hội theo school     → shark → competition · squid → program · còn lại → summer

Không có model, không có điểm số — chủ đích là khơi tò mò, không phải chấm điểm sự phù hợp.

Orca — fit score có verdict 4 mức ​

score = eligible×0.25 + skill×0.30 + schedule×0.25 + intensity×0.20
        (eligible = 0 hoặc quá hạn đăng ký → score bị ép ≤ 25)

≥80 Strongly Recommended · ≥65 Recommended · ≥45 Optional · <45 Not Recommended Now

"Not Recommended Now" là output hợp lệ và hữu ích — cùng tinh thần với dont_do của Parent Recommendation. Mỗi verdict kèm reasons[] nói rõ vì sao: ngoài độ tuổi, hết hạn đăng ký, mức cạnh tranh cao.

Đây vẫn là fit v1 tất định (SDD-016 §3); bản đầy đủ sẽ nối vào Recommendation Engine chính.


6. Cách DÙNG ​

NơiEndpoint
learn tab "Hôm nay"GET /v1/learners/{id}/cockpit?subject_id=
Sau khi nộp bài chẩn đoánPOST /v1/diagnostic/{sessionId}/submit trả luôn cockpit
Parent (WF-12)buildCockpit() được Parent Recommendation gọi lại để lấy gap + mode

Cockpit trả kèm hai tín hiệu quan trọng ngoài next_actions:

TrườngÝ nghĩa
evidence_reliability: "insufficient"Bằng chứng quá mỏng — UI phải nói rõ đây là phỏng đoán
item_countMôn có bao nhiêu câu hỏi thật. 0 câu → bài chẩn đoán là cánh cửa khoá chặt: bấm vào không có gì để làm, mà không bấm thì không vào được môn (SRC-189)

US-03 đặt ràng buộc UI: tab "Hôm nay" hiện đúng một nút hành động chính — 5 việc trong next_actions là để hệ thống chọn, không phải để bày hết ra cho learner tự chọn lại.


7. Khoảng trống đã biết ​

ViệcTrạng thái
Lưu reason + input_model_version + algorithm_version mỗi recommendation (SDD-002 §15)❌ chưa — không truy được "hôm đó hệ thống khuyên gì"
ProbabilityOfSuccess, Cost trong công thức priority❌ chưa đo được
Loại Rest, Ask Mentor, Skip trong cockpit❌ chưa sinh
Dùng Learning Plan để chọn action⏳ hai đường còn độc lập — plan chia phút, cockpit tự chọn bài
Tách thành modules/recommendation/ với engine thuần + test riêng⏳ hiện inline, chưa có file test riêng
Global Orchestrator quyết định cuối giữa các school engine (SDD-007 §9)🕓 hiện chỉ một môn một lần

Khoảng trống đầu bảng là nghiêm trọng nhất: mọi model khác đều xem lại được lịch sử, riêng recommendation thì không. Khi phụ huynh hỏi "tuần trước hệ thống bảo con học gì?", hôm nay không có câu trả lời.

8. Bất biến — vi phạm là bug ​

  1. Mọi action phải có reason viết bằng tên bài, không bằng node_id hay số mastery thô.
  2. Prereq chưa vững → đề xuất prereq, không đề xuất node gap.
  3. Review dùng cùng thang điểm với gap action — không được đè bẹp Learn/Repair.
  4. Retention lỗi không được chặn cockpit.
  5. exam_prep → luyện đề đứng đầu, bất kể gap map.
  6. Dedupe theo node_id — không đề xuất cùng một bài hai lần.
  7. Tối đa 5 action.

Trace ​