---
url: https://docs.nemo12.com/architecture/sdd-024-dory.md
description: >-
  Bản nháp Dory (SDD-024): quản lý quan hệ gia đình bằng một bảng và tag, Family
  360, 'không bao giờ quên gia đình'.
---

# SDD-024 — Dory

*Never forget the family.* Thiết kế kỹ thuật cho [PRD-003](../product/prd-003-dory-anchor.md) phần Dory.

Dory **dựng trên** [SDD-023](sdd-023-family-workspace/index.md), không thay thế nó: `families`, `family_contacts`, `learners`, `family_photos` giữ nguyên. Dory thêm đúng phần còn thiếu — **sự kiện có cấu trúc** và **niềm tin có nguồn**.

## 1. Nguyên tắc

1. **Ghi phải nhanh hơn nhớ.** Nếu ghi lại một cuộc gọi mất hơn một phút thì Dolphin sẽ không ghi, và mọi thứ phía sau sụp theo. Interaction Capture vì thế chỉ có ba ô bắt buộc (§4).
2. **Mọi niềm tin đều có nguồn.** Không có mẩu thông tin nào trong Dory tồn tại mà không trả lời được *"do đâu mà biết"* — xem ba tag `fact`/`opinion`/`hypothesis` ở §2.
3. **Suy luận không bao giờ mặc áo của lời khai.** Giao diện hiển thị tầng ngay cạnh nội dung, không gộp.
4. **Timeline là một dòng duy nhất.** Ghi chú, cuộc gọi, milestone, case đóng — tất cả cùng một dòng thời gian. Ba tab riêng cho ba loại sự kiện nghĩa là không ai dựng lại được câu chuyện.
5. **Không đẻ ra bảng note thứ hai.** Ghi chú vẫn là `interactions` với `target_type='family'` (Q-145).

## 2. Dữ liệu — MỘT bảng, và mọi thứ khác là tag

Chỉ đạo chủ dự án 2026-08-26: *"Ưu tiên notes, mỗi note có thể gắn zero tag, 1 tag, hay nhiều tag… Chỉ có đúng 1 tầng reply."* Bản phác đầu của mục này có năm bảng và hơn ba mươi cột; nó **đã bị thay**. Lý do đầy đủ và phần rà soát các hệ tương tự: [family-notes-review](../product/family-notes-review/decisions.md) §10c-10f.

```text
family_notes   id · family_id · body · tags_json
               · learner_id? → learners
               · contact_id? → family_contacts    -- SRC-620, migration 0160
               · reply_to_id? → family_notes      -- CHỈ trỏ tới note gốc
               · author_user_id → users · created_at · status(active|archived)
```

**Không có `family_visits`.** Một buổi thăm nhà là một note gắn `event`. Bảng thứ hai chỉ đáng tồn tại khi có truy vấn riêng cho nó, mà hiện không có; cần thống kê theo buổi thì dựng lại từ note gắn `event`, không mất dữ liệu nào.

**Không có `family_context_items`, không có `family_signals`.** Tất cả là tag.

### Mười hai tag, bốn trục, một danh sách phẳng

| Trục | Tag |
| --- | --- |
| Nói về ai | `parent` · `child` |
| Loại nội dung | `pain` · `jtbd` · `need` · `belief` · `event` · `goal` |
| Mức chắc chắn | `fact` · `opinion` · `hypothesis` |
| Độ nổi | `key` — thứ nên đọc trước; thi hành Q11 ("Lưu ý cho Mentor" hiện đầu tiên) |

**`need` và `belief` là lần mở rộng đầu tiên** của danh sách đóng (SRC-620), và có lý do cụ thể chứ không phải "cho đủ bộ": hồ sơ một người ([SDD-023](sdd-023-family-workspace/index.md) §15.2) hiện bốn danh sách mà Dolphin đọc trước khi gặp gia đình — `pain` · `jtbd` · `need` · `belief`. Thiếu hai tag này thì hai trong bốn cột vĩnh viễn trống.

* `need` **khác** `pain`: pain là chỗ đang đau, need là thứ phải có mới hết đau — và cùng một pain thường ra vài need khác nhau, đó chính là chỗ Dolphin chọn việc để làm.
* `belief` **khác** `opinion`: `opinion` nói về **độ chắc chắn của chính ghi chú** (ai đó nghĩ vậy, chưa kiểm), còn `belief` nói **nội dung** là một niềm tin của gia đình — "học thêm mới giỏi được" có thể đồng thời là `belief` và `fact` (đã xác minh rằng họ tin như vậy).

Bộ nhãn của prompt `dory.suggest-tags` lên **v2** cùng đợt: cùng một note nay nhận đề xuất khác trước, nên bảng đo tỉ lệ chấp nhận phải so được cùng bản.

Ba mức chắc chắn map gần như một-một vào **S/O/A của bệnh án SOAP** — một khuôn ngành y đã dùng từ những năm 1960 (§10e.1). Đó là bằng chứng cách chia này đúng chứ không phải một tầng trừu tượng thừa.

