---
url: https://docs.nemo12.com/architecture/sdd-010-lab-platform/foundations.md
description: >-
  Lab Platform dựng trên nguyên tắc nào: archetype thành engine, Lab là
  Experience, migrate lab legacy, evidence, shell, công nghệ, registry.
---

# SDD-010 · Nguyên tắc, archetype, Lab là Experience, migrate, evidence, shell và công nghệ

Một phần của [SDD-010](./index.md).

## 1. Nguyên tắc

> **Labs là content, không phải app.** 288 labs sập về **6 archetypes**; ~88% trở thành data thuần chạy trên engines dùng chung; chỉ simulations thật mới là code.

## 2. Interaction Archetypes → Engines (REQ-LAB-01)

| Engine | Archetype legacy | Labs | Dạng |
| --- | --- | --- | --- |
| `picker` | A1 chip picker → swap stage + note (+A4 static) | 187 | **JSON config, zero code/lab** |
| `parametric` | A2 slider → SVG/numeric viz | 58 | declarative `{params[], formula, viz}` |
| `stepper` | A3 animated/stepped simulation (rAF) | 9 | config + tick function đăng ký |
| `sim` | A5 multi-scene domain simulator | 29 | React components trên shared shell |
| `scene3d` | A6 three.js | 1 (anatomy) | lazy-load cô lập (734KB không đè lên lab khác) |
| `inquiry` | cross-cutting: predict → observe → checks + quiz | 288/288 | **một engine duy nhất**, mọi lab dùng |

## 3. Lab = Experience trong content system (REQ-LAB-02)

Lab là `LearningExperience` type `lab` (SDD-004): metadata D1 (`labs` registry: id, subject, grade **tường minh** — không suy từ level, group, accent, mode **suy từ implementation** — không tin nhãn tay vì legacy sai 24%), content JSON ở R2, immutable version. Một trang lab = engine + config — **bỏ mô hình MPA 9-touchpoint** (HTML entry + vite input + worker map per lab); router SPA + lazy chunk per engine.

### 3b. Một Unit — một lab (REQ-LAB-08, SRC-208)

Lab gắn vào **Unit**, không phải vào bài: `labs.unit` là chỗ neo, và một unique index **từng phần** trên `(subject_id, unit) WHERE status='live' AND unit IS NOT NULL` (migration `0048`) giữ luật "tối đa 1 lab / unit". Đặt ở DB chứ không ở script vì đợt import lab sau này phải **fail ngay lúc ghi**, thay vì âm thầm sinh ra unit hai lab rồi vài tuần sau mới lộ ra ở giao diện.

Ba hệ quả trong thiết kế:

* **Thứ tự trải nghiệm trong Unit** thành cố định: các bài → **lab** → ô đo. Nhờ đó thẻ Unit ở Toàn cảnh (REQ-UX-14) vẽ được mà không phải hỏi "unit này lấy lab nào".
* **Lab dôi ra chuyển `draft`, không xoá và không `retired`**: chúng không hỏng, chỉ thừa so với luật mới — `retired` đọc như "đã khai tử" và sẽ khiến người sau tưởng lab có vấn đề chất lượng.
* **Lab chưa có unit tương ứng vẫn `live`** (unit NULL được index miễn trừ) nên vẫn vào được Thư viện Lab (SRC-166): 70 lab Nghe/Nói tiếng Anh và Làm văn đang ở diện này, chờ chương trình mở thêm unit.

### 3c. Lab yêu thích của learner (REQ-LAB-09, SRC-212)

`lab_favorites (learner_id, lab_id)` — bảng riêng, **không** phải cột trên `labs`: đây là dữ liệu của từng learner, để lên `labs` thì hai learner đánh dấu là ghi đè nhau. Bỏ thích thì **xoá dòng**, không có cột trạng thái: lịch sử "đã từng thích rồi bỏ" không ai dùng, giữ lại chỉ khiến mọi truy vấn sau này phải nhớ lọc thêm một điều kiện.

Đọc qua `GET /v1/learners/{id}/lab-favorites`, **tách khỏi** `GET /v1/subjects/{id}/labs`: thư viện là tài nguyên CHUNG của môn (không phụ thuộc ai đang xem, cache được); nhét cờ "đã thích" vào đó là biến nó thành tài nguyên riêng của từng người. Client lấy hai thứ rồi ghép.

Ghi bằng `PUT`/`DELETE` theo `labId` nên bấm mấy lần cũng ra một kết quả; `PUT` kiểm lab có thật trước khi ghi, `DELETE` một lab chưa từng thích vẫn trả 200 vì kết quả learner muốn đã đúng.

