feat: align skills with real eGP data (lowercase types, IB codes, bid status)

The skills shipped fictional examples (entity_type="TBMT", code TBMT-2026-001)
that fail against the live openclaw-egp-server: entity_type is exact-match and
stored lowercase, so "TBMT" returns 0 rows (verified: tbmt=722, TBMT=0).

Fixes:
- entity_type guidance -> lowercase tbmt/khlcnt/kqlcnt across all skills + README
- entity_code examples -> real eGP format IB<digits>-<version> (e.g. IB2600250575-00)
- index_status values -> indexed/changed/indexing/converting/converted/uploaded/deleted
  (failed only exists at chunk level)

Exploit new entity columns now populated by the crawler:
- bid_close_date/bid_open_date (VN-time, UTC+7) -> dong/mo thau status computed in
  Vietnam time, never host clock
- title/investor_name/status_code -> fill DISCOVERY candidate table directly
- version_no -> detect amendments/extensions (>"00")

Bump plugin to v0.3.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
WIN-88OI7J0B6KB\vietnd 4 weeks ago
parent fa9b4c4ab5
commit ddbfc855fd

@ -5,7 +5,7 @@ RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Con
## Install
```bash
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.1.0
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.3.0
```
## Configure
@ -55,12 +55,16 @@ Then in chat, try:
> "Liệt kê các TBMT đã indexed"
The agent should call `list_entities(entity_type="TBMT", status="indexed")` and return a markdown table.
The agent should call `list_entities(entity_type="tbmt", status="indexed")` and return a markdown table.
> **`entity_type` is case-sensitive and lowercase** (`tbmt` / `khlcnt` / `kqlcnt`). The server stores
> lowercase types and filters by exact match, so `entity_type="TBMT"` returns zero rows. Entity codes
> follow the eGP format `IB<digits>-<version>` (e.g. `IB2600250575-00`), not `TBMT-2026-001`.
## Update
```bash
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.2.0
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.3.0
openclaw plugins restart
```

