---
url: https://docs.nemo12.com/reference/engines.md
description: >-
  Engine Reference: logic và thuật toán từng engine theo khuôn đầu vào, công
  thức thật, đầu ra, cùng luật deterministic chung.
---

# Engine Reference

Mỗi engine ở đây được mô tả theo đúng một khuôn: **đầu vào → thuật toán (công thức thật) → đầu ra**, kèm đường dẫn file để đối chiếu.

Luật chung cho mọi engine của Nemo12:

1. **Deterministic** — cùng input cho cùng output. Không engine nào trong danh sách này gọi LLM (ngoại lệ duy nhất: §8 Content Quality, và nó không chạm Learner Model).
2. **Engine thuần tách khỏi I/O** — hàm tính toán nằm ở `engine.ts` (không đọc DB, test được thẳng); phần đọc/ghi DB nằm ở `service.ts`.
3. **Mọi lần chạy đều để lại dấu** — qua `runEngine()`, ghi vào `engine_runs` kể cả khi output không đổi.
4. **Không engine nào được làm hỏng nghiệp vụ gọi nó** — hot path dùng `runModelsSafely()`, lỗi chỉ log `model_update_degraded`.
5. **Cấm test bằng regex source** (RISK-015) — chỉ test hành vi.

| # | Engine | File | Version | LLM |
| --- | --- | --- | --- | --- |
| 1 | Mastery & Confidence | `modules/knowledge/mastery.ts` | — (inline) | không |
| 2 | Goal | `modules/goals/engine.ts` | `goal-v1` | không |
| 3 | Learner Model | `modules/models/service.ts` | `learner-v2` | không |
| 4 | Context | `modules/models/service.ts` | `context-v1` | không |
| 5 | Readiness | `modules/knowledge/mastery.ts` + service | `readiness-v1` | không |
| 6 | Retention | `modules/retention/engine.ts` | `retention-v1` | không |
| 7 | Need Signal (chọn bài) | `modules/learning/routes.ts` | — (inline) | không |
| 8 | Content Quality | `modules/coral/routes.ts` | — | **có** (AI Gateway) |

***

## 1. Mastery & Confidence Engine

**Vì sao thế này**: thuật toán được port **nguyên vẹn** từ `assess-engine.ts` của chuyenchon — đã kiểm chứng trên học sinh thật, không phải phát minh lại.

### Đầu vào

| Trường | Nguồn |
| --- | --- |
| `prev` | `learner_skill_state`: `{ mastery, confidence, evidence_count }` — `null` nếu chưa từng đo |
| `performance` | 1 đúng / 0 sai |
| `difficulty` | 0–1, thuộc tính của item |
| `reliability` | 0–1, từ `attemptReliability()` |

### Thuật toán

```
surprise    = correct ? difficulty : (1 − difficulty)
decay       = 1 / (1 + 0.6 × prevCount)
lr          = (0.35 + 0.35 × surprise) × decay × reliability
mastery'    = clamp01(old + lr × (outcome − old))
evidence'   = prevCount + (reliability ≥ 0.15 ? 1 : 0)
confidence' = clamp01(1 − 0.55^evidence')
```

Đọc bằng lời:

* **`surprise`** — đúng một câu khó, hoặc sai một câu dễ, là thông tin lớn; đúng câu dễ gần như không nói lên điều gì.
* **`decay`** — câu đầu tiên dịch chuyển model mạnh nhất; câu thứ 20 gần như không. Model **càng biết nhiều càng khó bị lung lay**.
* **`confidence`** — chỉ phụ thuộc **lượng** bằng chứng, hoàn toàn độc lập với mastery. Học sinh giỏi mới làm 1 câu vẫn có confidence thấp.
* Prior khi chưa có gì: `mastery = 0.5`, `confidence = 0`.

### Reliability của một lần trả lời (`attemptReliability`)

Chống rác đầu vào từ đoán bừa (REQ-INT-21/22, SDD-002 §18):

