Commit 42963a54 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

refactor: clean up test scripts, dead code, and temporary files

parent 2b624706
---
name: "TDD Auto-Sandbox Loop"
description: "Vòng lặp tự động hóa việc phát triển tính năng bằng cách viết Script thử nghiệm (Test-Driven Sandbox), cho AI tự chạy, tự đọc lỗi, tự sửa code đến khi đầu ra chuẩn 100% trước khi ghép vào ứng dụng chính."
---
# TDD Auto-Sandbox Loop (Vòng lặp Tự Động Thử Nghiệm)
Sử dụng workflow này khi User yêu cầu: **"Xây dựng một tính năng mới", "Kiểm tra và sửa lỗi logic", "Tạo luồng kết nối DB mới (chọc SQL)"**, hoặc User trực tiếp gọi lệnh `/auto-tdd`.
## MỤC TIÊU
- TUYỆT ĐỐI KHÔNG sửa file API lõi (`router.py`, `engine.py`,...) ngay lập tức để tránh làm "chết" server hoặc sập Production.
- Mô phỏng và chạy thử nghiệm mọi logic mới hoàn toàn độc lập trong thư mục Sandbox (`backend/scripts/`).
- Tận dụng khả năng "chữa lành" (self-healing) của AI: Tự chạy script -> Tự đọc lỗi -> Tự fix code -> Lặp lại đến khi kết quả Data (Output) chính xác tuyệt đối.
- Chỉ "Cấy" (Inject) code vào Source chính khi User đã xác nhận hoặc Output Sandbox đã đạt 100% tỉ lệ thành công.
---
## 🛠 CÁC BƯỚC THỰC HIỆN BẮT BUỘC
### Bước 1: Phân tích & Viết Script Rác (Sandbox Setup)
1. Xác định rõ Input (Đầu vào) và Expected Output (Kết quả mong muốn) của User.
2. Tạo 1 file Python độc lập trong thư mục `backend/scripts/` có tên phản ánh tính năng.
*(Ví dụ: `backend/scripts/test_fetch_stock_logic.py`, `backend/scripts/scratch_sql_query.py`)*
3. Setup file này sao cho nó **có thể tự chạy được (`__main__`)**, tự import các common function, DB connections cần thiết từ hệ thống, và trả log kết quả ra cửa sổ Console (sử dụng in mã UTF-8 bằng `sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')`).
### Bước 2: Kích hoạt Vòng Lặp Chữa Lành (Self-Healing Auto-Run)
- **// turbo-all**
1. AI sử dụng công cụ Tự chạy Command (Terminal) để thực thi file python rác vừa tạo.
2. **KẾT QUẢ > LỖI (Exception/Syntax Error):** AI đọc Terminal Error -> Hiểu nguyên nhân -> Tự sửa lại file Test -> Chạy lệnh lại.
3. **KẾT QUẢ > SAI LOGIC (Data rác, không map đúng Rule):** AI phân tích Output thực tế vs Output kì vọng -> Tìm hiểu Rule hệ thống -> Tự sửa lại file Test -> Chạy lệnh lại.
4. Quá trình này **không cần sự can thiệp của User**, AI liên tục chạy lệnh và fix đến khi nào màn hình kết quả Terminal (Data in ra) đúng 100% logic.
### Bước 3: Show kết quả cho User Duyệt
- Sau khi có cái "Mộc xanh" trên Terminal (Output chuẩn).
- AI chụp (hoặc copy paste) cái Output Terminal đó gửi cho User kèm lời báo cáo: *"Script đã chạy thành công, log data chuẩn. Bro check xem ưng ý chưa để tôi cấy vào hệ thống chính?"*
- Nếu User OK -> Lên Bước 4.
- Nếu User muốn điều chỉnh -> Lại vòng về Bước 2.
### Bước 4: Cấy Ghép Vào Hệ Thống (Production Injection)
- Mở file hệ thống lõi (ví dụ: `worker/stylist_engine.py`, `api/router.py`).
- Triển khai chức năng đã TEST THÀNH CÔNG từ file Sandbox vào hàm chuẩn.
- Lưu ý khi cấy phải giữ nguyên toàn bộ chuẩn biến số của hàm hiện tại.
- Xóa các lệnh in rác (`print`) ở Sandbox và thay bằng `logger.info()` chuẩn.
### Bước 5: Cleanup & Commit
- Cleanup code thừa, chuyển file rác trong `scripts/` về trạng thái đọc tắt comment nếu cần thiết (hoặc đổi tên thành `archive_...` nếu Task yêu cầu luỹ kế) để giữ gọn Project backend.
## ⚠️ QUY TẮC SỐNG CÒN CỦA WORKFLOW
- **CHẶN SỬA MÙ:** Cấm sửa trực tiếp hệ thống logic vòng lặp lớn (như thuật toán Fashion Stylist Engine) khi chưa chạy Test Script Sandbox để kiểm chứng 1 input nhỏ lẻ!
- **ENCODING LUÔN LUÔN UTF-8:** Database Canifa chứa rất nhiều Tiếng Việt có dấu, khi in `print()` ra Windows Powershell thường bị lỗi charmap. Phải cài cắm header cho file Sandbox:
```python
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
```
- **TỰ CHỦ ĐỘNG:** Đừng rụt rè gõ ra để hỏi User là lỗi này phải sửa sao. Dùng Tool `run_command` fix liên tục cho đến khi gõ chạy Terminal ngon êm ru nhé!
---
description: Vòng lặp Dọn dẹp Rác Backend - Tự động quét và gom nhóm các file log, scratch script, temp output vào đúng thư mục chuẩn.
---
# Backend Junk Cleanup Workflow (Dọn Rác)
**CONCEPT:** Workflow này dùng để ra lệnh cho AI (như tao) tự động dọn dẹp các thư mục làm việc, đặc biệt là thư mục `backend`, tránh việc xả rác file tạm, file log và script nháp lung tung.
## CÁC BƯỚC THỰC HIỆN
### 🧹 Bước 1: Quét và Định vị Rác
Sử dụng tool `list_dir` để soi thư mục Root của `backend` (hoặc thư mục được chỉ định). Hãy tìm các loại file sau:
1. **Scratch Scripts:** Các file Python bắt đầu bằng `scratch_`, `test_`, `eval_`, `dump_`... (chỉ dùng để test nháp 1 lần).
2. **Output/Logs:** Các file `*.json`, `*.txt` do AI/Agent nhả ra trong lúc test (vd: `response.json`, `audit_out.txt`, `log.txt`).
3. **Runners:** Các file script điều khiển như `run.txt`, `*.sh`, `*.ps1`.
### 📂 Bước 2: Tạo cấu trúc thư mục Scripts chuẩn
Nếu chưa có, hãy tạo cấu trúc thư mục sau bên trong `backend/`:
- `scripts/`
- `scripts/scratch/` (để chứa các đoạn code test/nháp)
- `scripts/outputs/` (để chứa các file log, json output rác)
### 🚚 Bước 3: Di chuyển file
Sử dụng tool `run_command` (với lệnh PowerShell) để dọn dẹp:
1. **Move** tất cả file thuộc nhóm **Scratch Scripts** vào `scripts/scratch/`.
2. **Move** tất cả file thuộc nhóm **Output/Logs** vào `scripts/outputs/`.
3. **Move** tất cả **Runners** vào `scripts/` (trừ khi nó cần nằm ở ngoài phục vụ Docker thì giữ lại 1 bản).
> [!WARNING] Cẩn thận rác thật với hàng xịn
> Tuyệt đối không chạm vào các file cấu hình quan trọng như `.env`, `requirements.txt`, `server.py`, `worker.py`, hay `config.py`.
### ✅ Bước 4: Kiểm tra lại (Verification)
Chạy lại lệnh `ls` hoặc `list_dir` trong thư mục gốc.
Nếu chỉ còn lại các file chính thức yếu nhân của server, hãy thông báo: "🎉 Chúc mừng bro, nhà đã sạch bóng!"
---
name: CuCu Note Full Feature E2E & DB Migration Loop
description: An autonomous workflow for the AI Agent to test all features of CuCu Note using the Browser tool, apply surgical code fixes, and run SQLite migrations where necessary.
---
# Mục tiêu chung
Thực hiện chạy kiểm thử "End-to-End" toàn bộ các tính năng của `CuCu Note` bằng cách thao tác trực tiếp trên giao diện Browser (sử dụng Browser Tool của Agent). Nếu phát hiện lỗi, Agent sẽ tự động dò lại code, sửa lỗi (Surgical Changes) hoặc bổ sung Database Migration, sau đó test lại cho tới khi chạy thành công 100%.
# Nguyên tắc hành động của Agent
Agent PHẢI CHẤP HÀNH 4 nguyên tắc sau trong mỗi vòng lặp thực thi:
1. **Khởi động và Test bằng Browser Tool:**
- Mặc định Backend (cổng 5000) và Frontend (Vite) đã chạy (Nếu chưa chạy, gọi lệnh chạy ngầm).
- Truy cập vào ứng dụng bằng `browser_subagent` và thao tác click/gõ phím như một User thật. Không chạy bằng script npm test ảo.
2. **Phát hiện lỗi và "Sửa theo kiểu Phẫu thuật" (Surgical Changes - CLAUDE.md):**
- Khi phát hiện luồng bị nghẽn mặt UI hoặc Backend báo lỗi 500.
- CHỈ SỬA TRÚNG CÁI CHỖ HỎNG.
- **CẤM** đụng chạm, refactor, hay "tối ưu" những đoạn code đang chạy ngon lành. Không động vào code cũ nếu nó không trực tiếp gây ra lỗi hiện tại.
3. **Cơ chế Khắc phục Database (Migration):**
- Rất nhiều tính năng hiện tại bị lỗi là do Database SQLite đang **thiếu bảng** (ví dụ thiếu `cuccu_inbox`, `cuccu_attachments`, `cuccu_teams_...`).
- Nếu lỗi trả về từ API/Backend là do thiếu DB/Column, Agent **PHẢI** tạo các Script Migration (code Python) ném vào thư mục `miniapp/cuccu_note/backend/db/migrate`.
- Chạy script migrate này trước khi test lại.
4. **Vòng lặp Vô hạn (Loop until Done):**
- Quy trình phải là: **[Test bằng Browser] -> [Thấy Lỗi] -> [Tìm nguyên nhân: API/DB/UI] -> [Sửa Code / Viết Migrate] -> [Test Lại bằng Browser]**.
- Nếu Xong -> Đánh dấu X vào ô Checkbox (Tích `[x]`) ở danh sách bên dưới và tiếp tục tính năng mới.
---
# Danh sách kiểm thử (Agent tự động tích [x] sau khi pass)
Dưới đây là các User Flow cần được mô phỏng test bằng Browser Tool:
## Core Database Setup
- [ ] Rà soát source file `sqlite_client.py` để lấy danh sách toàn bộ constants Tables (VD: TABLE_TEAMS, TABLE_INBOX,...). So khớp với `memos.db` và chạy script tạo Migration Tool cho các bảng còn thiếu.
- [ ] Database đã được migrate đầy đủ các bảng cần thiết.
## 1. Xác thực & Phân quyền (Auth)
- [ ] Đăng ký tài khoản User mới qua giao diện.
- [ ] Login (Đăng nhập) thành công và điều hướng vào trang Dashboard/Home.
- [ ] Truy xuất thông tin Profile cá nhân.
## 2. Quản lý Ghi chú (Memo Management)
- [ ] Viết một Memo mới, nhấn Submit và đảm bảo Memo hiện lên Timeline.
- [ ] Chỉnh sửa (Edit) nội dung Memo vừa tạo.
- [ ] Thay đổi trạng thái Visibility (Private / Public / Workspace).
- [ ] Pin (Ghim) một Memo và un-pin nó.
- [ ] Đính kèm file (Upload ảnh/tài liệu) vào Memo.
## 3. Quản lý Cộng đồng / Đội nhóm (Teams)
- [ ] Chuyển hướng sang page Teams / Workspace.
- [ ] Tạo một Team mới với Tên và Mô tả.
- [ ] Viết một Memo mới ở phạm vi không gian Team (Workspace Memo).
## 4. Tương tác Mạng Xã Hội (Reactions & Comments)
- [ ] Thả cảm xúc (Reaction - Emoji) vào một bài Memo có sẵn.
- [ ] Viết một bình luận (Comment) phản hồi vào Memo của người khác.
## 5. Trung tâm Thông báo (Inbox System)
- [ ] Agent tự kích hoạt một thông báo đẩy về hệ thống.
- [ ] Truy cập mục The Inbox, đảm bảo thông báo hiển thị.
- [ ] Click Mark as Read (Đánh dấu đã đọc).
## 6. Tính năng Trí Tuệ Nhân Tạo (Chatbot)
- [ ] Mở mục Chatbot (Panel hoặc Page riêng).
- [ ] Chat với AI một câu chào hỏi đơn giản, đợi AI Loading và hiển thị luồng stream.
- [ ] Viết một truy vấn RAG kêu AI tổng hợp nội dung các Memo bạn vừa tự tạo ở Bước 2. Đảm bảo trả kết quả chuẩn.
---
*(Lặp lại cho đến khi toàn bộ tích `[x]` được hoàn thiện! Cuối cùng in ra thông báo hoàn thành.)*
# D:\cnf\chatbot-canifa-feedback\.agent\workflows\fashion-rules-verification.md
# Fashion Rules Verification Loop
## Mục đích
Tự động hóa việc kiểm thử hàng loạt (batch testing) các luật phối đồ (fashion rules) trên danh mục sản phẩm. Đảm bảo rằng Engine recommend các sản phẩm chính xác theo Giới tính (Gender), Dịp mặc (Occasion) và Luật kết hợp (Pairing Rules) đã được cấu hình trong database.
## Quy trình (Workflow)
**Mục tiêu:** Kiểm tra N sản phẩm (ví dụ: 100) để xác minh:
1. Sản phẩm có nhận diện đúng Giới tính và Phân loại (Product Line) không.
2. Kết quả recommend có chứa các Dịp mặc (Occasion) được phép theo luật hay không.
3. Các sản phẩm gợi ý (Target Items) có thuộc đúng Product Line quy định trong `chatbot_fashion_rules` hay không.
### Bước 1: Viết script kiểm thử tự động
Bạn có thể tự động sinh ra một file script bằng Python để fetch catalog trực tiếp thông qua `StylistEngine` và test:
- **Input:** Lấy ngẫu nhiên X sản phẩm từ catalog, đảm bảo có đủ giới tính (Nữ, Nam, Unisex, Bé Gái, Bé Trai).
- **Process:** Khởi tạo `StylistEngine`, gọi `compute_dynamic_rule_matches(code)`.
- **Verify:**
- Truy vấn luật trực tiếp từ hàm `_fetch_rules_with_reason(anchor_cat, gender)`.
- Đối chiếu kết quả recommend từ engine so với tập luật này.
- Ghi nhận: Dịp nào thiếu SP recommend (do catalog không có màu phù hợp), item nào bị recommend sai category (Vi phạm luật).
- **Output:** In ra báo cáo tóm tắt (Tổng số, Số SP pass, Số lượng lỗi if any).
### Bước 2: Chạy script và nhận report
// turbo
```bash
cd backend
python test_fashion_rules_batch.py --limit 100
```
### Bước 3: Đánh giá và Sửa lỗi
- Nếu phát hiện Occasion rỗng, nguyên nhân có thể do catalog thiếu sản phẩm có màu sắc phù hợp quy tắc phối, hoặc luật quá khắt khe.
- Nếu phát hiện Item recommend sai `product_line`, cần check lại hàm query rule của Engine hoặc việc fallback.
- Chỉnh sửa logic trong `stylist_engine.py` hoặc thêm/sửa rules trong bảng `chatbot_fashion_rules`. Lặp lại Bước 2.
This diff is collapsed.
---
description: Lead Bot QA Loop — Tự động test bot recommend sản phẩm có đúng context không, xác minh với DB, AI judge chấm điểm, đề xuất sửa prompt, lặp lại.
---
# 🔁 Lead Bot QA — Auto-Eval & Self-Improve Loop
**CONCEPT:** Chạy bộ câu hỏi ngữ cảnh (seasonal, situational) → Bot trả sản phẩm → Xác minh DB → AI Judge chấm → Phát hiện lỗi prompt → Đề xuất fix → Loop lại.
Script: `backend/scripts/lead_test/run_eval.py`
Output: `backend/scripts/lead_test/results/eval_YYYY-MM-DD_RX.txt`
---
## 🧠 Ý tưởng cốt lõi (Từ Prompt)
> Bot nhận câu hỏi **gián tiếp / theo ngữ cảnh** → phải **hiểu hoàn cảnh** để recommend đúng loại SP.
> Hiện tại prompt/search tool thiếu **context mapping**: "trời mưa" ≠ keyword "áo mưa" → phải map sang "áo khoác chống thấm / áo gió".
| Câu hỏi người dùng | Bot nên hiểu | Loại SP đúng |
|---|---|---|
| "áo mặc trời mưa" | chống thấm, nhẹ | Áo khoác gió, áo khoác chống thấm |
| "áo thu đông nam" | giữ ấm, thu đông | Áo nỉ, áo khoác len, áo hoodie |
| "váy dự tiệc cuối năm" | lịch sự, sang | Váy liền, đầm dự tiệc |
| "đồ đi làm công sở nữ" | thanh lịch, công sở | Sơmi, áo kiểu, quần âu |
| "áo mặc nhà thoải mái" | thường ngày | Áo phông, quần jogger |
| "đồ đi chơi cuối tuần" | casual, trẻ trung | Áo polo, áo thun, quần jean |
| "đồ đi biển" | thoáng mát, mùa hè | Áo thun cotton, short |
| "váy mặc Tết" | truyền thống, tươi | Áo dài, váy dáng A |
---
## THE LOOP
### ⚡ Phase 1: Chuẩn bị Test Set
1. Đọc `test_cases.json` — 20 câu hỏi chia 4 nhóm:
- `seasonal` (mùa, thời tiết)
- `occasion` (dịp: Tết, tiệc, đi làm, đi biển)
- `demographic` (nam/nữ/trẻ em + hoàn cảnh)
- `vague` (câu mơ hồ nhất: "có gì mặc đẹp không")
2. Xác nhận server đang chạy tại `http://localhost:5000`
3. Tạo folder `results/` nếu chưa có
*→ Phase 2*
---
### 🤖 Phase 2: Gọi Bot (Bot Call)
Với **mỗi câu hỏi** trong test set:
```
POST /api/agent/chat-lead-flow
body: { user_query: "...", conversation_id: uuid, device_id: "eval-bot" }
→ Nhận: {
ai_response: "...",
products: [{ sku, name, price, product_line, ... }],
lead_stage: { stage, tone_directive }
}
```
- Nếu `products = []` → đánh dấu `NO_PRODUCTS`
- Nếu API lỗi → đánh dấu `API_ERROR`, tiếp tục câu tiếp theo
*→ Phase 3*
---
### 🗄️ Phase 3: Xác minh DB (DB Verify)
Với **mỗi SKU** trong `products[]`:
```
GET /api/product/lookup?skus={sku}
→ Check: product tồn tại trong DB? (200 vs 404)
→ Lấy: product_line_vn, sale_price, size_scale
```
**Đánh giá tương quan:**
- Câu hỏi: "áo mặc trời mưa" → `product_line_vn` trả về là gì?
- `Áo khoác` → ✅ MATCH
- `Áo thun` → ❌ MISMATCH
- Không có SP → ⚠️ NO_PRODUCTS
*→ Phase 4*
---
### 🧑‍⚖️ Phase 4: AI Judge
Gọi LLM (Gemini Flash / GPT-4o-mini) với prompt:
```
Bạn là chuyên gia thời trang đánh giá chatbot bán hàng.
Câu hỏi user: "{question}"
Bot trả lời: "{ai_response}"
Sản phẩm gợi ý: {product_list}
[Loại SP trong DB: {product_lines}]
Đánh giá:
1. Sản phẩm có phù hợp với hoàn cảnh/mùa/dịp trong câu hỏi không? (0-10)
2. Câu trả lời AI có tự nhiên, đúng context không? (0-10)
3. Có thiếu loại SP nào quan trọng không?
4. Pattern lỗi nếu có (bot hay nhầm X → Y trong hoàn cảnh Z)
Trả về JSON: {
"context_score": 0-10,
"response_score": 0-10,
"verdict": "PASS|PARTIAL|FAIL",
"reason": "...",
"pattern_error": "..." | null,
"prompt_fix_hint": "..." | null
}
```
*→ Phase 5*
---
### 📝 Phase 5: Ghi Kết Quả
Ghi vào `results/eval_YYYY-MM-DD_R{round}.txt`:
```
═══════════════════════════════════════
LEAD BOT QA — Round {N} — {datetime}
═══════════════════════════════════════
📊 TỔNG KẾT:
Tổng câu: 20 | PASS: X | PARTIAL: Y | FAIL: Z
Avg Score: {score}/10
Câu không có sản phẩm: {n}
────────────────────────────────────────
📋 CHI TIẾT:
[1] "áo mặc trời mưa"
Verdict : ❌ FAIL (3/10)
Bot trả : Áo thun cotton (product_line: Áo phông)
Lý do : Áo thun không phù hợp điều kiện mưa, cần áo khoác chống thấm
Fix hint: "Thêm context: trời mưa → áo khoác / áo gió vào synonym mapping"
[2] "áo thu đông nam"
Verdict : ✅ PASS (8/10)
Bot trả : Áo khoác nỉ form rộng (product_line: Áo khoác)
Lý do : Đúng loại, đúng mùa
...
────────────────────────────────────────
🔍 PATTERN LỖI PHÁT HIỆN:
1. Bot thiếu mapping: mô tả thời tiết → loại SP tương ứng
2. Bot không ưu tiên mùa vụ khi filter sản phẩm
3. Câu hỏi dịch lễ (Tết, tiệc) → bot hay recommend casual thay formal
💡 ĐỀ XUẤT SỬA PROMPT:
→ lead_search_tool.py line ~45: Thêm context_to_product_line map
→ Stylist prompt: "Ưu tiên áo khoác / áo gió khi user đề cập thời tiết mưa hoặc lạnh"
→ Synonym: "thu đông" → product_line IN ('Áo khoác', 'Áo nỉ', 'Áo len')
═══════════════════════════════════════
```
*→ Phase 6*
---
### 🔧 Phase 6: Xem Kết Quả & Quyết Định
Đọc file kết quả:
- **Avg Score ≥ 8** → ✅ Bot đang tốt, stop loop hoặc test câu khó hơn
- **Avg Score 5-7** → ⚠️ PARTIAL — apply 1-2 fix hint đơn giản → quay Phase 1 (Round N+1)
- **Avg Score < 5** → ❌ FAIL — report đầy đủ pattern lỗi, cần sửa prompt nghiêm túc
**Áp dụng fix (nếu có):**
1. Sửa `lead_search_tool.py`: thêm `context_mapping` dict
2. Sửa `stylist_prompt.py`: thêm seasonal/occasion rules
3. Chạy lại **Round N+1** với cùng test set
> **LOOP TERMINATION:** Dừng khi Score ≥ 8 trên 3 round liên tiếp HOẶC user manually stop.
---
## 📁 File Structure
```
backend/scripts/lead_test/
├── run_eval.py ← Entry point: python run_eval.py [--round N] [--fix]
├── test_cases.json ← 20 câu hỏi + expected_product_types
├── judge.py ← AI Judge logic (Gemini/GPT call)
├── db_verify.py ← Gọi /api/product/lookup check SKU
└── results/
├── eval_2026-04-14_R1.txt
├── eval_2026-04-14_R2.txt
└── summary.json ← Score trend qua các round
```
---
## 🚀 Cách chạy
```bash
# Round 1 — chạy test cơ bản
python backend/scripts/lead_test/run_eval.py
# Round N+1 — sau khi đã sửa prompt
python backend/scripts/lead_test/run_eval.py --round 2
# Xem summary score trend
python backend/scripts/lead_test/run_eval.py --summary
```
---
name: Local Database Proxy API Tester
description: Vòng lặp test toàn bộ các API endpoint có kết nối Database (Postgres/StarRocks) qua SQLite Mock Proxy để phát hiện lỗi Dialect và sửa Regex.
---
# Local DB Proxy API Test Workflow
Workflow này dùng để tự động dò quét, curl test và vá lỗi cho toàn bộ các API có giao tiếp với Database thông qua lớp mạo danh `sqlite_mock.py`.
## Bước 1: Liệt kê các API giao tiếp với Database
- Quét nhanh thư mục `backend/api` để tìm các route gọi `execute_query` (StarRocks) hoặc `db_pool.get_conn()` (Postgres).
- Lập danh sách các API quan trọng cần test, điển hình:
- `/api/product-desc/list`
- `/api/product-desc/overview`
- `/api/products/filters`
- `/api/products/list`
- Báo cáo/Fashion rules routes.
## Bước 2: Test từng Endpoint (với Uvicorn đang chạy)
Dùng `run_command` để gọi PowerShell `Invoke-RestMethod` (hoặc curl) vào từng endpoint (http://127.0.0.1:5000).
```markdown
// turbo
Invoke-RestMethod -Uri "http://127.0.0.1:5000/api/product-desc/list?limit=10" -Method Get
```
## Bước 3: Đọc Log và Sửa Lỗi (Diagnose & Patch)
Nếu endpoint trả về lỗi `500 Internal Server Error`, Agent bắt buộc phải:
1. Đọc nội dung log lỗi trên Terminal Uvicorn đang rớt.
2. Kiểm tra xem lỗi xuất phát từ SQLite không hiểu cú pháp (ví dụ: thiếu Regex `?` hoặc sai tên bảng).
3. Quay lại file `backend/common/sqlite_mock.py` để bổ sung thuật toán `re.sub()` hoặc logic thay thế trong `translate_query()`.
4. Sau khi sửa mô-đun, chờ Uvicorn nháy reload và vòng lại Bước 2 test lại chính Endpoint đó.
## Bước 4: Validation (Xanh lè toàn bộ)
Khi toàn bộ API endpoint liệt kê ở Bước 1 đều trả về HTTP 200 (có chèn Data) với SQLite cục bộ, đánh dấu quá trình test kết thúc thành công.
# Plan Manager Workflow
<!-- Trigger: /plan -->
Quản lý kế hoạch dự án Canifa qua thư mục `backend/plan/`.
Mỗi task = 1 file markdown. Di chuyển qua 3 giai đoạn: ideas → doing → done.
---
## Cấu trúc thư mục
```
backend/plan/
├── ideas/ ← Ý tưởng chưa quyết làm
├── doing/ ← Đang làm, có checklist cụ thể
└── done/ ← Xong, tên file có date prefix
```
---
## Các lệnh bro có thể gõ
| Lệnh | Ý nghĩa |
|------|---------|
| `/plan status` | Xem tổng quan tất cả tasks |
| `/plan add idea [tên] [mô tả]` | Thêm ý tưởng mới vào ideas/ |
| `/plan start [tên]` | Chuyển idea → doing, thêm checklist |
| `/plan done [tên]` | Chuyển doing → done, thêm ngày hoàn thành |
| `/plan show [tên]` | Xem chi tiết 1 task |
| `/plan edit [tên]` | Mở file task để chỉnh sửa |
---
## Khi user gõ `/plan status`
1. Dùng `list_dir` để đọc `backend/plan/ideas/`, `backend/plan/doing/`, `backend/plan/done/`
2. Với mỗi file trong `doing/`, đọc metadata (Priority, Started)
3. Hiển thị bảng tổng quan như sau:
```
💡 IDEAS ([N] ý tưởng)
• tên-task-1 — mô tả ngắn
• tên-task-2 — mô tả ngắn
🔨 DOING ([N] đang làm)
• tên-task — [HIGH] — X ngày
• tên-task — [MED] — Y ngày
✅ DONE ([N] đã xong)
• 2026-04-16 — tên-task
• 2026-04-15 — tên-task
```
---
## Khi user gõ `/plan add idea [tên] [mô tả]`
1. Tạo file `backend/plan/ideas/[tên].md` với nội dung:
```markdown
# 💡 [Tên task]
**Status:** idea
**Added:** [ngày hôm nay]
**Priority:**
## Mô tả
[mô tả]
## Tại sao cần làm?
## Notes
```
2. Báo confirm: `✅ Đã thêm idea: [tên]`
---
## Khi user gõ `/plan start [tên]`
1. Đọc file từ `ideas/[tên].md`
2. Thêm/cập nhật các fields:
- `Status: doing`
- `Started: [ngày hôm nay]`
- Thêm section `## Checklist` nếu chưa có
3. Move file → `doing/[tên].md`
4. Xóa file cũ trong `ideas/`
5. Báo confirm
---
## Khi user gõ `/plan done [tên]`
1. Đọc file từ `doing/[tên].md`
2. Cập nhật:
- `Status: done`
- `Completed: [ngày hôm nay]`
3. Move file → `done/[YYYY-MM-DD]-[tên].md`
4. Xóa file cũ trong `doing/`
5. Báo confirm: `✅ Xong! [tên] → done/`
---
## Template file chuẩn (doing)
```markdown
# 🔨 [Tên task]
**Status:** doing
**Priority:** high | medium | low
**Started:** YYYY-MM-DD
**Owner:**
## Mục tiêu
[Mô tả ngắn gọn cần đạt được gì]
## Checklist
- [ ] Bước 1
- [ ] Bước 2
- [ ] Test
- [ ] Deploy / review
## Context & Notes
[Link, code path, DB table liên quan...]
## Blockers
```
---
## Lưu ý thực thi
- Tất cả files nằm tại: `D:\cnf\chatbot-canifa-feedback\backend\plan\`
- Dùng `write_to_file` để tạo/cập nhật file
- Dùng `list_dir` để đọc danh sách
- Dùng `view_file` để đọc nội dung
- Khi move: tạo file mới + xóa file cũ (dùng PowerShell `Remove-Item`)
- Date format: `YYYY-MM-DD` (VD: `2026-04-16`)
---
description: Ralph Wiggum "Infinite" Loop - A continuous cycle for perfection.
---
# Ralph Wiggum "Infinite" Loop
**CONCEPT:** This is an **INFINITE LOOP**. You do not exit this loop until the task is perfectly verified or the user forcibly stops you.
## THE LOOP
### 🔄 Phase 1: The "Dumb" Questions (Preparation)
**ENTRY POINT.** Always start here.
1. **"What exactly am I trying to do?"** (Explain it to a 5-year-old).
2. **"Do I have all the files and info I need?"** (If not, STOP and read them first).
3. **"What is the stupidest mistake I could make here?"** (Example: wiping the database).
4. **"Is there a simpler way?"** (Don't over-engineer).
*Decision:* If you are confused -> Stay in Phase 1. If clear -> Go to Phase 2.
### 🔄 Phase 2: Micro-Planning
Plan for **ONE** step only.
1. Define the **Single Next Action** (e.g., "Create file X").
2. Define **Success Criteria** for this specific action.
*Action:* Go to Phase 3.
### 🔄 Phase 3: Execution
1. **EXECUTE** the single tool call.
2. **STOP.** Do not do anything else.
*Action:* Go to Phase 4.
### 🔄 Phase 4: Verification
1. **VERIFY** the immediate result (Run code/Read file).
2. **ASK:** "Did it work 100%?"
- **YES:** Loop back to **Phase 1** (To prepare for the *next* micro-step).
- **NO:** Loop back to **Phase 1** (To re-evaluate why it failed).
> **CRITICAL RULE:** NEVER BREAK THE LOOP. Even if you think you are done, loop back to Phase 1 one last time to ask: "Is there absolutely nothing left to do?" Only then can you stop.
import codecs
import time
filepath = r'd:\cnf\chatbot-canifa-feedback\backend\static\fashion-matches\live-simulator.html'
with open(filepath, 'r', encoding='utf-8') as f:
text = f.read()
count1 = text.count(r'\`')
count2 = text.count(r'\${')
print("Found escaped backticks:", count1)
print("Found escaped dollars:", count2)
text = text.replace(r'\`', '`').replace(r'\${', '${')
with open(filepath, 'w', encoding='utf-8') as f:
f.write(text)
print("Fixed!")
# Plan Manager — Canifa Feedback Backend
## Cấu trúc
```
ideas/ ← Ý tưởng, chưa quyết làm
doing/ ← Đang làm
done/ ← Xong rồi
```
## Cách dùng
Nói với AI: `/plan status`, `/plan add idea`, `/plan start`, `/plan done`
Xem chi tiết: `.agent/workflows/plan-manager.md`
# Tự Động Test và Train Prompt Lead Bot & Hiển thị Hết Hàng (Vòng lặp Vô Tận)
## 🧠 Ngữ cảnh & Triết lý thiết kế (Cho AI đọc hiểu)
**Tại sao phải làm thế này?**
Thay vì để con người phải ngồi kiểm tra bằng tay từng log lỗi của Lead Bot và lọ mọ copy-paste để sửa System Prompt, ta sẽ tận dụng triết để **Cơ chế Sub-Agent của Claude** (Dựa theo chuẩn Evaluator-Optimizer pattern của Anthropic).
Hệ thống sẽ chạy một **vòng lặp vô tận (infinite loop)**: Test -> Chấm Điểm -> Đề xuất sửa đổi (Prompt/Thuật toán) -> Áp dụng -> Test lại. Nó chỉ dừng lại khi điểm Pass Rate đạt 100%.
**Sự khác biệt của Sub-Agent Pattern (Claude):**
Chúng ta không dùng 1 Prompt nhồi nhét. Chúng ta tách việc ra làm 2 Sub-Agents có Prompt chuyên biệt.
1. **Sub-Agent 1 (The Evaluator - Người chấm thi):** Làm nhiệm vụ cầm bộ `test_cases.json`, đối chiếu với kết quả Bot trả về, chấm điểm cực kỳ khắt khe. Trả về báo cáo lỗi chi tiết.
2. **Sub-Agent 2 (The Optimizer - Coder/Prompt Engineer):** Chỉ đọc báo cáo lỗi của Evaluator, lấy file nguồn (như `prompts.py` hoặc `lead_search_tool.py`) ra và tự đưa ra quyết định: *Sửa System Prompt hay Sửa thuật toán lấy Data?*. Sau đó tự động overwrite file.
---
## 📋 THIẾT KẾ CHI TIẾT CÁC SUB-AGENTS (CÂN LÀM)
### 1. The Evaluator Sub-Agent (Role: Khám bệnh)
**Nhiệm vụ:** Chạy 20 câu test.
**System Prompt của Evaluator:**
```text
Bạn là một AI Evaluator tối cao chuyên đánh giá chất lượng của một Fashion Retail Chatbot (Lead Bot).
Nhiệm vụ của bạn là lấy đầu vào từ User, so sánh với đầu ra của Bot và đối chiếu với Thực tế Data (Stock, Product Line).
Tiêu chí chấm:
1. Context Match (0-10): Bot có hiểu đúng bối cảnh (Ví dụ: "Trời mưa" phải gợi ý áo gió/chống nước, "Đám cưới" phải gợi ý đồ formal/lịch sự).
2. Data Accuracy (0-10): Bot có gợi ý sản phẩm hết hàng mà KHÔNG báo trước cho khách không?
3. Tool Usage (Pass/Fail): Bot có điền đúng các biến số vào Search Tool không?
Nếu phát hiện lỗi, hãy chỉ định rõ:
- Lỗi do System Prompt (Thiếu instruction ngữ cảnh).
- Lỗi do Data/Algorithm (Search tool thiếu cột stock, lấy nhầm category).
Trả về rập khuôn định dạng JSON chứa Feedback và Danh sách các case bị Fail.
```
### 2. The Optimizer Sub-Agent (Role: Bốc thuốc & Phẫu thuật)
**Nhiệm vụ:** Nhận JSON Feedback bên trên. Đọc code hiện hành. Ghi đè file.
**System Prompt của Optimizer:**
```text
Bạn là AI Optimizer. Bạn nhận được một bản án (Feedback) từ Evaluator chỉ ra rằng Lead Bot hiện tại đang hoạt động sai ở một số test case.
Trong tay bạn là mã nguồn hiện tại của System Prompt (`prompts.py`) và Search Algorithm (`lead_search_tool.py`).
Nhiệm vụ:
- Nếu lỗi là do Bot tư duy sai ngữ cảnh (vd: không biết trời mưa phải mặc gì): Hãy Viết lại (Rewrite) file `prompts.py`. Cập nhật thêm instruction, rule để che chắn lỗi này.
- Nếu lỗi là do Data thiếu (vd: Gợi ý sản phẩm hết hàng mà không biết): Hãy đề xuất việc thay đổi file thuật toán SQL `lead_search_tool.py` (vd: Join thêm `stock_quantity` và tự hardcode gắn `[TẠM HẾT HÀNG]` vào tool message) để Bot biết.
BẮT BUỘC trả về nội dung code hoàn chỉnh để tôi tự động ghi đè file. Không được làm hỏng các logic đang hoạt động tốt.
```
---
## 🚀 KẾ HOẠCH HÀNH ĐỘNG (ACTION ITEMS)
- [ ] Nghiên cứu và Cập nhật SQLite Mock (`canifa_ai_dump.sqlite`):
- [Only SQLite] Kiểm tra xem cần chèn cột `stock_quantity` vào SQLite (bảng mock của Postgres / StarRocks) bằng lệnh SQL.
- Cập nhật một số SKU ngẫu nhiên thành `0` để có sản phẩm hiển thị trạng thái "Hết hàng". Tuyệt đối chỉ thao tác với DB trong `backend/database`.
- [ ] Chỉnh sửa Thuật Toán Vòng Lặp (`lead_search_tool.py`):
- Nếu `stock_quantity = 0`, gắn cứng nội dung `(Lưu ý: Mẫu này hiện TẠM HẾT HÀNG)` vào ngay lập tức trong dữ liệu list string trả về cho Prompt/AI.
- [ ] Xây dựng Loop Chạy Vô Tận (Infinite Loop Script):
- Khởi tạo script `auto_train_lead_prompt.py`. Code thuật toán: `while true: run Evaluator -> if pass 100% break -> else run Optimizer -> overwrite files -> reload server -> sleep(3) -> continue`.
- [ ] Tích hợp Entrypoint Script:
- Cập nhật lại `D:\cnf\chatbot-canifa-feedback\backend\plan\run_train_lead.ps1` có hỗ trợ logging vòng lặp rõ ràng.
# AI Judge Prompt - Fashion Matches Evaluator
Mày là một Chuyên gia Thời trang và AI Giám khảo (AI Judge) cấp cao.
Nhiệm vụ của mày là kiểm tra kết quả trả về từ thuật toán "Fashion Matches" (Gợi ý đồ bộ) của ứng dụng Canifa.
Luật kiểm tra (Bắt lỗi):
1. **Lỗi mix rác (Demographic Mismatch):** Nếu sản phẩm gốc là đồ Người lớn (ví dụ áo phông Nam, váy Nữ), tuyệt đối KHÔNG ĐƯỢC PHÉP gợi ý đi kèm các món đồ của trẻ em (các món có chữ "bé", "kid" trong tên sản phẩm hoặc metadata tuổi/giới tính).
2. **Lỗi phớt lờ Dịp Mặc (Occasion Ignore):** Đồ mặc nhà (đồ ngủ) không được phối cho dịp "Đi chơi" hay "Đi làm".
Đầu vào (Văn bản JSON hoặc String):
{Kết quả xuất ra từ hệ thống phối đồ cho một số sản phẩm test tiêu biểu}
Nhiệm vụ đầu ra:
- Cẩn thận soi từng dòng kết quả phối. Nếu mày phát hiện bất kỳ món đồ nào dính lỗi 1 hoặc 2, hãy TỪ CHỐI (FAIL) và liệt kê chính xác Tên sản phẩm, Mã sản phẩm và Lý do lỗi để Backend Developer sửa thuật toán.
- Nếu mày đọc hết và thấy KẾT QUẢ ĐÚNG 100% SẠCH SẼ, không dính đồ trẻ em vào người lớn, dịp mặc xếp đúng, MÀY PHẢI IN RA MỘT DÒNG DUY NHẤT LÀ:
`PASS_EVAL`
# Ralph Loop: Fix AI Stylist (Demographic Mismatch & Occasion Ignore)
## Mục Tiêu (Goal)
Khắc phục 2 lỗi lõi trong thuật toán phối đồ (worker/stylist_engine.py):
1. **Lỗi mix rác (Demographic Mismatch):** Đồ người lớn bị gợi ý mix chung với đồ trẻ con do query SQL thiếu lọc và do hàm check `_pass_hard_filter` bị lọt khe với các sản phẩm Unisex thiếu `age_group`.
2. **Lỗi phớt lờ Dịp Mặc (Occasion Ignored):** Gợi ý đồ không quan tâm áo/quần đó dành cho "mặc nhà", "đi làm" hay "đi chơi".
## Ranh Giới (Boundaries)
- **FILE ĐƯỢC PHÉP SỬA:** `backend/worker/stylist_engine.py` (Và các query sinh ra từ file này).
- **FILE CẤM ĐỤNG VÀO:** `eval_stylist.py`, `run_ralph_fashion.sh`.
- **YÊU CẦU ĐÔNG MÁC:** Mọi thay đổi đều phải được xác nhận bằng cách chạy `python backend/eval_stylist.py` sao cho trả về PASS (exit=0).
## Các Hàm Chức Năng Cần Khoan Đục Nhất Định:
1. `_pass_hard_filter()`: Phải vá chặt. Nếu nguồn là Người Cụ Thể (Nam/Nữ) -> Phối phải chắc chắn KHÔNG phải đồ có hint trẻ con. Hãy check sâu theo cây category nếu cần.
2. `compute_super_classifications_sql()`: Câu `SELECT` phải bọc thêm query chặn `product_name NOT LIKE '%bé%'` nếu `gender` gốc không phải dành cho trẻ em.
3. `_score()``_occasion_score()`: Bỏ comment hàm `_occasion_score()`. Bắt buộc thuật toán phải móc vào `dip_mac` NLP tag của sản phẩm để cộng/điểm.
## Kịch Bản Thoát:
Sau khi `python eval_stylist.py` PASS, hãy tạo file `DONE.flag` tại thư mục này để kết thúc vòng lặp.
## [C?P NH?T T? H? TH?NG] - D? Li?u Chu?n Ha
- build xong local sqlite database v?i T?T C? d? tu?i v occasions (magento_ref_code, product_name, chatbot_fashion_rules d du?c b?c vo).
- Agent c th? tr?c ti?p test k?t h?p thng qua dump_matches.py ho?c eval_stylist.py. DB m?i dang dng canifa_local.sqlite.
# 🔨 Fashion AI Dashboard — 2-panel UI + Rules + Score Tester
**Status:** doing
**Priority:** high
**Started:** 2026-04-15
**Owner:** team
## Mục tiêu
Dashboard quản lý AI phối đồ cho admin: xem/edit matches, chỉnh rules, test score.
## Checklist
- [x] Thiết kế 2-panel layout (list trái + detail phải)
- [x] API list sản phẩm với `has_ai_matches`
- [x] Tab "Phối đồ AI" — xem matches theo dịp
- [x] Tab "Mô tả SP" — xem description_data
- [x] Tab "Test Score" — nhập mã SP đích, xem điểm breakdown
- [x] Modal Rules — Weights / Color / Style / Roles / Occasion
- [x] API `GET /rules/config``PUT /rules/config`
- [x] API `POST /score-test` với breakdown chi tiết
- [x] Engine `_score_breakdown()``_build_reason()`
- [x] Batch AI với progress bar polling
- [ ] Validate fashion_rules.json schema trước khi lưu
- [ ] Test batch full catalog (timeout?)
- [ ] Nút Reset rules về mặc định
## Context
- Frontend: `backend/static/fashion-matches/`
- Backend: `backend/api/fashion_matches_route.py`
- Engine: `backend/worker/stylist_engine.py`
- Rules: `backend/worker/fashion_rules.json`
## Blockers
# 🔨 Lead Agent — Enrich từ Postgres (ai_description + ai_matches)
**Status:** doing
**Priority:** high
**Started:** 2026-04-16
**Owner:** team
## Mục tiêu
Sau khi search StarRocks xong, batch-fetch thêm `description_data``ai_matches`
từ Postgres `ultra_descriptions` để AI có context phong cách + gợi ý phối đồ.
## Checklist
- [x] Thêm `_enrich_from_postgres()` vào `lead_search_tool.py`
- [x] Import `get_pooled_connection_compat`
- [x] Query `dashboard_canifa.ultra_descriptions WHERE magento_ref_code IN (...)`
- [x] Parse `description_data` → compact `ai_description` string
- [x] Merge `ai_matches` vào product item
- [ ] Cập nhật system prompt của lead agent để dùng `ai_description`
- [ ] Test A/B: so sánh response có/không có ai_description
- [ ] Monitor latency (thêm Postgres query ~50-100ms)
## Context
- File: `backend/agent/lead_stage_agent/lead_search_tool.py`
- Hàm: `_enrich_from_postgres()`, `_format_products()`
- DB: Postgres `dashboard_canifa.ultra_descriptions`
- Fields mới trong product item: `ai_description`, `ai_matches`
## Blockers
# ✅ Fix: Save description không lưu được + form bị trống sau save
**Status:** done
**Completed:** 2026-04-16
**Priority:** high
## Vấn đề (3 bugs)
### Bug 1: `update_tags` thiếu `conn.commit()`
Tags được update trong transaction nhưng không commit → rollback khi close connection.
### Bug 2: JS shared reference bug
`getSafeDetailData()` trả reference trực tiếp → `gatherInputsIntoData()` mutate ref
→ sau save, code `delete targetData[k]` wipe sạch object rỗng
→ form hiển thị trống, user tưởng không save được.
### Bug 3: `update_tags` chỉ tìm `internal_ref_code`
SP có color suffix (`-SA190`) không được update vì chỉ WHERE `internal_ref_code`.
## Fix
- `backend/common/ultra_desc_db.py`: thêm `conn.commit()` + rollback + magento fallback vào `update_tags()`
- `backend/static/product-desc/product-desc.js`: thay buggy in-memory wipe bằng re-fetch từ server sau save
## Kết quả
- Save thành công ✅
- Form hiển thị đúng data sau save ✅
- Tags lưu xuống DB đúng ✅
# ✅ Fix: search product không ra kết quả (wrong DB table)
**Status:** done
**Completed:** 2026-04-16
**Priority:** high
## Vấn đề
Trang product-desc dùng `shared_source.*` nhưng SP mới như `8BK26A001-SA190`
chỉ có trong `test_db.*` → search ra 0 kết quả.
## Fix
- File: `backend/api/product_desc_route.py` line 29
- Đổi `TABLE_NAME = "shared_source.*"``TABLE_NAME = "test_db.*"`
- API response trả về cả `products` (old field names) + `items` (new field names)
để backward compat với product-desc.js
## Kết quả
- Search `8BK26A001-SA190` → ra đúng 1 kết quả ✅
- Tất cả các SP khác vẫn hiển thị bình thường (1,890 SP)
# ✅ Smart Product Search — DONE
**Ngày hoàn thành:** 2026-04-09
## Mô tả
Triển khai tính năng Smart Product Search với tiered relevance ranking cho trang Product Performance.
## Thay đổi
### Backend (`api/product_route.py`)
- Thêm **relevance scoring** bằng SQL CASE WHEN khi có search query
- Tiered scoring: Exact SKU (100) → SKU Prefix (90) → Exact Name (85) → Name StartsWith (70) → Name Contains (50) → Product Line (40) → Partial (30)
- Auto-sort theo `relevance_score DESC, quantity_sold DESC` khi search
- Trả thêm field `sort``relevance_score` per product
### Frontend (`product.html` + `product-render.js`)
- Thêm option "🎯 Độ liên quan" trong sort dropdown
- **Auto-switch**: tự chuyển sang sort "Độ liên quan" khi gõ search, reset về "Bán chạy" khi xóa
- **Relevance badge**: hiện badge với điểm (🎯 100, ✓ 70, ~ 30) có màu (xanh > vàng > xám)
- **Highlight**: tô vàng từ khóa tìm kiếm trong tên sản phẩm
## Tham khảo
- `searchRanking.ts` từ obsidian-Smart2Brain (`calculateTitleBoost`)
- Tiered boosting pattern: exact > prefix > startsWith > contains > partial
## Backlog Ideas (chưa làm)
1. **Dashboard stat cards thông minh hơn** — SP bán chạy chưa có mô tả, SP cập nhật gần đây, SP thiếu trường
2. **AI Agent Mode** — Agent tự tìm data (product, size guide, similar SP) trước khi generate mô tả
3. **Conversation Summarization** — tự tóm tắt khi chat quá 80% context window
4. **Tag Boosting** — tìm SP theo tags ưu tiên exact > prefix > partial
5. **Google Drive + n8n Flow** — auto-fill mô tả SP mới từ ảnh trên Drive
Workflow sẽ là file `.md` nằm trong `.agent/workflows/` — khi bro gõ `/plan` thì tao tự động đọc và chạy theo instructions. Trông như này:
---
## Bro gọi `/plan` → Tao làm những việc này:
```
/plan add idea "Thêm memory cho lead bot"
→ Tạo file ideas/lead-bot-memory.md tự động
/plan start "lead-bot-memory"
→ Move ideas/ → doing/, thêm checklist template
/plan done "lead-bot-memory"
→ Move doing/ → done/2026-04-16-lead-bot-memory.md
/plan status
→ Liệt kê tất cả ideas | doing | done
```
---
## Workflow file trông như này:
```markdown
# Plan Manager Workflow
<!-- /plan-manager -->
Quản lý kế hoạch dự án qua folder structure.
## Commands
### Khi user gõ `/plan status`
1. Đọc tất cả files trong ideas/, doing/, done/
2. Hiển thị bảng tổng quan:
- 💡 X ideas đang pending
- 🔨 Y tasks đang làm
- ✅ Z tasks đã xong
### Khi user gõ `/plan add idea [tên]`
1. Tạo file ideas/[tên].md với template chuẩn
2. Báo confirm
### Khi user gõ `/plan start [tên]`
1. Move file từ ideas/ → doing/
2. Thêm Started date + Checklist section
### Khi user gõ `/plan done [tên]`
1. Move doing/[tên].md → done/YYYY-MM-DD-[tên].md
2. Thêm Completed date
```
---
## Thực tế khi dùng:
```
Bro: /plan status
Tao sẽ list:
┌─────────────────────────────────┐
│ 💡 Ideas (3) │
│ - ai-match-v2 │
│ - memory-system │
│ - export-csv │
│ │
│ 🔨 Doing (2) │
│ - fashion-dashboard [4 days] │
│ - lead-enrich [1 day] │
│ │
│ ✅ Done (8) │
│ - save-desc-fix (Apr 16) │
│ - search-fix (Apr 16) │
└─────────────────────────────────┘
```
---
**Tao tạo luôn chứ bro?** Sẽ tạo:
1. 📁 Folder structure `plan/ideas/`, `plan/doing/`, `plan/done/`
2. 📄 Templates
3. ⚙️ Workflow file `.agent/workflows/plan-manager.md`
4. Điền sẵn vài tasks từ những gì đã làm hôm nay vào `done/`
Tạo tất cả cùng lúc:
Created plan-manager.md
Tạo folder structure + điền sẵn tasks thực tế:
Created README.md
Tạo một vài tasks ideas + doing thực tế, và done từ hôm nay:
Created langfuse-score-observability.md
Created fashion-rules-reset-default.md
Created fashion-ai-dashboard.md
Created lead-agent-postgres-enrich.md
Created 2026-04-16-fix-search-wrong-db-table.md
Created 2026-04-16-fix-save-description-not-working.md
Xong! Bro `/plan status` thử ngay:
---
## 📊 `/plan status` — Hiện tại trông như này:
```
💡 IDEAS (2)
• langfuse-score-observability — track score breakdown trong Langfuse
• fashion-rules-reset-default — nút reset rules về mặc định
🔨 DOING (2)
• fashion-ai-dashboard [HIGH] — đang làm, còn 3 checklist
• lead-agent-postgres-enrich [HIGH] — đang làm, còn 3 checklist
✅ DONE (2, hôm nay)
• 2026-04-16 — fix-search-wrong-db-table
• 2026-04-16 — fix-save-description-not-working
```
---
## Structure đã tạo:
```
D:\cnf\chatbot-canifa-feedback\
├── .agent\workflows\
│ └── plan-manager.md ← workflow file, gõ /plan để dùng
└── backend\plan\
├── README.md
├── ideas\
│ ├── langfuse-score-observability.md
│ └── fashion-rules-reset-default.md
├── doing\
│ ├── fashion-ai-dashboard.md
│ └── lead-agent-postgres-enrich.md
└── done\
├── 2026-04-16-fix-search-wrong-db-table.md
└── 2026-04-16-fix-save-description-not-working.md
```
Lần sau bro chỉ cần nói `/plan add idea [tên]` hay `/plan done [tên]` là tao tự động quản lý nhé! 🚀
OK bro! Đây là ý tưởng cụ thể để upgrade dashboard AI Canifa dựa trên những gì hay từ Smart2Brain:
## 🎯 Tính năng đề xuất thêm cho Dashboard AI Canifa
### 1. 🔍 **Smart Product Search** (từ `searchRanking.ts`)
**Hiện tại:** Tìm SP bằng LIKE `%keyword%` — chậm, không thông minh
**Upgrade:** Ranked search với tiered boosting
```
Ưu tiên: Exact SKU match → Prefix match → Tên SP chứa keyword → Fuzzy
VD: Gõ "polo" → Kết quả ưu tiên:
1. [99%] Áo Polo Nam Cổ Bẻ (exact in name)
2. [85%] Áo Polo Active (prefix match)
3. [40%] Set quần áo polo nhí (partial match)
```
---
### 2. 📊 **Dashboard Overview thông minh hơn**
**Thêm stat cards:**
- 📈 SP bán chạy chưa có mô tả (ưu tiên generate)
- 🔄 SP cập nhật gần nhất (timeline)
- ⚠️ SP mô tả lỗi/thiếu trường
---
### 3. 🤖 **AI Agent Mode** (từ `Agent.ts` + Tools pattern)
**Hiện tại:** AI chỉ generate mô tả 1 chiều
**Upgrade:** AI có thể TỰ tìm thêm dữ liệu trước khi viết
```
User: "Generate mô tả cho 1TS26S005"
Agent: │→ Tool: search_product("1TS26S005") → lấy data
│→ Tool: get_size_guide("1TS26S005") → lấy bảng size
│→ Tool: get_similar_products() → lấy SP tương tự cho cross-sell
│→ Generate mô tả đầy đủ + chính xác hơn
```
---
### 4. 💬 **Conversation Summarization** (từ `summarization.ts`)
Cho chatbot Canifa — khi chat dài quá 80% context window → tự tóm tắt lịch sử, giữ 12 tin gần nhất + summary. Tránh "quên" context.
---
### 5. 🏷️ **Tag Boosting cho Search** (từ `calculateTagBoost`)
Tìm SP theo tags ưu tiên: exact tag match > prefix > partial
```
Tìm "func:thoang_mat" → boost SP có tag đó lên top
```
---
## ⚡ Ưu tiên triển khai:
| # | Tính năng | Effort | Impact |
|---|----------|--------|--------|
| 1 | Dashboard stat cards mới | 🟢 Thấp | 🟠 Trung bình |
| 2 | Smart search ranking | 🟡 Trung bình | 🔴 Cao |
| 3 | AI Agent Mode | 🔴 Cao | 🔴 Rất cao |
| 4 | Conversation summarization | 🟡 Trung bình | 🟠 Trung bình |
Bro muốn bắt tay làm cái nào trước? Hay muốn mình viết plan chi tiết hơn? 🤙
\ No newline at end of file
# 💡 Reset rules về mặc định trong Fashion Dashboard
**Status:** idea
**Added:** 2026-04-16
**Priority:** low
## Mô tả
Thêm nút "Reset về mặc định" trong modal Rules của Fashion Dashboard.
Khi admin chỉnh weight/matrix sai, cần reset an toàn.
## Tại sao cần làm?
Hiện tại nếu lưu rules sai → phải sửa tay file `fashion_rules.json`.
## Notes
- Rules file: `backend/worker/fashion_rules.json`
- API: `PUT /api/fashion-matches/rules/config`
- Cần backup bản gốc vào `fashion_rules.default.json`
# 💡 Observability — Tích hợp score breakdown vào Langfuse
**Status:** idea
**Added:** 2026-04-16
**Priority:** medium
## Mô tả
Ghi dữ liệu breakdown điểm phối đồ (color/style/occasion/role) vào Langfuse
để monitor "tại sao AI recommend SP X với SP Y" trong production.
## Tại sao cần làm?
Hiện tại score tester chỉ có trên UI admin. Muốn track được xu hướng theo thời gian.
## Notes
- Score tester API: `POST /api/fashion-matches/score-test`
- Langfuse SDK v3: dùng `lf.start_as_current_observation`
$ErrorActionPreference = "Continue" # Tranh bi dung dot ngot khi python in ra stderr
$env:PYTHONPATH = (Join-Path $PSScriptRoot "..")
$BACKEND_DIR = (Join-Path $PSScriptRoot "..")
$env:PYTHONIOENCODING="utf8"
Write-Host "========================================================" -ForegroundColor Magenta
Write-Host " LEAD BOT AUTO-EVAL LOOP (EVALUATOR-OPTIMIZER) " -ForegroundColor Magenta
Write-Host "========================================================" -ForegroundColor Magenta
# ==================== KHOI DONG BACKEND ====================
$PYTHON_EXEC = Join-Path $BACKEND_DIR ".venv\Scripts\python.exe"
Write-Host ""
Write-Host ">> DANG KHOI DONG UVICORN BACKEND (PORT 5000)..." -ForegroundColor Yellow
$backendProc = Start-Process -FilePath $PYTHON_EXEC -ArgumentList "-m uvicorn server:app --port 5000 --reload" -WorkingDirectory $BACKEND_DIR -PassThru -WindowStyle Normal
Write-Host ">> Cho 8s de server Uvicorn khoi dong hoan toan..." -ForegroundColor Gray
Start-Sleep -Seconds 8
# ==================== CLEAN UP ====================
@("DONE_LEAD.flag","tmp_eval_results.txt","tmp_claude_prompt.txt", "tmp_eval_errors.txt") | ForEach-Object {
if (Test-Path $_) { Remove-Item $_ }
}
$ITERATION = 1
$EVAL_SCRIPT = Join-Path $PSScriptRoot "..\scripts\lead_test\run_eval.py"
# ==================== INFINITE LOOP ====================
try {
while (!(Test-Path "DONE_LEAD.flag")) {
Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " ITERATION NUM: $ITERATION " -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
# ──────────────────────────────────────────────────────
# PHASE 1: EVALUATOR SUB-AGENT (PYTHON SCRIPT RUN TEST)
# ──────────────────────────────────────────────────────
Write-Host ""
Write-Host "=== PHASE 1: EVALUATOR CHAM BAI ===" -ForegroundColor Yellow
# Chay python thong qua Start-Process de luu log an toan, khong lam crash Powershell script
$evalParams = @{
FilePath = $PYTHON_EXEC
ArgumentList = """$EVAL_SCRIPT"""
NoNewWindow = $true
Wait = $true
RedirectStandardOutput = "tmp_eval_results.txt"
RedirectStandardError = "tmp_eval_errors.txt"
}
Start-Process @evalParams
# Kiem tra ket qua file output
if (Test-Path "tmp_eval_results.txt") {
Get-Content "tmp_eval_results.txt" -Tail 15 | Write-Host -ForegroundColor DarkGray
$rawLog = Get-Content "tmp_eval_results.txt" -Raw
if ($rawLog -match "Server khong" -or $rawLog -match "Server không") {
Write-Host " [WARN] Server uvicorn chua san sang hoac bi ngat. Xin cho..." -ForegroundColor Red
$ITERATION++
Start-Sleep -Seconds 3
continue
}
$failedCount = Select-String -Path "tmp_eval_results.txt" -Pattern "FAIL|PARTIAL" -AllMatches
$isAllPass = ($failedCount -eq $null -or $failedCount.Count -eq 0) -and ($rawLog -match "ROUND \d+ DONE|LOOP HOÀN THÀNH|LOOP HOAN THANH")
if ($isAllPass) {
Write-Host " [RESULT] 100% TEST PASS!" -ForegroundColor Green
New-Item -Path "DONE_LEAD.flag" -ItemType File | Out-Null
break
} else {
Write-Host " [RESULT] CON LOI (Fail/Partial: $($failedCount.Count)). Chuyen cho Optimizer..." -ForegroundColor Red
}
} else {
Write-Host " [WARN] Khong tim thay file ket qua evaluatator. Tra thu file errors.txt:" -ForegroundColor Red
if (Test-Path "tmp_eval_errors.txt") {
Get-Content "tmp_eval_errors.txt" | Write-Host -ForegroundColor Red
}
$ITERATION++
Start-Sleep -Seconds 3
continue
}
# ──────────────────────────────────────────────────────
# PHASE 2: OPTIMIZER SUB-AGENT (CLAUDE CODE CLI)
# ──────────────────────────────────────────────────────
Write-Host ""
Write-Host "=== PHASE 2: OPTIMIZER (CLAUDE CODE CLI) ===" -ForegroundColor Yellow
Write-Host ">> Goi Claude Code CLI doc loi va sua file..." -ForegroundColor Gray
$evalOutput = Get-Content "tmp_eval_results.txt" -Raw
$claudePrompt = @"
Hay dong vai mot AI Optimizer (Prompt Engineer & Backend Dev) bac thay.
Duoi day la Bao Cao Danh Gia Loi tu Evaluator Sub-agent cho cuc Lead Bot cua chung ta tren cong 5000:
===== ERROR REPORT =====
$evalOutput
========================
NHIEM VU CUA BAN (La 1 con Sub-agent sua code):
Phan tich nguyen nhan cot loi khien cac case test bi FAIL/PARTIAL o tren. Dua vao nguyen nhan:
1. Tu dong sua file `$BACKEND_DIR/agent/lead_stage_agent/prompts.py` de va System Prompt hoac xu ly loi.
2. Hoac sua thuat toan `$BACKEND_DIR/agent/lead_stage_agent/lead_search_tool.py` (Vi du: kiem tra loi filter).
3. Nghiem cam KHONG DUOC sua file Script Evaluator `run_eval.py` hay de thi `test_cases.json`.
4. Khong can xin phep, hay tu dong sua file. Sau khi sua xong, giai thich ngan gon 1 dong roi dung.
"@
$claudePrompt | Out-File -Encoding utf8 "tmp_claude_prompt.txt"
try {
$promptStr = Get-Content "tmp_claude_prompt.txt" -Raw
Write-Host "[Gemini] Dang phan tich loi va va lo hong... Xin cho..." -ForegroundColor Cyan
# Goi Gemini bang cmd de thuc thi yolo khong can hoi.
$geminiParams = @{
FilePath = "cmd.exe"
ArgumentList = "/c", "cd /d D:\cnf\chatbot-canifa-feedback && gemini --yolo -p ""@backend\plan\tmp_claude_prompt.txt"""
NoNewWindow = $true
Wait = $true
}
Start-Process @geminiParams
Write-Host ">> Gemini Optimizer da thuc thi hoan tat." -ForegroundColor Green
} catch {
Write-Host " [ERROR] Khong the goi toi Gemini CLI." -ForegroundColor Red
}
Write-Host ">> Cho 5s de uvicorn 5000 hot-reload code moi..." -ForegroundColor Gray
Start-Sleep -Seconds 5
$ITERATION++
}
} finally {
# Don dep
Write-Host ""
Write-Host ">> TAT CON SERVER UVICORN PORT 5000..." -ForegroundColor Yellow
if ($backendProc -and !$backendProc.HasExited) {
Stop-Process -Id $backendProc.Id -Force
}
}
# ==================== DONE THÀNH CÔNG ====================
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host " [DONE] HOAN TAT AUTO-TRAIN LOOP! " -ForegroundColor Green
Write-Host " Tong so vong lap da chay: $ITERATION " -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
@("tmp_eval_results.txt","tmp_claude_prompt.txt", "tmp_eval_errors.txt") | ForEach-Object {
if (Test-Path $_) { Remove-Item $_ }
}
Hay dong vai mot AI Optimizer (Prompt Engineer & Backend Dev) bac thay.
Duoi day la Bao Cao Danh Gia Loi tu Evaluator Sub-agent cho cuc Lead Bot cua chung ta tren cong 5000:
===== ERROR REPORT =====
❌ Server không chạy tại http://127.0.0.1:5000. Hãy start backend trước.
========================
NHIEM VU CUA BAN (La 1 con Sub-agent sua code):
Phan tich nguyen nhan cot loi khien cac case test bi FAIL/PARTIAL o tren. Dua vao nguyen nhan:
1. Tu dong sua file $BACKEND_DIR/agent/lead_stage_agent/prompts.py de va System Prompt hoac xu ly loi.
2. Hoac sua thuat toan $BACKEND_DIR/agent/lead_stage_agent/lead_search_tool.py (Vi du: kiem tra loi filter).
3. Nghiem cam KHONG DUOC sua file Script Evaluator un_eval.py hay de thi est_cases.json.
4. Khong can xin phep, hay tu dong sua file. Sau khi sua xong, giai thich ngan gon 1 dong roi dung.
❌ Server không chạy tại http://127.0.0.1:5000. Hãy start backend trước.
Binary files a/backend/plan/tmp_fashion_test.txt and /dev/null differ
# V2 Plan — Smart Shopping Journey Suggestion Chips
> **Mục tiêu**: Không để gãy hành trình mua sắm. Luôn gợi mở phối đồ (cross-sell/upsell). Tiến tới chốt đơn.
>
> **Tham khảo**: Zalando Assistant, feedback anh Tùng (CMi Director), chị Huyền (BA)
---
## 📊 Tổng quan hành trình
```
KHÁM PHÁ → TÌM SẢN PHẨM → ĐI SÂU / SO SÁNH → CHỐT ĐƠN → SAU MUA
↓ ↓ ↓ ↓ ↓
Chips v1 Chips v1 Per-product Journey Post-sale
(done ✅) (done ✅) buttons (v2) chips (v2) chips (v3)
```
---
## 🚀 Phase 1: Per-Product Action Buttons (ưu tiên cao)
### Ý tưởng (từ chị Huyền BA)
Mỗi product card hiển thị thêm 2 nút nhỏ:
| Button | Hành vi | Mục đích |
|--------|---------|----------|
| 💬 **Tư vấn chi tiết** | Gửi: "Tư vấn chi tiết [SKU]" → AI mô tả sâu: chất liệu, form, phối đồ | Giữ khách ở lại, tăng hiểu biết SP |
| 🔍 **Đồ tương tự** | Gửi: "Xem đồ tương tự [SKU]" → AI tìm SP cùng loại khác kiểu/giá | Cross-sell, không mất khách khi SP đầu không ưng |
### Implement
- **Frontend** (`index.html`): Thêm 2 button vào mỗi `.product-card` trong phần render product cards
- **Backend**: Không cần sửa — AI nhận câu hỏi tự nhiên và xử lý
- **Effort**: ~1-2h
### Mockup UI
```
┌─────────────────────────┐
│ [Ảnh SP] │
│ 6TS25S018 │
│ Áo phông nữ │
│ ~~399k~~ 279k │
│ 🛍️ Xem chi tiết │
│ │
│ [💬 Tư vấn] [🔍 Tương tự] │ ← MỚI
└─────────────────────────┘
```
---
## 🧠 Phase 2: Journey-Aware Smart Chips (ưu tiên cao)
### Logic
Chips thay đổi tự động theo giai đoạn mua sắm, dựa vào `user_insight.LAST_ACTION` + `GOAL`:
```python
# LOGIC PHÂN GIAI ĐOẠN:
if chưa_hi_SP:
# Giai đoạn KHÁM PHÁ
chips = ["Xem sản phẩm mới", "Đồ đang sale hot", "Tư vấn phối đồ"]
elif va_show_SP and chưa_chn_c_th:
# Giai đoạn TÌM KIẾM
chips = [
"{Loại phối đồ} phối cùng {SP}", # Cross-sell
"Xem thêm màu khác", # Variation
"Check size còn hàng", # Size check
]
elif đã_xem_2_SP_tr_lên:
# Giai đoạn SO SÁNH
chips = [
"So sánh 2 mẫu vừa xem", # Comparison
"Tư vấn size cho tôi", # Size guidance
"Đặt hàng ngay", # Checkout push
]
elif đã_cht_SP:
# Giai đoạn CHỐT ĐƠN
chips = [
"Phụ kiện kèm theo", # Accessory upsell
"Chính sách vận chuyển", # Shipping info
"Mã giảm giá nào không?", # Promotion
]
```
### Implement
- **Backend** (`controller.py`): Nâng cấp fallback logic → đọc `user_insight` để detect giai đoạn
- **Prompt** (`07_output_format.txt`): Thêm hướng dẫn AI sinh chips theo journey stage
- **Effort**: ~2-3h
---
## 🎯 Phase 3: Checkout Acceleration (ưu tiên trung bình)
### Chips đặc biệt khi gần chốt đơn
| Trigger | Chip hiện ra |
|---------|-------------|
| Khách nói "ưng mẫu này" | `"Đặt hàng online"`, `"Gọi hotline chốt nhanh"` |
| Khách hỏi size xong | `"Thêm vào giỏ"`, `"Xem sản phẩm phối thêm"` |
| Khách xem 3+ SP | `"SP nào hợp nhất với bạn?"` → AI recommend 1 SP tốt nhất |
### Implement
- **Backend**: Thêm logic detect "near-checkout" signals từ `LAST_ACTION`
- **Prompt**: Thêm instruction cho AI khi khách gần chốt đơn → push mạnh hơn
- **Effort**: ~2h
---
## 📦 Phase 4: Post-Purchase (ưu tiên thấp — v3)
Khi khách đã mua xong:
- `"Chính sách đổi trả"`
- `"Kiểm tra đơn hàng"`
- `"Gợi ý outfit cho tuần sau"`
> Cần tích hợp order tracking API → giai đoạn sau.
---
## 📋 Implementation Priority
| # | Task | Effort | Impact | Priority |
|---|------|--------|--------|----------|
| 1 | Per-product buttons (Tư vấn / Tương tự) | 1-2h | ⭐⭐⭐⭐⭐ | 🔴 Triển ngay |
| 2 | Journey-aware smart chips | 2-3h | ⭐⭐⭐⭐ | 🔴 Triển ngay |
| 3 | Checkout acceleration chips | 2h | ⭐⭐⭐⭐ | 🟡 Sau Phase 1+2 |
| 4 | Post-purchase chips | 3h+ | ⭐⭐ | 🟢 v3 |
---
## 🔧 Files cần sửa
### Phase 1 (Per-product buttons)
- `backend/static/index.html` — thêm 2 button vào product card render
### Phase 2 (Journey-aware chips)
- `backend/agent/controller.py` — nâng cấp fallback logic đọc user_insight
- `backend/agent/prompt_module/07_output_format.txt` — thêm hướng dẫn journey chips
### Phase 3 (Checkout acceleration)
- `backend/agent/controller.py` — detect near-checkout signals
- `backend/agent/prompt_module/04a_sales_core.txt` — sales push instructions
---
## ✅ Verification
1. Test hành trình đầy đủ: Chào → Tìm áo → Bấm "Tư vấn chi tiết" → Bấm "Quần phối cùng" → So sánh → Chốt đơn
2. Verify chips thay đổi theo từng giai đoạn
3. Đảm bảo không gãy UX — mọi chip đều dẫn đến bước tiếp theo có ý nghĩa
......@@ -91,11 +91,6 @@ async def startup_event():
asyncio.create_task(report_worker_loop())
logger.info("✅ Report Queue Worker started (background task)")
# Thêm worker chạy ngầm tự động đồng bộ Tồn Kho Cache
from api.stock_cache.stock_cache_route import stock_cache_worker_loop
asyncio.create_task(stock_cache_worker_loop())
logger.info("✅ Stock Cache Cron Job started (background task - 3 mins/sync)")
@app.on_event("shutdown")
async def shutdown_event():
......@@ -226,9 +221,6 @@ app.include_router(mock_auth_router) # Mock Auth (identity linking test)
from api.feedback_agent.feedback_agent_route import router as feedback_agent_router
app.include_router(feedback_agent_router) # Lõi Agent Rút Kinh Nghiệm (Langfuse -> Rules)
from api.stock_cache.stock_cache_route import router as stock_cache_router
app.include_router(stock_cache_router) # API Cache Tồn Kho Redis Crawler
from api.social_inbox.social_inbox_route import router as social_inbox_router
app.include_router(social_inbox_router) # Social Inbox (Facebook/Instagram/TikTok → Learning Loop)
......
......@@ -268,10 +268,10 @@ body{margin:0;display:flex;min-height:100vh}
<span>Feedback Demo</span>
<span class="nav-badge badge-new">NEW</span>
</a>
<a data-page="http://172.16.2.210:5006/static/index.html" class="nav-item" onclick="navigateTo(this)" title="Chatbot (Dev)">
<a data-page="save/save.html" class="nav-item" onclick="navigateTo(this)" title="Chatbot (Local)">
<span class="nav-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></span>
<span>Chatbot (Dev)</span>
<span class="nav-badge badge-beta">DEV</span>
<span>Chatbot (Local)</span>
<span class="nav-badge badge-beta">LOCAL</span>
</a>
<a data-page="ton-cache.html" class="nav-item" onclick="navigateTo(this)" title="Tồn Cache">
<span class="nav-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></svg></span>
......
<!-- <!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
......@@ -25,7 +25,7 @@
<div class="container">
<div class="chat-internal-wrapper">
<div class="header">
<h2>?? Canifa AI Chat</h2>
<h2>🤖 Canifa AI Chat</h2>
<div class="config-area" style="flex-wrap: wrap; display: flex; align-items: center; gap: 10px;">
<div style="display: flex; gap: 5px; align-items: center;">
<label style="font-size: 0.8em; color: #aaa;">Device ID:</label>
......@@ -43,33 +43,33 @@
</div>
<!-- Action Buttons -->
<button onclick="loadHistory(true)" title="Load History">? History</button>
<button onclick="loadHistory(true)" title="Load History"> History</button>
<button onclick="togglePromptEditor()"
style="background: #e6b800; color: #2d2d2d; font-weight: bold;">?? Prompt</button>
<button onclick="clearUI()" style="background: #d32f2f;">? UI</button>
style="background: #e6b800; color: #2d2d2d; font-weight: bold;">📝 Prompt</button>
<button onclick="clearUI()" style="background: #d32f2f;"> UI</button>
</div>
</div>
<div class="chat-box" id="chatBox">
<div class="load-more" id="loadMoreBtn" style="display: none;">
<button onclick="loadHistory(false)">Load Older Messages ??</button>
<button onclick="loadHistory(false)">Load Older Messages ⬆️</button>
</div>
<div id="messagesArea" style="display: flex; flex-direction: column; gap: 15px;"></div>
</div>
<div class="typing-indicator" id="typingIndicator">
<span style="font-style: normal;">??</span> AI is thinking...
<span style="font-style: normal;">🤖</span> AI is thinking...
</div>
<div class="input-area">
<input type="text" id="userInput" placeholder="Type your message..."
onkeypress="handleKeyPress(event)" autocomplete="off">
<button onclick="sendMessage()" id="sendBtn">? Send</button>
<button onclick="sendMessage()" id="sendBtn"> Send</button>
<button onclick="resetChat()" id="resetBtn" title="Reset Session"
style="background: #ffc107; color: #333; font-weight: bold; padding: 0 20px; margin-left: 10px; border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center;">??
style="background: #ffc107; color: #333; font-weight: bold; padding: 0 20px; margin-left: 10px; border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center;">🔄
Reset</button>
</div>
</div>
......@@ -78,8 +78,8 @@
<!-- Prompt Editor Panel -->
<div class="prompt-panel" id="promptPanel">
<div class="prompt-header">
<h3>?? System Prompt</h3>
<button class="btn-close-panel" onclick="togglePromptEditor()"></button>
<h3>📝 System Prompt</h3>
<button class="btn-close-panel" onclick="togglePromptEditor()">×</button>
</div>
<textarea id="systemPromptInput" class="prompt-textarea" placeholder="Loading prompt content..."
......@@ -88,8 +88,8 @@
<div class="panel-footer">
<span class="status-text" id="promptStatus">Ready to edit</span>
<div style="display: flex; gap: 10px;">
<button class="action-btn btn-reload" onclick="loadSystemPrompt()">? Reset</button>
<button class="action-btn btn-save" onclick="saveSystemPrompt()">?? Save & Apply</button>
<button class="action-btn btn-reload" onclick="loadSystemPrompt()"> Reset</button>
<button class="action-btn btn-save" onclick="saveSystemPrompt()">💾 Save & Apply</button>
</div>
</div>
</div>
......@@ -101,4 +101,4 @@
<script src="/static/save/save.js"></script>
</body>
</html> -->
\ No newline at end of file
</html>
\ No newline at end of file
const SVG = {
Init: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>`,
Doc: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M14 2v6h6M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`,
Database: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><ellipse cx="12" cy="5" rx="9" ry="3" stroke="currentColor" stroke-width="1.6"/><path d="M21 12c0 1.66-4.03 3-9 3s-9-1.34-9-3" stroke="currentColor" stroke-width="1.6"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5" stroke="currentColor" stroke-width="1.6"/></svg>`,
Score: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><path d="M22 12h-4l-3 9L9 3l-3 9H2" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>`,
Filter: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><path d="M3 6h18M7 12h10M11 18h2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`,
Check: `<svg viewBox="0 0 24 24" fill="none" width="100%" height="100%"><path d="M22 11.08V12a10 10 0 11-5.93-9.14" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M22 4L12 14.01l-3-3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`
};
const NODES = [
{ id:"init", step:"01", label:"Khởi động AI Engine", brief:"Chuẩn bị pipeline phối đồ", icon:"Init", color:"#a78bfa" },
{ id:"fetch_product", step:"02", label:"Phân tích SP Gốc", brief:"Nạp metadata, mã màu, type", icon:"Doc", color:"#38bdf8" },
{ id:"fetch_rules", step:"03", label:"Kéo luật phối DB", brief:"Truy vấn chatbot_fashion_rules", icon:"Database", color:"#facc15" },
{ id:"scoring", step:"04", label:"Scoring Engine", brief:"Tính Color Synergy, Material, Occasion", icon:"Score", color:"#fb923c" },
{ id:"dedup", step:"05", label:"Deduplication & Mở rộng", brief:"Lọc trùng, SQL Classifications", icon:"Filter", color:"#f87171" },
{ id:"complete", step:"06", label:"Hoàn tất", brief:"Đóng gói JSON trả frontend", icon:"Check", color:"#34d399" }
];
// Setup Nodes UI
function renderNodes() {
const wrap = document.getElementById('flowNodes');
let html = '';
NODES.forEach((n, idx) => {
html += \`<div id="node-\${n.id}" class="mf-node" style="border-color:\${n.color}55">
<div class="mf-node-icon" style="color:\${n.color};background:\${n.color}15">\${SVG[n.icon]}</div>
<div style="flex:1">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:2px">
<span class="mf-node-step" style="color:\${n.color}">\${n.step}</span>
<span class="mf-node-title">\${n.label}</span>
</div>
<div class="mf-node-brief">\${n.brief}</div>
</div>
</div>\`;
if (idx < NODES.length - 1) {
html += \`<div id="conn-\${n.id}" class="mf-connector"><div class="mf-connector-line"></div></div>\`;
}
});
wrap.innerHTML = html;
}
renderNodes();
// Search Input Logic
let searchTimeout = null;
const searchInput = document.getElementById('searchInput');
const searchResults = document.getElementById('searchResults');
let eventSource = null;
searchInput.addEventListener('input', (e) => {
const q = e.target.value.trim();
if (q.length < 2) {
searchResults.style.display = 'none';
return;
}
clearTimeout(searchTimeout);
searchTimeout = setTimeout(async () => {
try {
const res = await fetch(\`/api/fashion-matches/simulator/search?q=\${encodeURIComponent(q)}\`);
const data = await res.json();
if (data.ok && data.data.length > 0) {
let html = '';
data.data.forEach(item => {
html += \`
<div class="search-item" onclick="startSimulation('\${item.code}')">
<img src="\${item.image || ''}" class="search-item-img" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlMmU4ZjAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiLz48L3N2Zz4='">
<div class="search-item-info">
<div class="search-item-name">\${item.name}</div>
<div class="search-item-code">\${item.code} • \${item.color}</div>
</div>
</div>
\`;
});
searchResults.innerHTML = html;
searchResults.style.display = 'block';
} else {
searchResults.innerHTML = '<div style="padding: 16px; font-size: 11px; color:#94a3b8; text-align:center;">Không tìm thấy sản phẩm...</div>';
searchResults.style.display = 'block';
}
} catch (err) {
console.error(err);
}
}, 300);
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-box')) {
searchResults.style.display = 'none';
}
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const firstResult = searchResults.querySelector('.search-item');
if (firstResult && searchResults.style.display !== 'none') {
firstResult.click();
} else {
const q = e.target.value.trim();
if (q) startSimulation(q);
}
}
});
// Logger
const term = document.getElementById('logTerminal');
function logTerminal(msg, type = 'normal') {
const time = new Date().toLocaleTimeString('vi-VN', {hour12: false});
const div = document.createElement('div');
div.className = \`log-line \${type}\`;
div.innerHTML = \`<span class="log-time">[\${time}]</span> \${msg}\`;
term.appendChild(div);
term.scrollTop = term.scrollHeight;
}
// Reset UI
function resetUI() {
document.querySelectorAll('.mf-node').forEach(el => {
el.classList.remove('active', 'done');
});
document.querySelectorAll('.mf-connector').forEach(el => {
el.classList.remove('active');
});
term.innerHTML = '';
document.getElementById('finalResult').classList.remove('show');
document.getElementById('outfitGrid').innerHTML = '';
}
// Start Stream
function startSimulation(code) {
searchResults.style.display = 'none';
searchInput.value = code;
if (eventSource) {
eventSource.close();
}
resetUI();
logTerminal(\`Starting simulator for \${code}...\`);
eventSource = new EventSource(\`/api/fashion-matches/simulator/stream?code=\${encodeURIComponent(code)}\`);
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.error) {
logTerminal(data.status, 'error');
eventSource.close();
return;
}
// Un-activate all nodes, mark previous as done
document.querySelectorAll('.mf-node').forEach(el => {
if (el.classList.contains('active')) {
el.classList.remove('active');
el.classList.add('done');
}
});
const currentNode = document.getElementById(\`node-\${data.node}\`);
if (currentNode) {
currentNode.classList.add('active');
}
// Line connector active
if (data.step > 1) {
const prevId = NODES[data.step - 2].id;
const conn = document.getElementById(\`conn-\${prevId}\`);
if (conn) conn.classList.add('active');
}
// Log terminal
let logType = 'normal';
if (data.step === 6) logType = 'success';
logTerminal(data.status, logType);
// Render payload if step 6
if (data.step === 6 && data.payload && data.payload.ai_matches) {
renderFinalResults(data.payload.ai_matches);
eventSource.close();
}
};
eventSource.onerror = (err) => {
logTerminal("Connection closed or error", "error");
eventSource.close();
};
}
function renderFinalResults(matches) {
const grid = document.getElementById('outfitGrid');
let html = '';
for (const [role, items] of Object.entries(matches)) {
if (!items || items.length === 0) continue;
html += \`<div class="outfit-card">
<div class="outfit-role">Phối với \${role}</div>
<div class="product-combo">\`;
items.forEach(p => {
html += \`
<div class="product-mini">
<img src="\${p.image_url || ''}" onerror="this.src='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlMmU4ZjAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiLz48L3N2Zz4='">
<div class="product-mini-name">\${p.name || 'Không có tên'}</div>
<div class="product-mini-code">\${p.sku || p.code || ''}</div>
<div class="product-mini-code" style="color:var(--primary);margin-top:4px">Điểm: \${(p.score || 0).toFixed(1)}</div>
</div>
\`;
});
html += \`</div></div>\`;
}
grid.innerHTML = html;
document.getElementById('finalResult').classList.add('show');
}
......@@ -122,11 +122,11 @@ class StylistEngine:
# Fetch allowed mappings from DB: {occasion_tag: set(target_category)}
allowed_by_occ = self._fetch_allowed_mappings(anchor_cat, gender)
# ── FALLBACK: DB table trống → dùng hardcoded rules từ fashion_rules.json ──
# ── FALLBACK: DB table trống → dùng hardcoded rules từ fashion_rules.json (Đã comment theo yêu cầu) ──
# if not allowed_by_occ:
# allowed_by_occ = self._get_fallback_mappings(anchor_cat)
if not allowed_by_occ:
allowed_by_occ = self._get_fallback_mappings(anchor_cat)
if not allowed_by_occ:
logger.warning("[Stylist] No rules (DB + fallback) for anchor '%s'", anchor_cat)
logger.warning("[Stylist] No rules for anchor '%s'", anchor_cat)
return {}
# Initialize buckets only for occasions that have rules
......@@ -179,27 +179,32 @@ class StylistEngine:
return result
def _fetch_allowed_mappings(self, anchor_cat: str, gender: str = "") -> dict:
"""Fetch {occasion_tag: set(target_category)} from chatbot_fashion_rules.
Filters by gender_target: returns rules matching (gender, 'all').
"""
"""Fetch {occasion_tag: set(target_category)} from SQLite: pg__dashboard_canifa__ai_outfit_rules."""
import sqlite3
db_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "123.db")
conn = None
try:
conn = get_pooled_connection_compat()
conn = sqlite3.connect(db_path)
cur = conn.cursor()
gender_key = self._normalize_gender(gender)
try:
cur.execute(
"""SELECT occasion_tag, target_category FROM dashboard_canifa.chatbot_fashion_rules
WHERE UPPER(anchor_category) = UPPER(%s)
AND (gender_target = %s OR gender_target = 'all')""",
(anchor_cat, gender_key)
)
except Exception:
# Fallback: column may not exist yet
sqlite_gender_map = {
"nu": "women",
"nam": "men",
"be_gai": "girl",
"be_trai": "boy",
"unisex": "unisex"
}
target_gender = sqlite_gender_map.get(gender_key, "unisex")
cur.execute(
"SELECT occasion_tag, target_category FROM dashboard_canifa.chatbot_fashion_rules WHERE UPPER(anchor_category) = UPPER(%s)",
(anchor_cat,)
"""SELECT occasion, target_category
FROM pg__dashboard_canifa__ai_outfit_rules
WHERE UPPER(anchor_category) = UPPER(?)
AND (gender = ? OR gender = 'unisex')""",
(anchor_cat, target_gender)
)
mapping: dict[str, set[str]] = {}
for row in cur.fetchall():
occ, tgt = row[0], row[1]
......@@ -216,25 +221,21 @@ class StylistEngine:
conn.close()
def _get_fallback_mappings(self, anchor_cat: str) -> dict:
"""Fallback: đọc fallback_occasion_rules từ fashion_rules.json.
Trả về {occasion_tag: set(target_category_lower)} — cùng format với _fetch_allowed_mappings().
"""
fallback = self.rules.get("fallback_occasion_rules", {})
occ_map = fallback.get(anchor_cat, {})
if not occ_map:
# Thử case-insensitive match
for key, val in fallback.items():
if key.lower() == anchor_cat.lower():
occ_map = val
break
if not occ_map:
logger.debug("[Stylist] No fallback rules for anchor '%s'", anchor_cat)
"""Fallback: đọc fallback_occasion_rules từ fashion_rules.json. (Đã comment)"""
# fallback = self.rules.get("fallback_occasion_rules", {})
# occ_map = fallback.get(anchor_cat, {})
# if not occ_map:
# for key, val in fallback.items():
# if key.lower() == anchor_cat.lower():
# occ_map = val
# break
# if not occ_map:
# return {}
# result: dict[str, set[str]] = {}
# for occ, targets in occ_map.items():
# result[occ] = set(t.lower() for t in targets)
# return result
return {}
result: dict[str, set[str]] = {}
for occ, targets in occ_map.items():
result[occ] = set(t.lower() for t in targets)
logger.info("[Stylist] Using fallback rules for '%s': %d occasions", anchor_cat, len(result))
return result
......@@ -338,34 +339,39 @@ class StylistEngine:
return "unisex"
def _fetch_rules_with_reason(self, anchor_cat: str, gender: str = "") -> list[dict]:
"""Fetch full rules including occasion_tag, match_role, target_category, ai_reason.
Filters by gender_target.
"""
"""Fetch full rules including occasion, match_role, target_category, ai_reason from SQLite."""
import sqlite3
db_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "123.db")
conn = None
try:
conn = get_pooled_connection_compat()
conn = sqlite3.connect(db_path)
cur = conn.cursor()
gender_key = self._normalize_gender(gender)
try:
cur.execute(
"""SELECT occasion_tag, match_role, target_category, ai_reason
FROM dashboard_canifa.chatbot_fashion_rules
WHERE UPPER(anchor_category) = UPPER(%s)
AND (gender_target = %s OR gender_target = 'all')""",
(anchor_cat, gender_key)
)
except Exception:
sqlite_gender_map = {
"nu": "women",
"nam": "men",
"be_gai": "girl",
"be_trai": "boy",
"unisex": "unisex"
}
target_gender = sqlite_gender_map.get(gender_key, "unisex")
cur.execute(
"SELECT occasion_tag, match_role, target_category, ai_reason FROM dashboard_canifa.chatbot_fashion_rules WHERE UPPER(anchor_category) = UPPER(%s)",
(anchor_cat,)
"""SELECT occasion, match_role, target_category, ai_reason
FROM pg__dashboard_canifa__ai_outfit_rules
WHERE UPPER(anchor_category) = UPPER(?)
AND (gender = ? OR gender = 'unisex')""",
(anchor_cat, target_gender)
)
rules = []
for row in cur.fetchall():
rules.append({
"occ": row[0],
"role": row[1],
"target_cat": row[2],
"ai_reason": row[3],
"ai_reason": row[3] or "",
})
cur.close()
return rules
......
import sqlite3
import os
db_path = "backend/database/canifa_ai_dump.sqlite"
if not os.path.exists(db_path):
print(f"DB not found at {db_path}")
exit(1)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = cur.fetchall()
print("Tables:", [t[0] for t in tables])
# Check distinct values for gender_by_product and age_by_product
table_name = "sr__test_db__magento_product_dimension_with_text_embedding"
try:
cur.execute(f"SELECT DISTINCT gender_by_product FROM {table_name}")
genders = cur.fetchall()
print("Genders:", [g[0] for g in genders])
cur.execute(f"SELECT DISTINCT age_by_product FROM {table_name}")
ages = cur.fetchall()
print("Ages:", [a[0] for a in ages])
except Exception as e:
print(f"Error querying table {table_name}: {e}")
conn.close()
# 🔧 DOING: CuCu Note — Feature Parity & Beyond
> Cập nhật: 2026-04-19 | Owner: Dev Team
> Đây là file theo dõi tiến độ **đang làm**. Khi task xong → chuyển sang `/done`.
---
## 🔥 Epic 1 — Reactions & Comments → ✅ CODE XONG (Chờ Test)
### Backend `memo_routes.py` + `services.py`
- [x] `POST /memos/{id}/reactions` — Thêm emoji reaction
- [x] `DELETE /memos/{id}/reactions/{type}` — Xoá reaction
- [x] `GET /memos/{id}/comments` — Lấy danh sách comment
- [x] `POST /memos/{id}/comments` — Tạo comment mới
- [x] `MemoResponse` trả về kèm `reactions[]` list
- [x] Batch fetch reactions (hiệu năng, tránh N+1)
- [x] `list_comments()` method trong `MemoService`
### Frontend `memoService.ts`
- [x] `upsertMemoReaction()` → gọi API thật (bỏ mock)
- [x] `deleteMemoReaction()` → gọi API thật (bỏ mock)
- [x] `createMemoComment()` → POST `/memos/{id}/comments`
- [x] `listMemoComments()` → GET `/memos/{id}/comments`
### Cần Test
- [ ] Thêm reaction ❤️ vào memo → hiển thị ngay lập tức
- [ ] Xoá reaction → biến mất khỏi UI
- [ ] Comment vào memo → hiện trong MemoDetail
- [ ] Nested comment (reply to comment)
---
## 🔔 Epic 2 — Inbox & Notifications → ❌ CHƯA LÀM
### Backend `user_routes.py`
- [ ] `GET /users/{id}/notifications` — Lấy danh sách inbox
- [ ] `PATCH /users/{id}/notifications/{notif_id}` — Đánh dấu đã đọc
- [ ] `DELETE /users/{id}/notifications/{notif_id}` — Xoá thông báo
- [ ] Tự động tạo Notification khi có comment mới vào memo → gửi cho owner
### Frontend `userService.ts`
- [ ] `listUserNotifications()` → gọi API thật (bỏ stub empty)
- [ ] `updateUserNotification()` → gọi API thật
- [ ] `deleteUserNotification()` → gọi API thật
- [ ] Badge số đỏ trên Bell icon hoạt động với data thật
---
## 📊 Epic 3 — Heatmap Activity Calendar → ❌ Cần Verify
### Backend
- [ ] Verify `GET /users/{id}/stats` trả đúng `memoDisplayTimestamps[]`
- [ ] Test backend với user có nhiều memos
### Frontend
- [ ] Kiểm tra `ActivityCalendar` component render đúng từ data thật
- [ ] Test calendar highlight đúng ngày có ghi chú
---
## 🔑 Epic 4 — Personal Access Token (PAT) → ❌ CHƯA LÀM
### Backend
- [ ] `POST /users/{id}/access-tokens` — Tạo token mới
- [ ] `GET /users/{id}/access-tokens` — List tokens
- [ ] `DELETE /users/{id}/access-tokens/{tokenId}` — Thu hồi token
- [ ] Middleware nhận PAT trong `Authorization: Bearer <pat>` header
### Frontend
- [ ] `createPersonalAccessToken()` → API thật
- [ ] `listPersonalAccessTokens()` → API thật
- [ ] Settings UI hiển thị list tokens với nút revoke
---
## 🔗 Epic 5 — Webhooks → ❌ CHƯA LÀM
### Backend
- [ ] `GET /users/{id}/webhooks`
- [ ] `POST /users/{id}/webhooks`
- [ ] `DELETE /users/{id}/webhooks/{id}`
- [ ] Trigger webhook khi tạo/xoá Memo mới
### Frontend
- [ ] Settings > Webhooks tab hoạt động
---
## 🌐 Epic 6 — SSO / OAuth (Để Sau) → ❌ CHƯA LÀM
- [ ] OAuth callback handler
- [ ] IDP config lưu vào DB
- [ ] Nút "Đăng nhập với Google/GitHub" trên trang Auth
---
## 📝 Epic 7 — Advanced Note-Taking UX (Mới Thêm) → ❌ CHƯA LÀM
### 1. Trải nghiệm Editor (Slash Commands & Clipboard)
- [ ] Tính năng Kéo thả / Paste ảnh trực tiếp vào khung text (upload qua clipboard).
- [ ] Slash Commands (`/`) mở menu chọn (Heading, Checklist, Code Block, Quote).
- [ ] Auto-Suggest Tags (`#`): Hiện popup gợi ý các tag đã tồn tại.
### 2. Ghi chú liên kết (Bi-directional Linking)
- [ ]`[[` để gợi ý tên memo khác.
- [ ] Render link `[[memo_id]]` thành link bấm được (Zettelkasten style).
### 3. Rich Media & Cải tiến View
- [ ] Link Bookmark Preview: Tự động fetch metadata (Title, Image, Desc) cho URL.
- [ ] Voice Memos: Nút ghi âm trực tiếp, lưu file audio.
- [ ] Inline Editing: Bấm đúp vào memo để sửa ngay trên luồng, không nhảy modal.
- [ ] Focus/Zen Mode: Nút ẩn nhanh sidebar để tập trung gõ.
---
## 🎯 Epic 8 — Inbound Webhooks & CRM Inbox (Trung Tâm Hứng Feedback) → ✅ CODE XONG + TEST PASS
### 1. Kênh Thu Thập (Inbound Webhook API) 📥
- [x] Backend: Viết Endpoint `POST /api/v1/inbound_webhooks/{workspace_id}` để hứng payload.
- [x] Backend: Logic bóc tách JSON và tự động đẻ ra Memo mới trong Workspace.
- [x] Backend: Optional webhook secret verification qua `X-Webhook-Secret` header.
- [x] Backend: Memo từ webhook mặc định `is_read=False`.
- [ ] Frontend: Thêm màn hình `Settings > Webhooks (Inbound)` để phát sinh và quản lý link URL. **→ Defersau**
- [ ] Frontend: Hiển thị Log history của các webhook. **→ Defer sau**
### 2. Quản lý Hộp Thư UI (Inbox Unread / Read) 📬
- [x] Backend: Thêm trường `is_read BOOLEAN DEFAULT 0` vào DB Model của Memo (với auto-migration).
- [x] Backend: Thêm endpoint `GET /users/{user_id}/inbox/unread_count` trả về số lượng unread.
- [x] Backend: Thêm endpoint `GET /users/{user_id}/inbox/memos` list các unread memos.
- [x] Backend: Hỗ trợ filter theo `workspace_id` cho inbox endpoints.
- [x] Frontend: Cải tiến UI thanh Sidebar, thêm mục `Inbox` kèm Badge số lượng (kết hợp notifications + unread memos).
- [x] Frontend: Trang `Inboxes.tsx` với 2 tabs: Notifications và Unread Memos.
- [x] Frontend: Nút check ✅ (Mark as Read) cho unread memos, dùng PATCH `/memos/{id}` với `{ "is_read": true }`.
- [x] i18n: Thêm keys: `inbox.notifications`, `inbox.unread-memos`, `inbox.no-unread-memos`, `inbox.mark-as-read`.
### 3. Database & Performance
- [x] Thêm index `idx_memos_is_read` cho truy vấn unread nhanh.
- [x] Thêm index `idx_memos_workspace` cho workspace isolation.
- [x] Migration tự động: ALTER TABLE thêm `is_read` nếu column chưa tồn tại.
---
## 📋 Ghi Chú Kỹ Thuật
- **Auth:** JWT nội bộ, `user_id` lấy từ `request.state` (set bởi middleware)
- **DB:** MongoDB, collections: `memos`, `reactions`, `activities`, `inbox`
- **Backend:** FastAPI + Python 3.11+
- **Frontend:** React + Vite + TailwindCSS + shadcn/ui
- **API Prefix:** `/api/v1/`
# Kế Hoạch Chuyển Đổi CuCu Note sang SQLite (A-Z)
## 🎯 Mục Tiêu
Thay thế hoàn toàn MongoDB bằng SQLite cho hệ thống backend của CuCu Note.
- **Vị trí Database:** `C:\canifa-idea\chatbot-canifa-feedback\miniapp\cuccu_note\backend\database\cuccu_note.db`
- **Driver sử dụng:** `aiosqlite` (để hỗ trợ async/await chuẩn xác với FastAPI).
- **Mục đích:** Zero-setup, dễ dàng khởi chạy trên local Windows, phù hợp scale của note-taking app.
---
## 🏗️ Giai Đoạn 1: Initialization & Schema Design
*Thiết lập cơ sở dữ liệu và cấu trúc các bảng.*
### 1.1 Khởi tạo Database Layer
- Tạo thư mục `database` bên trong `backend/`.
- Thêm `aiosqlite` vào `requirements.txt`.
- Tạo file `common/sqlite_client.py` để thay thế `mongodb.py`. Cấu hình connection logic.
### 1.2 Thiết kế Schema (Schema Mapping)
Chuyển đổi các Collections của MongoDB thành các Tables của SQLite. Cần khởi tạo trong hàm `init_db()`.
Dưới đây là các bảng chính cần tạo (`CREATE TABLE IF NOT EXISTS`):
1. **`users`**
- `id` (INTEGER PRIMARY KEY AUTOINCREMENT)
- `email` (TEXT UNIQUE)
- `password_hash` (TEXT)
- `nickname` (TEXT)
- `created_at` (TIMESTAMP)
2. **`memos`**
- `id` (INTEGER PRIMARY KEY AUTOINCREMENT)
- `creator_id` (INTEGER, FK -> users.id)
- `content` (TEXT)
- `visibility` (TEXT: PUBLIC/PRIVATE/PROTECTED)
- `row_status` (TEXT: NORMAL/ARCHIVED)
- `pinned` (BOOLEAN)
- `created_at` (TIMESTAMP)
- `updated_at` (TIMESTAMP)
3. **`memo_relations`** (Backlinks / Linked notes)
- `memo_id` (INTEGER, FK -> memos.id)
- `related_memo_id` (INTEGER, FK -> memos.id)
- `relation_type` (TEXT: REFERENCE/COMMENT)
4. **`reactions`**
- `id` (INTEGER PRIMARY KEY AUTOINCREMENT)
- `memo_id` (INTEGER, FK -> memos.id)
- `creator_id` (INTEGER, FK -> users.id)
- `reaction_type` (TEXT e.g. 'THUMBS_UP')
5. **`comments`** (Cây bình luận dạng Flat list hoặc Adjacency list)
- Thực chất, **Memos** lưu comment bằng cách tạo một memo con, và liên kết bằng `memo_relations``type='COMMENT'`. Giữ nguyên pattern này để đồng bộ với Frontend. Cần confirm xem frontend cần bảng comments không hay chỉ cần Relations.
6. **`attachments`** (File/Ảnh đính kèm)
- `id` (INTEGER PRIMARY KEY)
- `memo_id` (INTEGER)
- `filename` (TEXT)
- `type` (TEXT)
- `size` (INTEGER)
- `url` (TEXT)
7. **`inbox` / `notifications`**
- `id` (INTEGER PRIMARY KEY)
- `user_id` (INTEGER)
- `type` (TEXT: MEMO_COMMENT, MENTION...)
- `status` (TEXT: UNREAD/READ/ARCHIVED)
- `payload` (JSON TEXT)
---
## 🛠️ Giai Đoạn 2: Xây Dựng ORM/Query Adapter (services.py)
*Viết lại `services.py` để sử dụng SQL thay vì MongoDB Query.*
### 2.1 Cập nhật `mongodb.py` -> `sqlite.py`
Thay thế con trỏ MongoDB bằng các async queries của SQLite.
- Xóa: `mongodb_client.memos.find()`
- Thay bằng: `async with db.execute("SELECT * FROM memos WHERE ...")`
### 2.2 Xử lý kiểu dữ liệu (Data Type Migration)
- **ID:** Đổi từ `ObjectId` (chuỗi 24 ký tự) sang **Integer/Text**. Cần cẩn thận ở API Params vì gRPC/REST hiện tại trả về chuỗi. Ở DB có thể dùng Integer, nhưng convert sang String khi trả về API.
- **Datetime:** SQLite chỉ hỗ trợ lưu dưới dạng ISO-8601 String (`YYYY-MM-DD HH:MM:SS`) hoặc Timestamp. Cần viết helper `parse_time``format_time` để map với `datetime` object của Python.
- **JSON Fields:** Các trường như tags, metadata phải serialize/deserialize bằng `json.dumps()``json.loads()` khi lưu vào cột `TEXT` của SQLite.
### 2.3 Ánh xạ các logic tìm kiếm
- Viết lại hàm `list_memos`: Thay `$match`, `$sort`, `$regex` bằng `WHERE`, `ORDER BY`, `LIKE`.
---
## 🔌 Giai Đoạn 3: Đấu Nối API & Test
*Kiểm tra độ chẩn xác sau khi đổi DB Engine.*
1. **Test Startup:** Khởi động server (`uvicorn server:app`). Verify file `cuccu_note.db` được hệ thống tạo tự động vào `backend/database/cuccu_note.db`.
2. **Test CRUD Memos:**
- Tạo memo -> Verify DB.
- Sửa nội dung -> Verify updated_at.
- List memo -> Kiểm tra xem parse thời gian và JSON đúng không.
3. **Test Relations (Comments/Reactions):**
- React vào Memo (Lệnh SQL INSERT phải vào đúng bảng).
- Test Join Query nếu cần load số lượng comments.
4. **Xóa MongoDB Dependencies:** Sau khi test mọi thứ ổn định, gỡ `motor` ra khỏi `requirements.txt`.
---
## 🚀 Hướng Dẫn Cho Agent / AI
Nếu AI nhận file này để tiếp tục code, hãy follow flow:
1. Tạo thư mục `database/` và init script table creation (`sqlite_client.py`).
2. Mở file `services.py` -> Chuyển từ class `MemoService` gọi mongodb sang gọi `sqlite_client` execute SQL query cho từng hàm: Gồm `create_memo`, `list_memos`, `update_memo`, `delete_memo`.
3. Làm tương tự tới các services khác (`Reaction`, `Notification`, ...).
4. Khởi chạy uvicorn, POST/GET bằng Python requests/pytest hoặc giao diện web để xác thực.
---
## 🤖 Cẩm Nang Ép Khuôn Cho AI (Execution Protocol & Anti-Traps)
*Nếu AI được giao nhiệm vụ này, BẮT BUỘC phải đọc kỹ các lỗi thường gặp (traps) sau để không làm code bị crash.*
### 🚨 5 Cạm Bẫy API Cần Né Khi Sang SQLite:
1. **Trap `row_factory`:** Motor trả về Type `Dict`. SQLite mặc định trả về `Tuple`.
-> **FIX:** Bắt buộc cấu hình `db.row_factory = aiosqlite.Row` ngay trong lúc tạo pool connection để có thể access theo kiểu `row["id"]`.
2. **Trap JSON Arrays:** SQLite không có kiểu Mảng (Array/List). Các trường như Tags, Payload...
-> **FIX:** Phải gọi `json.dumps(tags)` lúc `INSERT`, và gọi `json.loads(row["tags"])` lúc `SELECT`.
3. **Trap ID Casting:** Frontend và Pydantic schemas hiện tại nhận ID là chuỗi (`"1"`). SQLite trả về số nguyên (`1`).
-> **FIX:** Ép kiểu `str(row["id"])` ở tất cả mọi nơi trả về Response schema.
4. **Trap Booleans:** SQLite lưu boolean dạng số (`0` hoặc `1`).
-> **FIX:** Lúc fetch ra phải cast: `bool(row["pinned"])`.
5. **Trap Datetime Serialization:** Pydantic đôi khi không đọc được datetime ISO tùy biến.
-> **FIX:** Lưu timestamp float (unix epoch) hoặc ISO cứng. Hàm utc_now() phải nhất quán ghi xuống TEXT ISO.
### 📜 AI Execution Workflow (Khuyên Dùng)
Đừng refactor tất cả các service cùng một lúc! Hãy làm theo Trình tự:
1. Code `init_db()` trong file `common/sqlite_client.py` chạy độc lập trước.
2. Viết xong `AuthService``MemoService` -> Chạy Server test Endpoint `POST /api/v1/memos` ngay lập tức!
3. Nếu thành công, mới bắt đầu code nối tiếp các Service nhỏ lẻ lại (Reactions, Comments, Inbox).
4. Data từ MongoDB cũ: Bỏ trắng, chúng ta làm fresh DB hoàn toàn để giảm nợ kỹ thuật.
### 🧪 Automated Testing Protocol (BẮT BUỘC CHO AI)
Trách nhiệm của AI không dừng lại ở việc viết code. Mày **PHẢI** tự động chạy Test để chứng minh code chạy được!
1. Quét kho tàng test cũ: Trong `backend/tests/` đã có sẵn các file như `test_memos.py`, `test_api_integration.py`. Đừng xóa chúng, hãy SỬA để chúng chạy với SQLite.
2. **Setup Test Database:**
- Đừng test đè lên `database/cuccu_note.db`.
- Hãy cấu hình SQLite để dùng `database/test.db` hoặc database trong RAM (`sqlite:///:memory:`) khi biến môi trường `ENV=test` được bật. Điểu chỉnh lại `conftest.py` tương ứng.
3. **Lện Chạy Tự Động:** AI bắt buộc phải gọi Terminal / Run Command lệnh sau để tự nghiệm thu:
```bash
cd C:\canifa-idea\chatbot-canifa-feedback\miniapp\cuccu_note\backend
.\.venv\Scripts\activate
pytest tests/test_memos.py -v
pytest tests/test_api_integration.py -v
```
4. Chỉ khi Pass **100%** Test thì mày (AI) mới được phép báo cáo là Task hoàn thành. Đóng ticket và dời file này vào thư mục `done/`.
This diff is collapsed.
This diff is collapsed.
# Kiến trúc AI Stylist: Mapping Cứng theo `Product Line` & `Màu Sắc`
## Vấn Đề Hiện Tại
Hệ thống AI đang đọc các Tag Text NLP (`phong_cach`, `dip_mac`) trong `ultra_descriptions`. Do data nhập liệu tay lộn xộn, AI Recommend bị nhiễu tĩnh, gợi ý sai hoàn toàn các Set đồ so với Logic chuẩn.
## Giải Pháp Mới
- **BỎ hòan toàn quét NLP Tags.**
- Chỉ quét duy nhất cột **Danh Mục Sản Phẩm (Product Line)**. Hệ thống sẽ có Hard-coded Mappings chỉ định rõ Danh Mục A khi ở Dịp B thì ĐƯỢC PHÉP đi với Danh Mục C.
- **Tiêu chí Màu sắc** dùng làm "Trọng tài Cuối cùng" để tính điểm và Rank Top hiển thị.
---
## 1. Ma Trận Dịp Mặc (Occasions) × Danh Mục (Product Line)
Map cứng trực tiếp vào Logic của Engine (Hoặc DB `chatbot_fashion_rules`).
| Dịp (Occasion) | Nếu Khách Chọn (Anchor) | Thì Gợi Ý Phối Cùng (Targets) |
| :--- | :--- | :--- |
| **💼 Đi làm công sở** | `Áo Sơ mi`, `Áo Polo`, `Blouse` | `Quần khaki`, `Quần âu`, `Chân váy`, `Blazer/Vest` |
| | `Quần khaki`, `Quần âu` | `Áo Sơ mi`, `Áo Polo`, `Cardigan` |
| **🛍️ Đi chơi / dạo phố** | `Áo phông`, `Áo kiểu`, `Áo nỉ` | `Quần jean`, `Quần soóc`, `Chân váy ngắn`, `Áo khoác gió` |
| | `Quần jean`, `Quần soóc` | `Áo phông`, `Áo nỉ`, `Áo kiểu` |
| **🏠 Ở nhà / mặc ngủ** | `Áo phông`, `Áo hai dây` | `Quần mặc nhà`, `Quần đùi cotton` |
| **🏖️ Du lịch** | `Áo phông`, `Váy liền` | `Chân váy maxi`, `Quần soóc`, `Mũ/Kính râm` |
*(Chỉ có các sản phẩm thỏa mãn mảng Target Line này mới được đưa vào phễu tính điểm màu sắc).*
---
## 2. Lưới Lọc "Hòa Sắc" (Color Synergy) Điểm Vòng 2
Sau khi có danh sách Target Product Line hợp quy:
- **Áo Màu Trắng/Đen/Trung Tính:** Buff `+30đ` cho Quần Trắng/Đen (An toàn). Buff `+25đ` cho Quần Dark (Cá tính).
- **Áo Màu Nổi (Cam/Hồng):** Ép `+30đ` cho các Quần Đen/Trắng để tiết chế lại. Các quần cùng màu Hồng sẽ bị trừ điểm nặng hoặc chỉ cho `+5đ` nếu là Tone-sur-tone.
- **Lưới Nhân khẩu học:** Nam auto trừ điểm nhóm Pastel/Hồng. Bé Gái auto buff điểm nhóm Nhạt/Bèo nhún.
## 3. Checklist Nhiệm vụ cho AI Agent (Claude Code)
_(Claude Code vui lòng thực hiện tuần tự các bước sau và check `[x]` khi hoàn thành)_
- [x] **Bước 1:** Đọc file `backend/worker/fashion_rules.json``backend/worker/stylist_engine.py` (hàm `_score`, `compute_dynamic_rule_matches`) để hiểu code base hiện tại đang chấm điểm bừa bãi theo Tags thế nào.
- [x] **Bước 2:** Đọc file DB gốc tại `backend/database/canifa_ai_dump.sqlite`. Xem schema của bảng `chatbot_fashion_rules` (gồm `anchor_category`, `target_category`, `match_role`, `occasion_tag`, `ai_reason`).
- [x] **Bước 3:** Tạo 1 script Python tại `backend/scripts/seed_product_line_matrix.py`. Script này kết nối vào SQLite, chạy lệnh dọn dẹp `DELETE FROM chatbot_fashion_rules;` và insert hàng loạt các record ứng với Ma trận Mapping ở Mục 1 (Ví dụ: `anchor='Áo sơ mi'`, `target='Quần âu'`, `occasion='di_lam_cong_so'`).
- [x] **Bước 4:** Chạy script tạo dữ liệu thành công. (Đã insert 51 rules: di_lam 21, di_choi 18, mac_nha 4, du_lich 8)
- [x] **Bước 5:** Refactor lại tệp `backend/worker/stylist_engine.py`:
- Thêm `_fetch_allowed_mappings()``_fetch_rules_with_reason()` để lấy rules từ DB.
- Sửa `_compute_matches()`: chỉ cho phép target nếu target_category nằm trong DB rules cho occasion đó.
- Cắt bỏ hoàn toàn `_occasion_score``_style_score` trong `_score()`.
- Sửa `_score()` chỉ còn color + role + material.
- Sửa `compute_dynamic_rule_matches()` để dùng DB rules filter và `_score()` mới.
- [x] **Bước 6:** Sửa lại `_color_score` đã có sẵn logic Color Synergy (dùng color_group_matrix). Cập nhật `fashion_rules.json` weights về `{"color": 50, "role": 30, "material": 20}`.
- [x] **Bước 7:** Test lại bằng cách kích hoạt `run_batch()` hoặc request POST đến `/api/fashion-matches/batch`, đảm bảo hệ thống render thành công toàn bộ `ai_matches` mà không sập.
# Ý Tưởng & Chiến Lược Thực Thi (Cuccu Sales AI)
Tài liệu này định hướng bức tranh nghiệp vụ và vai trò của Backend / Frontend trong hệ thống Sales Automation Workflow. (Cập nhật liên tục khi phát sinh Idea mới).
## 1. Tầm Nhìn Sản Phẩm (Product Idea)
- Đây là một **Mini n8n cho dân chốt sale**.
- Thay vì cấu hình chatbot tĩnh, User kéo thả một Workflow: `Khách vào nhắn -> Ai phân tích Intent -> Nếu muốn mua: Cầm mã sản phẩm đi hỏi API check tồn kho -> Còn hàng -> AI Tự sinh tin nhắn chốt đơn`.
## 2. Việc của Backend (BE) làm thế nào?
Backend là Trái Tim (được viết bằng **FastAPI**).
- Gồm các Router quản lý: Webhook, Khách hàng, Sản phẩm, CRM Inbox.
- Lõi là `workflows.py`: Lưu cấu trúc Node/Edge do User kéo thả.
- **Tiếp theo BE phải làm gì?**
1. **Tạo Mock Test Framework:** Viết code tự tráo lõi Data `asyncpg` sang *In-Memory SQLite* hoặc Mock Data tĩnh. (Không được chạm DB thật khi chạy unit test).
2. **Viết Workflow Execution Engine:** Xây dựng cục Engine thực thi graph. Khi Frontend nhấn luồng "Chạy thử", Backend đệ quy chạy từng Node (Ví dụ: `Logic Node` để vạch đường, gọi `Mcp Node` chọc vào DB lấy giá sản phẩm, gọi `Agent Node` cho GPT-4o-mini đẻ ra chữ có tuỳ chọn **SSE Stream** trả về Frontend).
3. **Bridge Webhook Thực Tế:** Cho ghép nối Facebook / Zalo vào `/api/webhooks`.
## 3. Việc của Frontend (FE) làm thế nào?
Frontend là Bảng Điều Khiển (được viết bằng **React + Vite + Shadcn/Tailwind**). Nằm trong thư mục `/frontend/`.
- Frontend có `React Flow` để quản lý giao diện vẽ Biểu Đồ (Canvas).
- **Tiếp theo FE phải làm gì?**
1. Xóa bỏ hoặc bỏ qua hoàn toàn các file `.html` tĩnh đang được render bởi thẻ Jinja2 ở Backend. Cắt đứt sự phụ thuộc của FE vào server FastAPI.
2. Map config Vite proxy: Override config cổng Vite để proxy mọi request `/api/*` tới thẳng `http://localhost:8000` (FastAPI).
3. Hiển thị Stream Message (SSE) khi Workflow chạy.
---
*Lưu ý: Bất kỳ Agent AI nào (Claude, Forge) khi nhận Task mới, hãy mở file này ra xem BE/FE đang ở giai đoạn nào để nắm Context.*
---
## 4. CuCu Note — Mini-App Note-Taking (Memos-based)
Nằm ở `miniapp/cuccu_note/`. Đây là một **note-taking app** được fork từ Memos, tích hợp thêm:
- **Teams & Workspace** — quản lý nhóm
- **Deadline & Priority** — gắn deadline cho mỗi note
- **AI Chatbot** — hỏi đáp với OpenAI về nội dung notes
- **Anonymous notes** — ghi chú không cần đăng nhập
### Trạng thái hiện tại:
- ✅ Core: Auth, CRUD Memo, Pin, Archive, Tags, Attachments, Version History
- 🔧 Đang làm: Reactions & Comments (Epic 1 — code xong, chờ test)
- ❌ Chưa làm: Inbox Notifications, PAT, Webhooks, SSO
### File kế hoạch chi tiết:
- `plan/doings/cuccu-note-feature-parity.md` — Tasks đang làm
- `plan/ideas/cuccu-note-next-features.md` — Ideas tương lai (AI, Kanban, Templates...)
# 📥 CuCu Note: Tầm Nhìn "Inbound Event Receiver" (Trung Tâm Hứng Dữ Liệu)
Tài liệu này xác định vị thế mới của CuCu Note. Thay vì chỉ là một app ghi chú cá nhân, CuCu Note sẽ hoạt động như một **CRM Inbox (Hộp thư trung tâm)** để hứng mọi sự kiện, feedback từ các con Bot N8n và hệ thống bên ngoài. Ai cũng có thể đẩy dữ liệu vào đây thông qua Webhooks.
---
## 🏗️ 1. Các Trang (Pages/Views) Cần Phải Có Thêm
Để biến CuCu Note thành một phễu hứng data thực thụ, chúng ta cần bổ sung các giao diện (Pages) sau vào Frontend của hệ thống:
### A. Trang Quản Lý Nguồn Vào (Integrations / Webhooks Settings)
Đây là nơi Admin tạo ra các "cổng" để hứng dữ liệu.
- **Tạo Endpoint:** Nút `[+ Tạo nguồn mới]`. Hệ thống cấp ra 1 URL dạng `cucu.in/api/v1/hooks/{token}`.
- **Tùy biến Tên Nguồn:** Ví dụ "Chatbot Fanpage Canifa", "Chatbot Zalo", "Form Khiếu Nại".
- **Lịch sử Request (Logs):** Xem lại chi tiết data (JSON) đã được bắn vào Cổng này hôm nay. Nếu lỗi, dễ dàng debug.
- **Mapping Data (Tùy chọn nâng cao):** Cấu hình để hệ thống biết móc field `message` trong file JSON để làm nội dung Note, lấy field `customer_name` làm Title.
### B. Trang Hộp Thư Triage (Inbox / Triage View)
Timeline ghi chú hiện tại của Memos không phù hợp để xử lý dữ liệu dồn dập. Cần thêm một Tab độc lập gọi là **Inbox**.
- **Danh sách "Unread":** Mọi sự kiện từ Chatbot đẻ ra sẽ nằm tại đây với trạng thái Chưa Đọc.
- **Thao tác nhanh (Quick Actions):** Di chuột vào một Note hiển thị nút `[Đã xử lý (Check)]`, `[Bỏ qua]`.
- **Mục tiêu:** Giúp nhân viên mỗi sáng mở ra thấy `20 Note Chưa đọc`, và xử lý dần cho kỳ hết để Inbox trống rỗng (Cảm giác vinh quang Zero-Inbox).
### C. Trang Quản Lý Người Dùng / Liên Hệ (Contacts Directory)
Khi hứng feedback, ta thường hứng kèm SĐT hoặc tên người dùng của khách hàng.
- Sinh ra một bến đỗ lưu trữ "Danh bạ khách hàng".
- **Giao diện:** Bấm vào sđt `090xxxx`, nó sẽ lọc (Filter) hiện ra tất cả các Feedback/Memos từ trước đến nay của đúng vị khách đó. Rất hữu ích cho chăm sóc khách hàng.
### D. Trang Cài Đặt Phân Luồng Tự Động (Rules Engine Page)
- Một luật (Rule) đơn giản: `Nếu [Nguồn = Chatbot Zalo] VÀ [Nội dung chứa từ "Hoàn tiền"] => Tự động gắn thẻ #hoan_tien VÀ set Priority = RED`.
- Trang này biến hệ thống thành một luồng chia việc cực kỳ trơn tru.
---
## 🚀 2. Kịch Bản Vận Hành Thực Tế
1. **Khách hàng** phàn nàn trên Fanpage Facebook.
2. **Chatbot (N8n/Forge)** gọi HTTP POST bắn dữ liệu về `CuCu Note Webhook URL`.
3. **Rules Engine** của CuCu Note tóm được JSON này, tự động đẻ ra 1 Memo, gài tag `#complaint` và ném vào mục **Inbox** của Workspace "Customer Service".
4. **Nhân viên CSKH** vào ca làm việc, mở trang **Inbox**, thấy Note báo màu cam. Đọc nội dung mà không cần mở Facebook.
5. Xử lý xong, nhân viên bấm `[Đã giải quyết]`. Note biến mất khỏi Inbox (được lưu vĩnh viễn vào kho lưu trữ chung). Inbox trở nên sạch sẽ.
---
## 📋 3. Task Kế Tiếp Cho Dev Team (Đề Xuất)
Nếu triển khai hướng này, thứ vô giá nhất và cần code ngay tắp lự là:
👉 **Xây dựng API Endpoint Inbound Webhook (`POST /hooks/{token}`)** bên backend để có lỗ hổng cho tụi Chatbot bắn data vào trước. Lúc đó ta mới tính chuyện hiển thị lên UI.
# 💡 CuCu Note — Ideas Tính Năng Tiếp Theo
> Focus: **Note-taking xuất sắc + Collaboration nhẹ nhàng**
> Không thêm AI phức tạp. Tập trung vào trải nghiệm core ghi chú.
---
## ✍️ Nhóm 1: Writing & Note Experience
| # | Tính Năng | Mô Tả | Priority |
|---|---|---|---|
| W1 | **Note Templates** | Chọn template khi tạo note: Meeting Notes, Bug Report, Daily Log, OKR... | 🔴 Cao |
| W2 | **Kanban View** | Toggle giữa List View ↔ Board View (columns theo tag hoặc status) | 🔴 Cao |
| W3 | **Table of Contents** | Auto-gen TOC từ heading `##`, `###` — hiện side panel khi note dài | 🟡 |
| W4 | **Drag-and-Drop Reorder** | Kéo thả sắp xếp thứ tự memo trong list | 🟡 |
| W5 | **Focus / Zen Mode** | Ẩn sidebar, tập trung viết full screen | 🟢 |
| W6 | **Word Count** | Đếm số từ, ký tự, thời gian đọc ước tính | 🟢 |
| W7 | **Two-way Backlinks** | Gõ `[[Tên note]]` để link → hiện "Referenced by" panel | 🟡 |
---
## 👥 Nhóm 2: Collaboration (Nhẹ nhàng, Thiết thực)
| # | Tính Năng | Mô Tả | Priority |
|---|---|---|---|
| C1 | **@mention trong note** | Gõ `@username` trong nội dung → người đó nhận notification | 🔴 Cao |
| C2 | **Inbox Notifications** | Bell icon hiện unread count — khi ai comment/mention vào note của bạn | 🔴 Cao (Epic 2) |
| C3 | **Note Permission per Team** | Chọn note này share với Team nào, ai được edit/view | 🟡 |
| C4 | **Read Receipts (Team)** | Biết ai trong team đã đọc note chưa | 🟢 |
---
## 📤 Nhóm 3: Export & Portability
| # | Tính Năng | Mô Tả | Priority |
|---|---|---|---|
| E1 | **Export PDF** | Export 1 note hoặc chọn nhiều notes → PDF đẹp | 🔴 Cao |
| E2 | **Export Markdown ZIP** | Backup toàn bộ workspace ra `.md` files nén `.zip` | 🟡 |
| E3 | **Import từ Obsidian / Notion** | Dán markdown vào là hiểu cú pháp | 🟢 |
---
## 🗂️ Nhóm 4: Organization & Search
| # | Tính Năng | Mô Tả | Priority |
|---|---|---|---|
| O1 | **Nested Tags** | Tag `project/canifa`, `project/nexus` — hiện tree trong sidebar | 🟡 |
| O2 | **Saved Filters (Shortcut)** | Lưu bộ lọc hay dùng (đã có skeleton, cần hoàn thiện) | 🟡 |
| O3 | **Global Search (Ctrl+K)** | Command palette tìm note theo title + content cùng lúc | 🟡 |
| O4 | **Starred / Bookmark** | Đánh dấu sao một số note quan trọng (khác Pinned) | 🟢 |
---
## 📱 Nhóm 5: UX Polish
| # | Tính Năng | Mô Tả | Priority |
|---|---|---|---|
| U1 | **PWA (Offline)** | Cài app trên điện thoại, xem note offline | 🟡 |
| U2 | **Custom Themes** | Chọn accent color, font chữ | 🟢 |
| U3 | **Keyboard Shortcuts** | `Ctrl+K` = new note, `Ctrl+/` = shortcuts help | 🟢 |
---
## 📋 Thứ Tự Làm (Sau khi xong Epic 1-6 Feature Parity)
```
Bước 1 → C2 Inbox (Epic 2 đang plan)
Bước 2 → C1 @mention (gắn liền Inbox)
Bước 3 → W1 Note Templates (dễ làm, user love ngay)
Bước 4 → W2 Kanban View (upgrade lớn về UX)
Bước 5 → E1 Export PDF (thiết thực, hay được hỏi)
Bước 6 → W7 Backlinks (niche nhưng note-taking users yêu)
```
---
*Đơn giản, không phức tạp. Làm tốt từng thứ một.*
---
## ⚡ Nhóm 6: Power Features (Brainstorm Bổ Sung)
> Những thứ khiến user **không muốn dùng app khác**.
| # | Tính Năng | Mô Tả | Priority | Ghi Chú |
|---|---|---|---|---|
| P1 | **Daily Note** | Auto-tạo note cho ngày hôm nay khi vào `/daily` — như Obsidian Daily Notes | 🔴 Cao | Habit-forming, kéo user back mỗi ngày |
| P2 | **Global Todo Tracker** | Gom tất cả `- [ ]` checkbox từ **mọi note** vào 1 view `/todos` | 🔴 Cao | Cực thiết thực cho daily workflow |
| P3 | **Graph View** | Visualize links giữa notes thành đồ thị tương tác | 🟡 | `MemoRelationForceGraph` **đã có sẵn** trong code! Chỉ cần mở khóa |
| P4 | **Note Colors / Labels** | Gán màu cho note (Red, Blue, Yellow...) — scan bằng mắt nhanh | 🟡 | Giữ UI clean, hiện badge màu nhỏ |
| P5 | **Lock Note** | Khoá note để không ai (kể cả mình) sửa nhầm | 🟢 | Simple, 1 toggle |
| P6 | **Mermaid Diagrams** | Render sơ đồ từ code block ` ```mermaid ``` ` trong markdown | 🟡 | Dev & PM team love |
| P7 | **Reading Mode** | Clean view ẩn editor toolbar, chỉ hiện nội dung — print-friendly | 🟢 | |
| P8 | **Note Activity Feed (Team)** | Xem ai đã sửa/tạo note gì gần đây trong workspace | 🟡 | |
| P9 | **Reaction trên Comment** | React emoji vào từng comment riêng lẻ (không chỉ memo) | 🟢 | Gắn với Epic 1 |
---
## 🏆 Full Priority Summary (Tất Cả)
```
🔴 Làm ngay (sau xong parity):
Epic 2 → Inbox Notifications
C1 → @mention trong note
P1 → Daily Note
P2 → Global Todo Tracker
W1 → Note Templates
W2 → Kanban View
E1 → Export PDF
🟡 Làm tiếp theo (tháng 2-3):
W3 → Table of Contents
W4 → Drag-and-Drop Reorder
W7 → Two-way Backlinks
P3 → Graph View (đã có component!)
P4 → Note Colors / Labels
P6 → Mermaid Diagrams
O1 → Nested Tags
O3 → Global Search (Ctrl+K)
E2 → Export Markdown ZIP
P8 → Note Activity Feed
🟢 Nice to have (để sau):
W5 → Focus / Zen Mode
W6 → Word Count
C3 → Note Permission per Team
C4 → Read Receipts
O4 → Starred / Bookmark
P5 → Lock Note
P7 → Reading Mode
P9 → Reaction on Comment
U1 → PWA Offline
U2 → Custom Themes
E3 → Import Obsidian/Notion
```
# 🏗️ Kế Hoạch: Cuccu Note Workspace Isolation (Tách Biệt Human & AI)
## 🎯 Mục Tiêu Cốt Lõi
Biến Cuccu Note thành một "Trung tâm chỉ huy" bằng cách chia cắt hoàn toàn dữ liệu. Chúng ta sẽ áp dụng cơ chế **"Hứng trên đầu"** bằng `workspace_id`.
Mục đích:
- Giữ sạch sẽ không gian dòng thời gian (Timeline) của con người.
- Hứng toàn bộ Log, Cảnh báo (SOS), và Tóm tắt sự kiện từ **Cuccu Sales AI** vào một không gian riêng biệt (AI Workspace).
---
## 🛠️ Giai Đoạn 1: Database Schema (SQLite) & Cấu trúc
*Đòi hỏi: Chỉnh sửa lại file `common/sqlite_client.py`.*
1. **Hiệu chỉnh bảng `memos`**:
- Thêm cột `workspace_id` (TEXT) vào bảng `memos`.
- Giá trị mặc định (Default): `'PERSONAL'` (Dành cho người).
- Giá trị hệ thống (System): `'AI_SALES_CRM'` (Dành cho webhooks từ hệ thống Sales).
- Tạo Index trên cột `workspace_id` để Query không bị sụt giảm hiệu năng khi Data lớn. `CREATE INDEX idx_memos_workspace ...`
2. **(Tùy chọn) Bảng `workspaces`**:
- Tạm thời chưa cần tạo bảng Workspaces nếu hardcode 2 loại trên để làm nhanh, nhưng để chuẩn chỉnh thì cứ tạo 1 bảng `workspaces` định danh 2 cái Rổ này.
---
## 🔌 Giai Đoạn 2: Xử Lý Backend API (FastAPI)
*Đòi hỏi: Chỉnh sửa luồng Fetch & Insert trong `services.py` và `memo_routes.py`.*
1. **Luồng đẩy (POST /memos)**:
- Mở rộng Pydantic Schema (`MemoCreate`) để nhận thêm biến tùy chọn `workspace_id`.
- Nếu call từ Webhook của AI Sales -> Request BẮT BUỘC inject `workspace_id = "AI_SALES_CRM"`.
- Nếu tạo note bằng tay trên giao diện web -> Request mặc định đẩy xuống `workspace_id = "PERSONAL"`.
2. **Luồng đọc (GET /memos)**:
- Bổ sung tham số Query Params: `GET /api/v1/memos?workspace_id=PERSONAL`.
- File `services.py` BẮT BUỘC chèn câu lệnh truy vấn SQLite `WHERE workspace_id = ?`.
- Tuyệt đối không cho phép truy vấn lọt dữ liệu (Ví dụ: đang ở Personal mà fetch nhầm cái Note của AI).
---
## 🖥️ Giai Đoạn 3: Cập nhật Frontend UI
*Đòi hỏi: Chỉnh sửa giao diện React để có Dropdown Chuyển đổi tầng.*
1. **Top-Level Switcher**:
- Đặt một UI Dropdown (hoặc Toggle Button) to bảng ở thanh Menu bên trái (Sidebar).
- Các lựa chọn: **👤 Ghi chú Cá Nhân** vs **🤖 Báo cáo AI Sales**.
2. **State Management**:
- Khi bấm chuyển Rổ (Workspace) -> Set Global State.
- Frontend tự động Clear cái Cột Timeline, gọi API `GET /memos` mới kèm theo parameter `workspace_id` tương ứng để vẽ lại giao diện.
---
## 🧪 Giai Đoạn 4: Automated Testing Protocol (LUẬT THÉP CHO AI)
*Đây là yêu cầu ép khuôn cho AI Code, KHÔNG CÓ BÀI TEST THÌ KHÔNG ĐƯỢC CHỐT DONE!*
1. **Backend Integration Tests**:
- Mở (hoặc tạo) file `tests/test_memos.py`. Thêm kịch bản test:
- Tạo Memo 1 (không truyền `workspace_id`) -> Kiểm tra DB xem mặc định là `PERSONAL` chưa.
- Tạo Memo 2 (truyền `workspace_id="AI_SALES_CRM"`).
- Fetch `GET /memos?workspace_id=PERSONAL` -> Phải trả về số lượng 1 (Memo 1). Tuyệt đối không chứa Memo 2.
- Fetch `GET /memos?workspace_id=AI_SALES_CRM` -> Phải trả về Memo 2.
2. **Lệnh chạy bắt buộc**:
```bash
pytest tests/test_memos.py -k "test_workspace_isolation" -v
```
3. Pass 100% test case mới được gạch bỏ Task! Đóng ticket!
This diff is collapsed.
# Quy Luật Lập Trình (Coding Rules & Agent Guidelines)
Bất kỳ AI Agent nào (Claude Code, Forge) trước khi code phải đọc kỹ các Rule của dự án Cuccu Sales AI SaaS.
## 1. 4 Nguyên tắc "Chấn phái" (Theo chuẩn Andrej Karpathy)
> Các Rule này đã được nạp trong file `CLAUDE.md` ngoài root. Chi tiết như sau:
- **1. Suy Nghĩ Trước Khi Code (Think Before Coding):**
Không tự đoán. Nếu không hiểu kiến trúc thì phải gọi lệnh báo lỗi hỏi người dùng. Đưa ra các trường hợp (tradeoffs) trước khi quyết định viết đè file.
- **2. Đơn Giản Là Nhất (Simplicity First):**
Không viết dư tính năng chưa ai yêu cầu. Không đẻ ra Wrapper Class hay Abstraction nếu nó chỉ gọi 1 lần. Backend FastAPI càng thuần càng tốt.
- **3. Phẫu Thuật Chính Xác (Surgical Changes):**
Hỏng đâu sửa đó. Bạn đang chỉnh sửa Route `workflows.py` thì không được "ngứa tay" qua dọn dẹp biến ở `main.py` hay dọn dẹp thư viện không liên quan, trừ phi nó break hoàn toàn code hiện tại. Format đúng theo style code cũ đang có.
- **4. Code Mục Tiêu & Vòng Lặp Test (Goal-Driven Execution):**
Khi được yêu cầu viết tính năng, ví dụ "Viết API lấy user", việc đầu tiên phải chạy `pytest` hoặc viết Unit Test Fail trước, sau đó code sao cho test Pass. Chạy test lại liên tục bằng terminal sau mỗi lần sửa.
## 2. Tiêu Chuẩn Kỹ Thuật Dự Án (Tech Stack Conventions)
### [BACKEND] (FastAPI)
1. **DB Framework:** Bắt buộc dùng `asyncpg` theo cấu trúc Core Pool hiện có (`common/postgres_client.py`). **Không dùng SQLAlchemy** hoặc các ORM làm cục súc hiệu năng hệ thống.
2. **Schema Validation:** Data đầu vào và đầu ra phải 100% bọc qua Pydantic Class định nghĩa trong folder `schemas/`. Tránh trả về Dict `{"a": 1}` tự do.
3. **Môi Trường Testing:** Luôn phải cô lập DB khi Test vòng Unit. Mọi thay đổi không được ghi đè bản ghi có thực trong PostgreSQL (Sử dụng Fixture mock với SQLite hoặc `pytest-mock` trả fake Dict data).
### [FRONTEND] (React + Vite)
1. **TailwindCSS & Shadcn UI:** Không được dùng file CSS thả rông `.css` trừ mục `index.css`. Mọi UI Design phải dùng Utility Class của Tailwind. Ưu tiên xài component có sẵn trong `components/ui`.
2. **State & Fetch:** Mặc định gọi API bằng `axios` trỏ tới `/api/`. Không Hardcode domain tuyệt đối (ví dụ `http://localhost:8000/api`) vì Vite Proxy sẽ xử lý chuyển hướng chéo hông (CORS free).
---
*Kỷ luật là sức mạnh hệ thống. Kẻ viết sai rule sẽ phá hoại ứng dụng. Hãy code cẩn thận vì dự án này của Bro.*
Nguyễn Tự Tùng
Vị trí ứng tuyển: Data Scientist
tutung13579@gmail.com | +84-842-013-095 | github.com/cedarnguyen
Giới thiệu
Thạc sĩ Data Mining for IoT với nền tảng vững chắc về học máy, học sâu và phân tích dựa trên dữ
liệu. Có kinh nghiệm thiết kế và triển khai các pipeline dữ liệu end-to-end sử dụng Python, PyTorch,
TensorFlow và Scikit-learn. Thành thạo mô hình thống kê, kỹ thuật đặc trưng, giải thích mô hình
(explainability) và triển khai hệ thống dự đoán. Có tư duy nghiên cứu và ứng dụng AI,
Kỹ năng & Chuyên môn
• Ngôn ngữ: Python, Bash, Shell
• Frameworks & Thư viện: PyTorch, TensorFlow, Scikit-learn, Keras, NumPy, Torchvision,
SHAP
• Mô hình Học sâu: CNN, ResNet, CASER, FaceNet
• Hệ thống gợi ý: Lọc cộng tác, Lọc theo nội dung, Mô hình lai
• Kỹ thuật ML: Hồi quy, Phân loại, Gom cụm, PCA, AI có thể giải thích (XAI)
• Thị giác máy tính: OpenCV, Torchvision Transforms
• Công cụ: Google Colab, CUDA, Kaggle, Visual Studio
• Hệ điều hành: Linux, Windows
Học vấn
Thạc sĩ Data Mining for IoT 2023 –
Trường Đại học Khoa học và Công nghệ Hà Nội / INP Toulouse
Cử nhân CNTT & Truyền thông 2019 – 2022
Trường Đại học Khoa học và Công nghệ Hà Nội, Việt Nam
Dự án tiêu biểu
Phát hiện Email Lừa Đảo (Phishing) bằng Mô hình Transformer BERT
09/2025 – 10/2025
Công nghệ: Python, PyTorch, Hugging Face Transformers, Scikit-learn, Pandas
• Thu thập và tiền xử lý dữ liệu từ bộ CEAS (bao gồm tiêu đề, nội dung và thông tin người gửi)
để huấn luyện mô hình.
• Ứng dụng mô hình ngôn ngữ BERT (bidirectional transformer) để học biểu diễn ngữ cảnh và
phân loại email lừa đảo / hợp lệ.
• Thực hiện fine-tuning mô hình BERT trên dữ liệu email, sử dụng kỹ thuật tokenization và
attention mask để xử lý đầu vào, tối ưu hàm mất mát bằng Cross-Entropy.
• Đánh giá mô hình đạt hiệu quả phân loại cao (90%) và khả năng tổng quát tốt trên tập kiểm
thử.
• Quy mô nhóm: 1
Phân loại bệnh lá có thể giải thích với SHAP và Grad-CAM (Master Project)
04/2025 – 09/2025
1
Công nghệ: Python, PyTorch, SHAP, Grad-CAM, OpenCV, Matplotlib
• Phát triển pipeline có thể giải thích cho ResNet18 và DenseNet sử dụng SHAP và Grad-CAM.
• Chỉnh sửa kiến trúc ResNet18 và so sánh với DenseNet để đánh giá độ chính xác phân loại.
• Phân tích lỗi chi tiết bằng cách khảo sát các dự đoán sai có độ tin cậy cao và dự đoán đúng có
độ tin cậy thấp.
• Tích hợp bounding box và heatmap để làm rõ lý do và đặc trưng quan trọng mà mô hình sử
dụng.
• Kết quả: Đánh giá hiệu suất mô hình, so sánh đầu ra XAI với ground truth và phân tích các
trường hợp sai để hiểu nguyên nhân.
• Quy mô nhóm: 1
Dự đoán biến động thị trường với MoLE, XGBoost, Random Forest & CNN-
BiLSTM và Hồi quy Bayes
06/2025 – 08/2025
Công nghệ: Python, PyTorch, XGBoost, SHAP, Scikit-learn
• Xây dựng và so sánh mô hình MoLE, Hồi quy Bayes và các mô hình khác để dự đoán biến động
giá VN30F1M và chọn ra mô hình phù hợp.
• Tích hợp XGBoost + SHAP để chọn đặc trưng và cải thiện khả năng giải thích.
• Xử lý dữ liệu chuỗi thời gian, huấn luyện mô hình, tinh chỉnh siêu tham số và trực quan hóa
kết quả.
• Kết quả: Tối đa precision qua việc tăng threshold đạt được 52% precision với recall ổn định
trong tháng 6/2025, hỗ trợ giảm rủi ro đồng thời giữ khả năng sinh lợi.
Hệ thống gợi ý sử dụng CASER
04/2024 – 08/2024
Công nghệ: Python, PyTorch, CASER, Pandas
• Thiết kế và triển khai hệ thống gợi ý tuần tự bằng CASER để dự đoán mục kế tiếp.
• Xây dựng pipeline đầy đủ: tiền xử lý, sinh embedding, huấn luyện và đánh giá mô hình.
• So sánh CASER với FP-Growth để đánh giá hiệu suất trong bài toán gợi ý tuần tự.
• Kết quả: So sánh hiệu quả giữa phương pháp mới và truyền thống.
• Quy mô nhóm: 1
Dự án bổ sung
Tích hợp Oracle Database & Giám sát cảm biến
02/2025 – 04/2025
Công nghệ: Oracle 19c, Python, Arduino, Pandas
• Cài đặt và cấu hình Oracle 19c trên Linux, bao gồm tablespace, schema và quản lý người dùng.
• Thiết kế schema cho dữ liệu IoT chuỗi thời gian với indexing tối ưu để truy vấn nhanh.
• Mô phỏng và sinh dữ liệu cảm biến IoT bằng Python, chèn vào Oracle DB.
• Tích hợp CSDL với API của thành viên nhóm để thu thập dữ liệu thời gian thực.
• Kết quả: Đạt hiệu suất ổn định với độ trễ truy vấn thấp khi dữ liệu cảm biến liên tục đổ về.
• Quy mô nhóm: 2
Triển khai cụm Hadoop-Spark tự động với Ansible
02/2025 – 04/2025
2
Công nghệ: Ansible, Hadoop, Spark, Bash, Ubuntu
• Phát triển playbook Ansible có thể tái sử dụng để tự động triển khai cụm Hadoop-Spark 3 node.
• Cấu hình xác thực bằng SSH key và sudo cho điều phối an toàn.
• Tự động cài đặt Hadoop & Spark với cấu hình đồng bộ trên toàn bộ node.
• Kiểm thử chức năng cụm thông qua nộp và chạy job.
• Kết quả: Hoàn thiện giải pháp Ansible để tự động hóa toàn bộ triển khai cụm Hadoop–Spark.
• Quy mô nhóm: 1
Kinh nghiệm làm việc
AI Engineer – Goline
06/2025 – 09/2025
Công nghệ: Python, PyTorch, TensorFlow, XGBoost, Scikit-learn
• Nghiên cứu và xây dựng các mô hình AI cho giao dịch định lượng (quantitative trading).
• Phân tích dữ liệu chuỗi thời gian tài chính để tìm các mẫu dự đoán và tín hiệu giao dịch.
• Phát triển, thử nghiệm và đánh giá nhiều mô hình (XGBoost, LSTM, Bayesian...) kết hợp với
kỹ thuật giải thích mô hình (SHAP).
• Thử nghiệm các mô hình kết hợp (ensemble, hybrid) để nâng cao độ ổn định và khả năng sinh
lợi.
• Kết quả: Hoàn thiện các nguyên mẫu mô hình giao dịch với độ chính xác và khả năng quản lý
rủi ro cân bằng.
Database Administrator – MPS (Myanmar Platinum Solution)
05/2023 – 03/2025
Công nghệ: Oracle DBs, Data Guard (DG), Real Application Clusters (RAC), Linux
Kiểm tra hệ thống cơ sở dữ liệu Oracle cho khách hàng trong và ngoài nước, đảm bảo tính sẵn
sàng và hiệu suất ổn định.
\ No newline at end of file
import asyncio
import os
import sys
# Add backend to sys.path
sys.path.insert(0, os.path.abspath("backend"))
from agent.lead_stage_agent.lead_search_tool import lead_search_tool
from common.sqlite_db import sqlite_db
async def test():
# Test "áo mặc trời mưa"
print("Testing 'áo mặc trời mưa'...")
res = await lead_search_tool.ainvoke({
"product_line_vn": ["Áo khoác"],
"keywords": ["chống thấm", "trượt nước", "cản gió"],
"reasoning": "Khách cần đồ đi mưa"
})
print(res)
# Test "đồ đi làm văn phòng nữ thanh lịch"
print("\nTesting 'đồ đi làm văn phòng nữ thanh lịch'...")
res = await lead_search_tool.ainvoke({
"product_line_vn": ["Áo sơ mi", "Áo Polo", "Quần Khaki", "Quần jean", "Váy liền", "Blazer"],
"keywords": ["thanh lịch", "công sở"],
"gender_by_product": "women",
"reasoning": "Đồ công sở nữ"
})
print(res)
if __name__ == "__main__":
asyncio.run(test())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment