# 06 — Lead Flow History: Personalized Search dựa trên lịch sử chat user

## 📌 Idea
Bảng `pg__canifa_chat__lead_flow_history` (2742 rows) lưu toàn bộ lịch sử chat của user
(kèm `device_id`, `products` đã xem, `lead_stage`). Idea: dùng lịch sử này để **personalize
kết quả tìm kiếm** — nếu user đã xem SP Y trước đó, không trả lại Y nữa; và **boost**
các danh mục user hay xem.

## 🗂 Codebase Scope
- **Root:** `d:\cnf\chatbot-canifa-feedback\backend`
- **Affected:**
  - `agent/tag_search_agent_v2/tag_search_tool.py` — thêm personalization layer
  - `agent/tag_search_agent_v2/controller.py` — truyền `device_id` vào tool context
  - `agent/tag_search_agent_v2/tag_search_graph.py` — nhận device_id từ request
  - `backend/123.db` → `pg__canifa_chat__lead_flow_history`
- **Languages:** Python 3.12+
- **Size:** M-L

## 📊 Current State Analysis

Schema `pg__canifa_chat__lead_flow_history`:
```
id, device_id, conv_id, is_human, message, ai_response, products, lead_stage, created_at
```
- `products` field: có thể là JSON array các SKU user đã xem/click
- `lead_stage`: giai đoạn khách hàng (chưa mua, đang xem, đã mua...)

Hiện tại `tag_search_tool` không nhận `device_id` → mỗi request được xử lý độc lập,
không có personalization. Kết quả có thể lặp lại SP user đã thấy nhiều lần.

## 🎯 Goal / Definition of Done
- `TagSearchInput` thêm optional field `device_id: str | None`
- Trước khi search: fetch lịch sử 7 ngày gần nhất theo `device_id` từ SQLite
- Extract `viewed_skus` = set các SKU từ `products` field trong history
- Post-filter: loại bỏ SP có SKU trong `viewed_skus` khỏi kết quả (nếu > 3 SP còn lại)
- Boost: nếu lịch sử hay xem "Áo phông" → tăng ORDER rank cho Áo phông trong query
- Privacy: không log device_id vào Langfuse (sensitive)

## 📋 Execution Plan

### Phase 1: Extend TagSearchInput (15 phút)
- [ ] Thêm `device_id: str | None = Field(default=None, description="Device ID từ client để personalize")`
- [ ] AI Planner không cần fill field này (controller sẽ inject)

### Phase 2: Fetch user history (30 phút)
- [ ] Hàm `_fetch_user_history(device_id: str) -> dict`:
  ```python
  # Returns: {"viewed_skus": set[str], "fav_categories": list[str]}
  ```
- [ ] SQLite query: last 30 messages của device_id trong 7 ngày
- [ ] Parse `products` JSON field → extract SKU list
- [ ] Cache kết quả per device_id + TTL 5 phút (Redis nếu có, else in-memory)

### Phase 3: Apply personalization (30 phút)
- [ ] Sau cascading search, trước stock check:
  ```python
  if req.device_id:
      history = await _fetch_user_history(req.device_id)
      # Filter đã xem
      products = [p for p in products if p["magento_ref_code"] not in history["viewed_skus"]]
      # Fallback: nếu filter quá nhiều → giữ top 3 cũ
      if len(products) < 3:
          products = original_products[:5]
  ```
- [ ] Thêm metadata trong output: `"personalized": bool`

### Phase 4: Controller inject device_id (20 phút)
- [ ] `controller.py`: extract `device_id` từ request headers hoặc body
- [ ] Inject vào tool call thông qua `TagSearchInput`

## ⚠️ Risks & Side Effects
- **Privacy:** device_id + history là PII-adjacent → không log, không trả ra client
- **Cold start:** New device (chưa có history) → không personalize → OK
- **Over-filtering:** User đã xem 20 SP nhưng kết quả search chỉ có 10 → tất cả bị filter → phải có minimum threshold giữ lại ít nhất 3 SP
- **Performance:** SQLite read + JSON parse → ~5ms → acceptable

## 🔧 Tools / Scripts Needed
- Inspect lịch sử format `products` column: `SELECT products FROM pg__canifa_chat__lead_flow_history LIMIT 5`
- Redis nếu muốn cache device_id history cross-request

## ⏱ Effort Estimate
**M** — ~4 giờ (complex data flow, privacy considerations)

---
_Captured: 2026-04-28 | Type: Codebase-Aware_
