Files
labs/DAC-TA-OCR-TIEP-NHAN.md
2026-10-15 21:27:39 +07:00

81 KiB
Raw Permalink Blame History

Đặ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

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

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?