This commit is contained in:
Leonard-ThindPad-P50
2026-09-08 10:26:21 +07:00
commit c81f249920
169 changed files with 38726 additions and 0 deletions

View File

@@ -0,0 +1,192 @@
# AC — Acceptance Criteria — <US-id>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification) |
| **Status** | 🟡 Draft |
| **Approved by** | QA: — *(QA phải xác nhận mọi AC test được)* |
| **Source** | SRS_… v1.0 · BR_… v1.0 |
| **Scope** | US-0nn |
---
## 1. Bốn nhóm bắt buộc
| Nhóm | Nội dung | Tối thiểu | Số AC đã viết |
|---|---|---|---|
| 1. Luồng thành công | Đường đi đúng | 1/hành động | |
| 2. Validation | Từng rule kiểm tra dữ liệu | 1/rule | |
| 3. Lỗi hệ thống | Timeout, 5xx, mất mạng giữa chừng | ≥1 | |
| 4. Phân quyền | Không đủ quyền, sai trạng thái | ≥1/vai trò bị chặn | |
🔴 Chỉ có nhóm 1 ⇒ **spec chưa viết xong**, không phải "spec ngắn gọn".
---
## 2. Mẫu viết
```
AC-01 | <Tên ngắn>
Nhóm: Luồng thành công
Liên quan: F01, F02 · BR-021 · SCR-03
Given <tiền đề: trạng thái hệ thống + vai trò người dùng + dữ liệu có sẵn>
When <hành động cụ thể, một hành động>
Then <kết quả quan sát được>
And <kết quả phụ: dữ liệu lưu gì, log ghi gì, màn hình đi đâu>
```
**Ba quy tắc viết AC:**
| # | Quy tắc | Sai | Đúng |
|---|---|---|---|
| 1 | `Given` phải nêu **vai trò** và **dữ liệu cụ thể** | "Given đang ở màn hình danh sách" | "Given tôi đăng nhập với ROLE-01 và có 3 bản ghi trạng thái Nháp" |
| 2 | `When` chỉ **một** hành động | "When tôi nhập mã và bấm Lưu và quay lại" | Tách thành nhiều AC |
| 3 | `Then` phải **quan sát được** | "Then hệ thống xử lý đúng" | "Then hiện toast 'Đã lưu' và danh sách có thêm 1 dòng ở đầu" |
**Phép thử tính test được:** đọc AC và tự hỏi *"tôi ngồi trước màn hình, tôi làm gì để kiểm
chứng câu này?"*. Không trả lời được ⇒ AC chưa viết xong.
---
## 3. Danh sách AC
### Nhóm 1 — Luồng thành công
#### AC-01 — <tên>
| | |
|---|---|
| **Liên quan** | SCR-03 · F01, F02 · BR-021 |
| **Vai trò** | ROLE-01 |
```
Given …
When …
Then …
And …
```
**Dữ liệu mẫu để test:**
| Field | Giá trị |
|---|---|
---
### Nhóm 2 — Validation
#### AC-05 — <tên>
*Một AC cho mỗi rule. Gộp nhiều rule vào một AC làm QA không biết rule nào fail.*
```
Given …
When …
Then hiện lỗi `E-STL-0001` dưới field F01 với thông điệp "…"
And dữ liệu không được lưu
And các field khác giữ nguyên giá trị đã nhập
```
---
### Nhóm 3 — Lỗi hệ thống
#### AC-09 — Mất kết nối khi đang lưu
```
Given tôi đã điền đầy đủ form hợp lệ
When tôi bấm Lưu và request bị timeout sau 30 giây
Then hiện thông báo lỗi "…"
And nút Lưu bấm lại được
And 🔴 toàn bộ dữ liệu tôi đã nhập được giữ nguyên
```
🔴 AC "giữ nguyên dữ liệu sau lỗi" là AC hay bị quên nhất và gây bực bội nhất cho người dùng.
Viết nó cho **mọi form**.
Ba tình huống tối thiểu của nhóm này:
| # | Tình huống | AC |
|---|---|---|
| 1 | Server trả 5xx | |
| 2 | Timeout / mất mạng | |
| 3 | Dữ liệu bị người khác sửa/xoá trong lúc mình đang mở | |
---
### Nhóm 4 — Phân quyền
#### AC-12 — <tên>
*Một AC cho mỗi vai trò bị chặn, và cho mỗi trạng thái chặn hành động.*
```
Given tôi đăng nhập với ROLE-03 (không có quyền tạo)
When tôi mở màn hình danh sách
Then nút "Tạo mới" không hiển thị
Given tôi gọi thẳng API POST /api/v1/stores với token của ROLE-03
When request được gửi
Then nhận HTTP 403 và ghi log truy cập trái phép
```
🔴 Chặn ở giao diện là trải nghiệm, **chặn ở backend mới là bảo mật**. Viết AC cho cả hai.
---
## 4. Bảng dữ liệu biên
*Bảng QA dùng trực tiếp. Một dòng cho mỗi field có ràng buộc.*
| Field | Dưới ngưỡng | Ngưỡng dưới | Trong khoảng | Ngưỡng trên | Trên ngưỡng | Rỗng | Ký tự đặc biệt | Khoảng trắng đầu/cuối |
|---|---|---|---|---|---|---|---|---|
| F01 Mã (3–20) | 2 ký tự → lỗi | 3 ký tự → OK | 10 → OK | 20 → OK | 21 → lỗi | → lỗi bắt buộc | `<script>` → ? | " ABC " → cắt hay giữ? |
Ba cột cuối là ba chỗ hay lộ bug nhất:
| Cột | Câu hỏi phải trả lời trong spec |
|---|---|
| **Rỗng** | Chuỗi rỗng và null có khác nhau không? |
| **Ký tự đặc biệt** | Emoji có được nhập không? Ký tự có dấu tính 1 hay nhiều? HTML/script xử lý sao? |
| **Khoảng trắng** | Tự cắt (trim) hay giữ nguyên? Ảnh hưởng tới kiểm tra trùng không? |
---
## 5. Kịch bản kết hợp
*Những đường đi qua nhiều màn hình. Ít nhất một kịch bản đầu-cuối cho luồng chính.*
| # | Kịch bản | Các bước | AC liên quan |
|---|---|---|---|
| S1 | Tạo mới rồi tìm lại được | SCR-01 → SCR-03 → Lưu → SCR-01 → tìm | AC-01, AC-03 |
---
## 6. Truy vết
| AC | US | BR | Field/Thành phần | Mã lỗi | Test case (QA điền) | Kết quả (QA điền) |
|---|---|---|---|---|---|---|
| AC-01 | US-011 | BR-021 | F01, F02 | — | TC-… | |
Ô "Test case" do QA điền ở GĐ4. Ô trống sau khi QA đã viết test ⇒ AC bị bỏ sót khi test.
---
## 7. Xác nhận của QA
| | |
|---|---|
| **Người xác nhận** | |
| **Ngày** | |
| ☐ Mọi AC đều test được (không có AC mô tả cảm tính) | |
| ☐ Đủ 4 nhóm | |
| ☐ Bảng dữ liệu biên đủ dùng để viết test case | |
| ☐ Không có AC nào mâu thuẫn với AC khác | |
**AC bị QA từ chối:**
| AC | Lý do từ chối | BA sửa thế nào |
|---|---|---|

View File

