Upload files to "/"

This commit is contained in:
2026-10-15 21:27:39 +07:00
parent 2fefa615cc
commit e3c710c0ef
+644
View File
@@ -0,0 +1,644 @@
# Đặc tả Số hoá, OCR và Tiếp nhận văn bản: giai đoạn 1, phạm vi tối thiểu
> ⚠️ **GIAI ĐOẠN NÀY KHÔNG CÓ VĂN BẢN MẬT** (tuannq, 28/9, #9 c2706, #38 c2091). Không thiết kế, không làm, không hỏi thêm về độ mật, văn bản mật, bút phê mật, chỉ đạo mật. Ô «Độ mật» ẩn, giá trị luôn «Thường».
>
> ⚠️ **Environment note (operator, 29/9, #1 c3248):** every host name, IP, port, measurement and figure taken from Omen 192.168.1.67 and this LAN describes the **dev/test/debug environment, not production**. Sizing, network, security, backup targets and operations must be recalculated for the real production machine and network before go-live.
**Lịch sử sửa đổi**
- 28/9 (khuya, lượt 6, tuannq tạm duyệt; lead chốt theo đề xuất, #1 c2985): Q1–Q7 **đã chốt** (mục 20). Sửa theo: trạng thái đầu tệp; quyết định liên quan (#32 c2653: không tắt cổng trên .67, MỞ RỘNG Q6); 2.1 «Ai dùng» (mọi người có `Documents.Intake`, KHO Q3, PHÂN QUYỀN §12.6) và ý 2 (mặc định «Không có chiều» chốt cùng GIAO DIỆN Q4 = C20); 2.2 ý 7 (Q3: máy thật để Inbound tắt); 3.3 (Q2); 4 (Q1: làm một lần trên `feat/55-search` phase 1; **D11**: PDF có lớp chữ dùng được ở mọi trang tự đặt `Bm25Full`, ý 7); 6 và 15 ý 3 (Q4: tắt «Phân vùng bố cục» bằng cờ cấu hình); 8.1 (MỞ RỘNG Q4 = C68: văn bản ngắn không tự vượt cổng, chờ tối đa 15 phút); 8.2 (Q6); 8.5 (Q7); 9 ý 5 và 18 B12 (đã sửa trên `master` `d54c0eb`); 11 (nút chỉ cho văn bản có trang ảnh quét, D11); 12 ý 5 (TRÍCH XUẤT Q6 = C25: sửa L1, L2 ngay, checklist T02); 13 ý 6 và 19 (Q5: hoãn); 15 ý 1 (MỞ RỘNG Q3 = C67); 16 T15, T22 mới; 17 G1, G16.
- 28/9 (khuya, lượt 5, tuannq #59): **#59 Q7** chiều không bắt buộc với văn bản trong kho, tài liệu ISO nội bộ không có chiều (MO-HINH §2.8): ô «Chiều» ở Tiếp nhận thêm «Không có chiều», lần đầu mặc định «Không có chiều» (**đề xuất**), Inbound luôn «Đến» (mục 2.1 ý 2, 2.2 ý 1, 1 bảng trạng thái, 16 mới T21, 17 G9). **#59 Q2** số hệ thống thay `TMP-` khi vào sổ, vào sổ không bao giờ còn `TMP-` (khuôn `HT-<năm>-<số thứ tự>` là **đề xuất**, MO-HINH §2.9): mục 1.1, 12 ý 3, ý 5, 17 G13. **#59 Q1** bộ mẫu vàng do mình lập: T8 không đổi cách đo.
- 28/9 (khuya, lượt 4, #18 c2960 D9): nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản» **mở cho mọi người** (`LegalReviews.Create`, mọi vai nghiệp vụ); vẫn bấm tay, mỗi lần một văn bản, chạy OCR toàn bộ và đặt `Bm25Full`. Mục 11, T15.
- 28/9 (khuya, lượt 4, #18 c2956 D2): mục 11 thêm **nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản»** (một văn bản mỗi lần bấm, mức `Immediate`, không hàng loạt, đặt `Bm25Full`); lập đợt rà soát cũng đặt cờ; mục 4 (bảng, ý 7), 8.1, T15, phần liên quan đầu tệp. OCR toàn bộ nay qua rà soát QPPL, ai cũng rà soát được (PHÂN QUYỀN §12.7); ai thấy nút: C105.
- 28/9 (khuya, lượt 4, #18 c2956 D1): **không có «văn bản khẩn» khi xử lý**: độ khẩn không đổi mức ưu tiên (mục 0.1 dòng «Mức ưu tiên», 1.3 bước 2, 8.1); chỉ «Xử lý ngay» và thao tác người bấm, mỗi lần một văn bản, vượt cổng.
- 28/9 (khuya, BM25): mục 4 ý 7 mới: chữ trang đọc được không tự vào chỉ mục BM25 toàn văn; chỉ văn bản «BM25 FULL» (tuannq #56 c2901, TÌM KIẾM §3.6); không OCR thêm trang nào để đánh chỉ mục.
- 28/9 (khuya): mục 18 thêm B12: tranh chấp `ConcurrencyStamp` giữa «Xử lý ngay» và lần ghi kết quả job, **đã xác nhận sống** ngày 28/9 (job chạy lại sau khi lần ghi thành công bị từ chối); lỗi đã biết, chờ anh quyết có làm bản sửa riêng. Mục 9 dẫn về.
- 28/9: băng «không có văn bản mật» thay bằng đúng bản chung của mọi đặc tả (#9 c2706, #38 c2091).
- 28/9: mục 4 thêm định nghĩa chung «lớp chữ dùng được» (máy chủ đọc, có chữ, không nghi hỏng phông, không đi làn Chromium; trang như vậy không chạy Tesseract), để TÌM KIẾM §3.3, ĐỐI CHIẾU §9.4 và XỬ LÝ PHÍA NGƯỜI DÙNG §5.2 cùng một nghĩa; dẫn chiếu CSDL, MỞ RỘNG đổi sang trạng thái chờ duyệt.
- **Trạng thái:** chốt tạm để triển khai (tuannq 28/9 tạm duyệt); câu hỏi mục 20 lead chốt theo đề xuất (#1 c2985). Không còn câu mở.
- **Lý do có tài liệu này:** tuannq, 28/9: mọi đặc tả của hệ thống phải có trước khi dev/test; lỗi nhỏ không vá lẻ mà ghi vào mục lỗi/khoảng trống của đặc tả; «chưa có thì tiến hành». Mục «OCR và tiếp nhận» là dòng 🔄 trong bảng đặc tả theo module ở #1 c2633.
- **Đặc tả gốc để đối chiếu:** `YCNV.md` §4.1.1 (luồng 5 bước), §4.1.2 (OCR, chữ viết tay, phân vùng), §4.2 (đầu vào, hàng đợi, ≤ 30 s), §4.3 (API Inbound); `SRS.md` TS-OCR-001 (tiếp nhận bất đồng bộ, hàng đợi), TS-OCR-002 (phân vùng in/viết tay), TS-OCR-003 (OCR chữ in tiếng Việt), TS-OCR-004 (chữ viết tay, **chỉ mô tả**), TS-OPS-002 AC-R1 (worker treo). Cùng bản dùng ở issue #1.
- **Đặc tả liên quan** (tài liệu này **không lặp lại**, chỉ dẫn chiếu):
- `docs/DAC-TA-PHAN-QUYEN.md` (**đã duyệt**, ràng buộc): §4.1–4.2 quyền tiếp nhận và hàng đợi, §6 OCR toàn bộ chỉ qua rà soát QPPL (*28/9:* ai cũng rà soát được, §12.7), §12.8 nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản».
- `docs/DAC-TA-TIM-KIEM.md` (đã duyệt, #55): §3.3 đọc lớp chữ PDF cho **mọi trang**, OCR **3 trang đầu + 2 trang cuối**, gộp trang; §3.5 làm lại cho dữ liệu cũ.
- `docs/DAC-TA-MAN-DOI-CHIEU.md` (#38): §3 quy tắc 5 trang, §9.3 hàm chọn trang chung, §9.4 OCR đúng 5 trang và đọc lớp chữ, §9.6 điều kiện vào sổ.
- `docs/DAC-TA-TRICH-XUAT.md` (#6): trang máy đọc, mục tiêu 30 s (K14), lỗi L1 (vào sổ với mã `TMP-`), L8 (trùng khác SHA).
- `docs/DAC-TA-TOM-TAT.md` (#1 c2677): §4 luật 10 trang đầu, nguồn chữ theo trang.
- `docs/DAC-TA-KHO-TAI-LIEU.md` (#9): §2 xếp kho khi tiếp nhận, §5.3 «Tài liệu của tôi» khi tải lên và khi tải trùng.
- `docs/DAC-TA-MO-RONG-NANG-LUC.md` (#1 c2748 + c2816, chờ duyệt): §6.2 cổng CPU, §6.3 watchdog, §6.4 ai-worker, Q3 định nghĩa «hoàn thành ≤ 30 s», Q4 văn bản ngắn và cổng. **Hàng đợi và cổng CPU thiết kế ở đó**; ở đây chỉ nói phần tiếp nhận chạm vào.
- `docs/DAC-TA-CSDL-LUU-TRU.md` (#1 c2737 + c2816, chờ duyệt): §2.1 kho blob, §2.2 SHA-256, §2.4 trang OCR trong CSDL, §2.6 giới hạn kích thước.
- **Quyết định liên quan:**
- #37 và ADR-0033: mỗi tệp là một văn bản; trùng nội dung (SHA-256) thì không tạo văn bản mới. #37 c2647: **tính năng tệp đính kèm bị bỏ**, không bàn nữa.
- #23: OCR chạy tuần tự, «chạy khi máy rảnh»; OCR ngầm tối đa 5 trang đầu (nay thay bằng quy tắc trang của #38/#55).
- #32 c1937: cổng CPU, «chạy từng tài liệu 1». #32 c2653 và #20 c2631: trên .67 cổng không bao giờ mở. *(Đã chốt theo đề xuất, lead #1 c2985, MỞ RỘNG Q6 = C70)* **Không** tắt cổng trên .67; khi test dùng «Xử lý ngay» hoặc `scripts/ai-force.sh`.
- #33: lỗi làn Chromium (đã sửa 27/9), văn bản không kẹt «Đang xử lý» khi AI hỏng, nút «Xử lý ngay», tệp thêm sau vẫn được OCR (mục G, nay không còn ý nghĩa vì mỗi văn bản một tệp).
- #45 c2286: **chữ viết tay có thể bị huỷ**, mọi lỗi liên quan tạm hoãn.
- #50 c2394: OCR toàn bộ chỉ dành cho Pháp chế. *(28/9, sau #18 c2956: qua rà soát QPPL, ai cũng rà soát được, PHÂN QUYỀN §12.7; thêm nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản», D2, mục 11.)*
- **Nền mã:** `integration/20260927` @ `9971f57`, đúng bản đang chạy trên .67. Chỉ đọc mã, không sửa.
- **Nguồn số liệu:** đo ngày 28/9/2026 (khoảng 18:00–19:00 giờ VN) trên CSDL `aivpct` của máy .67, chỉ đọc (`BEGIN READ ONLY`, chỉ `SELECT`); `scripts/trace.sh` cho 2 văn bản; Playwright chỉ xem (tài khoản `vanthu`, `quantri`), không tải lên, không bấm nút ghi dữ liệu. Không ghi gì vào Docker.
- **Phạm vi:** chỉ **phương án tối thiểu**. Phần vượt đặc tả ghi «*mở rộng, có thể hoãn*» và gom ở mục 19.
Mỗi hạng mục có ba dòng:
- **Ai dùng**
- **Đặc tả nói gì** (mã mục SRS/YCNV, hoặc ghi «không có»)
- **Tối thiểu**: phần phải làm ở giai đoạn này.
---
## 0. Hiện trạng (audit 28/9)
### 0.1 Mã nguồn
| Mảng | Có gì (`9971f57`) | Thiếu hoặc lệch so với đặc tả |
|---|---|---|
| **Kênh tiếp nhận** | (1) Màn «Tiếp nhận» `/documents/new`: **chỉ kéo thả**, mỗi tệp gọi một lượt `POST …/document/intake-file` (`IntakeFileAsync`): kiểm tệp → chặn trùng SHA → tạo văn bản «Chưa phân loại» → lưu tệp → xếp AI, trong **một** đơn vị công việc. (2) API máy–máy `InboundDocumentController` (client `AIVPCT_Inbound`, quyền `Documents.Inbound`, có `ExternalReference` để gửi lại không tạo trùng). (3) Tải tệp vào văn bản có sẵn (`UploadFileAsync`), chỉ khi văn bản chưa có tệp (`AllowMultipleFiles=false`, lỗi `059`). | Trên .67 **chưa cấu hình** client `AIVPCT_Inbound` (bảng `OpenIddictApplications` chỉ có `AIVPCT_Web`, `AIVPCT_Swagger`); 0 văn bản đến qua kênh này. Kênh Inbound gọi `StartProcessingAsync` cần `Documents.Update`, quyền mà #41 đã **gỡ** khỏi client này (lỗi B1). «Connector» (ADR-0030) chỉ là nút liên kết đăng nhập `trang-noi-bo`, **không** đưa văn bản vào AIVPCT. |
| **Định dạng** | Backend nhận theo **nội dung** (magic bytes), không tin đuôi: PDF, JPEG, PNG, TIFF, DOCX, XLSX, PPTX, DOC, XLS, PPT, TXT, MD; chặn macro; ≤ 50 MB (`UploadedFileValidator`). AI đọc được (`OcrCapableMimeTypes`): PDF, ảnh, DOCX, XLSX, TXT, MD. PPTX/DOC/XLS/PPT lưu được nhưng AI không đọc → văn bản nằm «Chưa phân loại», có nút «Chuyển đối chiếu thủ công». | Không kiểm PDF hỏng/có mật khẩu lúc tải lên (SRS TS-OCR-001 AC-R1 đòi `400 INVALID_DOC_QUALITY`); tệp hỏng được nhận rồi hỏng ở AI sau 3 lượt thử × 6 job (lỗi B6). ai-worker nhận thêm BMP/WEBP nhưng backend không cho tải lên, vô hại. |
| **DPI** | Đo lúc tải lên (`ScanMetricsReader`); dưới 200 DPI thì **cảnh báo, vẫn nhận** (dòng vàng trên khung kéo thả). | SRS đòi **từ chối**. Với PDF sinh từ máy tính có logo, «DPI» tính từ ảnh logo nên ra **34 DPI** giả (QT-QLSTĐ, QT-MNH…), cảnh báo sai (lỗi B5). |
| **Làn Chromium (bẫy phông)** | PDF nghi hỏng phông (`is_font_trap_candidate`: phông cũ thiếu ToUnicode…) được dựng trang bằng Chromium thay vì PDFium; từ #33 dựng **từng trang đơn** và rơi về PDFium nếu Chromium hỏng; `init: true` dọn zombie. Làn tuần tự, có giãn cách. | Đủ. Không đổi. |
| **Trang được đọc** | OCR tesseract **5 trang đầu** (`OCR_MAX_PAGES=5`), 300 DPI. Lớp chữ sẵn có của PDF **chỉ** dùng cho màn gỡ lỗi (`dem_chu_san_co`). Office/TXT đọc thẳng chữ. | Trang cuối (người ký, nơi nhận) không được đọc; lớp chữ PDF bị bỏ (29/50 PDF có lớp chữ, phủ 94 % số trang, audit #42). Đã có hướng sửa ở TÌM KIẾM §3.3 và ĐỐI CHIẾU §9.4 (mục 4). |
| **OCR** | Tesseract 5.5.0, ngôn ngữ **chỉ `vie`** (đo thấy `vie+eng` làm rụng dấu); phóng 2× ảnh nhỏ; thêm lượt «quét thưa» PSM 12 vá từ bị bỏ sót (20 trang đầu); điểm tin cậy từng từ, từng trang; một tesseract tại một thời điểm (`ocr_concurrency=1`); đệm kết quả trong bộ nhớ theo tệp (`LruCache(64)`). | **Không có chỉnh nghiêng** (TS-OCR-003 AC-R1 đòi deskew ±15°). Chưa đo độ chính xác ký tự (TS-OCR-003 đòi ≥ 95 %). Ngưỡng «từ đọc không chắc» 0,60, SRS nói 0,70. |
| **Phân vùng bố cục** | `layout.py`: mỗi khối tesseract = vùng `PrintedText`; con dấu đỏ tìm bằng màu (`Stamp`); vùng `Handwriting` tìm bằng heuristic mật độ mực. Không mô hình học máy. Không lưu ảnh cắt (`crops`). | Vùng `Handwriting` **không ai dùng**: bộ đọc chữ viết tay gửi **cả trang** cho mô hình ảnh, không dùng vùng cắt. ĐỐI CHIẾU §9.8 ghi «layout vẫn dùng để cắt vùng chữ viết tay» là **không đúng với mã** (mục 6). |
| **Chữ viết tay** | `handwriting.py`: gửi ảnh 2 trang đầu cho mô hình ảnh (`gpt-6-luna`), trần tin cậy để luôn bị tô màu. | Chỉ mô tả (#45 c2286), mục 7. |
| **Chuỗi job** | Khi tiếp nhận: 5 job `LayoutAnalysis`, `Ocr`, `HandwritingRecognition`, `MetadataExtraction`, `Summarization` + 1 job `TextRefinement` (làm mượt chữ) xếp riêng. Các job chạy **song song**, mỗi endpoint của worker tự lấy OCR từ đệm. Văn bản sang «Chờ đối chiếu» khi **metadata** xong, hoặc khi metadata **hỏng hẳn** (#33 C). | Đủ cho luồng. Các điểm nhỏ ở mục 8, 14. |
| **Mức ưu tiên** | `Immediate` (người bấm, hoặc độ khẩn ≥ Khẩn) → hàng ABP `High`, có **chỗ chạy riêng** và **bỏ qua cổng CPU**; `Interactive` (≤ 5 trang hoặc chưa biết số trang) → `AboveNormal`; `Background` (> 5 trang) → `Normal`. Cổng CPU chỉ chặn việc **bắt đầu một văn bản tự động mới**. | *(28/9, #18 c2956 D1)* Nhánh «độ khẩn ≥ Khẩn → Immediate» trái quyết định «không có văn bản khẩn», bỏ ở `fix/no-urgency-priority` (mục 8.1). Job «làm mượt chữ» xếp sau «Nhận dạng lại» và sau «OCR toàn bộ» luôn mang nguồn `Intake`, nên **bị cổng giữ** trong khi các job cùng lượt chạy ngay (lỗi B4). |
| **«Xử lý ngay»** | Văn thư: `POST …/document/{id}/request-immediate-processing` (quyền `Documents.Update`); Quản trị: qua `/ai-queue` (`AiJobs.Manage`). Nâng mọi job đang chờ của văn bản lên `Immediate`. Nút có trên từng dòng của khung kéo thả, trên thẻ tiến trình của trang văn bản, và ở `/ai-queue`. | Đủ. |
| **«Nhận dạng lại»** | `ReprocessAsync` (quyền `AiJobs.Enqueue`, chỉ Văn thư): xếp lại đủ 5 job (nguồn `UserAction`) + làm mượt chữ; không đổi trạng thái văn bản; không ghi đè ô người đã xác nhận. | Worker trả **kết quả OCR cũ trong đệm** nếu tệp không đổi và worker chưa khởi động lại, nên tesseract không chạy lại dù hộp xác nhận ghi «chạy lại OCR» (lỗi B11, thấp). |
| **OCR toàn bộ** | Đã chuyển sang luồng Pháp chế (#41 §6): `RequestFullOcrAsync` cần `LegalReviews.Create` và văn bản phải trong đợt rà soát chưa đóng; `LegalReviewAppService.CreateAsync` tự yêu cầu. Trần 10.000 trang. | Đủ. |
| **Số tạm** | `TMP-{yyyyMMdd}-{12 ký tự}`; AI ghi đè bằng số thật khi đọc được; không có ràng buộc duy nhất. Từ #37 B2, các tệp thả cùng lúc có mã khác nhau. | `Register()` vẫn nhận văn bản còn mã `TMP-` (TRÍCH XUẤT L1; sửa ở ĐỐI CHIẾU §9.6). |
| **Trùng tệp** | `EnsureFileIsNotDuplicateAsync`: SHA-256 trùng với tệp của văn bản **chưa xoá** → lỗi `DuplicateFileContent` kèm số và Id văn bản cũ; khung kéo thả hiện liên kết «Mở văn bản đang giữ tệp này». Kiểm **trước** khi ghi blob. | Chưa ghi nhận «người này cũng cần văn bản đó» (KHO §5.3 đổi thành 200 + thêm vào «Tài liệu của tôi»). Cùng giấy scan hai lần (SHA khác) không bị phát hiện (TRÍCH XUẤT L8). |
| **Lỗi** | Job hỏng: lưu mã lỗi, endpoint, HTTP, nội dung (#33 D); tối đa 3 lượt thử; engine không gọi được thì trả job về hàng chờ **không tốn lượt**, tối đa 6 giờ; metadata hỏng hẳn → văn bản sang «Chờ đối chiếu» để nhập tay, có thông báo; `AiQueueStallWatchdogWorker` bắn `ai.queue.stalled` khi job cũ nhất chờ > 30 phút. | Lỗi `422` (tệp không đọc được) vẫn thử lại 3 lượt dù chắc chắn hỏng lại. Không có «dead letter» riêng (TS-OPS-002 AC-R1); watchdog chỉ cảnh báo. |
### 0.2 Số đo trên .67 (28/9, chỉ đọc)
**Văn bản và tệp**
- **76 văn bản** chưa xoá (14 đã xoá): 18 «Chưa phân loại», 15 «Đang xử lý», 26 «Chờ đối chiếu», 17 «Đã vào sổ». **15 văn bản không có tệp** (dữ liệu mẫu, đều «Chưa phân loại»).
- **72 tệp** đang hoạt động: 61 PDF (354 trang, trung bình 6,0, lớn nhất 39), 8 TXT, 2 PPTX, 1 DOCX. Không văn bản nào giữ quá 1 tệp (sau #42).
- **Số trang mỗi tệp:** 1 trang: 30 · 2 trang: 10 · 3–5: 3 · 6–10: 4 · 11–30: 9 · > 30: 3 · không đếm trang (Office/TXT): 13. Tức **43/59 tệp có trang (73 %) ≤ 5 trang**.
- **DPI đo được:** ≥ 300: 5 · 200–299: 19 · < 200: 12 · không đo (không có ảnh, hoặc không phải PDF/ảnh): 36. Trong 12 tệp «< 200», có 5 tệp «34 DPI» là PDF sinh từ máy tính (ảnh logo), không phải bản scan (lỗi B5).
- **Mã tạm:** 27 văn bản còn mã `TMP-`, trong đó **2 đã vào sổ** (TRÍCH XUẤT L1) và 6 đang «Chờ đối chiếu».
- **Trùng SHA:** 1 cặp (`TEST-AGENT-A-1.pdf` và bản thả lại), **cả hai văn bản đã xoá**; đúng thiết kế. Bản ghi tệp của văn bản đã xoá vẫn giữ (`IsDeleted=false` ở bảng tệp, 11 dòng), khớp CSDL §2.2 và §3.
**OCR** (bảng `DocOcrPages`, 157 trang)
- 155 trang tesseract (54 tệp), 1 trang Office, 1 trang TXT.
- Điểm tin cậy trang (trung bình điểm từ của tesseract): trung bình **0,898**; P10 0,866; P50 0,924; P90 0,942.
- Phân bố trên 148 trang PDF: < 0,50: **1** · 0,50–0,59: **2** · 0,60–0,69: **6** · 0,70–0,79: 3 · 0,80–0,89: 25 · ≥ 0,90: 111. Tức **9/148 trang (6 %) dưới 0,70**. Ví dụ trang 3 của QT-QLSTĐ: 0,60 (trang bảng).
- **Lưu ý:** đây là điểm **tesseract tự chấm**, không phải độ chính xác ký tự. Chỉ tiêu ≥ 95 % của TS-OCR-003 **chưa đo được** vì .67 chưa có bộ mẫu vàng (TRÍCH XUẤT Q1).
- **Phủ trang:** 51 tệp PDF của văn bản đang dùng, 337 trang; đã có chữ **133 trang (39 %)**. 12 tệp chưa có trang nào, 11 tệp mới có một phần, 27 tệp đủ.
- 81/157 trang có bản làm mượt (`RefinedText`), tổng 1.041 chỗ sửa.
**Job AI** (bảng `AiAiJobs`, 25/9 → 28/9)
- 518 job: **396 thành công, 8 hỏng, 14 huỷ, 100 đang chờ**. 0 job thành công phải thử lại.
- **8 job hỏng:** 7 job của văn bản 093/NCPT ngày 27/9 (làn Chromium cắt nhầm trang cuối, #33, **đã sửa**; lỗi lưu lúc đó còn là câu vô nghĩa «Exception of type BusinessException», nay đã lưu đủ); 1 job tóm tắt «summaryText can not be null» (TÓM TẮT §7 đã xử lý ca chữ rỗng).
- **100 job đang chờ của 20 văn bản**, job cũ nhất xếp 06:55Z, tức **301 phút** lúc đo. Lý do: cổng CPU, `host_busy` (tải máy 94–176 %, ngưỡng 80 %).
- **Thời gian từng bước** (job thành công, `DurationMs`):
| Bước | Số job | P50 | P95 | Max |
|---|---|---|---|---|
| OCR | 79 | 2,7 s | 41,6 s | 305,7 s |
| Phân vùng bố cục | 62 | 2,9 s | 49,1 s | 82,3 s |
| Chữ viết tay | 63 | 3,6 s | 31,7 s | 43,5 s |
| Metadata | 64 | 10,8 s | 69,6 s | 100,8 s |
| Tóm tắt | 61 | 3,2 s | 6,6 s | 20,6 s |
| Làm mượt chữ | 67 | 16,1 s | 244,8 s | 430,7 s |
OCR khoảng **3,0 s/trang** (P50, 68 job PDF; P95 15,4 s/trang). Số P50 thấp một phần vì job OCR hay lấy được kết quả từ đệm do job bố cục chạy trước. Trong trace QT-QLSTĐ (máy tải 176 %), tesseract mất **29,8 s cho 5 trang ≈ 6 s/trang**. TS-OCR-003 AC-P1: ≤ 2,5 s/trang trên RTX 3060.
- **Từ lúc job đầu bắt đầu tới «Chờ đối chiếu»** (mỗi lượt xử lý, không tính chờ hàng):
| Nhóm | Lượt | P50 | P95 | Max | Số lượt ≤ 30 s |
|---|---|---|---|---|---|
| PDF 1 trang | 32 | 21,7 s | 64,1 s | 80,0 s | **25/32** |
| PDF 2–5 trang | 12 | 47,7 s | 81,9 s | 96,4 s | **5/12** |
| PDF > 5 trang (chỉ đọc 5 trang) | 10 | 33,0 s | 89,4 s | 100,8 s | 4/10 |
| TXT / Office | 10 | 7,9 s | 15,2 s | 19,3 s | 10/10 |
- **Thời gian chờ hàng** (từ xếp tới lúc job bắt đầu): P50 16–40 s ở mọi mức; P95 **8,4 phút** (Background), **227,5 phút** (Interactive), **216,6 phút** (Immediate). Mức Immediate cũng có P95 lớn vì phần lớn là job **đã chờ sẵn rồi mới được nâng** bằng «Xử lý ngay» (thời điểm xếp giữ nguyên); sau khi bấm, job bắt đầu sau **2–4 s** (trace ở 0.3).
- **Phân vùng bố cục:** 62 kết quả, 791 vùng chữ in, 4 vùng con dấu, **98 vùng «chữ viết tay» trong 24/62 văn bản**. Trong khi đó bộ đọc chữ viết tay (mô hình ảnh) báo «không thấy chữ viết tay» ở văn bản đã trace. Nhiều khả năng heuristic nhận nhầm chữ ký, logo, bảng (mục 6, 7).
**Màn hình** (Playwright, `vanthu`, chỉ xem)
- `/documents/new`: khung kéo thả, dòng «PDF, JPEG, PNG, TIFF, Word, Excel, PowerPoint, văn bản thuần (.txt, .md) · tối đa 50.0 MB mỗi tệp». Không có ô chọn chiều.
- Trang văn bản đang bị cổng giữ (`TMP-20260928-08313DCE0A59`, 17 trang): thẻ «Đang chờ máy rảnh để xử lý tự động · 0/5 bước · Xử lý ngay»; danh sách vẫn ghi trạng thái «Đang xử lý». Trang vẫn hiện dòng **«Độ mật: Thường»** (theo banner đầu tài liệu phải ẩn).
- Engine (`/engine-status`, `quantri`): tesseract 5.5.0, ngôn ngữ cài sẵn `eng`, `osd`, `vie`; mô hình chữ `gpt-6-luna`/`gemma4:31b-cloud` (đám mây, anh đã chấp nhận cho giai đoạn thử).
### 0.3 Hai hồ sơ `trace.sh`
**A. QT-QLSTĐ** (`3a23f8f8-ec3a-…`, PDF sinh từ máy tính, 16 trang, có lớp chữ, được tách từ văn bản cũ ở #42):
| Mốc (UTC, 28/9) | Sự kiện |
|---|---|
| 06:55:30 | Xếp 6 job (tách #42) |
| 06:55 → 10:31 | Bị cổng giữ, lý do `host_busy` (tải 176 %, ngưỡng 80 %): **3 giờ 36 phút** |
| 10:31:59 | `admin` bấm «Xử lý ngay» ở `/ai-queue` |
| 10:32:03 | 4 job bắt đầu song song (**4 s** sau khi bấm) |
| 10:32:08 | Chữ viết tay xong (5,3 s): mô hình ảnh đọc 2 trang, «không thấy chữ viết tay» |
| 10:32:33 | OCR xong 5/16 trang (29,8 s), tin cậy từng trang 0,91 / 0,88 / **0,60** / 0,94 / 0,93; log `ocr.truncated` |
| 10:32:34 | Bố cục xong (30,8 s): **6 vùng «chữ viết tay»** trên 4 trang, trái với kết quả mô hình ảnh |
| 10:32:43 | Tóm tắt xong (5,3 s, «5 trang đưa vào prompt») |
| 10:32:57,9 | Metadata xong (54,7 s: đường ảnh 10,9 s, đường chữ 11,4 s, FR03 2,1 s) → **«Chờ đối chiếu» sau 55 s** |
| 10:36:21 | Làm mượt chữ xong (223,8 s, 83 chỗ sửa) |
Nhận xét: tệp này có lớp chữ đầy đủ, nhưng vẫn phải raster và OCR 5 trang (≈ 30 s), trang 3 chỉ đạt 0,60. Đọc lớp chữ thì gần như tức thì và đúng tuyệt đối (mục 4).
**B. 1245/EVNNPC-KT** (`3a23e922-3af5-…`, bản scan 1 trang, 200 DPI):
- 25/9: 6 job chạy ngay (hồi đó chưa có cổng), metadata xong **11,3 s** sau khi bắt đầu, tin cậy trang 0,93. **Đạt ≤ 30 s.**
- 28/9 08:58: một lượt «OCR toàn bộ» (luồng Pháp chế) xếp job OCR `Immediate` (xong 2,9 s) **và** job làm mượt chữ ở mức `Interactive`. Job làm mượt này **vẫn đang chờ cổng** lúc đo (lỗi B4).
---
## 1. Luồng tiếp nhận đầu–cuối
- **Ai dùng:** Văn thư (tiếp nhận, «Xử lý ngay», «Nhận dạng lại», đối chiếu); hệ thống ngoài qua API Inbound; Quản trị (hàng đợi).
- **Đặc tả nói gì:**
- YCNV §4.1.1 bước 1 «Tiếp nhận & Nhập liệu: bản scan/PDF qua tải lên thủ công hoặc API»; bước 2 «Xử lý & Bóc tách AI (ngầm)»; bước 3 «Kiểm duyệt & Vào sổ».
- SRS §1.2: trạng thái `PENDING_OCR`, `WAITING_VERIFICATION`, `VERIFIED`, `SUMMARIZED`.
- TS-OCR-001 AC-F1: lưu tệp, tạo bản ghi `PENDING_OCR`, đẩy job, trả `202` kèm `document_id`.
- **Tối thiểu:** giữ đúng 6 trạng thái văn bản hiện có (đổi enum sẽ lan ra bộ lọc, thống kê, kiểm toán). Ánh xạ với SRS:
| SRS | Mã hiện tại | Nhãn trên màn |
|---|---|---|
| `PENDING_OCR` | `Draft` (chưa gửi AI), `Processing` | «Chưa phân loại», «Đang xử lý» (nhãn phụ **«Chờ máy rảnh»** khi chưa job nào chạy, mục 8.4) |
| `WAITING_VERIFICATION` | `PendingReview` | «Chờ đối chiếu» |
| `VERIFIED` | `Registered` | «Đã vào sổ» (*#59 Q7:* «Đã vào kho» khi văn bản không có chiều, MO-HINH §2.8 ý 5) |
| `SUMMARIZED` | không là trạng thái riêng; tóm tắt là thẻ đi kèm (TÓM TẮT §0.2) | — |
| — | `Rejected`, `Archived` | «Từ chối», «Lưu trữ» |
### 1.1 Sơ đồ trạng thái văn bản
```mermaid
stateDiagram-v2
[*] --> Draft: thả tệp hoặc Inbound (kiểm tệp, SHA, lưu blob, số TMP-)
[*] --> TrungTep: SHA trùng văn bản đang có
TrungTep --> [*]: không tạo văn bản, thêm vào «Tài liệu của tôi» (KHO §5.3)
Draft --> Processing: tệp AI đọc được (tự động trong lượt tiếp nhận)
Draft --> PendingReview: «Chuyển đối chiếu thủ công» (PPTX, DOC…)
Processing --> PendingReview: metadata xong, hoặc metadata hỏng hẳn (nhập tay)
PendingReview --> Registered: tick «đã rà soát» và Vào sổ (TMP- hoặc trống → số hệ thống HT-…, #59 Q2)
Draft --> Rejected: Từ chối
Processing --> Rejected: Từ chối
PendingReview --> Rejected: Từ chối
Rejected --> Processing: gửi AI lại
Rejected --> PendingReview: đối chiếu thủ công
Registered --> Archived: Lưu trữ
```
- «Nhận dạng lại» và «OCR toàn bộ» **không đổi trạng thái** văn bản; kết quả AI mới chỉ điền ô còn trống hoặc ô do AI điền (ADR-0033 mục C).
- Văn bản đã xoá: job đang chờ bị huỷ (#37 B5).
### 1.2 Trạng thái job AI
```mermaid
stateDiagram-v2
[*] --> Queued: xếp (Intake hoặc UserAction)
Queued --> Queued: cổng CPU giữ (chỉ job tự động của văn bản mới)
Queued --> Running: được nhận
Running --> Succeeded
Running --> Retrying: lỗi, còn lượt (tối đa 3)
Retrying --> Running
Running --> Queued: engine không gọi được (không tốn lượt, tối đa 6 giờ)
Running --> Failed: hết lượt, hoặc engine chết quá 6 giờ
Queued --> Cancelled: văn bản bị xoá, hoặc Quản trị huỷ
Failed --> [*]: «Chạy lại» (Quản trị) tạo job mới mức Immediate
```
### 1.3 Các bước trong một lượt xử lý
1. **Tiếp nhận** (đồng bộ, trong một đơn vị công việc): kiểm tệp (mục 3) → SHA-256 → chặn trùng (mục 13) → tạo văn bản `Draft`, số `TMP-` (mục 12) → ghi blob → đo số trang/DPI → đọc chữ ký số PDF → gửi AI nếu đọc được → trả kết quả cho dòng của khung kéo thả.
2. **Xếp job** (mục 8): 5 job + làm mượt chữ, mức ưu tiên theo số trang và nguồn (độ khẩn **không** tính, #18 c2956 D1).
3. **Chạy** (ai-worker): đọc chữ theo quy tắc trang (mục 4) → OCR (mục 5) → bố cục (mục 6) → chữ viết tay (mục 7, chờ #45) → metadata (TRÍCH XUẤT) → tóm tắt (TÓM TẮT). Làm mượt chữ chạy sau, **không** chặn đối chiếu.
4. **Sang «Chờ đối chiếu»** khi metadata xong hoặc hỏng hẳn; gửi thông báo cho người tiếp nhận.
5. **Xếp kho gợi ý** ngay sau metadata (KHO §2); văn thư xác nhận ở màn Đối chiếu.
---
## 2. Kênh tiếp nhận
### 2.1 Kéo thả trên màn «Tiếp nhận»
- **Ai dùng:** **mọi người có `Documents.Intake`**, tức mọi vai nghiệp vụ (Lãnh đạo, Trợ lý, Văn thư, Pháp chế, Cán bộ tra cứu; KHO Q3 «ai có tài khoản cũng được thả», PHÂN QUYỀN §12.6). Màn hiện theo `Documents.Intake`, không còn theo `Documents.Create` + `Documents.Upload`. Người không có quyền này (Quản trị) thấy khung khoá với lời giải thích (đã có, #37 B6).
- **Đặc tả nói gì:**
- YCNV §4.1.1 bước 1: «tải lên thủ công».
- TS-OCR-001: phản hồi tải lên < 500 ms, P95 ≤ 350 ms với 20 lượt đồng thời (không tính truyền tệp).
- «Mỗi tệp một văn bản», «không có form điền trước»: quyết định #37, ADR-0033; không có trong SRS.
- **Tối thiểu:**
1. Giữ nguyên: nhiều tệp một lượt, mỗi tệp một yêu cầu `intake-file`, mỗi dòng báo kết quả riêng, tự thử lại khi mất mạng hoặc gặp 409, dòng vàng khi DPI thấp (mục 3.3).
2. **Thêm ô «Chiều» trên màn Tiếp nhận** (*#59 Q7:* **Không có chiều** / Đến / Đi / Nội bộ), **nhớ theo từng người dùng**. ~~Mặc định «Đến».~~ *(#59 Q7)* Lần đầu mặc định **«Không có chiều»** (*đã chốt theo đề xuất, lead #1 c2985, cùng GIAO DIỆN Q4 = C20: bỏ 8 ô nhớ phiên, chỉ giữ ô «Chiều»*: chiều không bắt buộc với văn bản trong kho, mọi vai nghiệp vụ nay thả tài liệu vào kho theo KHO Q3, và mặc định «Đến» là nguyên nhân tài liệu ISO mang sai chiều ở KHO L8; Văn thư chọn «Đến» một lần là được nhớ). Mọi tệp của lượt thả nhận chiều đang chọn. Đây là đề xuất #42 mục 7 mà ĐỐI CHIẾU §4 mục 9 đã dựa vào («mặc định theo ô Chiều ở Tiếp nhận»). `ReceivedDate` chỉ đặt tự động cho chiều «Đến» (TRÍCH XUẤT L9, K10); «Không có chiều» lưu `Direction = null`, không có ngày đến (MO-HINH §2.8).
3. Kết quả tiếp nhận thêm `outcome` (`Created` · `Duplicate`) theo KHO §5.3; khung kéo thả hiện dòng vàng «Đã có trong kho … Đã thêm vào Tài liệu của tôi» cho ca trùng.
4. Tải lên thành công: văn bản tự vào «Tài liệu của tôi» của người tải (KHO §5.3 ý 1), trừ khi người tải có `Documents.Register` (KHO Q4).
5. Trả lời nhanh: `intake-file` hiện chạy đồng bộ cả việc đọc chữ ký số và đo DPI (vài chục ms với tệp nhỏ). Đo P95 dưới 20 lượt đồng thời theo mục 16 (T3); chưa đạt thì đưa đọc chữ ký số ra sau (*mở rộng, có thể hoãn*).
### 2.2 API máy–máy (Inbound)
- **Ai dùng:** hệ thống ngoài (văn bản đến từ Tổng công ty), qua client `AIVPCT_Inbound` (client credentials).
- **Đặc tả nói gì:** YCNV §4.1.1 bước 1 «hoặc API»; YCNV §4.3 «Xây dựng luồng API Inbound (Nhận văn bản từ TCT)»; TS-OCR-001 (`POST /api/v1/documents/upload`, `202`).
- **Tối thiểu:**
1. Giữ endpoint hiện có (`InboundDocumentController`, multipart), cùng kiểm tệp, chặn trùng, xếp AI như kéo thả. *(#59 Q7)* Văn bản Inbound là văn bản Tổng công ty gửi tới nên luôn chiều «Đến»; mặc định «Không có chiều» ở mục 2.1 **không** áp cho kênh này. Không làm endpoint thứ hai theo đường dẫn của SRS; chỉ ghi ánh xạ đường dẫn vào tài liệu API.
2. **Sửa lỗi B1:** bước «bắt đầu xử lý» của kênh Inbound không được đòi `Documents.Update`. Cách làm: gọi thẳng phương thức miền và phát sự kiện xử lý bên trong `InboundDocumentAppService` (đã được gác bằng `Documents.Inbound`), không gọi qua `IDocumentAppService.StartProcessingAsync`. **Không** trả lại `Documents.Update` cho client (#41 audit c2227 mục 2.3).
3. Trường `SecurityLevel` trong yêu cầu Inbound **bị bỏ qua**, văn bản luôn ghi «Thường» (banner đầu tài liệu).
4. `ReceivedDate` mặc định lấy **ngày theo giờ Việt Nam**, không lấy `Clock.Now.Date` theo UTC (lỗi B3).
5. Tìm theo `ExternalReference` không được giới hạn 2.000 văn bản mới nhất (lỗi B2). Tối thiểu: lọc bằng chuỗi JSON trên cột `ExtraProperties` trong SQL.
6. Có một kịch bản kiểm thử tích hợp cho kênh này (hiện **không có** test nào gọi `InboundDocumentAppService`).
7. Trên .67 cấu hình client để test bằng giá trị dev/test. *(Q3 Đã chốt theo đề xuất (lead, #1 c2985): coi như bên gửi thật chưa sẵn sàng)* Trên máy thật kênh Inbound **để tắt** (không tạo client) tới khi có bên gửi; khi đó Quản trị tạo khoá.
### 2.3 Tải tệp vào văn bản có sẵn
- **Ai dùng:** Văn thư, khi văn bản tạo tay (không tệp) cần gắn bản scan.
- **Đặc tả nói gì:** không có riêng; TS-OCR-001 áp chung.
- **Tối thiểu:** giữ nguyên: chỉ khi văn bản **chưa có tệp** (lỗi `059`); tệp trùng thì giữ lỗi `DuplicateFileContent` (KHO §5.3 ý 2, gạch cuối). Tệp được gắn vào văn bản đã gửi AI thì tự xếp chuỗi AI (#33 G).
### 2.4 Connector `trang-noi-bo`
- **Ai dùng:** người dùng tự liên kết tài khoản (ADR-0030).
- **Đặc tả nói gì:** không có.
- **Tối thiểu:** **không phải kênh tiếp nhận**. Không thiết kế gì ở đây. Kéo văn bản từ `trang-noi-bo` hay hệ thống ISO cũ vào AIVPCT là *mở rộng, có thể hoãn*.
---
## 3. Định dạng, kích thước, chất lượng tệp
- **Ai dùng:** Văn thư (thấy lỗi ngay trên dòng), hệ thống ngoài (mã lỗi).
- **Đặc tả nói gì:**
- YCNV §4.2: «PDF, JPEG, PNG (Chất lượng ≥ 200 DPI). Tiếng Việt».
- TS-OCR-001: PDF/PNG/JPEG, ≤ 50 MB, ≥ 200 DPI; AC-R1: tệp hỏng (thiếu EOF) hoặc < 200 DPI → `400 INVALID_DOC_QUALITY`, không để lại tệp hay job.
- Word, Excel, PowerPoint, TXT: **không có** trong đặc tả; mở rộng theo ADR-0013.
### 3.1 Bảng định dạng
| Định dạng | Nhận | AI đọc bằng | Quy tắc trang | Ghi chú |
|---|---|---|---|---|
| PDF | có | lớp chữ PDF (mọi trang) + OCR trang được chọn không có lớp chữ | mục 4 | làn Chromium khi nghi bẫy phông |
| JPEG, PNG | có | OCR | 1 trang | |
| TIFF (nhiều trang) | có | OCR | như PDF (mục 4) | |
| DOCX, XLSX | có | đọc thẳng chữ, tin cậy 1,0 | không áp quy tắc trang | |
| TXT, MD | có | đọc thẳng | không áp | |
| PPTX, DOC, XLS, PPT | có (lưu trữ) | **không** | — | văn bản ở «Chưa phân loại», văn thư «Chuyển đối chiếu thủ công» và nhập tay |
| Tệp có macro, tệp đổi đuôi, tệp lạ | **từ chối** | — | — | lỗi theo nội dung thật, không theo đuôi |
- **Tối thiểu:** giữ bảng này. **Không** thêm bộ đọc PPTX/DOC ở giai đoạn này: kho hiện có 2 PPTX, 0 DOC (*mở rộng, có thể hoãn*).
### 3.2 PDF hỏng, có mật khẩu
- **Tối thiểu:**
1. Lúc tải lên, backend **mở thử** PDF (PdfSharp đã dùng để đếm trang). Không mở được, hoặc có mật khẩu mở tệp → từ chối với lỗi mới `InvalidDocumentFile` («Tệp PDF hỏng hoặc có mật khẩu, không đọc được»), **không** tạo văn bản, **không** ghi blob (đúng AC-R1). PDF chỉ có mật khẩu chủ (cấm in, cấm sửa) vẫn nhận.
2. ai-worker trả `422` (tệp không đọc được) thì backend **không thử lại** job đó; ghi lỗi một lần, các job khác của cùng tệp cũng dừng, và văn bản sang «Chờ đối chiếu» để nhập tay (lỗi B6).
### 3.3 DPI
- **Tối thiểu:**
1. **Cảnh báo, không từ chối.** Văn thư không chọn được chất lượng bản scan bên gửi gửi tới, và từ chối thì văn bản đến không vào được sổ. Đây là chỗ lệch có chủ ý so với AC-R1 (*Q2 Đã chốt theo đề xuất (lead, #1 c2985)*: cảnh báo và vẫn nhận; lệch SRS ghi vào biên bản).
2. Chỉ cảnh báo cho **trang ảnh quét**: PDF có lớp chữ trên trang 1 thì **không** tính DPI từ ảnh (tránh «34 DPI» giả từ logo, lỗi B5). Ảnh nhỏ hơn 50 % bề ngang trang cũng không tính.
3. Cảnh báo hiện ở dòng kéo thả (đã có) và trên thẻ tiến trình của văn bản.
### 3.4 Kích thước
- **Tối thiểu:** giữ 50 MB/tệp (backend `UploadedFileValidator`, Kestrel 60 MB, giao diện kiểm trước). Không đổi (CSDL §2.6).
### 3.5 Làn Chromium (bẫy phông)
- **Ai dùng:** không ai thao tác; ai-worker tự rẽ.
- **Đặc tả nói gì:** không có. Là bản sửa lỗi thật ngày 3–4/9 (`BAO-CAO-FIX-PDF-FONT-OCR-20260904.md`) và #33.
- **Tối thiểu:** giữ nguyên: một điểm kiểm duy nhất trước khi raster; dựng từng trang đơn; Chromium hỏng thì rơi về PDFium cho trang đó; làn tuần tự; `init: true`. Trang đã đi làn Chromium thì **lớp chữ PDF của trang đó bị coi là hỏng**, không dùng (TÌM KIẾM §3.3).
---
## 4. Trang được đọc
- **Ai dùng:** job nền; ảnh hưởng tới Văn thư (đối chiếu), mọi vai (tìm kiếm, tóm tắt).
- **Đặc tả nói gì:**
- TS-EXT-001, TS-VER-001: trường hành chính ở trang đầu và trang cuối.
- YCNV §4.1.4, TS-SUM-001: tóm tắt đọc tối đa 10 trang đầu.
- TS-OCR-003: chữ OCR để «search indexable».
- Không có yêu cầu OCR hết văn bản (PHÂN QUYỀN §6, #50 c2394).
- Quy tắc «3 đầu + 2 cuối»: quyết định #38 c2382, không có trong SRS.
- **Tối thiểu:** một quy tắc, một hàm, dùng chung cho mọi bước. *(Q1 Đã chốt theo đề xuất (lead, #1 c2985))* Làm **một lần**, trên `feat/55-search` phase 1 (nhánh này đã làm đúng quy tắc), cho OCR, màn Đối chiếu và tìm kiếm:
| Nguồn chữ | Trang | Ai quy định |
|---|---|---|
| **Lớp chữ sẵn có của PDF** | **mọi trang**, trừ trang nghi hỏng phông | TÌM KIẾM §3.3; ĐỐI CHIẾU §9.4 |
| **OCR** | trang được chọn bởi `ReviewPageSelection.Select(N)`: N ≤ 5 → tất cả; N ≥ 6 → trang 1, 2, 3, N−1, N. Chỉ OCR trang được chọn **không có** lớp chữ dùng được | ĐỐI CHIẾU §3.1, §9.3–9.4 |
| Ảnh một trang | trang 1 | — |
| DOCX, XLSX, TXT, MD | toàn bộ chữ (giới hạn theo dòng của bộ đọc) | — |
| **OCR toàn bộ** | mọi trang **không** có lớp chữ dùng được | đợt rà soát QPPL, hoặc nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản», nút chỉ dành cho văn bản có trang ảnh quét (PHÂN QUYỀN §6, §12.7, §12.8; mục 11) |
**Lớp chữ dùng được** (định nghĩa chung, dùng nguyên cho TÌM KIẾM §3.3, ĐỐI CHIẾU §9.4 và XỬ LÝ PHÍA NGƯỜI DÙNG §5.2; các tài liệu đó dẫn về đây):
- lớp chữ do **máy chủ** (ai-worker) đọc bằng `pypdfium2`, **không** nhận lớp chữ trình duyệt gửi lên (XỬ LÝ PHÍA NGƯỜI DÙNG §5.2: trình duyệt chỉ hiện trước);
- trang có chữ thật: không rỗng sau khi bỏ khoảng trắng, không chỉ là vài ký tự lẻ (số trang, logo). Ngưỡng ký tự tối thiểu là tham số kỹ thuật của worker, đặt bằng số đo trên kho thử, không phải câu hỏi cho anh;
- **không** nghi hỏng phông (`_nghi_ngo_hong_phong` = sai), và trang **không** phải đi làn Chromium (mục 3.5).
Trang có lớp chữ dùng được thì **không chạy Tesseract**, kể cả khi nằm trong 5 trang được chọn. Nguồn ghi `EngineName="pdf-text"` (XỬ LÝ PHÍA NGƯỜI DÙNG gọi là `PdfTextLayer`; cùng một thứ).
1. **Thay `OCR_MAX_PAGES=5` (5 trang đầu)** bằng danh sách trang do backend gửi xuống (`OcrRequest.pageIndices`), để trang văn thư nhìn thấy ở màn Đối chiếu đúng là trang máy đã đọc.
2. **Lớp chữ PDF** ghi vào `DocOcrPages` với `EngineName="pdf-text"`, `Confidence=1,0`. Trang đã có bản OCR thì giữ bản OCR; trang có cả hai thì bản lớp chữ **thắng** cho tìm kiếm, tóm tắt, metadata.
3. **Gộp trang theo thứ tự** thành một chuỗi chữ của văn bản cho metadata và tóm tắt, theo thứ tự ưu tiên của TÓM TẮT §4 ý 1: bản sửa tay → lớp chữ PDF → chữ làm mượt → chữ OCR. Các bước này lấy chữ từ `DocOcrPages` do backend gửi, **không** tự OCR lại trong worker.
4. **Metadata** đọc trang 1–3 và N−1..N (TRÍCH XUẤT, ĐỐI CHIẾU §9.4). **Tóm tắt** đọc trang 1..min(10, N) **có chữ**; không OCR thêm trang chỉ để tóm tắt (TÓM TẮT §4 ý 2). **Chữ viết tay** đọc trang 1–2 (mục 7).
5. **Báo phạm vi đã đọc:** `TotalPages` (số trang tệp) và số trang có chữ lưu trên văn bản, để màn tìm kiếm ghi «Đã đọc N/M trang» (TÌM KIẾM §3.4) và thẻ tóm tắt ghi chân (TÓM TẮT §4 ý 5).
6. **Dữ liệu cũ:** đọc lớp chữ cho mọi PDF đang có bằng lệnh quản trị một lần (TÌM KIẾM §3.5). Không OCR lại.
7. *(28/9, tuannq #56 c2901)* **Chữ đọc được khác chữ được tìm bằng BM25.** Mọi văn bản được tìm theo tóm tắt và metadata; chữ các trang (toàn văn) chỉ vào chỉ mục BM25 khi văn bản có cờ **«BM25 FULL»** (`DAC-TA-TIM-KIEM.md` §3.6; `DAC-TA-MO-HINH-DU-LIEU.md` §5.9). Cờ tự nó **không** xếp việc OCR nào; quy tắc trang ở bảng trên giữ nguyên. *(28/9, #18 c2956 D2; MH-Q12 đã chốt)* Cờ đặt theo ba đường:
- *(D11, Đã chốt theo đề xuất (lead, #1 c2985))* **PDF có lớp chữ dùng được** (định nghĩa ở trên) ở **mọi trang**: backend **tự đặt** `Bm25Full = true` ngay khi lớp chữ đã ghi vào `DocOcrPages`, và toàn văn vào BM25, **không cần bấm**. Không OCR thêm trang nào (chữ đã có sẵn), nên không trái D2. PDF có dù chỉ một trang không có lớp chữ dùng được (trang ảnh quét, trang nghi hỏng phông) thì **không** tự đặt: dùng nút;
- nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản» (kèm OCR toàn bộ, mục 11), **chỉ còn dùng cho bản scan**;
- tự đặt khi đợt rà soát QPPL yêu cầu OCR toàn bộ.
---
## 5. OCR chữ in tiếng Việt (TS-OCR-003)
- **Ai dùng:** job nền.
- **Đặc tả nói gì:** TS-OCR-003: PaddleOCR/VietOCR ONNX; giữ 100 % dấu tiếng Việt trên các dòng hành chính; trả hộp và điểm tin cậy **từng từ** (0–1); ≥ 95 % ký tự đúng; ≤ 2,5 s/trang trên RTX 3060; AC-R1: chỉnh nghiêng tới ±15°, từ < 0,70 gắn `low_confidence: true`, không lỗi ma trận.
- **Tối thiểu:**
1. **Giữ tesseract 5, ngôn ngữ `vie`**, 300 DPI, lượt quét thưa. Không đổi sang PaddleOCR/VietOCR ở giai đoạn này: chưa có số đo cho thấy tesseract không đạt, và đổi engine phải đo lại mọi thứ. Việc đổi engine là *mở rộng, có thể hoãn*, chỉ làm nếu T8 (mục 16) không đạt.
2. **Chỉnh hướng và nghiêng** trước khi OCR (ảnh quét, không áp cho trang lớp chữ):
- xoay 90°/180°/270° theo `tesseract --psm 0` (gói `osd` **đã cài**);
- chỉnh nghiêng nhỏ tới ±15° bằng phép chiếu ngang trên ảnh đã nhị phân (Pillow/NumPy, không thêm thư viện);
- lỗi chỉnh hướng thì bỏ qua bước này, không làm hỏng trang (AC-R1).
3. **Điểm tin cậy:** giữ điểm từng từ, điểm trang = trung bình điểm từ. Ngưỡng «từ đọc không chắc» mặc định đổi **0,60 → 0,70** theo AC-R1 (`OCR_LOW_CONF_THRESHOLD`). Ngưỡng tô màu **trường** ở màn Đối chiếu là chuyện khác, giữ 0,75/0,60 (TRÍCH XUẤT §5).
4. **Mục tiêu chất lượng** (đo trên bộ mẫu vàng của TRÍCH XUẤT Q1, chỉ trang ảnh quét ≥ 200 DPI):
- tỉ lệ ký tự đúng ≥ **95 %** (TS-OCR-003), tính bằng khoảng cách chỉnh sửa trên chữ đã chuẩn hoá khoảng trắng;
- dòng quốc hiệu «CỘNG HÒA XÃ HỘI CHỦ NGHĨA VIỆT NAM» và dòng «Số: …» đúng **100 %** ký tự và dấu;
- trang < 200 DPI chỉ báo số đo, không tính vào chỉ tiêu.
5. **Tốc độ:** mục tiêu ≤ 2,5 s/trang gắn với GPU (TS-OCR-003 AC-P1); máy đích và GPU do MỞ RỘNG Q1 quyết. Trên CPU, đo và ghi lại (hiện ≈ 3 s/trang khi máy rảnh, ≈ 6 s/trang khi máy tải 176 %).
6. **Làm mượt chữ** (`TextRefinement`, mô hình sửa chữ OCR, không ghi đè `Text`): giữ, chạy sau, **không** chặn «Chờ đối chiếu». Không có trong SRS; giữ vì đã chạy và có ích cho tìm kiếm (1.041 chỗ sửa). Không áp cho trang lớp chữ PDF (chữ đã đúng).
---
## 6. Phân vùng bố cục (TS-OCR-002)
- **Ai dùng:** hiện không ai. Khung vẽ vùng trên màn Đối chiếu bị gỡ theo ĐỐI CHIẾU §9.8.
- **Đặc tả nói gì:** TS-OCR-002 (P0): raster, tách hộp `PRINTED` / `HANDWRITTEN`, cắt vùng viết tay ra `/storage/crops/`, ≤ 800 ms/trang trên GPU, trang không có bút phê thì 0 vùng viết tay.
- **Hiện trạng thật:**
- Vùng chữ in = khối tesseract. Chữ in **không** được «định tuyến» sang OCR theo vùng: OCR chạy trên cả trang.
- Vùng viết tay tìm bằng heuristic; **không** có ảnh cắt nào được lưu (CSDL §2.1: 0/16 dòng có `CroppedBlobName`).
- Bộ đọc chữ viết tay gửi **cả trang** cho mô hình ảnh, không dùng vùng cắt. Như vậy ĐỐI CHIẾU §9.8 («layout vẫn dùng để cắt vùng chữ viết tay cho TS-OCR-004») **không khớp mã**; cần sửa câu đó.
- Đo trên .67: 24/62 văn bản có vùng «chữ viết tay», trong khi dữ liệu thử gần như không có bút phê (mục 0.2).
- **Tối thiểu:**
1. **Không đầu tư thêm** vào phân vùng trong khi #45 chưa quyết.
2. *(Q4 Đã chốt theo đề xuất (lead, #1 c2985))* **Tắt job `LayoutAnalysis` khỏi chuỗi tự động bằng cờ cấu hình, mặc định tắt** (tiết kiệm một lượt raster và P95 49 s máy), không chờ #45; giữ mã và endpoint để gỡ lỗi. Không trường nào dùng kết quả của job này. Tìm con dấu đỏ không có trong đặc tả; hiện không ai dùng.
3. Nếu #45 **giữ** chữ viết tay: bật lại cờ, và TS-OCR-002 phải làm đúng nghĩa (cắt vùng, lưu vào container `crops` riêng theo CSDL §2.1 quy tắc 2, bộ đọc chữ viết tay dùng vùng cắt). Việc này ghi vào đặc tả chữ viết tay khi có.
---
## 7. Chữ viết tay, bút phê (TS-OCR-004): chỉ mô tả
- **Ai dùng:** Văn thư (soát), Lãnh đạo (chỉ đạo).
- **Đặc tả nói gì:** YCNV §4.1.2 ý 2 (mô hình có sẵn, mức gợi ý, người soát lại); TS-OCR-004: tin cậy < 0,50 → tiền tố `[CẦN_RÀ_SOÁT]`, `requires_manual_edit`, ≤ 1,2 s mỗi vùng.
- **Hiện trạng:** mô hình ảnh đám mây đọc 2 trang đầu; mọi kết quả bị chặn trần tin cậy để luôn phải soát; kết quả thành chỉ đạo chờ xác nhận (12 chỉ đạo trên .67, 4 do máy). Chi tiết: TRÍCH XUẤT §6.3, ĐỐI CHIẾU §7, CHỈ ĐẠO.
- **Tối thiểu:** **không làm gì** ở đặc tả này (#45 c2286: anh đang tính huỷ; mọi lỗi liên quan tạm hoãn, kể cả test `test_layout_and_handwriting…`). Job `HandwritingRecognition` giữ như hiện nay cho tới khi anh quyết.
---
## 8. Hàng đợi và mức ưu tiên
- **Ai dùng:** Văn thư (thời gian chờ); Quản trị (`/ai-queue`, ngưỡng cổng ở `/admin`).
- **Đặc tả nói gì:**
- YCNV §4.2: hàng đợi tác vụ ngầm; ≤ 5 trang ưu tiên, xong ≤ 30 s; > 5 trang chạy ngầm và báo khi xong.
- TS-OCR-001: ưu tiên theo số trang, `queue:high_priority`; hàng đợi PostgreSQL SKIP LOCKED hoặc Redis qua `IJobQueue`.
- TS-OPS-002 AC-R1: worker treo 60 s → khởi động lại; job treo → dead letter.
- Cổng CPU, «từng văn bản một»: quyết định #23, #32; không có trong SRS.
- **Thiết kế hàng đợi, cổng CPU, watchdog, số chỗ worker: ở `DAC-TA-MO-RONG-NANG-LUC.md` §6.2–6.4.** Mục này chỉ quy định phần tiếp nhận chạm vào.
- **Tối thiểu:**
### 8.1 Mức ưu tiên (giữ quy tắc hiện có, trừ độ khẩn)
| Mức | Khi nào | Hàng ABP | Cổng CPU |
|---|---|---|---|
| **Immediate** | **chỉ** người bấm, mỗi lần một văn bản («Xử lý ngay», «Nhận dạng lại», «Chạy lại», OCR toàn bộ (nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản» hoặc khi lập đợt rà soát), «Tóm tắt lại»). Độ khẩn **không** tính (#18 c2956 D1) | `High`, có chỗ chạy riêng | **bỏ qua** |
| **Interactive** | ≤ 5 trang, hoặc chưa biết số trang (Office, TXT) | `AboveNormal` | qua cổng |
| **Background** | > 5 trang | `Normal` | qua cổng |
- Hàng đợi giữ ABP BackgroundJobs trên PostgreSQL (tương đương «PostgreSQL SKIP LOCKED» của SRS). Không thêm Redis (MỞ RỘNG §4).
- *(MỞ RỘNG Q4 = C68, Đã chốt theo đề xuất (lead, #1 c2985))* Văn bản ngắn **không** tự bỏ qua cổng tải: theo D1 chỉ thao tác người bấm mới vượt cổng. Chống đói bằng **luật chờ tối đa 15 phút** (MỞ RỘNG §6.2), không phải mức ưu tiên.
- *(28/9, #18 c2956 D1)* **Không có «văn bản khẩn» khi xử lý.** Anh: «tôi không có khái niệm văn bản khẩn, chỉ có nút làm ngay nếu vội». Độ khẩn (`UrgencyLevel`) giữ là trường của sổ (NĐ 30, MO-HINH D9), **không** đổi mức, không vượt cổng, không bỏ luật số trang. Chỉ «Xử lý ngay» (bấm tay, mỗi lần một văn bản) và các thao tác người bấm khác trong cột «Khi nào» mới vượt cổng; không có nút hay API hàng loạt nào mang mức `Immediate`. Mã: nhánh `fix/no-urgency-priority` (MỞ RỘNG §6.2 ý 6).
### 8.2 Mọi job của cùng một lượt người bấm mang cùng mức
- «Nhận dạng lại» và «OCR toàn bộ» xếp job làm mượt chữ với nguồn `UserAction` như các job còn lại, để không bị cổng giữ nửa chừng (lỗi B4). *(Q6 Đã chốt theo đề xuất (lead, #1 c2985))* Làm mượt chữ **chạy ngay cùng lượt, không qua cổng**, cùng mức với các job khác của lượt người bấm (D1: thao tác người bấm thì chạy ngay; sửa B4).
### 8.3 Văn bản dài: báo khi xong
- YCNV §4.2: văn bản > 5 trang «chạy ngầm và báo notification». Hiện đã có thông báo «văn bản sẵn sàng đối chiếu» (`DocumentReadyForReviewEto`) cho **mọi** văn bản, gửi người tiếp nhận. **Tối thiểu:** giữ; không thêm kênh.
### 8.4 Nói đúng trạng thái chờ
- Thẻ tiến trình đã ghi «Đang chờ máy rảnh» (#33 c2049). **Tối thiểu:** danh sách văn bản và trang văn bản dùng cùng nhãn phụ **«Chờ máy rảnh»** cho văn bản `Processing` chưa có job nào chạy, thay vì «Đang xử lý» (hiện 15 văn bản «Đang xử lý», phần lớn đang chờ cổng). Suy ra từ `AiJobs`, không thêm trạng thái.
### 8.5 Thứ tự trong mỗi mức
- Cũ trước mới sau theo thời điểm xếp. Không có dự báo thời gian chờ cho người dùng (*mở rộng, có thể hoãn*; SRS AC-F1 có `estimated_wait_seconds: 30`; *Q7 Đã chốt theo đề xuất (lead, #1 c2985): hoãn*, vì khi còn cổng CPU con số này sai nhiều).
---
## 9. «Xử lý ngay»
- **Ai dùng:** Văn thư (trên dòng kéo thả, trên thẻ tiến trình của văn bản); Quản trị (`/ai-queue`).
- **Đặc tả nói gì:** không có. Quyết định #23 mục 2 và #33 c2006 («có nút force thì không cần hạ gate»).
- **Tối thiểu:**
1. Giữ hai đường: Văn thư qua `Documents.Update` (endpoint văn bản), Quản trị qua `AiJobs.Manage` (PHÂN QUYỀN §4.2). Không thêm quyền.
2. Hành vi: nâng mọi job đang chờ của văn bản lên `Immediate`, bỏ qua cổng; gọi nhiều lần hoặc gọi khi đã xong là vô hại. Đo được: job bắt đầu **2–4 s** sau khi bấm.
3. Nút chỉ hiện khi văn bản còn job đang chờ. Sau khi bấm, nhãn đổi «Đã xin xử lý ngay» (đã có).
4. Mỗi lần bấm ghi log `ai.job.raised` (người bấm, số job) để đếm được «Xử lý ngay» dùng bao nhiêu lần một ngày: con số này cho biết cổng CPU có làm phiền văn thư hay không (MỞ RỘNG §6.2).
5. *(28/9)* Hai lỗi đã biết của «Xử lý ngay»: tranh chấp `ConcurrencyStamp` làm job chạy lại (B12, mục 18; **đã sửa trên `master` `d54c0eb`**); job Immediate còn lại bị hoãn khi văn bản khác đang chạy (MỞ RỘNG §6.8 L-Q1).
---
## 10. «Nhận dạng lại»
- **Ai dùng:** Văn thư (`AiJobs.Enqueue`), trên màn Đối chiếu và trang văn bản.
- **Đặc tả nói gì:** không có. Hợp với YCNV §4.1.1 bước 3 (văn thư sửa, xác nhận).
- **Tối thiểu:**
1. Giữ: xếp lại đủ chuỗi với nguồn `UserAction`; không đổi trạng thái văn bản; ô người đã xác nhận giữ nguyên; tóm tắt đã xác nhận không bị ghi đè.
2. **Đọc lại thật:** gửi cờ `fresh=true` để worker bỏ đệm OCR của tệp đó cho lượt này (lỗi B11). Chỉ cần khi cấu hình OCR đổi (DPI, ngôn ngữ, chỉnh nghiêng mới); với cấu hình cũ, kết quả tesseract không đổi.
3. Làm mượt chữ đi cùng mức (mục 8.2).
4. **Không** cho «Nhận dạng lại» với văn bản «Lưu trữ». Với văn bản đã vào sổ: cho phép (cập nhật chữ cho tìm kiếm), nhưng không đổi trường nào của văn bản (đã xác nhận hết).
---
## 11. OCR toàn bộ (qua rà soát QPPL và nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản»)
- **Ai dùng:** người lập đợt rà soát QPPL; người bấm nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản». *(28/9)* Cả hai gác bằng `LegalReviews.Create`, quyền này nay mọi vai nghiệp vụ có (PHÂN QUYỀN §12.7). *(Đã chốt, tuannq #18 c2960 D9)* Nút **mở cho mọi người** (PHÂN QUYỀN §12.8).
- **Đặc tả nói gì:** không có (PHÂN QUYỀN §6, #50 c2394). Nút: quyết định #18 c2956 D2 («chỉ có nhóm pháp chế mới hay tìm kiếm nhiều trên 1 văn bản dài … thêm 1 nút to và rõ, OCR toàn bộ để tìm kiếm toàn bộ văn bản»).
- **Tối thiểu:** giữ đúng PHÂN QUYỀN §6 (đã triển khai trên `9971f57`). Thêm hai điểm:
1. Làm mượt chữ đi cùng mức (mục 8.2, lỗi B4).
2. Trang đã có lớp chữ PDF thì **không** OCR lại (mục 4), kể cả trong OCR toàn bộ. Như vậy «OCR toàn bộ» chỉ tốn máy cho trang ảnh quét. *(D11, Đã chốt theo đề xuất (lead, #1 c2985))* PDF có lớp chữ dùng được ở mọi trang đã **tự** có `Bm25Full = true` (mục 4 ý 7), không cần nút.
3. *(28/9, #18 c2956 D2)* **Nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản»** trên trang văn bản (kiểu nút: GIAO DIỆN §17 ý 4):
- *(D11)* **chỉ hiện với văn bản có trang ảnh quét** (ít nhất một trang không có lớp chữ dùng được, mục 4) và chưa có `Bm25Full`; PDF đủ lớp chữ không hiện nút vì cờ đã tự đặt;
- gọi `RequestFullOcrAsync` cho **đúng một** văn bản; endpoint nhận cả văn bản **không** nằm trong đợt rà soát (sửa đổi PHÂN QUYỀN §12.8 cho §6 ý 4);
- theo luật «Xử lý ngay»: mức `Immediate`, bỏ qua cổng (mục 8.1), **không** có chọn nhiều, không có API nhận danh sách văn bản;
- đặt `Bm25Full = true` trong cùng giao dịch (TÌM KIẾM §3.6 ý 3 a); trang đọc xong tới đâu thì vào chỉ mục tới đó;
- hộp xác nhận ghi số trang sẽ đọc và «có thể lâu»; ghi «Lịch sử thay đổi» và log `ai.job.raised` như «Xử lý ngay» (mục 9 ý 4);
- bấm khi đã có OCR toàn bộ đang chờ, đang chạy hoặc đã xong: vô hại, không xếp thêm.
4. *(28/9, #18 c2956 D2)* Lập đợt rà soát QPPL (PHÂN QUYỀN §6 ý 3) cũng đặt `Bm25Full = true` cho văn bản mới và cũ của đợt, cùng lần yêu cầu OCR toàn bộ.
---
## 12. Số tạm `TMP-`
- **Ai dùng:** Văn thư (thấy trong danh sách, trên trang văn bản); AI (ghi đè).
- **Đặc tả nói gì:** không có. SRS dùng `document_id` (UUID) làm định danh ngay khi tải lên.
- **Tối thiểu:**
1. Giữ dạng `TMP-{yyyyMMdd}-{12 ký tự}` làm **định danh nội bộ** để văn bản có số ngay khi tạo.
2. **Không hiện mã `TMP-` cho người dùng:** danh sách và trang văn bản hiện «Chưa có số» màu xám, trích yếu trống thì hiện **tên tệp** (audit #42 mục 2). Mã vẫn tìm được bằng ô tìm.
3. **Vào sổ không bao giờ để lại mã `TMP-`.** *(#59 Q2 đã chốt)* Văn bản không có số (phiếu, giấy ủy quyền, bản nháp): khi vào sổ, `Register()` thay `TMP-` bằng **số hệ thống** `HT-<năm>-<số thứ tự>` (khuôn đã chốt theo đề xuất (lead, #1 c2985), MO-HINH §2.9); Văn thư không phải tick hay nhập gì. Bỏ tick «Văn bản không có số».
4. AI ghi đè mã `TMP-` bằng số đọc được (đã có). Văn thư sửa tay thì AI không ghi đè (ADR-0033 C).
5. Dữ liệu cũ: 2 văn bản đã vào sổ còn mã `TMP-` (TRÍCH XUẤT L1). ~~Liệt kê cho văn thư sửa tay; không sửa bằng lệnh.~~ *(#59 Q2: văn thư không quan tâm số của văn bản không có số)* Lệnh quản trị chạy một lần gán số `HT-`, ghi «Lịch sử thay đổi» (MO-HINH §2.9 ý 7); làm cùng đợt sửa L1. *(TRÍCH XUẤT Q6 = C25, Đã chốt theo đề xuất (lead, #1 c2985))* Sửa L1, L2 **ngay**, là việc T02 của checklist (Đợt 1), không chờ đợt màn Đối chiếu.
---
## 13. Tệp trùng
- **Ai dùng:** Văn thư khi tiếp nhận; hệ thống ngoài qua Inbound.
- **Đặc tả nói gì:** SRS có chỉ mục duy nhất chống trùng **văn bản** ở tầng trích xuất (CSDL §2.2). Trùng **tệp** (SHA-256) là quyết định #37/ADR-0033. «Vẫn thêm vào Tài liệu của tôi» là quyết định #9 c2570.
- **Tối thiểu:**
1. **Trùng nội dung (SHA-256)** với văn bản chưa xoá mà người tải xem được: theo **KHO §5.3 ý 2**: không tạo văn bản, không lưu tệp, trả 200 `outcome: "Duplicate"`, thêm văn bản cũ vào «Tài liệu của tôi» (`Source = 2`), dòng vàng «Đã có trong kho». Giai đoạn này mọi văn bản đều xem được (banner).
2. Trùng với văn bản **đã xoá**: cho tạo văn bản mới (như hiện nay).
3. Kênh **Inbound**: trùng SHA trả `outcome: "Duplicate"` và `documentId` cũ, **không** báo lỗi (bên gửi thử lại là chuyện thường). `ExternalReference` trùng thì trả văn bản cũ như hiện nay.
4. Tải tệp vào văn bản có sẵn (mục 2.3): giữ lỗi.
5. Hai lượt tải cùng tệp cùng lúc: chấp nhận rủi ro (CSDL §2.2), không thêm khoá.
6. **Cùng giấy, khác tệp** (scan hai lần, SHA khác; TRÍCH XUẤT L8; KHO §0.2: QT-KSTL có 3 bản ghi): *mở rộng, có thể hoãn* (*Q5 Đã chốt theo đề xuất (lead, #1 c2985): hoãn*). Khi làm, cách rẻ nhất: sau khi metadata xong, nếu (số ký hiệu, cơ quan, ngày ban hành) trùng một văn bản chưa xoá **và** số ký hiệu không phải `TMP-` hay rỗng, màn Đối chiếu hiện dòng vàng «Có thể trùng với …» kèm liên kết. **Không chặn** vào sổ.
---
## 14. Lỗi và trạng thái lỗi
- **Ai dùng:** Văn thư (thấy trên dòng kéo thả và thẻ tiến trình); Quản trị (`/ai-queue`, log).
- **Đặc tả nói gì:** TS-OCR-001 AC-R1 (từ chối sạch, không để lại rác); TS-OCR-002 AC-R1, TS-OCR-003 AC-R1, TS-OCR-004 AC-R1 (không làm sập chuỗi); TS-OPS-002 AC-R1 (worker treo, dead letter).
- **Tối thiểu:** bảng lỗi và cách xử lý:
| # | Tình huống | Ở đâu | Kết quả cho người dùng | Trạng thái văn bản |
|---|---|---|---|---|
| E1 | Định dạng không nhận, macro, đổi đuôi, > 50 MB | tải lên | dòng đỏ, lỗi theo mã, **không** tạo gì | — |
| E2 | PDF hỏng, có mật khẩu mở | tải lên (mới, mục 3.2) | dòng đỏ «Tệp PDF hỏng hoặc có mật khẩu» | — |
| E3 | Trùng SHA | tải lên | dòng vàng «Đã có trong kho…» (mục 13) | văn bản cũ không đổi |
| E4 | Mất mạng, 409 khi tải | trình duyệt | tự chờ mạng, tự thử lại (đã có) | — |
| E5 | DPI < 200 | tải lên | dòng vàng, vẫn nhận (mục 3.3) | Draft → Processing |
| E6 | Định dạng lưu được nhưng AI không đọc | tải lên | «AI chưa đọc được định dạng này, mở văn bản để nhập tay» | Draft; «Chuyển đối chiếu thủ công» |
| E7 | Worker trả `422` (tệp không đọc được) | job | thẻ tiến trình ghi lỗi thật; **không thử lại** (mục 3.2) | → Chờ đối chiếu, nhập tay |
| E8 | Worker lỗi khác (500, hết giờ) | job | thử lại tối đa 3 lượt; hết lượt thì như E7 | → Chờ đối chiếu nếu là metadata |
| E9 | Worker/LLM không gọi được | job | job trả về hàng, không tốn lượt, tối đa 6 giờ; `/ai-queue` ghi «engine không gọi được» | giữ Processing |
| E10 | Một trang hỏng khi dựng (Chromium) | worker | rơi về PDFium cho trang đó, ghi cảnh báo | tiếp tục |
| E11 | Trang trống, trang không có chữ | worker | trang có chữ rỗng; không lỗi; tóm tắt không gọi mô hình (TÓM TẮT §7) | tiếp tục |
| E12 | Chỉnh nghiêng lỗi | worker | bỏ bước chỉnh, OCR ảnh gốc | tiếp tục |
| E13 | Cổng CPU giữ lâu | hàng đợi | «Chờ máy rảnh» + nút «Xử lý ngay»; `ai.queue.stalled` cho Quản trị sau 30 phút | Processing («Chờ máy rảnh») |
| E14 | Worker treo | vận hành | theo MỞ RỘNG §6.3 (restart + healthcheck; job chạy dở được nhặt lại khi tiến trình backend khởi động lại) | — |
| E15 | Văn bản bị xoá khi còn job | job | job huỷ, không chạy | — |
- **Dead letter:** không làm hàng riêng. Job `Failed` chính là «dead letter»: xem và «Chạy lại» ở `/ai-queue` (Quản trị). Ghi rõ ánh xạ này vào tài liệu vận hành.
- **Lỗi phải có lời:** mọi job hỏng lưu mã lỗi, endpoint, mã HTTP và đoạn đầu nội dung lỗi (đã có từ #33 D). Không còn câu «Exception of type … was thrown».
---
## 15. Thời gian
- **Ai dùng:** Văn thư (chờ đối chiếu), Lãnh đạo (tóm tắt).
- **Đặc tả nói gì:** YCNV §4.2: ≤ 5 trang xong ≤ 30 s; TS-OCR-001: phản hồi tải lên < 500 ms (P95 ≤ 350 ms với 20 lượt đồng thời); TS-OCR-002: ≤ 800 ms/trang (GPU); TS-OCR-003: ≤ 2,5 s/trang (GPU); TS-EXT-001: xem TRÍCH XUẤT §3.3.
- **Tối thiểu:**
1. **«Hoàn thành» tính tới mốc «Chờ đối chiếu»** (metadata xong), không tính làm mượt chữ. *(MỞ RỘNG Q3 = C67, Đã chốt theo đề xuất (lead, #1 c2985))* Tính **từ lúc job bắt đầu chạy** tới «Chờ đối chiếu» (PendingReview); thời gian chờ máy rảnh không tính (đánh đổi đã chọn ở #23), việc gấp thì bấm «Xử lý ngay». Tài liệu này vẫn ghi cả thời gian chờ để theo dõi.
2. Số đo hôm nay (mục 0.2): PDF 1 trang P50 21,7 s (25/32 lượt ≤ 30 s); PDF 2–5 trang P50 47,7 s (5/12 ≤ 30 s). Chậm nhất là metadata (hai lượt LLM đám mây, TRÍCH XUẤT K14) và OCR khi máy tải cao.
3. Phần việc của đặc tả này giúp rút thời gian mà **không** cần GPU:
- đọc lớp chữ PDF thay vì OCR (PDF sinh từ máy tính: ≈ 0 s thay vì 3–6 s/trang);
- OCR một lần rồi dùng chung, các bước sau đọc `DocOcrPages` thay vì tự OCR (mục 4 ý 3);
- tắt job bố cục bằng cờ cấu hình (mục 6, Q4).
4. Đo lại trên máy đích theo T11–T12 (mục 16). Muốn chắc chắn ≤ 30 s với bản scan nhiều trang thì cần GPU hoặc mô hình cục bộ: MỞ RỘNG Q1.
---
## 16. Kiểm thử chấp nhận
Chạy bằng tài khoản `vanthu` (và client Inbound dev/test) trên dữ liệu thử, rồi kiểm DB chỉ đọc và `docker logs` của backend, ai-worker trong lúc chạy.
| # | Bấm / gọi | Kỳ vọng | Căn cứ |
|---|---|---|---|
| T1 | Thả 5 tệp: PDF scan 1 trang, PDF có lớp chữ 16 trang, JPG, DOCX, PPTX | 5 văn bản riêng, mỗi dòng có liên kết; PPTX «Chưa phân loại» + «Chuyển đối chiếu thủ công»; 4 tệp còn lại vào hàng AI; mã `TMP-` khác nhau, **không** hiện ra danh sách | §2.1, §3.1, §12 |
| T2 | Chọn «Chiều: Đi» rồi thả 1 tệp; đăng xuất, đăng nhập lại | văn bản mang chiều «Đi», không tự đặt ngày đến; lần sau ô «Chiều» vẫn là «Đi» | §2.1 ý 2 |
| T3 | 20 lượt `intake-file` đồng thời, PDF 5 trang | P95 thời gian xử lý của máy chủ (không tính truyền tệp) ≤ 350 ms | TS-OCR-001 AC-P1 |
| T4 | Thả `.exe` đổi đuôi `.pdf`; DOCX có macro; tệp 51 MB | 3 dòng đỏ đúng lý do; không có văn bản, blob hay job nào mới | TS-OCR-001 AC-R1 |
| T5 | Thả PDF cắt cụt (thiếu EOF), PDF có mật khẩu mở | dòng đỏ «Tệp PDF hỏng hoặc có mật khẩu»; không văn bản, không blob | §3.2 |
| T6 | Thả PDF scan 150 DPI; thả PDF sinh từ Word có logo | tệp 1: dòng vàng «150 DPI», vẫn nhận; tệp 2: **không** cảnh báo | §3.3 |
| T7 | Thả lại đúng tệp đã có | dòng vàng «Đã có trong kho: <số>»; văn bản cũ vào «Tài liệu của tôi»; số văn bản không đổi | §13, KHO §5.3 |
| T8 | Bộ mẫu vàng (TRÍCH XUẤT Q1), trang scan ≥ 200 DPI | ký tự đúng ≥ 95 %; quốc hiệu và dòng «Số:» đúng 100 % | TS-OCR-003 |
| T9 | Ảnh scan xoay 90°, và ảnh nghiêng 10° | chữ đọc ra đúng chiều, không lỗi; tin cậy không thấp hơn bản thẳng quá 0,05 | TS-OCR-003 AC-R1 |
| T10 | PDF 39 trang có lớp chữ (42/QĐ-KTQLB) | `DocOcrPages` có **39** trang `pdf-text`; không job OCR nào raster trang; tìm «camera» ra văn bản | §4, TÌM KIẾM T1 |
| T11 | PDF scan 12 trang không lớp chữ | OCR đúng trang 1, 2, 3, 11, 12; màn Đối chiếu hiện đúng 5 trang đó | §4, ĐỐI CHIẾU §3.1 |
| T12 | Hàng đợi trống, cổng tắt hoặc đã bấm «Xử lý ngay»: 10 bản scan 1–2 trang, lần lượt | thời gian từ job đầu bắt đầu tới «Chờ đối chiếu»: ghi P50/P95; so với ≤ 30 s theo định nghĩa chốt ở MỞ RỘNG Q3 | YCNV §4.2 |
| T13 | Máy bận (cổng đóng), thả 1 tệp | thẻ và danh sách ghi «Chờ máy rảnh»; bấm «Xử lý ngay» → job bắt đầu ≤ 10 s; log có `ai.job.raised` | §8.4, §9 |
| T14 | Văn bản «Chờ đối chiếu» → «Nhận dạng lại» | đủ 6 job, **cả** làm mượt chữ chạy ngay khi cổng đóng; ô đã xác nhận giữ nguyên; trạng thái vẫn «Chờ đối chiếu» | §10, lỗi B4 |
| T15 | `phapche` lập đợt rà soát với văn bản scan 12 trang; *(28/9)* `tracuu` bấm «OCR toàn bộ để tìm kiếm toàn bộ văn bản» ở một văn bản scan khác, ngoài đợt | OCR đủ 12 trang; làm mượt chữ không bị cổng giữ; cả hai văn bản có `Bm25Full = true`; lần bấm của `tracuu` → 200 (nút mở cho mọi người, #18 c2960 D9); `quantri` gọi `request-full-ocr` → 403 | §11, PHÂN QUYỀN §6, §12.7, §12.8 |
| T16 | Giả lập worker trả 422 cho một tệp | job hỏng **một** lượt, không thử lại; văn bản sang «Chờ đối chiếu», thông báo «nhập tay» | §14 E7 |
| T17 | Dừng ai-worker 2 phút trong lúc có job | job trở về hàng, `RetryCount` không tăng; worker lên lại thì job chạy xong | §14 E9 |
| T18 | Inbound: gửi PDF với `StartProcessing=true`, `SecurityLevel` khác «Thường»; gửi lại cùng `ExternalReference`; gửi tệp trùng SHA với `ExternalReference` khác | lần 1: 200, văn bản «Đang xử lý», độ mật «Thường»; lần 2: `alreadyExisted=true`; lần 3: `outcome: "Duplicate"`, không lỗi | §2.2, lỗi B1 |
| T19 | Xoá văn bản đang chờ AI | mọi job của nó «Huỷ» | §14 E15 |
| T20 | Mở trang văn bản với `vanthu`, `lanhdao` | không có dòng «Độ mật» | banner |
| T22 | *(D11)* Thả PDF 16 trang có lớp chữ ở mọi trang; thả PDF scan 12 trang | tệp 1: `Bm25Full = true` ngay sau khi lớp chữ ghi xong, không ai bấm, không job OCR nào; trang văn bản **không** hiện nút «OCR toàn bộ để tìm kiếm toàn bộ văn bản»; tệp 2: `Bm25Full = false`, nút hiện | §4 ý 7, §11 ý 3 |
| T21 | *(#59 Q7)* Tài khoản mới (chưa từng chọn chiều) thả QT-KSHS; gửi một PDF qua Inbound | văn bản kéo thả: ô «Chiều» hiện «Không có chiều», `Direction` = `null`, `ReceivedDate` trống; văn bản Inbound: «Đến», có ngày đến | §2.1 ý 2, §2.2 ý 1 |
---
## 17. Bảng khoảng trống
| Mã | Hiện trạng | Đặc tả | Việc cần làm |
|---|---|---|---|
| G1 | OCR 5 trang **đầu**; lớp chữ PDF bỏ phí | TS-EXT-001, TS-VER-001, TS-OCR-003; TÌM KIẾM §3.3; ĐỐI CHIẾU §9.4 | Hàm chọn trang chung + `pageIndices`; đọc lớp chữ mọi trang vào `DocOcrPages` (`pdf-text`); lệnh chạy một lần cho dữ liệu cũ (mục 4). **Làm chung một lần** với TÌM KIẾM §3.3 và ĐỐI CHIẾU §9.3–9.4, trên `feat/55-search` phase 1 (Q1 đã chốt); PDF đủ lớp chữ tự đặt `Bm25Full` (D11) |
| G2 | Metadata, tóm tắt tự OCR lại trong worker từ đệm | TÓM TẮT §4 ý 1 | Các bước đọc chữ đã gộp từ `DocOcrPages` do backend gửi (mục 4 ý 3) |
| G3 | Không chỉnh hướng, không chỉnh nghiêng | TS-OCR-003 AC-R1 | OSD + deskew ±15° trước OCR (mục 5 ý 2) |
| G4 | Ngưỡng từ không chắc 0,60 | TS-OCR-003 AC-R1 (0,70) | Đổi mặc định (mục 5 ý 3) |
| G5 | Chưa đo độ chính xác ký tự | TS-OCR-003 (≥ 95 %) | Đo trên bộ mẫu vàng của TRÍCH XUẤT Q1 (T8) |
| G6 | PDF hỏng/mật khẩu được nhận, hỏng ở AI; 422 bị thử lại | TS-OCR-001 AC-R1 | Mở thử PDF lúc tải lên; 422 không thử lại (mục 3.2) |
| G7 | DPI giả với PDF sinh từ máy tính | YCNV §4.2 | Chỉ tính DPI cho trang ảnh quét (mục 3.3) |
| G8 | Tải trùng báo lỗi, không ghi «Tài liệu của tôi» | #9 c2570; KHO §5.3 | Theo KHO §5.3; Inbound trả `Duplicate` (mục 13) |
| G9 | Không có ô «Chiều» ở Tiếp nhận; mọi tệp là «Đến» | #42 mục 7; ĐỐI CHIẾU §4 mục 9; #59 Q7 | Ô «Chiều» nhớ theo người dùng, có «Không có chiều», lần đầu mặc định «Không có chiều» (mục 2.1 ý 2) |
| G10 | Inbound không bắt đầu được xử lý (thiếu `Documents.Update`); chưa có test; nhận `SecurityLevel` từ bên gửi | YCNV §4.3, TS-OCR-001 | Mục 2.2 ý 2–6 |
| G11 | Làm mượt chữ sau «Nhận dạng lại»/«OCR toàn bộ» bị cổng giữ | quyết định #33 (người bấm thì chạy ngay) | Mục 8.2 |
| G12 | Danh sách ghi «Đang xử lý» cho văn bản đang chờ cổng | YCNV §4.1.9 (chống nhầm lẫn) | Nhãn phụ «Chờ máy rảnh» (mục 8.4) |
| G13 | Mã `TMP-` hiện cho người dùng; vào sổ được với `TMP-` | YCNV §4.1.1 bước 3; #59 Q2 | Mục 12; khi vào sổ `TMP-` được thay bằng số hệ thống `HT-…` (MO-HINH §2.9); điều kiện vào sổ ở ĐỐI CHIẾU §9.6 |
| G14 | «Nhận dạng lại» không OCR lại khi có đệm | — | Cờ `fresh` (mục 10 ý 2) |
| G15 | Không log số lần «Xử lý ngay» | — | `ai.job.raised` (mục 9 ý 4) |
| G16 | Vùng «chữ viết tay» của bố cục không ai dùng, nhiều vùng nhầm | TS-OCR-002 | Tắt job bố cục khỏi chuỗi bằng cờ cấu hình, mặc định tắt (mục 6, Q4 đã chốt); bật lại nếu #45 giữ chữ viết tay |
| G17 | Trang văn bản hiện «Độ mật» | banner (tuannq 28/9, #9 c2706); PHÂN QUYỀN §1.5 | Ẩn dòng này ở mọi màn |
| G18 | ĐỐI CHIẾU §9.8 nói bố cục dùng để cắt vùng viết tay | — | Sửa câu trong `DAC-TA-MAN-DOI-CHIEU.md` cho khớp mã |
**Thứ tự đề xuất:** G1 + G2 trước (chung với #55 và #38, lợi cho tìm kiếm, tóm tắt, thời gian); G10, G6, G11 (lỗi thật); G8, G9, G13 (cùng đợt KHO và #38); G3–G5 khi có bộ mẫu vàng; còn lại theo đợt giao diện.
---
## 18. Lỗi phát hiện khi audit
| Mã | Lỗi | Bằng chứng (chỉ đọc, .67, 28/9) | Mức |
|---|---|---|---|
| B1 | Kênh Inbound gọi `IDocumentAppService.StartProcessingAsync`, phương thức gác bằng `Documents.Update`; client `AIVPCT_Inbound` đã bị gỡ quyền này ở #41 (`OpenIddictDataSeedContributor`, `AivpctRoleRevocationStep`). Với mặc định `StartProcessing=true`, yêu cầu sẽ bị từ chối quyền và **cả lượt cuộn ngược** | Đọc mã `InboundDocumentAppService.cs`, `DocumentAppService.cs:314`. **Chưa thử sống được**: .67 chưa cấu hình client. Không có test nào gọi service này | cao (khi bật kênh) |
| B2 | Inbound tìm `ExternalReference` chỉ trong **2.000 văn bản mới nhất**; chú thích nói «thu hẹp bằng số ký hiệu» nhưng mã không làm vậy. Quá 2.000 văn bản thì gửi lại sẽ gặp lỗi trùng SHA thay vì nhận văn bản cũ | `FindByExternalReferenceAsync` | thấp |
| B3 | Inbound đặt `ReceivedDate = Clock.Now.Date` (ngày UTC): gửi trước 07:00 giờ VN ghi thành hôm trước. Kéo thả đã tránh lỗi này | `InboundDocumentAppService.cs` | thấp |
| B4 | Job làm mượt chữ sau «Nhận dạng lại» và sau «OCR toàn bộ» xếp với nguồn `Intake` (`EnqueueTextRefinementAsync` không nhận `trigger`), nên bị cổng CPU giữ trong khi các job cùng lượt chạy ngay | 1245/EVNNPC-KT: job OCR toàn bộ xong 08:59:02; job làm mượt `3a23f969-eba4-…` vẫn «Queued» lúc đo (hơn 3 giờ) | trung bình |
| B5 | DPI của PDF tính từ ảnh rộng nhất trên trang 1, kể cả logo trên PDF sinh từ máy tính → cảnh báo «dưới 200 DPI» sai | 5 tệp «34 DPI» (QT-QLSTĐ, QT-MNH…), đều có lớp chữ | thấp |
| B6 | PDF hỏng hoặc có mật khẩu **được nhận** lúc tải lên (chỉ kiểm phần đầu tệp), rồi hỏng ở AI; worker trả 422 nhưng backend vẫn thử lại 3 lượt cho mỗi job | `UploadedFileValidator`, `ProcessDocumentJob` (chỉ lỗi «engine chết» mới xử lý riêng) | trung bình |
| B7 | Danh sách văn bản ghi «Đang xử lý» cho văn bản chưa job nào chạy (đang bị cổng giữ) | 15 văn bản «Đang xử lý», 20 văn bản có job chờ cổng từ 06:55Z | thấp |
| B8 | Trang văn bản vẫn hiện «Độ mật: Thường» | Playwright `vanthu`, `TMP-20260928-08313DCE0A59` | thấp |
| B9 | Bố cục đánh dấu «chữ viết tay» ở 24/62 văn bản; trên QT-QLSTĐ 6 vùng, trong khi mô hình ảnh đọc cùng trang báo không có chữ viết tay | `AiAiResults` (98 vùng), trace 0.3 A | thấp, **tạm hoãn** (#45) |
| B10 | ĐỐI CHIẾU §9.8 ghi «layout vẫn dùng để cắt vùng chữ viết tay cho TS-OCR-004»; mã thì bộ đọc chữ viết tay gửi cả trang, không dùng vùng | `handwriting.py`, `main.py` | thấp (lệch tài liệu) |
| B11 | «Nhận dạng lại» không chạy lại tesseract khi worker còn đệm OCR của tệp (khoá theo tên, cỡ, mtime), dù hộp xác nhận ghi «chạy lại OCR» | `OcrEngine.recognize`, `ReprocessAsync` gửi `MaxPages=null` | thấp |
| B12 | *(28/9)* **Tranh chấp `ConcurrencyStamp` giữa «Xử lý ngay» và lần ghi kết quả job**: bấm «Xử lý ngay» đổi dòng job (nâng mức) trong khi job đang chạy; lần ghi «thành công» của job sau đó bị từ chối vì `ConcurrencyStamp` đã đổi, và job **chạy lại** | **đã xác nhận sống** ngày 28/9 trên .67 (job chạy lại sau khi lần ghi thành công bị từ chối) | trung bình: tốn một lượt AI, kết quả vẫn đúng. **Đã sửa trên `master` `d54c0eb`** |
| — | Đã biết, ghi lại để đủ: test `test_ocr_max_pages_mac_dinh_5_trang…` đỏ thường trực (#45, waitlist #54); job tóm tắt hỏng vì chữ rỗng (TÓM TẮT §7); vào sổ với mã `TMP-` (TRÍCH XUẤT L1); trùng khác SHA (TRÍCH XUẤT L8) | | |
---
## 19. Mở rộng, có thể hoãn
- Đổi engine OCR sang PaddleOCR/VietOCR hoặc chạy trên GPU (chỉ khi T8 không đạt; GPU theo MỞ RỘNG Q1).
- Bộ đọc chữ cho PPTX, DOC, XLS, PPT.
- Kéo văn bản từ `trang-noi-bo`, hệ thống ISO cũ qua Connector.
- Dự báo thời gian chờ cho người tải (`estimated_wait_seconds`).
- Hàng đợi ưu tiên nhiều mức hơn 3 mức hiện có; hàng «dead letter» riêng.
- Phát hiện trùng gần đúng (cùng giấy, khác tệp): Q5 đã chốt hoãn.
- Đưa đọc chữ ký số PDF ra sau lượt tải lên nếu T3 không đạt.
- Lưu ảnh cắt vùng và phân vùng bằng mô hình học máy (chỉ khi giữ chữ viết tay, #45).
- Dò tệp «cùng nội dung khác định dạng» (bản Word và bản PDF của cùng văn bản).
---
## 20. Câu hỏi cho anh
*(28/9 khuya, lượt 6)* Anh tạm duyệt đặc tả và để lead tự chốt câu còn mở theo đề xuất (#1 c2985). **Không còn câu mở.**
1. ~~**Q1 — Lớp chữ PDF và quy tắc trang.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C12): **có**. Một quy tắc cho OCR, màn Đối chiếu và tìm kiếm: đọc lớp chữ PDF ở mọi trang; trang không có lớp chữ dùng được thì OCR trang 1–3 và 2 trang cuối. Làm một lần, trên `feat/55-search` phase 1 (mục 4, G1). ĐỐI CHIẾU Q2 gộp vào câu này. Câu gốc (lịch sử): dùng một quy tắc chung, làm một lần cùng #55 và #38?
2. ~~**Q2 — Bản scan dưới 200 DPI.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C13): **cảnh báo và vẫn nhận**; lệch SRS AC-R1 ghi vào biên bản. DPI chỉ tính cho trang ảnh quét (B5, mục 3.3). Câu gốc (lịch sử): SRS đòi từ chối; đồng ý lệch?
3. ~~**Q3 — Kênh Inbound.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C14; QUẢN TRỊ Q7 gộp): **coi như bên gửi chưa sẵn sàng**. Vẫn sửa B1 và viết test trên .67; trên máy thật để Inbound tắt tới khi có bên gửi (mục 2.2 ý 7). Câu gốc (lịch sử): Tổng công ty đã sẵn sàng gửi chưa?
4. ~~**Q4 — Phân vùng bố cục.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C15): **tắt khỏi chuỗi tự động bằng cờ cấu hình, mặc định tắt**, giữ mã để gỡ lỗi; bật lại nếu #45 giữ chữ viết tay (mục 6 ý 2). Câu gốc (lịch sử): nếu #45 huỷ chữ viết tay, bỏ job «Phân vùng bố cục»?
5. ~~**Q5 — Cùng giấy scan hai lần (khác SHA).**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C17): **hoãn** (mục 13 ý 6, mục 19). Câu gốc (lịch sử): hoãn, hay làm ngay dòng «Có thể trùng với …»?
6. ~~**Q6 — Làm mượt chữ sau khi người bấm.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C18): **có**, chạy ngay cùng lượt, không qua cổng (mục 8.2, sửa B4; D1). Câu gốc (lịch sử): chạy ngay cùng lượt như các bước khác?
7. ~~**Q7 — Thời gian chờ dự kiến.**~~ **Đã chốt theo đề xuất (lead, #1 c2985)** (= C19): **hoãn** `estimated_wait_seconds` (mục 8.5, 19). Câu gốc (lịch sử): hoãn vì con số sai nhiều khi còn cổng CPU?