## 4. Content Migration (REQ-LAB-03)

1. Script reverse-parse `.tsx` → JSON (`ITEMS`, `INQUIRY`, `SCENARIOS` là top-level literals; `~highlight~` syntax giữ nguyên) — 186 picker labs tự động 100%.
2. 58 parametric: trích `{params, formula, viz-spec}` bán tự động.
3. 29 bespoke: port logic (solver thật: circuits, photosynthesis, algorithms…), **bỏ 206KB CSS trùng lặp** + chrome tự chế; re-parent lên shared shell + Design System tokens.
4. Toàn bộ 288 inquiry blocks vào bảng `inquiries` (**chưa dựng** tính tới 2026-08-20; hiện nội dung lab nằm trong `lab_content.content_json`) — lần đầu tiên bespoke labs có correctness signal.
5. Nội dung VI-first, cấu trúc i18n-ready (Q-011).

## 5. Evidence contract (REQ-LAB-04)

Lab phát evidence chuẩn SDD-002 §5 (thay telemetry path-sniffing legacy — truyền `lab_id` tường minh):

```text
lab_open · inquiry_open · inquiry_answer(correct, attempt) · lab_step · lab_complete
```

`lab_complete` và `inquiry_open` phải hoạt động từ ngày đầu (legacy khai báo nhưng chết). Mapping lab→skill qua `entity_relations teaches/assesses` (127 node links kế thừa, 0 broken).

### 5.1 `inquiry_answer` thành bằng chứng thật (REQ-LRN-41 — SRC-478, SRC-479)

Hợp đồng trên khai từ đầu nhưng **chưa từng chạy**: learner nghịch xong một lab thì hồ sơ năng lực im lặng, và chủ dự án báo đúng triệu chứng đó ("vừa làm xong một lesson mà không thấy ghi nhận"). `POST /v1/labs/{labId}/evidence` khép lại:

* Độ tin cậy **0,45**, thấp hơn hẳn bài đo: lab là chỗ thử và đoán, không phải chỗ chấm. Cho nó cùng trọng số với Assessment là làm hỏng thước đo.
* `event_id = lab:{labId}:{learnerId}:{ngày}` — **một bằng chứng mỗi lab mỗi ngày**, để nghịch đi nghịch lại một lab trong một buổi không tự bơm mastery lên.
* Lab **không gắn được vào node nào thì không ghi được gì**. Vì vậy 70 lab mồ côi phải trỏ về Unit (SRC-479): 61 gắn được, 9 để trống có chủ đích vì cây Pearl chưa có Unit nào phủ (kỹ năng nghe, yếu tố kịch, xung đột, bối cảnh, nhạc tính thơ, điện phân). Chín cái đó là **tín hiệu thiếu nội dung**, không phải rác: gắn xấp xỉ cho đủ số sẽ làm hồ sơ nói dối về một kỹ năng learner chưa chạm.

## 6. Shell, A11y & Design System (REQ-LAB-05)

Một `LabShell` trong `@nemo12/design-system` (patterns): nav, inquiry sheet, quiz renderer, accent theo subject. Sửa một lần cho cả 288: modal đúng `role="dialog"` + focus trap + Esc + focus restore; radiogroup cho options; `aria-live` cho kết quả; bỏ `position:fixed inset:0 overflow:hidden` (scroll được viewport ngắn); `prefers-reduced-motion` cho stepper; sàn 14px + contrast AA (DS-001 §3). Không dark-theme riêng của lab — theo tokens.

## 7. Công nghệ (REQ-LAB-06 — "mới nhất, dùng dài hạn")

* React 19 + TS + Vite 8; SVG procedural là chuẩn render chính (giữ triết lý zero-asset — 288 labs trong 2MB).
* three.js chỉ cho `scene3d`, lazy. Canvas/WebGPU cân nhắc per-engine khi cần.
* Config schema versioned (zod) — validate lúc build content + runtime; engine version độc lập content version (SDD-006 §13).
* Discussion per lab → Interaction System (SDD-005) qua `target_type=lab` — không xây LabDiscussion riêng.

## 8. Registry & Quality (REQ-LAB-07)

Labs qua Quality Engine như mọi content (SDD-003): rubric + deterministic checks (config validate, mapping tồn tại, mode đúng implementation) + learner evidence loop. Taxonomy subject/group/accent + quota grid (24 labs/math strand…) kế thừa làm coverage map trong Quality dashboard.