| Tình huống | reliability |
| --- | --- |
| Bấm "Chưa biết" (trung thực) | 0.7 |
| Không đo được thời gian | 1.0 |
| ≥ 2500 ms (`RAPID_MS`) | 1.0 |
| < 2500 ms, chuỗi rapid 1–2 | 0.4 |
| < 2500 ms, chuỗi rapid ≥ 3 | **0.05** — gần như không dịch chuyển model, và **không tính là "đã đo"** |

### Đầu ra

`{ mastery, confidence, trajectory, evidence_count, observed_performance }`, trong đó `trajectory` = `improving` (+0.02) / `stable` / `declining` (−0.02) / `unknown` (lần đầu).

### Trạng thái hiển thị (`stateFor`)

```
confidence < 0.25             → unknown     "Đang làm quen"
mastery ≥ 0.8                 → chac        "Chắc rồi"
mastery ≥ 0.5                 → lung_lay    "Đang lung lay"
còn lại                       → hong        "Cần xây lại từ gốc"
```

Confidence thấp **luôn thắng**: chưa đủ bằng chứng thì không được phép kết luận "hổng".

***

## 2. Goal Engine

`buildGoalModel(input)` — thuần, không I/O.

### Đầu vào

`examTargets` (đích thi đã khai, chỉ `status='active'`) · `examSchedule` (lịch thi học kỳ) · `enrollments` · `contextBlueprint`.

### Thuật toán

**Bước 1 — Exam target → 2 goal.** Mỗi đích thi sinh một goal gốc `exam_school|graduation|ielts|sat|ap` và một goal con `subject_readiness` cho môn chuyên. Môn không chuyên là **thuộc tính** của goal gốc, không sinh goal riêng (Q-102).

**Bước 2 — Lịch thi học kỳ → goal `semester_exam`** (`priority = 1`).

**Bước 3 — Enrollment → goal nền**, **chỉ khi chưa có goal nào**. Có mục tiêu thật rồi thì không thêm mục tiêu giả.

**Bước 4 — Horizon & urgency** theo số ngày còn lại:

```
horizon:  ≤14 ngày → operational · ≤90 → tactical · >90 → strategic · không deadline → undated
urgency:  quá hạn → 1 · ≥365 ngày → 0.05 · còn lại → 1/(1 + days/30)
```

Hàm `1/(1+d/30)` dốc hẳn lên trong 30 ngày cuối — đó là lý do **thi học kỳ 5 ngày nữa thắng kỳ thi chuyên 6 tháng nữa**, dù kỳ thi chuyên "to" hơn.

**Bước 5 — Conflict detection.** Engine **phát hiện chứ không tự giải quyết** (Q-099):

| Loại | Điều kiện |
| --- | --- |
| `deadline_passed` | `days_to_deadline < 0` |
| `time_competition` | hai goal gốc có deadline cách nhau ≤ 7 ngày |

Xung đột được escalate cho Planning/phụ huynh — hệ thống không tự ý bỏ mục tiêu của ai.

**Bước 6 — Sắp xếp**: `urgency` giảm dần → `priority` tăng dần → `key`.

### Đầu ra

`{ algorithm_version, computed_at, goals[], conflicts[], summary }`. `summary.empty = true` là hợp lệ.

***

## 3. Learner Model Engine