**Danh sách đóng** ở đợt đầu, không cho gõ tag mới. **Không có nhóm loại trừ**: gắn cả `fact` lẫn `hypothesis` vẫn được, hệ chỉ nhắc nhẹ và để nút gợi ý AI đề nghị bỏ bớt. Chặn cứng là quay lại làm biểu mẫu.

### Reply đúng một tầng

`reply_to_id` **chỉ trỏ tới note gốc**; API từ chối reply vào một reply. Bất biến rẻ nhất có thể kiểm: note được trỏ tới phải có `reply_to_id IS NULL`.

Đây là lựa chọn của Slack, và có lý do: diễn đàn cho lồng vô hạn thì một câu quan trọng nằm ở tầng bảy coi như đã mất. Một tầng đủ cho bổ sung · đính chính · kết luận; cần sâu hơn thì tạo note gốc mới.

### Ba thứ có được mà không tốn cột nào

1. **Hộp vào không còn là khái niệm riêng.** Note **zero tag** *chính là* inbox. Phân loại = gắn tag.
2. **Chuỗi giả thuyết** — `hypothesis` → tiêu chí kiểm chứng → kết luận, mỗi bước là một reply. Không cần bảng nào.
3. **Mục bắt buộc thành tín hiệu thay vì rào chắn** — hệ nhìn cả nhà và nói *"nhà này chưa có note nào gắn `goal`"*, không chặn người đang ghi vội.

### Hồ sơ sống là thứ DẪN XUẤT, không có nút Sửa

Family Model ([reference](../reference/family-model.md)) dựng từ các note đã gắn tag và **không sửa tay được**. Muốn đổi thì ghi thêm note mới. Có nút sửa nghĩa là có một câu trong hồ sơ không có note nào đỡ lưng — đúng thứ phương án C (Q1) được chọn để tránh.

## 3. Family 360 (REQ-DOR-01)

Một truy vấn: lấy mọi note đang `active` của nhà đó, rồi **gom theo tag**. Không có bảng nào khác để join.

Thứ tự khối trên màn hình cố định, theo thứ tự câu hỏi một Dolphin thật sự hỏi khi mở một nhà:

| # | Khối | Lấy từ |
| --- | --- | --- |
| 1 | **Đọc trước** | note gắn `key` |
| 2 | **Ai** | contacts + learners ([SDD-023](sdd-023-family-workspace/index.md) §3) |
| 3 | **Đang lo gì** | note gắn `pain` |
| 4 | **Đang nợ gì** | commitments đang mở ([Anchor](sdd-025-anchor.md)) |
| 5 | **Gần đây** | 10 note mới nhất, bất kể tag |
| 6 | **Muốn gì** | note gắn `goal` · `jtbd` |
| 7 | **Chưa phân loại** | note **zero tag** — chỉ hiện **con số**, bấm vào mới mở |

Khối 1 đứng đầu là cách thi hành Q11 (*"Lưu ý cho Mentor" hiện đầu tiên*). Khối 7 chỉ hiện con số vì nội dung chưa phân loại mà trộn vào các khối trên thì phá luôn ý nghĩa của việc phân loại.

Goals xuống gần cuối là có chủ đích: mục tiêu ít đổi, còn "đang lo gì" và "đang nợ gì" đổi từng tuần và là thứ quyết định cuộc gọi hôm nay.

## 4. Ghi nhanh (REQ-DOR-03)

**Một ô. Zero tag cũng lưu được.** Đây là toàn bộ luồng ghi.

Ngưỡng thiết kế: **dưới 30 giây** — dán cả một bài viết sau buổi thăm nhà vào cũng được. Không có trình soạn thảo giàu định dạng, không bắt chọn learner, không bắt chọn tag.

Gắn tag là **một bước riêng, làm sau**, và làm bởi bất kỳ ai (§6). Đây là chỗ mô hình này khác hẳn một biểu mẫu: người ghi không phải quyết định phân loại đúng lúc họ vừa đi thăm nhà về và chỉ muốn ghi cho kịp.

**Reply đúng một tầng.** `reply_to_id` chỉ trỏ tới note gốc; API từ chối reply vào một reply. Bổ sung · đính chính · kết luận cho một giả thuyết đều là reply. Cần sâu hơn thì tạo note gốc mới.

**Không có cột `visibility`.** Ghi chú gia đình là nội bộ đội Dolphin, đúng như Q42 đã chốt (chưa làm cờ khoá). Thêm một cột quyền cho một luật chưa tồn tại là thêm một cột sẽ để sai.

## 5. Tín hiệu (REQ-DOR-05) — luật, không phải bảng

Luật chạy trên `family_notes` và trả kết quả **tại chỗ**. Không có bảng `family_signals`: một bảng như thế sẽ đầy những hàng lặp lại mỗi lần cron chạy.