@@ -0,0 +1,163 @@
# API — Contract đề xuất — <US-id>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification) |
| **Status** | 🟡 Draft |
| **Nguồn contract** | ⚠️ **BA đề xuất — chờ BE xác nhận** / ✅ BE cung cấp |
| **Approved by** | BE Lead: — |
| **Source** | SRS_… v1.0 |
| **Scope** | US-0nn |
> 🔴 **Đọc dòng `Nguồn contract` trước.** Contract do BA đề xuất là **giả định**, không phải
> sự thật — dev phải đối chiếu với tài liệu BE trước khi code. Contract do BE cung cấp mới
> là ràng buộc.
---
## 1. Ba điểm phải chốt trước tiên
*Ba chỗ này gây bug nhiều nhất và thường không ai hỏi.*
| # | Vấn đề | Quyết định | Lý do |
|---|---|---|---|
| 1 | **Số lớn** (id, số tiền) truyền dạng gì | `string` / `number` | Vượt `2^53` thì JavaScript làm tròn sai ⇒ id 19 chữ số bị hỏng |
| 2 | **Thời gian** định dạng gì, múi giờ nào | ISO-8601 UTC / … | |
| 3 | **Phân trang** kiểu gì | offset (`page`,`size`) / cursor | Có trả `total` không? Có `hasNext` không? |
🔴 Điểm 3: nếu API kiểu offset mà **không trả `hasNext`**, giao diện không biết còn trang sau
hay không — nút "Trang sau" sẽ hỏng. Chốt ngay ở đây.
## 2. Quy ước chung
| | |
|---|---|
| **Base URL** | |
| **Xác thực** | |
| **Định dạng phản hồi** | `{ code, message, data }` / … |
| **Mã HTTP dùng** | 200 · 400 · 401 · 403 · 404 · 409 · 500 |
| **Ngôn ngữ thông điệp** | Trả mã lỗi (FE tự dịch) / Trả text đã dịch theo header |
🔴 **Lỗi nghiệp vụ phải trả mã HTTP 4xx, không phải 200 kèm cờ lỗi trong body.** Trả 200
khiến tầng gọi API coi là thành công và giao diện không hiện lỗi — bug im lặng, rất khó phát hiện.
---
## 3. Endpoint
### 3.1 `GET /api/v1/<resource>` — Lấy danh sách
| | |
|---|---|
| **Mục đích** | |
| **Màn hình** | SCR-01 |
| **Quyền** | ROLE-01 (🔶 chỉ cửa hàng phụ trách), ROLE-02, ROLE-04 |
**Query parameters**
| Tên | Kiểu | Bắt buộc | Mặc định | Ràng buộc | Ghi chú |
|---|---|---|---|---|---|
| `keyword` | string | ❌ | — | ≤ 100 ký tự | Tìm theo mã hoặc tên |
| `status` | enum | ❌ | tất cả | `DRAFT`\|`ACTIVE` | |
| `page` | int | ❌ | 0 | ≥ 0 | **0-based hay 1-based?** ← chốt rõ |
| `size` | int | ❌ | 20 | 1–100 | |
| `sort` | string | ❌ | `code,asc` | | |
**Response 200**
```json
{
"code": "SUCCESS",
"message": null,
"data": {
"items": [
{
"id": "1234567890123456789",
"code": "STR-001",
"name": "…",
"status": "ACTIVE",
"createdAt": "2026-08-30T07:05:00Z"
}
],
"page": { "page": 0, "size": 20, "total": 137, "hasNext": true }
}
}
```
**Từng trường**
| Trường | Kiểu | Có thể null | Nguồn | Ghi chú |
|---|---|---|---|---|
| `id` | string | ❌ | | **String vì là số 19 chữ số** |
| `createdAt` | string | ❌ | | ISO-8601, UTC |
**Response lỗi**
| HTTP | `code` | Khi nào | Mã lỗi SRS |
|---|---|---|---|
| 400 | `INVALID_PARAM` | Tham số sai định dạng | `E-STL-0010` |
| 403 | `FORBIDDEN` | Không đủ quyền | `E-STL-0403` |
---
### 3.2 `POST /api/v1/<resource>` — Tạo mới
**Request body**
```json
{ "code": "STR-001", "name": "…", "typeId": "12345" }
```
| Trường | Kiểu | Bắt buộc | Ràng buộc | Field SRS |
|---|---|---|---|---|
| `code` | string | ✅ | 3–20 ký tự, `^[A-Z0-9-]+$` | F01 |
🔴 **Ràng buộc ở API phải khớp với bảng field trong SRS.** Lệch nhau ⇒ giao diện chặn một
kiểu, backend chặn kiểu khác, người dùng gặp lỗi khó hiểu.
**Response 200 / lỗi**
| HTTP | `code` | Khi nào | Mã lỗi SRS |
|---|---|---|---|
| 409 | `DUPLICATE_CODE` | Mã đã tồn tại | `E-STL-0001` |
---
## 4. Bảng đối chiếu mã lỗi
*Mọi mã lỗi trong SRS §4.1 phải có một dòng ở đây, và ngược lại.*
| Mã lỗi SRS | Endpoint | HTTP | `code` của API | ☐ Khớp |
|---|---|---|---|---|
| `E-STL-0001` | POST /stores | 409 | `DUPLICATE_CODE` | ☐ |
Mã lỗi SRS không có endpoint nào sinh ra ⇒ hoặc lỗi chỉ ở giao diện (ghi rõ), hoặc bị bỏ sót.
## 5. Điểm cần BE xác nhận
*Chỉ dùng khi contract do BA đề xuất.*
| # | Điểm cần chốt | Đề xuất của BA | BE trả lời | Ngày |
|---|---|---|---|---|
| 1 | `id` trả string hay number | string | | |
| 2 | `page` 0-based hay 1-based | 0-based | | |
| 3 | Có trả `hasNext` không | Có | | |
| 4 | Lỗi nghiệp vụ trả 4xx hay 200 | 4xx | | |
## 6. Hành vi khi API lỗi *(giao diện phải làm gì)*
| Tình huống | Giao diện làm gì | AC |
|---|---|---|
| 401 hết phiên | Chuyển về đăng nhập, giữ đường dẫn để quay lại | |
| 403 | Hiện thông báo không đủ quyền, không xoá dữ liệu đã nhập | AC-12 |
| 5xx / timeout | Hiện lỗi, cho thử lại, **giữ nguyên dữ liệu đã nhập** | AC-09 |
| Mạng chậm | Hiện trạng thái đang tải, khoá nút gửi để tránh gửi hai lần | |
🔴 **Khoá nút gửi khi đang xử lý** — không có nó thì người dùng bấm hai lần tạo ra hai bản ghi.
## 7. Open Questions
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|---|---|---|---|---|

View File

@@ -0,0 +1,148 @@
# NFR — Yêu cầu phi chức năng — <US / Module>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification) |
| **Status** | 🟡 Draft |
| **Approved by** | Tech Lead: — |
| **Source** | |
| **Scope** | |
---
## Quy tắc
Mỗi NFR bắt buộc ba thứ. Thiếu một là khẩu hiệu, không phải yêu cầu:
1. **Con số đo được**
2. **Điều kiện đo** (bao nhiêu dữ liệu, bao nhiêu người dùng, môi trường nào)
3. **Cách verify** (ai đo, bằng công cụ gì, khi nào)
❌ "Hệ thống phải nhanh."
✅ "Danh sách chênh lệch trả về ≤ 2 giây ở p95, với 100.000 bản ghi và 20 người dùng đồng
thời, trên staging. Verify: k6 kịch bản S1, chạy trước mỗi release."
**Chỉ viết NFR áp dụng cho phạm vi này.** Copy cả bộ tiêu chuẩn công ty vào làm loãng và
không ai kiểm.
---
## 1. Hiệu năng — `NFR-PERF-nn`
| ID | Yêu cầu | Ngưỡng | Điều kiện đo | Cách verify | Ai chốt |
|---|---|---|---|---|---|
| NFR-PERF-01 | Thời gian tải danh sách | ≤ 2s (p95) | 100.000 bản ghi, 20 user đồng thời, staging | k6 kịch bản S1 | |
| NFR-PERF-02 | Thời gian lưu bản ghi | ≤ 1s (p95) | | | |
| NFR-PERF-03 | Xuất file | ≤ 30s cho 50.000 dòng | | | |
**Hỏi người dùng để lấy ngưỡng, đừng tự đặt:** *"Chậm bao lâu thì anh/chị thấy không chấp
nhận được?"* — con số họ nói mới là ngưỡng thật.
## 2. Dung lượng & tăng trưởng — `NFR-CAP-nn`
| Đại lượng | Hiện tại | Sau 1 năm | Sau 3 năm | Nguồn số liệu |
|---|---|---|---|---|
| Số bản ghi | | | | |
| Số giao dịch/ngày | | | | |
| Số người dùng đồng thời (giờ cao điểm) | | | | |
| Dung lượng file đính kèm | | | | |
🔴 **Điền bảng này trước rồi mới chốt ngưỡng hiệu năng.** Ngưỡng đặt trên khối lượng hôm nay
sẽ vỡ sau một năm.
| Giờ cao điểm | Khi nào | Vì sao |
|---|---|---|
| | *(vd: 9–10h sáng, cuối tháng)* | |
## 3. Bảo mật & quyền riêng tư — `NFR-SEC-nn`
| ID | Yêu cầu | Chi tiết | Căn cứ | Cách verify |
|---|---|---|---|---|
| NFR-SEC-01 | Dữ liệu cá nhân được che khi hiển thị | Che 6 số giữa của CCCD | | |
| NFR-SEC-02 | Chặn quyền ở backend, không chỉ ở giao diện | Mọi endpoint kiểm tra vai trò | RBAC §6 | Test gọi thẳng API |
| NFR-SEC-03 | Xuất dữ liệu nhạy cảm phải nhập lý do | | | |
**Rà bốn câu:**
| Câu hỏi | Trả lời |
|---|---|
| Có thông tin cá nhân không? Loại nào? | |
| Lưu bao lâu? Xoá thế nào khi hết hạn? | |
| Ai được xuất ra ngoài hệ thống? | |
| Có quy định pháp luật nào áp dụng không? | |
## 4. Lưu vết — `NFR-AUD-nn`
| Hành động | Ghi vết | Nội dung ghi | Giữ bao lâu | Ai xem được |
|---|---|---|---|---|
| Tạo/Sửa/Xoá | ✅ | ai · lúc nào · trước→sau | | |
| Phê duyệt | ✅ | ai · lúc nào · ghi chú | | |
| Xuất dữ liệu | ✅ | ai · lúc nào · **lý do** · số bản ghi | | |
**Log có phải hiển thị được cho người dùng không, hay chỉ để tra khi có sự cố?** — câu này
quyết định có cần làm màn hình lịch sử hay không.
## 5. Khả dụng & xử lý sự cố — `NFR-AVL-nn`
| ID | Yêu cầu | Ngưỡng | Ghi chú |
|---|---|---|---|
| NFR-AVL-01 | Thời gian hoạt động | | Trong giờ làm việc: … |
| NFR-AVL-02 | Cửa sổ bảo trì cho phép | | |
| NFR-AVL-03 | Hành vi khi hệ thống ngoài lỗi | | Xem bên dưới |
**Hệ thống ngoài lỗi thì nghiệp vụ này làm gì?** *(bắt buộc trả lời — đây là chỗ spec hay
im lặng và dev tự quyết)*
| Hệ thống ngoài | Nếu lỗi/chậm | Người dùng thấy gì | Dữ liệu xử lý sao |
|---|---|---|---|
| | Chặn hoàn toàn / Cho làm tiếp và đồng bộ sau / Chuyển thủ công | | |
## 6. Đa ngữ & định dạng — `NFR-I18N-nn`
🔴 **Nhóm hay bị bỏ nhất và gây bug ở môi trường thật nhiều nhất.**
| Khía cạnh | Yêu cầu | Ghi chú |
|---|---|---|
| Ngôn ngữ hỗ trợ | | Thiếu bản dịch thì hiển thị gì? |
| Múi giờ lưu trữ | | UTC hay giờ địa phương |
| Múi giờ hiển thị | | Theo người dùng hay cố định |
| Định dạng ngày | | |
| Dấu phân cách số | | `1.234,56` hay `1,234.56` |
| Đơn vị tiền | | Có đa tiền tệ không |
| Sắp xếp chuỗi có dấu | | "Ă" đứng trước hay sau "B" |
| Độ dài text sau khi dịch | | Text tiếng Đức/Hàn dài hơn — giao diện có vỡ không |
## 7. Khả năng truy cập & thiết bị — `NFR-ACC-nn`
| Khía cạnh | Yêu cầu |
|---|---|
| Trình duyệt hỗ trợ | |
| Kích thước màn hình nhỏ nhất | |
| Dùng trên điện thoại không | |
| Thao tác bằng bàn phím | |
| Tương phản màu | |
---
## 8. Nhóm không áp dụng
*Ghi rõ, đừng xoá — để người đọc biết là đã cân nhắc, không phải đã quên.*
| Nhóm | N/A vì |
|---|---|
| | |
## 9. Open Questions
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì |
|---|---|---|---|---|
## 10. Xác nhận
| Vai trò | Người | Nội dung xác nhận | Ngày |
|---|---|---|---|
| Tech Lead | | ☐ Ngưỡng khả thi với kiến trúc hiện tại | |
| QA | | ☐ Mọi NFR đều có cách verify chạy được | |
| PO | | ☐ Ngưỡng phù hợp với kỳ vọng người dùng | |

