init git
This commit is contained in:
@@ -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 |
|
||||
|---|---|---|
|
||||
163
.claude/skills/ba-3-specification/templates/api-contract.md
Normal file
163
.claude/skills/ba-3-specification/templates/api-contract.md
Normal 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ì |
|
||||
|---|---|---|---|---|
|
||||
148
.claude/skills/ba-3-specification/templates/nfr-checklist.md
Normal file
148
.claude/skills/ba-3-specification/templates/nfr-checklist.md
Normal 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 | |
|
||||
@@ -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)* |
|
||||
@@ -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.
|
||||
@@ -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 | |
|
||||
@@ -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.
|
||||
169
.claude/skills/ba-3-specification/templates/srs-part2/screen.md
Normal file
169
.claude/skills/ba-3-specification/templates/srs-part2/screen.md
Normal 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ụ |
|
||||
214
.claude/skills/ba-3-specification/templates/srs.md
Normal file
214
.claude/skills/ba-3-specification/templates/srs.md
Normal 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 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| | | | | | | | | | | | | |
|
||||
Reference in New Issue
Block a user