**Vào**: `learner_skill_state` join `skill_nodes`, tổng hợp `learner_evidence` (kèm phân bố theo `type`/`source`), đếm node graph theo môn, `assessment_sessions`/`assessment_responses` (behavior), hồ sơ `learners`, Goal Model version mới nhất.
**Ra**: [Learner Model](models.md#2-learner-model-learner-v2).

Điểm cần biết: gom theo môn → tính `avg_mastery`/`avg_confidence` **chỉ trên node đã có evidence**; `coverage` = nodes_assessed / **cả graph của môn** (SRC-528); `strengths` = top 5, `gaps` = bottom 5; model confidence = `1 − 0.9^(số evidence)`. Trường mới nào cũng phải **đổi theo dữ liệu, không theo đồng hồ** — vì vậy không có `freshness_days`, và `avg_response_ms` làm tròn bậc 100ms.

***

## 4. Context Engine

**Vào**: `learner_context_models`, `school_enrollments`, `learners`, goal urgent nhất (`ORDER BY urgency DESC, priority ASC LIMIT 1`).
**Ra**: [Context Model](models.md#3-learner-context-model-context-v1) — chỉ giữ `active_goal_ref`, không sao chép goal.

***

## 5. Readiness Engine

`computeReadiness(weights, mastery, maxScore, cutScore)`.

### Thuật toán

```
với mỗi node trong blueprint:
    threshold = REQUIRED_THRESHOLD[required_state]     // chac 0.8 · lung_lay 0.5 · hong 0 · unknown 0
    achieved  = threshold > 0 ? clamp01(mastery / threshold) : 1
    achievedWeight += weight × achieved
    nếu mastery < threshold → ghi vào gaps { weight, deficit }

ratio           = achievedWeight / totalWeight
estimated_score = ratio × max_score
on_track        = estimated_score ≥ cut_score
```

**Xếp hạng gap theo `weight × deficit`** — không theo mức yếu. Một node yếu nhẹ nhưng trọng số cao có thể quan trọng hơn node hổng nặng mà đề gần như không hỏi. Top 1/3 = `critical`, giữa = `important`, còn lại = `nice_to_have`.

`largest_uncertainty` xếp theo `weight × (1 − confidence)`, lấy node có `confidence < 0.5` — đây là danh sách "cần đo", khác hoàn toàn danh sách "cần học".

***

## 6. Retention Engine

Mô hình: **R(t) = R₀ × exp(−Δt / S)**, S = stability tính bằng ngày. Truy xuất thành công làm S **tăng** (quên chậm lại); thất bại làm S **co** (nhắc dày hơn).

### Hằng số (`CFG`)

| Nhóm | Giá trị |
| --- | --- |
| Stability | `S_MIN 3` · `S_MAX 365` · `S₀ = 6 + 14 × mastery` |
| Review Ladder | `T_NONE .85` · `T_LIGHT .75` · `T_ACTIVE .6` · `T_RELEARN .4` |
| Review window | `W_EARLIEST .85` · `W_IDEAL .75` · `W_LATEST .65` |
| Ngưỡng khác | `MASTERED_MIN .6` · `R_CAP .98` · probe khi `R < .75` và `conf < .4` |

### Phân tầng bằng chứng (`evidenceTier`)

```
reliability < 0.15                    → exposure    (đoán bừa — không kết luận nhớ hay quên)
sai                                   → failed
reliability < 0.6                     → assisted
đúng và Δt ≥ S (spacingRatio ≥ 1)     → transfer    (bằng chứng mạnh nhất)
còn lại                               → independent
```

### Cập nhật (`applyEvidence`)

| Tier | Retention | Stability |
| --- | --- | --- |
| `exposure` | `+0.05 × w × spacingWeight` | `× (1 + 0.05 × spacingWeight)` |
| `failed` | kéo về `min(R×0.5, 0.45)` theo `w` | `× (1 − 0.5 × spacingWeight × w)` |
| `assisted` | tiến tới sàn 0.8 | `× 1.25` |
| `independent` | tiến tới sàn 0.9 | `× (1.5 + 0.5×min(sr,2))` |
| `transfer` | tiến tới sàn 0.94 | `× (1.8 + 0.6×min(sr,2))` |

Hai lớp giảm chấn quan trọng:

* **`spacingWeight = min(1, elapsedDays)`** — làm lại cùng ngày gần như không đổi stability. Cày 50 câu trong một buổi **không** tạo trí nhớ dài hạn, và model không được phép giả vờ là có.
* **`w = reliability`** — mọi boost/penalty nhân theo độ tin. Spam đoán bừa không "đánh bóng" được retention.

Một lần sai **không** kéo retention về 0 (bảo vệ context-failure: mệt, đọc nhầm đề).

### Cửa sổ ôn (`reviewWindowDays`)

Nghịch đảo của đường quên: `t = S × ln(R / x)`.
Trả về **khoảng** `{ earliest, ideal, latest }`, không phải một mốc. Đã dưới ngưỡng → 0 (đến hạn ngay).

### Ưu tiên & mức gấp

```
ReviewPriority = ForgettingRisk × Importance × GoalRelevance × Timing
  ForgettingRisk = clamp01(historical_mastery − current_retention)
  Importance     = 1 + 0.15 × min(10, blocks_count)
  GoalRelevance  = inGoal ? 1.5 : 0.6
  Timing         = daysToExam ≤14 → 2 · ≤45 → 1.3 · còn lại → 1
```

`urgencyFor()` bắt đầu từ thang R (`NONE/LOW/MEDIUM/HIGH`), rồi **+1 bậc** nếu thuộc goal và thi trong 14 ngày, **+1 bậc** nếu đang chặn ≥3 bài sau (prerequisite protection), **−1 bậc** nếu không thuộc goal nào và không có kỳ thi nào.

**Retention thấp một mình KHÔNG đủ để bắt ôn.** Quên một thứ không ai cần đến thì cứ để quên.

`historical_mastery < 0.6` → luôn `NONE` và `groupLabel = null`: chưa từng vững thì không phải chuyện trí nhớ.

### Nhãn hiển thị

`dang_chac` (R≥.8) · `nhac_lai` (≥.65) · `nguy_co_quen` (≥.45) · `nen_on_lai`. Không hiện công thức, không phán xét (REQ-INT-28).

### Review Queue

`topReviewCandidates()` tính lại **mỗi lần đọc**, lấy top-k theo priority. **Không backlog, không đếm "quá hạn"** — hệ thống không tạo cảm giác nợ nần (SDD-017 §9).

***

> 📄 **Recommendation Engine có trang riêng: [recommendation-engine.md](recommendation-engine.md)** — "hôm nay học bài nào", 5 bước chọn việc, và bốn engine đề xuất khác trong hệ thống.

## 7. Need Signal Engine (chọn bài trong Phòng Lab)

Chạy inline trong `GET /v1/learners/{learnerId}/labs`. Trả lời câu hỏi của learner: *"bài này có cần học không?"*

```
state = stateFor(mastery, confidence)
  hong      → need = "urgent"   🔴 Cần học
  lung_lay  → need = "review"   🟡 Nên ôn
  chac      → need = "solid"    🟢 Đã vững
  unknown   → need = "new"      ⚪ Chưa học
```

Kèm hai tín hiệu ngữ cảnh, đều tính từ dữ liệu thật:

* `blocks_count` — số UNIT phía sau bị chặn, từ `unit_prereqs` (SRC-396) → "🔒 đang chặn 3 bài sau". Trước SRC-396 đọc từ `skill_edges` cấp node; đổi vì quan hệ tiên quyết nay sống ở cấp Unit.
* `in_goal` — node có trong `blueprint_weights` của goal đang active → "🎯 thuộc mục tiêu".

***

> 📄 **Quality Engine có trang riêng: [quality-engine.md](quality-engine.md)** — 4 lớp gác, rubric CC-QAF 8 chiều, cổng publish.

## 8. Content Quality Engine (AI)

Engine **duy nhất** dùng LLM. Chạy trong Coral (`modules/coral/routes.ts`), model `@cf/meta/llama-3.3-70b-instruct-fp8-fast`, **bắt buộc qua AI Gateway `nemo12`** (QG-010, cấm probe-fallback — RISK-022).

Dùng để soạn/đánh giá nội dung. **Không chạm vào Learner Model** — không có đường nào từ output LLM đi thẳng vào mastery. Chi tiết: [AI Registry](ai-registry.md).

***

## Kiểm chứng

| Engine | Test |
| --- | --- |
| Retention | `modules/retention/engine.test.ts` — test hành vi |
| Còn lại | QG-005 (behavior tests), đối chiếu chuyenchon với engine mastery |

## Trace

* REQ-INT-16/21/22/23..28, REQ-LRN-10/15, REQ-VIS-08.
* Thiết kế: [SDD-002](../architecture/sdd-002-learner-intelligence/models-and-engines.md) §7/§8/§16/§17/§18, [SDD-008](../architecture/sdd-008-overview-views.md) §5, [SDD-017](../architecture/sdd-017-retention.md).
* Kiểm chứng: QG-005, QG-010.