View File

@@ -0,0 +1,172 @@
# PART 2 — biến thể `api-service`
> **Dùng khi** `PRODUCT = api-service` — sản phẩm không có giao diện; người tiêu thụ là hệ
> thống khác hoặc team khác. Cắm khối này vào chỗ PART 2 của [`../srs.md`](../srs.md).
>
> **Tiêu chí G3 riêng của biến thể này**: mỗi endpoint có bảng tham số/schema đầy đủ · mọi
> mã lỗi map về endpoint · đã trả lời xong ba câu idempotency / tương thích ngược / phân
> trang · có ít nhất một team tiêu thụ đã đọc và xác nhận.
🔴 **Với loại này, `API_<US>.md` là artifact chính, không phải phụ lục.** PART 2 ở đây mô tả
*hợp đồng nhìn từ phía người tiêu thụ*; chi tiết kỹ thuật từng endpoint vẫn ở
[`../api-contract.md`](../api-contract.md). Đừng chép trùng — PART 2 trả lời "có những khả
năng gì", `API` trả lời "gọi thế nào".
---
## 2.1 Người tiêu thụ
*Thay cho "danh sách màn hình". Ai gọi API này quyết định AC viết thế nào.*
| ID | Người tiêu thụ | Là ai | Gọi để làm gì | Tần suất dự kiến | Đầu mối |
|---|---|---|---|---|---|
| CON-01 | | Hệ thống nội bộ / Đối tác ngoài / App di động | | | |
**Ba câu bắt buộc:**
| Câu hỏi | Trả lời |
|---|---|
| Có người tiêu thụ nào **ngoài tổ chức** không? | *(quyết định mức chặt của versioning và bảo mật)* |
| Người tiêu thụ có tự thử được không, hay cần môi trường sandbox? | |
| Ai được thêm người tiêu thụ mới, và bằng quy trình gì? | |
## 2.2 Danh sách khả năng
| ID | Khả năng nghiệp vụ | Endpoint | Method | Người tiêu thụ | Đồng bộ/Bất đồng bộ | BR |
|---|---|---|---|---|---|---|
| CAP-01 | Tra cứu … | `/api/v1/…` | GET | CON-01 | Đồng bộ | |
| CAP-02 | Ghi nhận … | `/api/v1/…` | POST | CON-01, CON-02 | Bất đồng bộ (trả 202 + callback) | BR-0nn |
## 2.3 Sơ đồ luồng gọi
```mermaid
sequenceDiagram
autonumber
participant C1 as CON-01 (người gọi)
participant SVC as Service
participant DB as Cơ sở dữ liệu
participant Q as Hàng đợi
participant C2 as CON-02 (người tiêu thụ event)
C1->>SVC: POST /orders (Idempotency-Key)
SVC->>DB: Ghi bản ghi
alt Ghi thành công
DB-->>SVC: OK
SVC->>Q: publish OrderCreated
SVC-->>C1: 201 + id
Q-->>C2: OrderCreated
else Trùng Idempotency-Key
DB-->>SVC: đã tồn tại
SVC-->>C1: 200 + id cũ (không tạo bản ghi thứ hai)
else Lỗi ghi
DB--xSVC: lỗi
SVC-->>C1: 503 · E-XXX-0503 (retry được)
end
```
🔴 **Bắt buộc vẽ ba nhánh**: thành công · **gọi lại trùng** · lỗi. Nhánh giữa là nhánh chứng
minh §2.4.1 idempotency hoạt động — thiếu nó thì người tiêu thụ không biết gọi lại có an toàn không.
**Bảng đi kèm** *(quy tắc W13)*:
| Bước | Đồng bộ / Bất đồng bộ | Timeout | Retry được | Mã lỗi |
|---|---|---|---|---|
| 1 | Đồng bộ | 5s | ✅ với `Idempotency-Key` | |
| 5 | Bất đồng bộ | — | Hàng đợi tự retry 3 lần | |
Với luồng **bất đồng bộ**, bắt buộc trả lời ba câu — sơ đồ không nói được:
| Câu hỏi | Trả lời |
|---|---|
| Người gọi biết kết quả bằng cách nào | polling `GET /orders/{id}` · callback · event |
| Chờ tối đa bao lâu | |
| Quá hạn mà chưa có kết quả thì làm gì | |
---
## 2.4 CAP-01 — <Tên khả năng>
### 2.4.1 Hợp đồng
| | |
|---|---|
| **Endpoint** | `POST /api/v1/…` |
| **Quyền** | scope `…` / client `…` |
| **Idempotent** | ✅/❌ — nếu ✅: khoá idempotency là gì, giữ bao lâu |
| **Gọi lại an toàn (retry)** | ✅/❌ |
| **Thời gian phản hồi mục tiêu** | ≤ … ms (p95) |
| **Giới hạn tần suất** | … req/phút/client · vượt thì trả gì |
🔴 **Ba câu này là chỗ hay bỏ sót nhất của API spec:**
1. **Idempotency** — người gọi timeout rồi gọi lại, có tạo hai bản ghi không? Nếu không
idempotent thì phải nói rõ để người tiêu thụ tự xử lý.
2. **Retry** — lỗi nào được retry, lỗi nào không? Khuyến nghị backoff bao nhiêu?
3. **Đồng thời** — hai request cùng sửa một tài nguyên thì sao? Có optimistic locking không?
### 2.4.2 Tham số / Request
| Tên | Kiểu | Vị trí | Bắt buộc | Mặc định | Ràng buộc | BR |
|---|---|---|---|---|---|---|
| | string | query / path / body | ✅ | — | 3–20 ký tự, `^[A-Z0-9-]+$` | BR-0nn |
*Cột `Ràng buộc` là tương đương của "bảng field" ở biến thể `screen` — phải cụ thể ngang vậy.*
### 2.4.3 Response thành công
| Trường | Kiểu | Có thể null | Nghĩa nghiệp vụ | Ghi chú |
|---|---|---|---|---|
| | | | | |
### 2.4.4 Response lỗi
| HTTP | `code` | Khi nào | Người gọi nên làm gì | Mã lỗi SRS §4.1 |
|---|---|---|---|---|
| 409 | | | Không retry, sửa dữ liệu | `E-XXX-0001` |
| 503 | | | Retry với backoff | `E-XXX-0503` |
**Cột "Người gọi nên làm gì" là cột thay thế cho "hiển thị ở đâu" của biến thể `screen`.**
Không có nó thì mỗi team tiêu thụ tự đoán một kiểu xử lý lỗi.
---
## 2.5 Hợp đồng dữ liệu chung
| # | Vấn đề | Quyết định |
|---|---|---|
| 1 | **Số lớn** (id, số tiền) | `string` / `number` — vượt `2^53` thì JS làm tròn sai |
| 2 | **Thời gian** | Định dạng, múi giờ |
| 3 | **Phân trang** | offset / cursor · có `total`? có `hasNext`? |
| 4 | **Sắp xếp** | Cú pháp, trường nào cho phép |
| 5 | **Trường null vs. vắng mặt** | Có khác nghĩa không |
| 6 | **Enum** | Người tiêu thụ gặp giá trị lạ (mới thêm) thì xử lý sao |
## 2.6 Phiên bản & tương thích ngược
| | |
|---|---|
| **Cách đánh version** | URL `/v1/` · header · … |
| **Thay đổi nào là phá vỡ tương thích** | *(bỏ trường, đổi kiểu, thêm ràng buộc, đổi nghĩa mã lỗi)* |
| **Thay đổi nào là an toàn** | *(thêm trường optional, thêm giá trị enum — **chỉ khi** §2.5 #6 đã định nghĩa)* |
| **Báo trước bao lâu khi bỏ version cũ** | |
| **Chạy song song mấy version** | |
🔴 **Thêm một giá trị enum là thay đổi phá vỡ tương thích** nếu §2.5 #6 không nói người tiêu
thụ phải làm gì với giá trị lạ. Đây là lỗi tương thích phổ biến nhất và im lặng nhất.
## 2.7 Trạng thái tương đương "màn hình rỗng"
| Tình huống | Trả về gì | HTTP |
|---|---|---|
| Truy vấn hợp lệ, không có bản ghi nào | Mảng rỗng + paging `total: 0` — **không phải 404** | 200 |
| Tài nguyên không tồn tại | | 404 |
| Tài nguyên tồn tại nhưng không có quyền | 404 hay 403? *(404 giấu sự tồn tại — chọn theo mức nhạy cảm)* | |
## 2.8 Môi trường & tích hợp thử
| | |
|---|---|
| Sandbox | Có / Không — đường dẫn |
| Dữ liệu mẫu cho người tiêu thụ thử | |
| Cách cấp credential | |
| Tài liệu tích hợp bàn giao ở đâu | *(đây là `MANUAL` của GĐ5 với loại sản phẩm này)* |

View File

@@ -0,0 +1,176 @@
# PART 2 — biến thể `batch-job`
> **Dùng khi** `PRODUCT = batch-job` — job chạy theo lịch, không giao diện: đối soát đêm,
> sinh báo cáo định kỳ, dọn dữ liệu, gửi thông báo hàng loạt. Cắm khối này vào chỗ PART 2
> của [`../srs.md`](../srs.md).
>
> **Tiêu chí G3 riêng của biến thể này**: mỗi job có lịch + cửa sổ + phụ thuộc · quy tắc xử
> lý từng bản ghi đầy đủ · **đã trả lời xong idempotency và thất bại giữa chừng** · có kế
> hoạch cảnh báo và người trực.
**Phân biệt với `data-pipeline`:** pipeline có sản phẩm là **dữ liệu để phân tích** (cần
data contract, lineage, đối soát nguồn–đích); batch-job có sản phẩm là **việc được làm xong**
(cần idempotency, checkpoint, cảnh báo). Job vừa di chuyển dữ liệu lớn vừa làm việc nghiệp
vụ ⇒ nạp cả hai biến thể.
---
## 2.1 Danh sách job
| ID | Job | Làm gì | Lịch | Cửa sổ cho phép | Phụ thuộc job nào | Khối lượng/lần |
|---|---|---|---|---|---|---|
| JOB-01 | | | Hằng ngày 01:00 | 01:00–05:00 | JOB-00 xong | ~… bản ghi |
**Cột `Cửa sổ cho phép`** — job phải xong trước mấy giờ, và vì sao (nghiệp vụ nào bắt đầu
lúc đó). Không có cột này thì không ai biết chạy chậm bao lâu là sự cố.
## 2.2 Sơ đồ phụ thuộc
```mermaid
flowchart LR
J00["JOB-00 · 02:00 Nạp dữ liệu"]
J01["JOB-01 · 02:30 Đối soát"]
J02["JOB-02 · 03:00 Gửi báo cáo"]
STOP(["⛔ Dừng chuỗi · cảnh báo trực đêm"])
J00 -->|xong| J01
J01 -->|xong| J02
J00 -->|thất bại| STOP
J01 -->|thất bại| STOP
```
*Nhãn node mang **giờ chạy**; ID khớp cột `ID` bảng §2.1.*
🔴 **Vẽ cả cạnh thất bại.** Sơ đồ chỉ có đường "xong" bỏ sót đúng câu hỏi quan trọng nhất:
job trước hỏng thì job sau **chạy hay dừng**.
**Bảng đi kèm** *(quy tắc W13)*:
| Job | Phụ thuộc | Job trước thất bại ⇒ | Chạy với dữ liệu cũ có nguy hiểm không | Ai được báo |
|---|---|---|---|---|
| JOB-01 | JOB-00 | Dừng, không chạy | 🔴 Có — đối soát trên dữ liệu thiếu ra kết quả sai | Trực đêm |
| JOB-02 | JOB-01 | Dừng, không gửi báo cáo | 🔴 Có — gửi báo cáo sai còn tệ hơn không gửi | Trực đêm + kế toán |
Cột áp chót là cột quyết định: dữ liệu cũ vô hại ⇒ cho chạy tiếp; nguy hiểm ⇒ phải dừng.
Không trả lời được ⇒ `OQ`, đừng mặc định cho chạy.
---
## 2.3 JOB-01 — <Tên job>
### 2.3.1 Định danh
| | |
|---|---|
| **Kích hoạt bởi** | Lịch / Sự kiện / Gọi tay |
| **Đầu vào** | *(bảng, file, hàng đợi — và phạm vi: ngày nào, trạng thái nào)* |
| **Đầu ra** | *(bản ghi được cập nhật, file sinh ra, thông báo gửi đi)* |
| **Thời gian chạy dự kiến** | ~… phút với khối lượng bình thường |
| **Chạy bao lâu thì coi là treo** | |
| **Có thể chạy đồng thời nhiều bản không** | 🔴 Không ⇒ cơ chế khoá là gì |
### 2.3.2 Phạm vi xử lý
| | |
|---|---|
| **Chọn bản ghi nào để xử lý** | *(điều kiện chính xác)* |
| **Bản ghi đã xử lý rồi nhận biết bằng gì** | 🔴 *(cột trạng thái? bảng log? mốc thời gian?)* |
| **Thứ tự xử lý có quan trọng không** | |
| **Giới hạn số bản ghi mỗi lần chạy** | *(và phần còn lại xử lý khi nào)* |
### 2.3.3 Quy tắc xử lý từng bản ghi
*Đây là phần thay thế cho "bảng field" của biến thể `screen` — phải cụ thể ngang vậy.*
| # | Điều kiện | Hành động | Kết quả ghi vào đâu | BR |
|---|---|---|---|---|
| 1 | | | | BR-0nn |
**Bản ghi lỗi giữa chừng:**
| Tình huống | Xử lý | Ghi log gì |
|---|---|---|
| Dữ liệu bản ghi không hợp lệ | Bỏ qua và tiếp tục / Dừng cả job | |
| Gọi hệ thống ngoài thất bại | Retry mấy lần? Rồi sao? | |
🔴 **"Bỏ qua và tiếp tục" phải kèm ngưỡng.** Bỏ qua 3 bản ghi là bình thường; bỏ qua 30% số
bản ghi là sự cố nhưng job vẫn báo "thành công" — đó là chế độ hỏng im lặng.
| | |
|---|---|
| **Ngưỡng tỷ lệ lỗi để job tự đánh dấu thất bại** | > …% |
### 2.3.4 Idempotency — chạy lại
🔴 **Câu hỏi bắt buộc, không được để trống:**
| Câu hỏi | Trả lời |
|---|---|
| Chạy lại cùng một ngày hai lần ⇒ kết quả có giống lần đầu không? | ✅/❌ |
| Nếu ❌: hậu quả cụ thể là gì | *(gửi email hai lần? cộng tiền hai lần?)* |
| Nếu ❌: quy trình dọn trước khi chạy lại | |
| Ai được phép chạy lại | |
| Chạy lại có cần khoảng thời gian cụ thể không | |
Job **gửi thông báo, ghi bút toán, gọi API bên ngoài** mà không idempotent là rủi ro nghiêm
trọng — nêu rõ trong `RISK` chứ không chỉ ghi ở đây.
### 2.3.5 Thất bại giữa chừng
| | |
|---|---|
| **Có checkpoint không** | ✅/❌ — lưu ở đâu, mức nào (mỗi bản ghi? mỗi lô?) |
| **Chạy lại tiếp tục từ checkpoint hay từ đầu** | |
| **Có rollback không** | Toàn bộ / Không có / Từng lô |
| **Trạng thái dở dang có làm hỏng nghiệp vụ khác không** | 🔴 *(job sau đọc dữ liệu chưa xong)* |
| **Cách nhận biết job đang chạy dở vs. đã xong** | |
### 2.3.6 Chạy tay
| | |
|---|---|
| Ai được chạy tay | |
| Tham số truyền được | *(ngày nào, phạm vi nào)* |
| Có cần phê duyệt không | *(bắt buộc nếu `RIGOR = strict`)* |
| Chạy tay có ghi vết khác chạy tự động không | |
## 2.4 Trạng thái tương đương "màn hình rỗng"
| Tình huống | Job làm gì | Coi là thành công? |
|---|---|---|
| Không có bản ghi nào thoả điều kiện | Kết thúc, ghi log "0 bản ghi" | ✅ Có — **không phải lỗi** |
| Đầu vào chưa sẵn sàng (job trước chưa xong) | Chờ / Bỏ qua lần này / Báo lỗi | |
| Chạy đúng lịch nhưng hôm đó nghỉ lễ | | |
🔴 Phân biệt **"không có gì để làm"** với **"không lấy được dữ liệu"**. Cả hai đều ra 0 bản
ghi nhưng ý nghĩa ngược nhau, và gộp chúng làm sự cố bị bỏ qua nhiều ngày.
## 2.5 Cảnh báo và vận hành
| Sự kiện | Mức | Báo cho ai | Qua kênh nào | Trong bao lâu |
|---|---|---|---|---|
| Job thất bại | 🔴 | | | Ngay |
| Job chạy quá cửa sổ cho phép | 🔴 | | | Ngay |
| Tỷ lệ bản ghi lỗi vượt ngưỡng | 🟠 | | | Ngay |
| **Job không chạy** *(lịch không kích hoạt)* | 🔴 | | | Sau … phút quá giờ |
🔴 **Dòng cuối là dòng hay bị quên nhất.** Job thất bại thì có cảnh báo; job *không chạy*
thì im lặng hoàn toàn — không có gì để báo lỗi. Phải có cơ chế phát hiện "đến giờ mà chưa
thấy job nào bắt đầu".
| | |
|---|---|
| **Ai trực ban đêm** | |
| **Sổ tay xử lý sự cố ở đâu** | *(đây là `MANUAL` của GĐ5 với loại sản phẩm này)* |
| **Hỏng bao lâu thì phải báo người dùng nghiệp vụ** | |
## 2.6 Nghiệm thu — thay cho UAT thông thường
| # | Cách kiểm | Tiêu chí đi tiếp |
|---|---|---|
| 1 | Chạy trên dữ liệu sao chép từ môi trường thật | Kết quả khớp với xử lý tay trên mẫu … bản ghi |
| 2 | **Chạy song song với cách làm cũ** ≥ 1 chu kỳ nghiệp vụ | Sai lệch = 0, hoặc mọi sai lệch giải thích được |
| 3 | Thử chạy lại | Kết quả không đổi (nếu idempotent) |
| 4 | Thử ngắt giữa chừng rồi chạy lại | Không mất, không trùng bản ghi |
Bước 4 hay bị bỏ, và nó là bước duy nhất chứng minh §2.3.5 hoạt động thật.

View File

@@ -0,0 +1,164 @@
# PART 2 — biến thể `data-pipeline`
> **Dùng khi** `PRODUCT = data-pipeline` — sản phẩm là **dữ liệu**: ETL, ingest, kho dữ
> liệu, báo cáo BI. Cắm khối này vào chỗ PART 2 của [`../srs.md`](../srs.md).
>
> **Tiêu chí G3 riêng của biến thể này**: mỗi luồng có data contract nguồn và đích · bảng
> ánh xạ trường đầy đủ · quy tắc chất lượng có ngưỡng và hành vi khi vi phạm · **có mục đối
> soát nguồn–đích** · đã trả lời xong chạy lại/backfill/dữ liệu đến muộn.
🔴 **Người dùng của bạn không nhìn thấy sản phẩm — họ nhìn thấy những con số.** Con số sai
mà không ai biết là chế độ hỏng nguy hiểm nhất của loại sản phẩm này, vì nó im lặng. Vì vậy
§2.6 (đối soát) và §2.5 (chất lượng) là hai mục quan trọng nhất, không phải phần phụ.
---
## 2.1 Danh sách luồng dữ liệu
| ID | Luồng | Nguồn | Đích | Tần suất | Kiểu | Khối lượng/lần | BR |
|---|---|---|---|---|---|---|---|
| FLW-01 | | | | Hằng đêm 02:00 | Toàn bộ / Tăng dần / CDC | ~… bản ghi | |
## 2.2 Sơ đồ lineage
```mermaid
flowchart LR
POS[["POS API"]]
ERP[["ERP export CSV"]]
STG[("staging.pos_raw")]
DW[("dw.fact_sales")]
RPT(["Báo cáo doanh thu"])
NGUOIDOC(["👤 Kế toán, Ban giám đốc"])
POS -->|FLW-01| STG
STG -->|FLW-02| DW
ERP -->|FLW-03| DW
DW --> RPT
RPT --> NGUOIDOC
```
*Trụ `[( )]` = kho dữ liệu · khung đôi `[[ ]]` = nguồn ngoài, không do mình sở hữu · bo tròn
`([ ])` = đầu ra và người đọc. Nhãn cạnh là mã luồng `FLW-nn`, khớp bảng §2.1.*
🔴 **Vẽ tới tận người tiêu thụ cuối** — báo cáo nào, **ai đọc**. Dừng ở bảng dữ liệu thì khi
luồng hỏng lúc 2 giờ sáng không ai biết phải báo cho ai, và không đánh giá được mức nghiêm
trọng.
**Bảng đi kèm** *(quy tắc W13)*:
| Luồng | Nguồn → Đích | Tần suất | SLA độ tươi | Hỏng thì ai bị ảnh hưởng | Chủ sở hữu nguồn |
|---|---|---|---|---|---|
| FLW-01 | POS API → staging | Hằng đêm 02:00 | D+1 08:00 | Toàn bộ chuỗi phía sau | NCC X — anh Huy |
## 2.3 FLW-01 — <Tên luồng>
### 2.3.1 Nguồn
| | |
|---|---|
| **Hệ thống nguồn** | |
| **Cách lấy** | API / file / CDC / queue |
| **Ai sở hữu nguồn** | *(đầu mối khi schema đổi)* |
| **Nguồn có báo trước khi đổi schema không** | 🔴 Không ⇒ phải có phát hiện schema drift |
| **Cửa sổ dữ liệu sẵn sàng** | *(từ mấy giờ nguồn mới có đủ dữ liệu hôm qua)* |
### 2.3.2 SLA độ tươi
*Thay cho "thời gian phản hồi" của biến thể `screen`.*
| | |
|---|---|
| **Dữ liệu ngày D phải sẵn sàng trước** | D+1 08:00 |
| **Trễ tối đa chấp nhận được** | |
| **Ai được báo khi trễ** | |
| **Người dùng thấy gì khi dữ liệu chưa tới** | 🔴 Số cũ? Số rỗng? **Có nhãn cảnh báo không?** |
🔴 Câu cuối là câu hay bị bỏ nhất. Báo cáo hiển thị số của hôm kia mà không có nhãn "dữ liệu
tới 28/08" là cách người dùng ra quyết định trên số cũ mà không biết.
### 2.3.3 Data contract — schema nguồn
| Trường nguồn | Kiểu | Có thể null | Nghĩa nghiệp vụ | Giá trị hợp lệ | Ghi chú |
|---|---|---|---|---|---|
### 2.3.4 Ánh xạ trường
| Trường đích | Từ trường nguồn | Phép biến đổi | Khi nguồn null | Khi nguồn sai định dạng | BR |
|---|---|---|---|---|---|
| `store_code` | `shop.id` | upper(trim(x)) | → `UNKNOWN` | → quarantine | BR-0nn |
| `amount` | `total` | chia 100 (nguồn lưu đơn vị nhỏ nhất) | → 0 | → quarantine | BR-0nn |
🔴 **Ba cột cuối là phần thay thế cho "validation + message lỗi" của biến thể `screen`.**
Bỏ trống ⇒ kỹ sư dữ liệu tự quyết, và mỗi luồng một kiểu.
### 2.3.5 Khoá và trùng lặp
| | |
|---|---|
| **Khoá nghiệp vụ** | *(cái gì xác định một bản ghi là duy nhất)* |
| **Nguồn có gửi trùng không** | |
| **Trùng thì xử lý sao** | Giữ bản mới nhất / cộng dồn / báo lỗi |
| **Bản ghi bị sửa ở nguồn** | Ghi đè / giữ lịch sử (SCD loại mấy) |
## 2.4 Quy tắc chất lượng dữ liệu
| ID | Kiểm tra gì | Ngưỡng | Vi phạm thì làm gì | Ai được báo |
|---|---|---|---|---|
| DQ-01 | Số bản ghi so với trung bình 7 ngày | ±30% | ⚠️ Cảnh báo, vẫn nạp | |
| DQ-02 | Tỷ lệ `store_code` không map được | > 1% | 🛑 **Dừng luồng** | |
| DQ-03 | Tổng tiền âm | > 0 bản ghi | 🔴 Quarantine bản ghi đó | |
**Ba hành vi khi vi phạm — chọn rõ một, không được để mơ hồ:**
| Hành vi | Nghĩa | Dùng khi |
|---|---|---|
| `drop` | Bỏ bản ghi, ghi log | Bản ghi rác đã biết, không ảnh hưởng tổng |
| `quarantine` | Tách sang bảng riêng để xử lý tay | 🔴 **Mặc định nên chọn** — giữ được dữ liệu để điều tra |
| `fail` | Dừng cả luồng | Sai lệch có thể làm hỏng báo cáo tài chính |
🔴 **`drop` im lặng là chế độ hỏng tệ nhất.** Số liệu thiếu mà không ai biết. Chọn `drop`
phải kèm ngưỡng cảnh báo.
## 2.5 Trạng thái tương đương "màn hình rỗng"
| Tình huống | Xử lý | Người dùng thấy gì |
|---|---|---|
| Ngày không có giao dịch nào (chủ nhật, lễ) | Nạp 0 bản ghi — **không phải lỗi** | Báo cáo hiện 0, có nhãn "không có giao dịch" |
| Nguồn không phản hồi | | |
| Nguồn trả rỗng bất thường | 🔴 Phân biệt với ca trên bằng cách nào? | |
Phân biệt **"không có dữ liệu"** với **"chưa lấy được dữ liệu"** là bắt buộc. Hai thứ này
nhìn giống nhau trên báo cáo nhưng ý nghĩa ngược nhau.
## 2.6 Đối soát nguồn – đích
🔴 **Mục bắt buộc, không được bỏ.** Đây là thứ duy nhất chứng minh dữ liệu không bị mất
giữa đường.
| # | Đối chiếu gì | Nguồn | Đích | Sai lệch cho phép | Tần suất | Ai kiểm |
|---|---|---|---|---|---|---|
| 1 | Số bản ghi | count(pos_api) | count(fact_sales) | 0 | Mỗi lần chạy | Tự động |
| 2 | Tổng tiền | sum(total) | sum(amount)×100 | ≤ 1 đơn vị (làm tròn) | Hằng ngày | Tự động |
| 3 | Đối chiếu với báo cáo hệ thống cũ | | | | Hằng tháng | Kế toán |
**Sai lệch vượt ngưỡng thì làm gì, ai chịu trách nhiệm xử lý:** …
## 2.7 Chạy lại, backfill, dữ liệu đến muộn
| Câu hỏi | Trả lời |
|---|---|
| **Chạy lại cùng một ngày hai lần** → kết quả có giống không (idempotent)? | 🔴 Không idempotent ⇒ nói rõ quy trình dọn trước khi chạy lại |
| **Backfill** lịch sử N ngày làm thế nào? Mất bao lâu? Ảnh hưởng báo cáo đang chạy không? | |
| **Dữ liệu đến muộn** (giao dịch hôm qua tới hôm nay) | Nạp vào ngày phát sinh hay ngày nhận? |
| Nạp vào ngày phát sinh ⇒ **báo cáo đã chốt có thay đổi không?** | 🔴 Nếu có, ai được báo |
| **Thất bại giữa chừng** | Rollback toàn bộ / tiếp tục từ checkpoint |
## 2.8 Vận hành
| | |
|---|---|
| Chạy tự động lúc | |
| Chạy tay được không, ai được chạy | |
| Cảnh báo gửi đi đâu | |
| Ai trực khi luồng hỏng ban đêm | |
| Hỏng bao lâu thì phải báo người dùng | |

View File

@@ -0,0 +1,170 @@
# PART 2 — biến thể `ml-model`
> **Dùng khi** `PRODUCT = ml-model` — sản phẩm là mô hình dự đoán: phân loại, hồi quy, xếp
> hạng, gợi ý, sinh nội dung. Cắm khối này vào chỗ PART 2 của [`../srs.md`](../srs.md).
>
> **Tiêu chí G3 riêng của biến thể này**: định nghĩa nhãn rõ ràng và có người gán · tập đánh
> giá được chốt và cách ly · mọi metric có **ngưỡng chấp nhận** và ngưỡng đó truy về được
> chi phí nghiệp vụ · có hành vi fallback · có tiêu chí giám sát drift.
---
## 🔴 Đọc trước: yêu cầu ở đây mang tính xác suất
Given/When/Then **không mô tả được** phần dự đoán. Không tồn tại AC kiểu *"Given ảnh này,
When chạy mô hình, Then trả về đúng nhãn"* — mô hình sẽ sai một tỷ lệ nào đó, và điều đó
không phải bug.
Chia làm hai phần và đối xử khác nhau:
| Phần | Đặc tả bằng | Ai nghiệm thu |
|---|---|---|
| **Hệ thống bao quanh** — API, hàng đợi, lưu kết quả, hiển thị, xử lý lỗi | AC Given/When/Then bình thường, 4 nhóm đầy đủ | QA |
| **Chất lượng dự đoán** | **Metric + ngưỡng chấp nhận** trên tập đánh giá đã chốt | PO + người sở hữu nghiệp vụ |
Nhầm hai phần này là lỗi kinh điển: QA viết test case đòi mô hình đúng 100% trên vài mẫu tự
chọn, rồi kết luận fail.
---
## 2.1 Bài toán
| | |
|---|---|
| **Loại** | Phân loại nhị phân / đa lớp / Hồi quy / Xếp hạng / Gợi ý / Sinh nội dung |
| **Đầu vào** | *(thực thể nào, có những thông tin gì)* |
| **Đầu ra** | *(nhãn? điểm số? danh sách xếp hạng? văn bản?)* |
| **Quyết định nghiệp vụ nào phụ thuộc đầu ra này** | |
| **Ai/cái gì hành động dựa trên đầu ra** | Người xem rồi quyết / Hệ thống tự động thực thi |
🔴 **Câu cuối quyết định mọi thứ phía sau.** Mô hình chỉ gợi ý cho người xem thì sai số chịu
được cao hơn nhiều so với mô hình tự động chặn giao dịch.
## 2.2 Định nghĩa nhãn / ground truth
*Mục quan trọng nhất và bị bỏ nhiều nhất. Nhãn định nghĩa lỏng ⇒ mọi metric phía sau vô nghĩa.*
| | |
|---|---|
| **Nhãn là gì** | *(định nghĩa nghiệp vụ chính xác, không phải "gian lận" chung chung)* |
| **Ai gán nhãn** | |
| **Gán theo quy tắc nào** | *(kèm ví dụ ca khó)* |
| **Hai người gán có ra cùng kết quả không** | *(đo bằng gì, tỷ lệ đồng thuận bao nhiêu)* |
| **Nhãn có sẵn tự nhiên không** | *(vd: khách có click hay không — nhãn ngầm)* |
| **Độ trễ có nhãn** | 🔴 *(vd: biết một khoản vay xấu phải chờ 6 tháng)* |
| Ca biên | Gán nhãn thế nào |
|---|---|
| | |
## 2.3 Dữ liệu
| Tập | Khoảng thời gian | Số bản ghi | Tỷ lệ lớp dương | Cách chọn |
|---|---|---|---|---|
| Train | | | | |
| Validation | | | | |
| **Test / giữ lại** | | | | 🔴 Chốt trước, **không ai được xem trong lúc phát triển** |
**Rà rò rỉ dữ liệu (data leakage)** — bốn chỗ hay rò:
| # | Chỗ rò | Kiểm tra |
|---|---|---|
| 1 | Trường chỉ tồn tại **sau** khi biết kết quả | ☐ Mọi trường đầu vào đều có tại thời điểm cần dự đoán |
| 2 | Chia tập ngẫu nhiên trong khi dữ liệu có thứ tự thời gian | ☐ Chia theo thời gian nếu bài toán có yếu tố thời gian |
| 3 | Cùng một thực thể xuất hiện ở cả train và test | ☐ Chia theo nhóm thực thể |
| 4 | Chuẩn hoá/thống kê tính trên toàn bộ dữ liệu trước khi chia | ☐ Chỉ tính trên train |
## 2.4 Metric và ngưỡng chấp nhận
*Thay cho "acceptance criteria" của phần dự đoán.*
| ID | Metric | Đo trên | Ngưỡng chấp nhận | Baseline hiện tại | Truy về chi phí nghiệp vụ nào |
|---|---|---|---|---|---|
| M-01 | Precision @ ngưỡng 0.7 | Test | ≥ 0,85 | Quy tắc tay: 0,62 | Mỗi FP tốn … công xử lý tay |
| M-02 | Recall | Test | ≥ 0,70 | 0,45 | Mỗi FN tốn … thiệt hại |
| M-03 | Metric theo phân khúc *(xem §2.6)* | Test | Không phân khúc nào < 0,60 | | |
🔴 **Mỗi ngưỡng phải truy về được chi phí nghiệp vụ.** "Precision ≥ 0,85" chọn từ đâu? Nếu
không trả lời được thì đó là con số ai đó thấy đẹp — và nó sẽ bị tranh cãi lại đúng lúc mô
hình đạt 0,84.
**Baseline bắt buộc:** so với **cách làm hiện tại** (quy tắc tay, con người, hoặc đoán theo
lớp phổ biến nhất). Mô hình đạt 0,85 mà quy tắc tay đã đạt 0,84 thì không đáng triển khai.
## 2.5 Ngưỡng quyết định và đánh đổi
| | |
|---|---|
| **Đầu ra thô** | Điểm số 0–1 |
| **Ngưỡng cắt** | *(giá trị, và ai được đổi nó)* |
| **Đổi ngưỡng có cần triển khai lại không** | 🔴 Nên là **không** — để nghiệp vụ tự điều chỉnh |
| Ngưỡng | Precision | Recall | Số ca/ngày phải xử lý tay | Ghi chú |
|---|---|---|---|---|
| 0,5 | | | | |
| 0,7 | | | | ← đề xuất |
| 0,9 | | | | |
Bảng này là bảng **PO đọc để chọn**, không phải BA chọn thay.
## 2.6 Công bằng và phân khúc
| Phân khúc | Vì sao cần kiểm riêng | Metric | Ngưỡng | Kết quả |
|---|---|---|---|---|
| Khách hàng mới (< 30 ngày) | Ít dữ liệu lịch sử | | | |
| Theo vùng/chi nhánh | Phân bố khác nhau | | | |
Metric tổng thể đẹp mà một phân khúc quan trọng rất tệ là tình huống phổ biến, và người dùng
sẽ phát hiện ra trước bạn.
## 2.7 Hành vi khi không chắc chắn & fallback
*Đây là phần **có** viết được bằng AC bình thường.*
| Tình huống | Hệ thống làm gì | AC |
|---|---|---|
| Điểm số nằm trong vùng xám (0,4–0,6) | Chuyển người xử lý tay / gán nhãn "không xác định" | AC-nn |
| Thiếu trường đầu vào bắt buộc | 🔴 Đoán đại hay từ chối dự đoán? | AC-nn |
| Mô hình không phản hồi / timeout | Trả kết quả mặc định nào? | AC-nn |
| Đầu vào ngoài phân phối đã học | | AC-nn |
🔴 **"Từ chối dự đoán" phải là một đầu ra hợp lệ.** Bắt mô hình luôn trả lời là bắt nó đoán
bừa ở đúng những ca nó không biết.
## 2.8 Giám sát và huấn luyện lại
| Theo dõi gì | Ngưỡng cảnh báo | Ai được báo | Hành động |
|---|---|---|---|
| Phân phối đầu vào lệch so với train | | | |
| Tỷ lệ dự đoán lớp dương | | | |
| Metric trên nhãn thật *(khi có)* | | | |
| Tỷ lệ rơi vào vùng xám | | | |
| | |
|---|---|
| **Tiêu chí huấn luyện lại** | *(theo lịch? theo ngưỡng drift? theo lượng nhãn mới?)* |
| **Ai duyệt mô hình mới trước khi thay** | |
| **So sánh mô hình mới với cũ bằng gì** | *(cùng tập test đã chốt ở §2.3)* |
| **Rollback về mô hình cũ thế nào** | |
## 2.9 Giải thích được và khiếu nại
| | |
|---|---|
| Người bị ảnh hưởng có quyền biết lý do không | *(có yêu cầu pháp lý không)* |
| Hiển thị lý do ở mức nào | Không / Yếu tố chính / Đầy đủ |
| Người dùng phản đối kết quả thì quy trình nào | |
| Lưu vết: đầu vào, phiên bản mô hình, đầu ra, giữ bao lâu | 🔴 Bắt buộc nếu `RIGOR = strict` |
## 2.10 Nghiệm thu — thay cho UAT thông thường
| Giai đoạn | Làm gì | Tiêu chí đi tiếp |
|---|---|---|
| 1. Offline | Đánh giá trên tập test đã chốt | Đạt mọi ngưỡng §2.4 |
| 2. Shadow | Chạy song song, **không tác động nghiệp vụ**, so với quyết định của người | Sai lệch trong ngưỡng, tối thiểu … ngày |
| 3. Thí điểm | Bật cho một phân khúc nhỏ | Metric online giữ ngưỡng, không có sự cố |
| 4. Mở rộng | | |
🔴 Bỏ bước **shadow** là rủi ro lớn nhất của loại sản phẩm này. Metric offline đẹp mà dữ liệu
thật khác phân phối là chuyện xảy ra thường xuyên, và chỉ shadow mới phát hiện được trước
khi có thiệt hại.

View File

@@ -0,0 +1,169 @@
# PART 2 — biến thể `screen`
> **Dùng khi** `PRODUCT = screen` — sản phẩm có giao diện người dùng (web admin, web app,
> app di động). Cắm khối này vào chỗ PART 2 của [`../srs.md`](../srs.md).
>
> **Tiêu chí G3 riêng của biến thể này** *(thay cho dòng "bảng field" và "wireframe" ở
> [workflow.md §2](../../../ba-lifecycle/references/workflow.md))*:
> mỗi màn hình có bảng thành phần đủ 8 cột · mỗi field có bảng field đủ 10 cột · hai trạng
> thái rỗng khác nhau · mọi thành phần có điều kiện ẩn/khoá/read-only rõ ràng.
---
## 2.1 Danh sách màn hình
| ID | Tên màn hình | Loại | Đường dẫn | Vai trò truy cập | Vào từ đâu | Ra đi đâu |
|---|---|---|---|---|---|---|
| SCR-01 | | Danh sách / Chi tiết / Form / Modal | | | | |
Liệt kê **từng màn hình riêng**, kể cả modal và trang lỗi.
## 2.2 Sơ đồ điều hướng
```mermaid
flowchart LR
MENU(["Menu"]) --> SCR01["SCR-01 Danh sách"]
SCR01 -->|chọn dòng| SCR02["SCR-02 Chi tiết"]
SCR01 -->|nút Tạo mới| SCR03["SCR-03 Form"]
SCR03 -->|lưu thành công| SCR01
SCR03 -->|huỷ / back| SCR01
SCR02 -->|back / breadcrumb| SCR01
```
*ID node khớp cột `ID` của bảng §2.1. Nhãn cạnh là **hành động gây điều hướng**.*
🔴 **Vẽ cả cạnh quay lại.** Sơ đồ chỉ có chiều đi là sơ đồ bỏ sót đúng phần hay lỗi nhất —
quay lại có giữ trạng thái danh sách không, huỷ giữa chừng thì đi đâu.
**Ba câu bắt buộc trả lời cho mỗi màn hình chi tiết/form:**
| Câu hỏi | SCR-02 | SCR-03 |
|---|---|---|
| Hai đường quay lại (nút back + breadcrumb)? Quay lại có giữ trạng thái danh sách (trang, bộ lọc)? | | |
| Vào bằng URL trực tiếp với id không tồn tại / không có quyền ⇒ hiện gì? | | |
| Rời màn hình khi form đang dở ⇒ có cảnh báo mất dữ liệu không? | | |
---
## 2.3 SCR-01 — <Tên màn hình>
**Wireframe:** `WF_SCR-01.png` *(bố cục — hành vi xem bảng bên dưới)*
### 2.3.1 Bảng thành phần
| ID | Tên thành phần | Loại | Nhãn (nguyên văn) | Placeholder / Hint | Hành vi & sự kiện | Điều kiện ẩn/khoá | BR |
|---|---|---|---|---|---|---|---|
| C01 | btn_create | Nút | Tạo mới | — | Mở SCR-03 | ❌ ẩn với ROLE-03 | |
| C02 | txt_search | Ô nhập | — | Nhập mã hoặc tên | Enter hoặc bấm Tìm mới gọi API | — | |
**Cột `Điều kiện ẩn/khoá` — chọn rõ một trong ba (quy tắc W7):**
| Ký hiệu | Nghĩa | Người dùng thấy gì |
|---|---|---|
| `❌ ẩn` | Không render | Không biết chức năng tồn tại |
| `🔒 disable` | Render, không bấm được, **có tooltip nêu lý do** | Biết có, biết vì sao chưa dùng được |
| `👁 read-only` | Hiện giá trị, không sửa được | Tra cứu được |
Ghi "tuỳ quyền" là **chưa đặc tả xong**.
### 2.3.2 Bảng dữ liệu hiển thị *(màn hình danh sách)*
| # | Cột | Nguồn dữ liệu | Định dạng | Sắp xếp được | Mặc định | Xử lý khi rỗng | Độ rộng |
|---|---|---|---|---|---|---|---|
| 1 | Mã | `code` | Chữ hoa | ✅ | Sắp tăng | `—` | 120px |
| | |
|---|---|
| **Sắp xếp mặc định** | |
| **Số dòng mỗi trang** | mặc định … · tuỳ chọn … |
| **Kiểu phân trang** | Offset / Cursor |
| **Bấm vào dòng** | Mở chi tiết / Không |
### 2.3.3 Bảng field *(màn hình có nhập liệu)*
| ID | Tên field | Kiểu | Bắt buộc | Độ dài / Khoảng | Default | Nguồn giá trị | Validation | Message khi sai | BR |
|---|---|---|---|---|---|---|---|---|---|
| F01 | | Text | ✅ | 3–20 ký tự (code point UTF-8) | — | Người dùng nhập | | `E-XXX-0001` | BR-0nn |
| F02 | | Chọn 1 | ✅ | — | | API `/…`, lọc `active=true`, sắp theo `order` | phải thuộc danh sách | `E-XXX-0002` | |
🔴 **Bốn cột không được để trống:**
| Cột | Nếu bỏ trống |
|---|---|
| **Độ dài/Khoảng** | DB nhận 255, UI không chặn ⇒ lỗi 500 khi dán đoạn dài. Ghi kèm **đơn vị** (ký tự? byte?) |
| **Default** | Mỗi màn hình một kiểu, báo cáo lệch vì bản ghi cũ null |
| **Nguồn giá trị** | Dropdown lấy từ đâu, **lọc theo gì**, **sắp xếp thế nào** |
| **Message khi sai** | Dev tự viết ⇒ mỗi màn hình một giọng, không dịch được |
### 2.3.4 Sơ đồ luồng *(bắt buộc khi hành động chạm ≥ 3 bên)*
*Chỉ vẽ khi luồng đi qua người dùng → giao diện → hệ thống → bên thứ ba, hoặc có bước bất
đồng bộ. Luồng đơn giản (bấm Lưu, gọi 1 API) thì bảng §2.3.5 là đủ.*
```mermaid
sequenceDiagram
autonumber
actor U as Người dùng
participant FE as Giao diện
participant BE as Hệ thống
participant EXT as Hệ thống ngoài
U->>FE: Bấm Lưu
FE->>FE: Validate phía giao diện
FE->>BE: POST /… (khoá nút Lưu)
BE->>EXT: Kiểm tra … (timeout 3s)
alt Phản hồi kịp
EXT-->>BE: 200 OK
BE-->>FE: 200 + id
FE-->>U: Toast "Đã lưu", về danh sách
else Timeout / lỗi
EXT--xBE: timeout
BE-->>FE: 503 · E-XXX-0503
FE-->>U: Báo lỗi, GIỮ NGUYÊN dữ liệu đã nhập, mở lại nút Lưu
end
```
🔴 **Bắt buộc vẽ cả nhánh lỗi** (`alt`/`else`). Sequence chỉ có luồng thành công là vi phạm
quy tắc W4, và đó chính là nhánh dev hay tự bịa.
**Bảng đi kèm** *(quy tắc W13 — sơ đồ không nói được mã lỗi và ngưỡng)*:
| Bước | Mô tả | Timeout | Thất bại thì sao | Mã lỗi | AC |
|---|---|---|---|---|---|
| 3 | Gửi form lên hệ thống | 30s | Giữ dữ liệu, mở lại nút | `E-XXX-0503` | AC-09 |
| 4 | Kiểm tra với hệ thống ngoài | 3s | Cho lưu và đồng bộ sau / chặn? | | |
*Số ở cột **Bước** là số `autonumber` trong sơ đồ.*
### 2.3.5 Hành động trên màn hình
| Hành động | Điều kiện được phép | Xác nhận trước khi làm | Kết quả thành công | Kết quả thất bại | Vai trò | AC |
|---|---|---|---|---|---|---|
| Lưu | Form hợp lệ | Không | Toast + về danh sách | Giữ nguyên dữ liệu đã nhập, hiện lỗi | | AC-01 |
| Xoá | Trạng thái = Nháp | ✅ Modal + **nhập lý do** | Toast + xoá khỏi danh sách | Hiện lỗi, không xoá | | AC-07 |
🔴 **Thất bại phải giữ nguyên dữ liệu người dùng đã nhập.** Xoá trắng form sau lỗi mạng là
lỗi trải nghiệm nghiêm trọng và rất hay xảy ra khi spec không nói.
### 2.3.6 Trạng thái rỗng, đang tải, lỗi
| Trạng thái | Hiển thị gì | Text nguyên văn | Nút hành động |
|---|---|---|---|
| Đang tải | | | |
| **Chưa có dữ liệu nào** | | "Chưa có bản ghi nào. Tạo bản ghi đầu tiên?" | ✅ Tạo mới |
| **Bộ lọc không khớp** | | "Không tìm thấy kết quả phù hợp với bộ lọc." | ✅ Xoá lọc |
| Lỗi tải dữ liệu | | | ✅ Thử lại |
🔴 Hai trạng thái rỗng **phải khác nhau**. Dùng chung một câu khiến người dùng tưởng mất dữ liệu.
---
## Lưu ý cho `PRODUCT = screen` + app di động B2C
Biến thể này viết cho phần mềm nghiệp vụ có vai trò. Với app tiêu dùng B2C, ba chỗ lệch:
| Chỗ | Điều chỉnh |
|---|---|
| `RBAC` ở GĐ2 | Thường chỉ 1–2 vai trò ⇒ ma trận gần như rỗng. Ghi rõ thay vì bỏ, và chuyển trọng tâm sang **phạm vi dữ liệu của chính người dùng** |
| Elicitation ở GĐ1 | Không phỏng vấn được hàng vạn người ⇒ dựa vào analytics + phỏng vấn sâu vài người + khảo sát |
| UAT ở GĐ4 | "Đúng người dùng thật" không scale ⇒ thay bằng **usability test 5–8 người + beta có giám sát**, tiêu chí pass đổi thành tỷ lệ hoàn thành tác vụ |

View File

@@ -0,0 +1,214 @@
# SRS — <US-id> <Tên User Story>
| | |
|---|---|
| **Version** | 1.0 |
| **Date** | YYYY-MM-DD |
| **Author** | <BA> (skill ba-3-specification) |
| **Status** | 🟡 Draft |
| **Approved by** | PO: — · Tech Lead: — · QA: — |
| **Source** | BACKLOG_… v1.0 · BR_… v1.0 · RBAC_… v1.0 · IMPACT_… v1.0 |
| **Scope** | US-0nn |
| **Profile** | `screen · brownfield · standard` *(PRODUCT · LIFECYCLE · RIGOR)* |
| **Biến thể PART 2** | `srs-part2/screen.md` |
| **Ngôn ngữ hiển thị** | VI / EN / KO *(N/A nếu PRODUCT không có giao diện)* |
## Change Log
| Version | Date | Người sửa | Thay đổi | CR |
|---|---|---|---|---|
| 1.0 | | | Bản đầu | — |
> **Quy ước đọc tài liệu này** *(quy tắc W11 — chỉ áp dụng khi `PRODUCT = screen`)*
> Bảng thành phần quyết định **một phần tử có tồn tại hay không** và **nó hành xử thế nào**.
> Wireframe quyết định **nó nằm ở đâu, to bằng nào**. Khi hai thứ mâu thuẫn: bảng thắng về
> sự tồn tại và hành vi, hình thắng về bố cục — và mâu thuẫn đó phải được báo cho BA.
---
## PHẦN 1 — NGHIỆP VỤ
### 1.1 Tóm tắt cho người quyết định
*Ba câu, ngôn ngữ nghiệp vụ. Không tên bảng dữ liệu, không tên component.*
| | |
|---|---|
| **US này giải quyết** | RQ-0nn: … |
| **Người dùng được gì** | |
| **Khác hiện tại chỗ nào** | |
### 1.2 Vai trò và quyền
| Vai trò | Được làm gì trong US này | Không được làm gì | RBAC |
|---|---|---|---|
### 1.3 Business rule áp dụng
*Tham chiếu, không chép lại. Chép lại là tạo ra hai nguồn sự thật.*
| BR | Phát biểu ngắn | Áp dụng ở màn hình/field nào | Khi vi phạm |
|---|---|---|---|
| BR-021 | | | Chặn → `E-STL-0001` |
### 1.4 Vòng đời trạng thái *(nếu có)*
| Nguồn | Sự kiện | Điều kiện | Đích | Ai được làm | BR |
|---|---|---|---|---|---|
### 1.5 Ngoài phạm vi *(quy tắc W12)*
*Những gì người đọc có thể tưởng là có nhưng không có, và xử lý ở đâu.*
| # | Không làm gì | Vì sao | Xử lý ở đâu/khi nào |
|---|---|---|---|
---
## PHẦN 2 — ĐẶC TẢ SẢN PHẨM
> 🔴 **Phần này thay đổi theo `PRODUCT` trong profile.** Nạp đúng một (hoặc nhiều) biến thể
> dưới đây rồi chèn nội dung vào chỗ này — đừng viết PART 2 từ đầu.
| `PRODUCT` | Biến thể nạp | PART 2 mô tả gì |
|---|---|---|
| `screen` | [`srs-part2/screen.md`](srs-part2/screen.md) | Màn hình, bảng thành phần, bảng field, trạng thái rỗng |
| `api-service` | [`srs-part2/api-service.md`](srs-part2/api-service.md) | Người tiêu thụ, khả năng, hợp đồng dữ liệu, tương thích ngược |
| `data-pipeline` | [`srs-part2/data-pipeline.md`](srs-part2/data-pipeline.md) | Luồng dữ liệu, data contract, chất lượng, đối soát nguồn–đích |
| `ml-model` | [`srs-part2/ml-model.md`](srs-part2/ml-model.md) | Bài toán, nhãn, metric + ngưỡng, fallback, drift |
| `batch-job` | [`srs-part2/batch-job.md`](srs-part2/batch-job.md) | Job, lịch, idempotency, thất bại giữa chừng, cảnh báo |
| `process-only` | — | Không có PART 2 — nội dung nằm ở `PROCESS` của GĐ2 |
**Một US thường có nhiều loại** (màn hình + API, hoặc màn hình + job đêm). Khi đó nạp nhiều
biến thể, mỗi biến thể một mục con: `2.A Màn hình` · `2.B API` · `2.C Job`. Ghi rõ đã nạp
biến thể nào vào dòng **Biến thể PART 2** ở header.
Chọn `PRODUCT` thế nào: [domain-profiles.md §1](../../ba-lifecycle/references/domain-profiles.md).
🔴 Loại chưa có biến thể (nhúng / IoT / firmware) ⇒ **nói rõ với người dùng là phải tự viết
PART 2**, đừng nhét vào biến thể gần đúng nhất.
*(chèn nội dung biến thể vào đây)*
---
## PHẦN 3 — TIÊU CHÍ NGHIỆM THU
*Chi tiết ở `AC_<US>.md`, hoặc viết trực tiếp ở đây theo mẫu `templates/acceptance-criteria.md`.*
| AC | Nhóm | Tóm tắt | Field/Thành phần | BR | Test case (QA điền) |
|---|---|---|---|---|---|
| AC-01 | Thành công | | | | |
| AC-05 | Validation | | | | |
| AC-09 | Lỗi hệ thống | | | | |
| AC-12 | Phân quyền | | | | |
**Mỗi US phải có đủ bốn nhóm.** Chỉ có nhóm "Thành công" ⇒ spec chưa viết xong.
---
## PHẦN 4 — MÃ LỖI VÀ TEXT HIỂN THỊ
### 4.1 Mã lỗi
| Mã | Khi nào xảy ra | Thông điệp hiển thị (nguyên văn) | Hiển thị ở đâu | Người dùng làm gì tiếp | BR/AC |
|---|---|---|---|---|---|
| `E-STL-0001` | Mã cửa hàng đã tồn tại | "Mã cửa hàng này đã được sử dụng. Vui lòng nhập mã khác." | Dưới field F01 | Sửa mã | BR-021 |
Đặt mã theo `E-<DOMAIN>-<4 số>`. **Không tái sử dụng mã.** Dự án đã có dãy mã ⇒ dùng tiếp số.
### 4.2 Text màn hình *(chỉ khi có giao diện cho người)*
`PRODUCT` không có giao diện ⇒ ghi "N/A — không có text hiển thị", **đừng xoá mục**.
Với `api-service` và `batch-job`, phần tương đương là **thông điệp trả cho người gọi /
nội dung cảnh báo vận hành** — đặc tả ở PART 2 của biến thể tương ứng.
| Khoá | Ngữ cảnh | VI | EN | KO | Giới hạn ký tự |
|---|---|---|---|---|---|
### 4.3 Định dạng hiển thị *(chỉ khi có giao diện cho người)*
| Loại dữ liệu | Định dạng | Ví dụ | Ghi chú |
|---|---|---|---|
| Ngày | `dd/MM/yyyy` | 30/08/2026 | |
| Ngày giờ | `dd/MM/yyyy HH:mm` | 30/08/2026 14:05 | **Múi giờ hiển thị: …** |
| Số tiền | `#,##0` + " ₫" | 1.234.567 ₫ | Làm tròn: … |
| Số lượng | | | |
| Rỗng/null | `—` | | Thống nhất toàn hệ thống |
---
## PHẦN 5 — PHI CHỨC NĂNG
*Chi tiết ở `NFR_<US>.md`. Ở đây chỉ nêu cái áp dụng riêng cho US này.*
| ID | Nhóm | Yêu cầu (có số đo) | Điều kiện đo | Cách verify |
|---|---|---|---|---|
---
## PHẦN 6 — DỮ LIỆU VÀ TÍCH HỢP
### 6.1 API sử dụng
*Chi tiết ở `API_<US>.md`.*
| # | Mục đích | Method | Endpoint | Nguồn contract |
|---|---|---|---|---|
| 1 | Lấy danh sách | GET | `/api/v1/…` | ⚠️ BA đề xuất / ✅ BE cung cấp |
### 6.2 Tác động dữ liệu
*Tham chiếu `IMPACT_….md`. Nêu ngắn cái liên quan trực tiếp US này.*
---
## PHẦN 7 — BÀN GIAO CHO DEV
| | |
|---|---|
| **Trạng thái** | 🟡 Chưa sẵn sàng / ✅ Ready for Dev |
| **Tài liệu cần đọc kèm** | *(liệt kê theo thứ tự)* |
| **Quyết định đã chốt, không phải mặc định** | *(dev không được tự đổi)* |
| **Điểm còn mở** | *(OQ chưa trả lời, ảnh hưởng gì)* |
| **Không được sao chép từ đâu** | *(màn hình cũ có phần đã lỗi thời)* |
---
## PHẦN 8 — OPEN QUESTIONS
| ID | Câu hỏi | Hỏi ai | Từ ngày | Chặn gì | 🔴 chặn G3? | Phương án BA đề xuất |
|---|---|---|---|---|---|---|
| OQ-0nn | | | | AC-05, F03 | 🔴 | *(đề xuất, chưa phải quyết định)* |
---
## Tự chấm
**Gate G3**
| # | Tiêu chí | ☐/✅ | Ghi chú |
|---|---|---|---|
| 1 | Đủ mọi mục bắt buộc (mục N/A có ghi lý do) | | |
| 2 | **Tiêu chí riêng của biến thể PART 2 đã nạp** *(xem đầu file biến thể)* | | |
| 3 | Mỗi US có AC đủ 4 nhóm, có luồng lỗi *(`ml-model`: xem §2.4 metric + ngưỡng)* | | |
| 4 | Bảng mã lỗi đầy đủ, mỗi mã có thông điệp | | |
| 5 | NFR có số đo + cách verify | | |
| 6 | API contract có, ghi rõ nguồn | | |
| 7 | QA xác nhận mọi AC test được | | |
| 8 | Không còn OQ mở ảnh hưởng hành vi | | |
| 9 | Không còn `TBD` trong bảng đặc tả PART 2 và bảng mã lỗi | | |
| 10 | Đã áp đúng bảng "Bớt ở light" / "Thêm ở strict" theo `RIGOR` | | |
**Quét bắt buộc trước khi nộp:**
```bash
grep -niE "nhanh|mượt|thân thiện|v\.v|phù hợp|tương ứng|nên |có thể " SRS_….md # W2 — phải rỗng
grep -n "TBD\|TODO\|???" SRS_….md # phải rỗng
```
**Quy tắc viết W1–W13**
| W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| | | | | | | | | | | | | |