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,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ụ |