@ -2,7 +2,7 @@
"id": "openclaw-egp",
"name": "OpenClaw EGP",
"description": "RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Connects an OpenClaw agent to a running openclaw-egp-server instance via remote MCP.",
"version": "0.2.0",
"version": "0.3.0",
"configSchema": {
"type": "object",
"required": ["serverUrl"],

@ -7,38 +7,53 @@ description: Xem chi tiết một hồ sơ theo entity_code — metadata, danh s
## Khi nào dùng
- "Cho tôi chi tiết hồ sơ TBMT-2026-001"
- "Cho tôi chi tiết hồ sơ IB2600250575-00"
- "Hồ sơ X có bao nhiêu file?"
- "Trạng thái index của KHLCNT-... ?"
- "Trạng thái index của IB...-00 ?"
- "Gói này đóng thầu khi nào, ai là chủ đầu tư?"
- "File chính của hồ sơ này tên gì?"
## Workflow
1. Cần `entity_code` chính xác. Nếu user nói mơ hồ ("hồ sơ A", "cái gói thầu hôm qua"):
1. Cần `entity_code` chính xác (mã eGP dạng `IB<digits>-<version>`, vd. `IB2600250575-00`).
Nếu user nói mơ hồ ("hồ sơ A", "cái gói thầu hôm qua"):
- Hỏi lại để clarify, hoặc
- Dùng `list_entities` để tìm trước rồi xác nhận với user.
2. Gọi `describe_entity(entity_code="<mã>")`.
3. Xử lý lỗi:
- Tool raise `entity_not_found` → thông báo user "Không tìm thấy hồ sơ <mã>", gợi ý dùng `list_entities` để xem danh sách.
4. Nếu OK: trả lời gồm:
- Metadata: type, status, ngày tạo / cập nhật.
- Bảng files: mỗi row có `id` (cần cho `fetch-raw-file`), `file_name`, `file_type`, `size`.
- **Metadata** (entity giờ giàu trường — surface cái nào liên quan câu hỏi):
`entity_type`, `index_status`, `title` (tên gói), `investor_name` (chủ đầu tư),
`status_code`, `public_date` (ngày đăng), `version_no` (phiên bản/sửa đổi),
`bid_open_date` / `bid_close_date` (mở/đóng thầu — giờ VN), `created_at`/`updated_at`.
- **Bảng files**: mỗi row có `id` (cần cho `fetch-raw-file`), `file_name`, `file_type`, `size`,
`convert_status`, `index_status`.
## Ví dụ
**User:** "Chi tiết hồ sơ TBMT-2026-001"
**User:** "Chi tiết hồ sơ IB2600250575-00"
**Agent:**
```
→ describe_entity(entity_code="TBMT-2026-001")
← {entity: {...}, files: [{id: 10, file_name: "main.pdf", ...}, ...]}
→ describe_entity(entity_code="IB2600250575-00")
← {entity: {entity_type: "tbmt", title: "...", investor_name: "...",
bid_close_date: "2026-06-12T09:00:00", version_no: "00", ...},
files: [{id: 10, file_name: "main.pdf", file_type: "pdf", ...}, ...]}
```
**Reply:** metadata + bảng files với cột `id` để user/agent có thể tham chiếu khi cần download.
**Reply:** metadata (tên gói, chủ đầu tư, đóng thầu, phiên bản) + bảng files với cột `id` để tham chiếu khi cần download.
## Đóng/mở thầu & phiên bản
- `bid_close_date`/`bid_open_date` là ISO **giờ VN (UTC+7, không có tz suffix)** → tính đang mở / sắp đóng / đã đóng theo giờ VN, không theo giờ máy chủ.
- `version_no` > `"00"` ⇒ hồ sơ đã có sửa đổi/gia hạn (notice được đăng lại). Khi user hỏi "có sửa đổi không", dùng trường này thay vì đoán.
## Anti-patterns
- ❌ Đừng đoán entity_code — phải hỏi nếu user mơ hồ.
- ❌ Đừng bịa mã kiểu `TBMT-2026-001` — mã thật là `IB<digits>-<version>`.
- ❌ Đừng tóm tắt nội dung file dựa vào file_name — đó là việc của `search_procurement_docs`.
- ❌ Đừng giấu `id` của file — agent/user cần `id` để tải file gốc qua `fetch_raw_file_url`.
- ❌ Đừng kết luận đóng/mở thầu bằng giờ máy chủ — luôn tính theo giờ VN.

@ -9,16 +9,24 @@ description: Liệt kê các hồ sơ (TBMT/KHLCNT/KQLCNT) có trong kho dữ li
- "Có những hồ sơ nào trong hệ thống?"
- "Liệt kê các TBMT đã upload"
- "Bao nhiêu KHLCNT đang ở trạng thái indexed?"
- "Bao nhiêu hồ sơ đang ở trạng thái indexed?"
- "Cho tôi xem 10 record mới nhất"
- "Gói nào đang mở / sắp đóng thầu?"
## Workflow
1. Gọi `list_entities()` — không tham số nếu user hỏi chung.
2. Lọc theo `entity_type` nếu user nêu loại cụ thể (`"TBMT"` / `"KHLCNT"` / `"KQLCNT"`).
3. Lọc theo `status` nếu user nêu trạng thái (`"indexed"`, `"uploaded"`, `"failed"`).
2. Lọc theo `entity_type` nếu user nêu loại cụ thể. **Giá trị phải LOWERCASE**:
`"tbmt"` / `"khlcnt"` / `"kqlcnt"`. Filter là exact-match — gọi `"TBMT"` (hoa) trả về **0 kết quả**.
- Corpus hiện tại chủ yếu là `tbmt`; ngoài ra còn `tbmst_pre` (thông báo mời sơ tuyển / pre-notice).
`khlcnt`/`kqlcnt` được hỗ trợ nhưng có thể chưa có dữ liệu.
3. Lọc theo `status` nếu user nêu trạng thái. Giá trị `index_status` thực tế:
`"indexed"`, `"changed"`, `"indexing"`, `"converting"`, `"converted"`, `"uploaded"`, `"deleted"`.
(Không có `"failed"` ở mức entity — `failed` chỉ tồn tại ở mức chunk.)
4. Mặc định `limit=50` — đủ cho hầu hết câu hỏi.
5. Trả lời dạng bảng markdown với các cột: `Code`, `Type`, `Status`, `Updated`.
5. Mỗi row trả về `SELECT *` nên giàu metadata. Surface các cột có ý nghĩa:
`entity_code` | `entity_type` | `title` (tên gói) | `investor_name` (chủ đầu tư) |
`status_code` | `bid_close_date` (đóng thầu) | `index_status` | `updated_at`.
## Ví dụ
@ -27,14 +35,24 @@ description: Liệt kê các hồ sơ (TBMT/KHLCNT/KQLCNT) có trong kho dữ li
**Agent:**
```
→ list_entities(entity_type="TBMT", status="indexed")
← [{entity_code: "TBMT-2026-001", ...}, ...]
→ list_entities(entity_type="tbmt", status="indexed")
← [{entity_code: "IB2600250575-00", title: "...", investor_name: "...",
bid_close_date: "2026-06-12T09:00:00", index_status: "indexed", ...}, ...]
```
**Reply:** bảng markdown gồm các record + tổng số.
**Reply:** bảng markdown — cột `Mã` | `Tên gói` | `Chủ đầu tư` | `Đóng thầu` | `Trạng thái` + tổng số.
## Đóng/mở thầu (bid status)
`bid_close_date` / `bid_open_date`**ISO giờ Việt Nam (UTC+7, không có hậu tố tz)**. Khi user hỏi
"gói nào đang mở / đã đóng", tính trạng thái theo **giờ VN** (Asia/Ho_Chi_Minh), không dùng giờ máy chủ:
`đang mở` (deadline tương lai) · `sắp đóng` (~trong 48h) · `đã đóng` (đã qua) · `không xác định` (null/invalid).
## Anti-patterns
- ❌ Đừng gọi `entity_type` viết HOA — DB lưu lowercase, sẽ trả rỗng.
- ❌ Đừng bịa mã kiểu `TBMT-2026-001` — mã thật là eGP `IB<digits>-<version>` (vd. `IB2600250575-00`).
- ❌ Đừng dùng `list_entities` rồi tự duyệt trả lời nội dung — đó là việc của `search_procurement_docs`.
- ❌ Đừng dump toàn bộ raw row cho user — chỉ surface các trường có ý nghĩa: code, type, status, updated.
- ❌ Đừng dump toàn bộ raw row cho user — chỉ surface các cột có ý nghĩa ở trên.
- ❌ Đừng kết luận đóng/mở thầu bằng giờ máy chủ — luôn tính theo giờ VN.
- ❌ Đừng tăng `limit` lên rất lớn (>200) — gây nhiễu cho user. Nếu cần xem nhiều hơn, page bằng cách lọc theo `entity_type`.

@ -48,9 +48,10 @@ Phân loại câu hỏi vào 1 trong 4 intent:
Khi `intent = DISCOVERY`:
1. Extract keyword/filter từ câu hỏi: ngành, hàng hóa/dịch vụ, địa điểm, trạng thái, thời gian, khoảng giá.
2. Gọi `list_entities(entity_type?)` (filter loại nếu user chỉ định) + `search_procurement_docs(query=<keyword>, limit=10..20)`.
2. Gọi `list_entities(entity_type?)` (filter loại nếu user chỉ định — **giá trị LOWERCASE** `tbmt`/`khlcnt`/`kqlcnt`, viết hoa trả rỗng) + `search_procurement_docs(query=<keyword>, limit=10..20)`.
- `list_entities` trả `SELECT *` nên đã có sẵn `title`, `investor_name`, `bid_close_date`, `status_code`, `version_no`**điền thẳng** vào bảng candidate, KHÔNG cần moi lại từ search.
3. Trả **bảng candidate** với cột:
- `rank` | `entity_type` | `entity_code` | `tên gói/thông báo/kế hoạch` | `bên mời thầu/chủ đầu tư` (nếu có) | `giá gói thầu` (nếu có) | `deadline/đóng thầu` (nếu có) | `trạng thái` (nếu có) | `highlight ngắn` (lý do match hoặc điểm đáng chú ý) | `citation/source`.
- `rank` | `entity_type` | `entity_code` | `tên gói` (`title`) | `chủ đầu tư` (`investor_name`, nếu có) | `giá gói thầu` (nếu có) | `đóng thầu` (`bid_close_date`, giờ VN) | `trạng thái` (mở/sắp đóng/đã đóng tính theo giờ VN, hoặc `status_code`) | `highlight ngắn` (lý do match) | `citation/source`.
4. **KHÔNG run full 5-part analysis** cho từng candidate.
5. Trước bảng, nếu có candidate deadline gấp / rủi ro nổi: thêm mục "**Ưu tiên xem trước**".
6. Sau bảng:
@ -81,11 +82,12 @@ Khi intent thuộc 3 nhóm này, render đủ **schema 5 phần** ở dưới.
#### Part 1 — Tóm tắt gói thầu
- Mã TBMT/E-TBMT
- Mã TBMT/E-TBMT (`entity_code`)
- Mã KHLCNT
- Tên gói thầu
- Chủ đầu tư / Bên mời thầu
- **Trạng thái**: đang mở / đã đóng / đã hủy / đã gia hạn. **Chỉ kết luận khi**`bidCloseDate`, `status`, hoặc trường nguồn ghi rõ. Thiếu dữ liệu → "không xác định từ nguồn đã truy xuất".
- Tên gói thầu (`title`)
- Chủ đầu tư / Bên mời thầu (`investor_name`)
- **Trạng thái đóng/mở thầu**: ưu tiên cột **`bid_close_date`/`bid_open_date`** trên entity (`describe_entity`) — đây là ISO **giờ VN (UTC+7, không có tz suffix)**. Tính theo giờ VN: `đang mở` (deadline tương lai) / `sắp đóng` (~trong 48h) / `đã đóng` (đã qua). Có thể đối chiếu thêm `status_code`. **Chỉ kết luận khi**`bid_close_date` hợp lệ hoặc `status_code` rõ. Thiếu cả hai → "không xác định từ nguồn đã truy xuất". KHÔNG bao giờ tính bằng giờ máy chủ.
- **Phiên bản/sửa đổi**: `version_no` > `"00"` ⇒ notice đã được đăng lại (có sửa đổi/gia hạn) — nêu rõ.
- 1 câu kết luận theo intent.
#### Part 2 — Thông tin KHLCNT / TBMT / E-HSMT quan trọng
@ -155,7 +157,7 @@ Khi rút gọn: header thêm hậu tố `(không phải trọng tâm câu hỏi,
- **Audit TBMT**: đủ mã / tên gói / bên mời / chủ đầu tư / mốc thời gian / bảo đảm / trạng thái?
- **Audit E-HSMT**: đủ Chỉ dẫn NT / Bảng dữ liệu / Tiêu chuẩn ĐG / YC kỹ thuật / Biểu mẫu / Điều kiện HĐ / Mẫu HĐ?
- **Khớp giữa 3 hồ sơ** (chỉ render khi CROSS_DOC_AUDIT hoặc đã tìm được related docs): bảng compare field-by-field, ✓ / ✗ / "không kiểm tra được".
- **Sửa đổi / gia hạn / làm rõ**: có/không, phiên bản mới nhất.
- **Sửa đổi / gia hạn / làm rõ**: dùng `version_no` trên entity (`"00"` = bản gốc; `> "00"` = đã đăng lại/sửa đổi). Nêu phiên bản hiện có. Lưu ý: hệ thống có thể chỉ giữ 1 snapshot — nếu web có phiên bản mới hơn (deadline đã đổi) mà entity còn `version_no` cũ, cảnh báo "có thể có bản cập nhật chưa crawl".
- **Kết luận audit**: ① Đủ để công khai / ② Cần bổ sung / ③ Rủi ro cần rà soát pháp lý — kèm lý do.
#### Part 5 — Rủi ro, điểm cần làm rõ & nguồn trích dẫn
@ -187,7 +189,7 @@ Khi rút gọn: header thêm hậu tố `(không phải trọng tâm câu hỏi,
3. **"Không thấy" ≠ "không có"**: thiếu trong retrieve KHÔNG có nghĩa thực tế gói thầu không có. Không chuyển nhãn.
4. **Đa bằng chứng**: check ≥ 3-5 results, không kết luận từ top-1. Nếu < 3 hits, expand query / rephrase trước khi nói "thiếu chng cứ".
5. **Báo hit count khi < 3**: ghi rõ số hit thực tế, đánh dấu bằng chứng chưa đủ.
6. **Status conclusion gate**: KHÔNG kết luận "đang mở/đã đóng/đã hủy" nếu thiếu mốc thời gian / `status`.
6. **Status conclusion gate**: KHÔNG kết luận "đang mở/đã đóng/đã hủy" nếu thiếu mốc thời gian (`bid_close_date`) / `status_code`. Khi có `bid_close_date`, tính trạng thái theo **giờ VN (UTC+7)**, không bao giờ theo giờ máy chủ (lệch ~15h → sai kết luận).
7. **Conclusion strength gate**: cite yếu (≤ 1 cite, hoặc cite không match claim) → bỏ kết luận mạnh ("nên tham dự" / "đủ công khai" / "rủi ro pháp lý"), fallback sang "cần kiểm tra thêm" hoặc "không đủ dữ liệu để kết luận".
8. **Retrieval-weakness reporting**: results = [] hoặc `limitations` có warning → báo rõ giới hạn + đề xuất thêm file/keyword.
9. **Industry synonym expansion**: keyword mơ hồ (vd. "y tế" → thiết bị / dược / vật tư / dịch vụ) → có thể expand, NHƯNG bắt buộc log keyword đã dùng trong reply.

@ -10,14 +10,15 @@ description: Tìm kiếm trong kho hồ sơ đấu thầu (TBMT, KHLCNT, KQLCNT)
Khi user hỏi câu cụ thể đòi nội dung từ hồ sơ — ví dụ:
- "Yêu cầu kinh nghiệm trong gói thầu X là gì?"
- "Tiêu chí đánh giá năng lực tài chính trong TBMT-2026-001?"
- "Phạm vi cung cấp hàng hoá của KHLCNT-...?"
- "Tiêu chí đánh giá năng lực tài chính trong IB2600250575-00?"
- "Phạm vi cung cấp hàng hoá của gói IB...?"
## Workflow
1. Gọi tool `search_procurement_docs(query=<câu hỏi nguyên văn>)`.
- Nếu user nêu rõ mã hồ sơ → thêm `entity_code="<mã>"`.
- Nếu user nêu loại hồ sơ → thêm `entity_type="TBMT"` / `"KHLCNT"` / `"KQLCNT"`.
- Nếu user nêu rõ mã hồ sơ → thêm `entity_code="<mã>"` (mã eGP dạng `IB<digits>-<version>`, vd. `IB2600250575-00`).
- Nếu user nêu loại hồ sơ → thêm `entity_type` **LOWERCASE**: `"tbmt"` / `"khlcnt"` / `"kqlcnt"`.
Viết HOA (`"TBMT"`) sẽ không khớp filter và trả về rỗng.
- Mặc định `limit=5`; nâng lên 10 khi user muốn tổng hợp rộng.
2. **Đọc `limitations` trước**:
- `"collection_missing"` → trả lời: "Hệ thống chưa khởi tạo collection, vui lòng admin cấu hình trước."
@ -28,16 +29,16 @@ Khi user hỏi câu cụ thể đòi nội dung từ hồ sơ — ví dụ:
## Ví dụ
**User:** "Yêu cầu hợp đồng tương tự trong gói KHLCNT-2026-001 là gì?"
**User:** "Yêu cầu hợp đồng tương tự trong gói IB2600250575-00 là gì?"
**Agent:**
```
→ search_procurement_docs(query="yêu cầu hợp đồng tương tự", entity_code="KHLCNT-2026-001")
→ search_procurement_docs(query="yêu cầu hợp đồng tương tự", entity_code="IB2600250575-00")
← {results: [...], limitations: []}
```
**Reply:** "Theo hồ sơ KHLCNT-2026-001, yêu cầu hợp đồng tương tự gồm: [tóm tắt]. Trích nguồn: [KHLCNT-2026-001, Chương III, page 12]."
**Reply:** "Theo hồ sơ IB2600250575-00, yêu cầu hợp đồng tương tự gồm: [tóm tắt]. Trích nguồn: [IB2600250575-00, Chương III, page 12]."
## Anti-patterns