| Tín hiệu | Luật |
| --- | --- |
| `no_notes` | nhà có learner đang học mà **không có note nào** |
| `missing_goal` | không có note nào gắn `goal` |
| `untagged_pileup` | từ 10 note zero tag trở lên |
| `open_hypothesis` | note gắn `hypothesis` quá 30 ngày mà **chưa có reply nào** |
| `silent_family` | không có note mới trong 45 ngày |
| `unclaimed_family` | không Dolphin nào đang chăm ([SDD-023](sdd-023-family-workspace/index.md) §11) |

Mỗi tín hiệu **luôn kèm câu giải thích vì sao nó bật**. Một cảnh báo không nói được lý do thì người nhận chỉ có hai lựa chọn: tin mù, hoặc tắt đi.

Bỏ qua một tín hiệu thì ghi **một note gắn `event`** nói rõ đã bỏ qua — không cần bảng trạng thái riêng.

## 6. Gắn tag bằng AI (REQ-DOR-04)

`POST /v1/mentor/notes/{id}/suggest-tags` → Workers AI qua gateway `nemo12`:

```json
{ "add":    [{ "tag": "pain", "why": "..." }],
  "remove": [{ "tag": "fact", "why": "note này đang phỏng đoán, không phải quan sát" }] }
```

Bốn ràng buộc, rút từ bài học của các hệ đã chạy AI gắn nhãn ở quy mô thật ([review](../product/family-notes-review/decisions.md) §10e.4):

1. **Chỉ chọn trong mười tag**, không bao giờ bịa tag mới. Tag lạ do mô hình trả về bị **loại bỏ ở server**, không hiện ra cho người dùng.
2. **Mỗi đề xuất kèm một câu lý do.**
3. **Người bấm từng cái.** Có nút áp dụng tất cả, nhưng không bao giờ tự áp.
4. **Ghi lại tỉ lệ chấp nhận.** Một tag bị từ chối quá nửa số lần thì **định nghĩa tag đó sai hoặc prompt sai**, không phải người dùng sai.

**Chỉ gửi thân note đang xét.** Không gửi hồ sơ gia đình, không gửi note của nhà khác, không gửi dữ liệu học của con. Workers AI chạy trong hạ tầng Cloudflare nên dữ liệu không rời nhà cung cấp, nhưng đây vẫn là lần đầu văn bản về một gia đình đi vào một mô hình — nên phạm vi gửi là thứ phải hẹp nhất có thể ngay từ đầu.

Agent kiểm thêm một quy ước không tốn tag nào: note gắn `opinion` mà **chưa nói rõ ý kiến của ai** thì nhắc ([review](../product/family-notes-review/decisions.md) §10f).

## 7. API

| Method | Path | Quyền |
| --- | --- | --- |
| GET | `/v1/mentor/families/{id}/notes` | 🔐 role `mentor` |
| POST | `/v1/mentor/families/{id}/notes` | 🔐 role `mentor` |
| PATCH | `/v1/mentor/notes/{id}` | 🔐 role `mentor` |
| POST | `/v1/mentor/notes/{id}/suggest-tags` | 🔐 role `mentor` |

Bốn endpoint, hết. `GET` nhận `tags` (danh sách), `match=any|all` (mặc định `any`), `untagged=1`, và `person=contact:<id>|learner:<id>` (SRC-620).

**`match` sửa một lỗi im lặng** (SRC-620): trước đây `GET` luôn lọc AND, nên bộ lọc "Goals and motivation" (`goal` + `jtbd`) chỉ khớp ghi chú mang cả hai — trên dữ liệu thật là gần như không ghi chú nào. Bộ lọc trông vẫn chạy, chỉ là luôn trả rỗng, nên không ai báo hỏng. Mọi bộ lọc gộp nhiều tag ở đầu trang đều mang nghĩa "hoặc". Không có `DELETE`: lưu trữ là `PATCH status='archived'`.

Mọi lượt đọc một nhà ghi `audit_log` như [SDD-023](sdd-023-family-workspace/index.md) §8. Giao diện tiếng Anh theo REQ-MEN-16.

## 8. Cái chưa làm

| Việc | Vì sao |
| --- | --- |
| AI viết Relationship Summary tự do | Cần truy vết từng câu về bản ghi; xem §6 |
| Đồng bộ Zalo/email tự động vào timeline | Cần quyền truy cập hộp thư của Dolphin, là một quyết định riêng |
| Gợi ý "nên nói gì với nhà này" | Vượt ranh giới: Dory kể cái đã biết, không viết kịch bản hộ |
| Gia đình tự xem timeline của mình | Nội bộ đã, mở cho gia đình là một đợt có ràng buộc riêng tư riêng |

## Trace

REQ-DOR-01→§3, [SDD-023](sdd-023-family-workspace/index.md) §15.2 · REQ-DOR-02→§2, §7 · REQ-DOR-03→§4 · REQ-DOR-04→§2, [family-model](../reference/family-model.md) §3 · REQ-DOR-05→§5 · REQ-DOR-06→§6.
