Commit 3e89d304 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

feat: add Event Daily Engine and Reaction Simulator Deep cookbooks (16 chapters total)

parent 3487d1a5
Blazer
Bộ mặc nhà
Bộ quần áo
Bộ thể thao
Cardigan
Chân váy
Chăn cá nhân
Găng tay chống nắng
Khăn
Khăn lau đầu
Khăn mặt
Khăn tắm
Khẩu trang
Mũ
Mũ thể thao
Pyjama
Quần Body
Quần Khaki
Quần culottes
Quần dài
Quần giữ nhiệt
Quần jean
Quần leggings
Quần leggings mặc nhà
Quần lót
Quần lót tam giác
Quần lót đùi
Quần mặc nhà
Quần nỉ
Quần soóc
Quần váy
Túi xách
Tất
Váy liền
Áo Body
Áo Polo
Áo Sơ mi
Áo ba lỗ
Áo giữ nhiệt
Áo hai dây
Áo khoác
Áo khoác chần bông
Áo khoác chống nắng
Áo khoác dáng ngắn
Áo khoác dạ
Áo khoác gilet chần bông
Áo khoác gió
Áo khoác lông vũ
Áo khoác nỉ có mũ
Áo khoác nỉ không mũ
Áo khoác sợi
Áo kiểu
Áo len
Áo len gilet
Áo lót
Áo mặc nhà
Áo nỉ
Áo nỉ có mũ
Áo phông
\ No newline at end of file
This diff is collapsed.
...@@ -5,7 +5,7 @@ from pathlib import Path ...@@ -5,7 +5,7 @@ from pathlib import Path
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(prefix="/event_daily", tags=["Event Daily"]) router = APIRouter(prefix="/api/event_daily", tags=["Event Daily"])
BASE_DIR = Path(__file__).resolve().parents[2] BASE_DIR = Path(__file__).resolve().parents[2]
DB_PATH = BASE_DIR / "database" / "autotrain.db" DB_PATH = BASE_DIR / "database" / "autotrain.db"
...@@ -17,6 +17,8 @@ def get_db() -> sqlite3.Connection: ...@@ -17,6 +17,8 @@ def get_db() -> sqlite3.Connection:
class EventPromptUpdate(BaseModel): class EventPromptUpdate(BaseModel):
prompt: str prompt: str
start_date: str | None = None
end_date: str | None = None
@router.get("/events") @router.get("/events")
async def list_events(): async def list_events():
...@@ -38,7 +40,9 @@ async def list_events(): ...@@ -38,7 +40,9 @@ async def list_events():
"type": row["event_type"], "type": row["event_type"],
"date": row["event_date"], "date": row["event_date"],
"isToday": bool(row["is_today"]), "isToday": bool(row["is_today"]),
"prompt": row["prompt"] "prompt": row["prompt"],
"startDate": row["start_date"],
"endDate": row["end_date"]
}) })
return events return events
...@@ -46,7 +50,7 @@ async def list_events(): ...@@ -46,7 +50,7 @@ async def list_events():
async def update_event_prompt(event_id: str, data: EventPromptUpdate): async def update_event_prompt(event_id: str, data: EventPromptUpdate):
conn = get_db() conn = get_db()
cur = conn.cursor() cur = conn.cursor()
cur.execute("UPDATE event_daily_prompts SET prompt = ?, updated_at = CURRENT_TIMESTAMP WHERE event_id = ?", (data.prompt, event_id)) cur.execute("UPDATE event_daily_prompts SET prompt = ?, start_date = ?, end_date = ?, updated_at = CURRENT_TIMESTAMP WHERE event_id = ?", (data.prompt, data.start_date, data.end_date, event_id))
if cur.rowcount == 0: if cur.rowcount == 0:
conn.close() conn.close()
raise HTTPException(status_code=404, detail="Event not found") raise HTTPException(status_code=404, detail="Event not found")
......
...@@ -9,7 +9,7 @@ from api.api_sql.text_to_sql_route import router as text_to_sql_router ...@@ -9,7 +9,7 @@ from api.api_sql.text_to_sql_route import router as text_to_sql_router
from api.api_sql.user_insight_route import router as user_insight_router from api.api_sql.user_insight_route import router as user_insight_router
from api.autotrain.auto_pipeline_route import router as pipeline_router from api.autotrain.auto_pipeline_route import router as pipeline_router
from api.autotrain.autotrain_route import router as autotrain_router from api.autotrain.autotrain_route import router as autotrain_router
from api.autotrain.event_daily_route import router as event_daily_router from api.event_daily.event_daily_route import router as event_daily_router
from api.cache.cache_route import router as cache_router from api.cache.cache_route import router as cache_router
from api.common.auth_route import router as auth_router from api.common.auth_route import router as auth_router
......
Chân váy
Găng tay chống nắng
Khăn
Khẩu trang
Mũ
Quần Body
Quần Khaki
Quần dài
Quần giữ nhiệt
Quần jean
Quần leggings
Quần nỉ
Túi xách
Váy liền
Áo Body
Áo Sơ mi
Áo ba lỗ
Áo giữ nhiệt
Áo hai dây
Áo kiểu
Áo len
Áo nỉ
Áo nỉ có mũ
Áo phông
\ No newline at end of file
import sqlite3
import json
conn = sqlite3.connect('D:/cnf/chatbot-canifa-feedback/backend/database/autotrain.db')
conn.row_factory = sqlite3.Row
print(conn.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='event_daily_prompts'").fetchone()[0])
rows = conn.execute("SELECT * FROM event_daily_prompts").fetchall()
print(json.dumps([dict(row) for row in rows], ensure_ascii=False, indent=2))
import sqlite3
def migrate():
conn = sqlite3.connect('D:/cnf/chatbot-canifa-feedback/backend/database/autotrain.db')
cur = conn.cursor()
try:
cur.execute("ALTER TABLE event_daily_prompts ADD COLUMN start_date TEXT")
except Exception as e:
print("start_date:", e)
try:
cur.execute("ALTER TABLE event_daily_prompts ADD COLUMN end_date TEXT")
except Exception as e:
print("end_date:", e)
conn.commit()
conn.close()
print("Migration complete")
if __name__ == '__main__':
migrate()
import sqlite3
def mock_events():
conn = sqlite3.connect('D:/cnf/chatbot-canifa-feedback/backend/database/autotrain.db')
cur = conn.cursor()
events = [
("evt_daily", "Mặc định hàng ngày", "Daily", "All days", 1, "- Luôn chào hỏi thân thiện\n- Gợi ý đồ dạo phố, công sở nhẹ nhàng", None, None),
("evt_tet", "Tết Nguyên Đán 2027", "Campaign", "Tháng 1/2027", 0, "- Chúc mừng năm mới\n- Ưu tiên màu đỏ, vàng, trang phục may mắn", "2027-01-01", "2027-02-15"),
("evt_304", "Giải phóng Miền Nam", "Campaign", "30/04/2026", 0, "- Chào mừng đại lễ 30/4\n- Gợi ý áo thun năng động, trang phục du lịch", "2026-04-20", "2026-05-02"),
("evt_15", "Quốc tế Lao động", "Campaign", "01/05/2026", 0, "- Kỷ niệm Quốc tế lao động\n- Trang phục thoải mái nghỉ dưỡng", "2026-04-20", "2026-05-02"),
("evt_29", "Quốc Khánh 2/9", "Campaign", "02/09/2026", 0, "- Chào mừng Quốc khánh\n- Áo cờ đỏ sao vàng, màu cờ sắc áo", "2026-08-25", "2026-09-05"),
("evt_summer", "Chiến dịch Mùa Hè", "Campaign", "Hè 2026", 0, "- Mùa hè rực rỡ\n- Đẩy mạnh quần short, áo thun mát mẻ, đồ bơi", "2026-05-01", "2026-08-31"),
("evt_winter", "Chiến dịch Mùa Đông", "Campaign", "Đông 2026", 0, "- Mùa đông ấm áp\n- Tập trung áo khoác dạ, áo len lông cừu", "2026-10-01", "2026-12-31"),
("evt_gioto", "Giỗ tổ Hùng Vương", "Campaign", "Mùng 10 tháng 3", 0, "- Nhớ về cội nguồn\n- Đồ lịch sự, trang nhã, đi đền chùa", "2026-03-25", "2026-04-05"),
]
for evt in events:
try:
# Using INSERT OR REPLACE to ensure they exist and overwrite if needed
cur.execute("""
INSERT OR REPLACE INTO event_daily_prompts
(event_id, event_name, event_type, event_date, is_today, prompt, start_date, end_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", evt)
except Exception as e:
print("Error inserting:", evt[0], e)
conn.commit()
conn.close()
print("Mock events inserted.")
if __name__ == '__main__':
mock_events()
# Triết lý thiết kế: Tại sao LLM là Router, không phải Stylist?
> **Tóm tắt:** Tài liệu này giải thích triết lý cốt lõi nhất của hệ thống AI Canifa: **LLM không phải là chuyên gia thời trang. LLM là kẻ điều hướng tọa độ trên bản đồ dữ liệu (Graph Router).** Chuyên môn thời trang nằm chết cứng trong Database. LLM chỉ làm đúng 1 việc: chọn cửa nào để mở.
---
## 1.1. Bài toán gốc: "Con AI bịa đồ"
Khi xây dựng chatbot thời trang, hầu hết các team sẽ nghĩ:
> *"Nhét kiến thức thời trang vào Prompt → LLM tự phối đồ → Trả về kết quả"*
**Đây là cái bẫy chết người.** Tại sao?
| Vấn đề | Mô tả | Hậu quả thực tế |
|--------|-------|-----------------|
| **Hallucination** | LLM bịa ra SKU không tồn tại | Khách click link → 404 → Mất niềm tin |
| **Inconsistency** | Hỏi 2 lần, trả lời 2 kiểu | Khách hoang mang, không dám mua |
| **Demographic Blindness** | Gợi ý đồ người lớn cho trẻ em | Vi phạm brand safety → Scandal |
| **Season Mismatch** | Gợi áo phao giữa mùa hè | Trải nghiệm kỳ quặc |
| **No Inventory Awareness** | Gợi đồ hết hàng | Khách frustrated, bounce rate tăng |
> **Insight từ ngành:** Theo nghiên cứu của Envive.ai (2025), chatbot AI tăng tỉ lệ upsell **20-30%** — nhưng CHỈ KHI gợi ý dựa trên **dữ liệu thật** (kho hàng, lịch sử mua, demographic). Dùng "cảm hứng" của LLM → tỉ lệ return tăng, NPS giảm.
---
## 1.2. Triết lý "Người mù và Cái rãnh" (The Blind Man & The Rail)
Hãy tưởng tượng con AI như một **người mù rất giỏi nói chuyện**. Nó có thể đối thoại cực kỳ tự nhiên, duyên dáng. Nhưng nó **không nhìn thấy đường**.
Nếu để nó tự đi (Pure LLM) → nó sẽ lao xuống mương (Hallucinate).
**Giải pháp của Canifa:** Xây sẵn **những cái rãnh trên đường** (Pre-computed JSON trong Database). Người mù (LLM) cứ chọc gậy vào rãnh (Data) là tự khắc đi đúng đường.
```
┌─────────────────────────────────────────────────────┐
│ PURE LLM APPROACH │
│ │
│ User ──→ LLM (Tự phối đồ) ──→ Response │
│ ↑ │
│ Kiến thức = Training Data (cũ, thiếu) │
│ Kết quả = ❌ Không kiểm soát được │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ CANIFA HYBRID APPROACH │
│ │
│ User ──→ LLM (Chọn cửa) ──→ Database (Lấy data) │
│ ↑ ↓ │
│ Vai trò = Router Kết quả = ✅ 100% │
│ sản phẩm thật │
└─────────────────────────────────────────────────────┘
```
---
## 1.3. Ba chìa khóa của Graph Router
Mỗi Sản phẩm (SKU) trong Database là một **Trạm dừng chân (Node)**. Khi AI đứng ở một Trạm, nó cầm trong tay **3 chùm chìa khóa** (3 mảng JSON đã được tính toán sẵn):
```mermaid
graph TD
SKU["🏷️ SKU Hiện Tại<br/>(Trạm đang đứng)"]
KEY1["🔑 Chìa 1: outfit_recommendations<br/>Cross-sell / Phối đồ<br/><i>Áo A → Quần B, Váy C</i>"]
KEY2["🔑 Chìa 2: similar_items<br/>Down-sell / Thay thế<br/><i>Áo A → Áo A1 rẻ hơn, Áo A2 khác màu</i>"]
KEY3["🔑 Chìa 3: Product Metadata (DNA)<br/>Upsell / Mở rộng<br/><i>Form, Chất liệu, Dịp, Giá</i>"]
SKU --> KEY1
SKU --> KEY2
SKU --> KEY3
KEY1 -->|"Khách: Phối gì?"| CROSS["✅ Gợi đồ phối"]
KEY2 -->|"Khách: Có rẻ hơn?"| ALT["✅ Đồ thay thế"]
KEY3 -->|"Khách: Tìm thêm?"| EXPAND["✅ Tìm kiếm mở rộng"]
style SKU fill:#f59e0b,stroke:#d97706,color:#000
style KEY1 fill:#3b82f6,stroke:#2563eb,color:#fff
style KEY2 fill:#10b981,stroke:#059669,color:#fff
style KEY3 fill:#8b5cf6,stroke:#7c3aed,color:#fff
```
| Chìa khóa | Cột Database | Khi nào dùng? | Complexity |
|------------|-------------|---------------|------------|
| **Outfit (Cross-sell)** | `outfit_recommendations` | Khách hỏi "phối gì?" | O(1) — JSON lookup |
| **Similar (Down-sell)** | `similar_items` | Khách hỏi "có cái nào giống vậy?" | O(1) — JSON lookup |
| **DNA (Upsell/Expand)** | Product metadata columns | Khách hỏi "tìm thêm kiểu này" | O(log n) — Indexed query |
---
## 1.4. Khác biệt cốt tử so với các hệ thống khác
| Tiêu chí | Pure LLM | RAG thuần (Vector Search) | **Canifa (Graph Router)** |
|----------|---------|--------------------------|--------------------------|
| **Nguồn kiến thức** | Training data (cũ) | Embedding similarity | Pre-computed JSON (do Merchandising duyệt) |
| **Độ chính xác** | ~60% (hay bịa) | ~75% (hay nhầm) | **~99%** (data thật, duyệt thủ công) |
| **Tốc độ** | 1-3s (LLM thinking) | 200-500ms (vector search) | **<5ms** (JSON lookup) |
| **Kiểm soát** | ❌ Không | ⚠️ Phụ thuộc embedding | ✅ **100% kiểm soát** |
| **Hallucination** | Cao | Trung bình | **Zero** |
| **Multi-turn** | Dễ lạc topic | Mỗi turn search lại | **Mượt mà** (Pivoting) |
> **Kết luận:** Con LLM trong hệ thống Canifa không cần biết gì về thời trang. Nó chỉ cần biết: *"Khi khách hỏi A, tôi mở cửa nào?"* Mọi kiến thức thời trang đã được "đóng gói" sẵn trong Database bởi đội Merchandising.
# Kiến trúc Pre-computed Graph (Đồ thị tính toán sẵn)
> **Tóm tắt:** Mỗi sản phẩm trong kho Canifa không phải là một "hàng hóa đơn lẻ" — nó là một **Node (Đỉnh)** trên một mạng lưới khổng lồ. Các cạnh nối (Edges) giữa các Node chính là các cột JSON `outfit_recommendations` và `similar_items`. Hệ thống này biến toàn bộ catalog ~2000 SKU thành một **Knowledge Graph** mà AI có thể "đi bộ" (traverse) với tốc độ O(1).
---
## 2.1. Tổng quan: Product Knowledge Graph
Thay vì dùng Neo4j hay Graph Database phức tạp, Canifa mô phỏng hoàn hảo một Knowledge Graph bằng **2 cột JSON** trong bảng SQL:
```mermaid
graph LR
subgraph "📦 Bảng Product Dimension"
A["Áo Polo<br/>POLO_01"]
B["Quần Jean<br/>JEAN_02"]
C["Giày Sneaker<br/>SHOE_03"]
D["Túi Tote<br/>BAG_04"]
E["Áo Thun<br/>TSHIRT_05"]
F["Quần Khaki<br/>KHAKI_06"]
end
A -->|"outfit_rec"| B
A -->|"outfit_rec"| C
B -->|"outfit_rec"| C
B -->|"outfit_rec"| D
C -->|"outfit_rec"| D
A -.->|"similar"| E
B -.->|"similar"| F
style A fill:#f59e0b,stroke:#d97706,color:#000
style B fill:#3b82f6,stroke:#2563eb,color:#fff
style C fill:#10b981,stroke:#059669,color:#fff
style D fill:#8b5cf6,stroke:#7c3aed,color:#fff
style E fill:#f59e0b,stroke:#d97706,color:#000
style F fill:#3b82f6,stroke:#2563eb,color:#fff
```
**Chú thích:**
- Đường liền `──→` = `outfit_recommendations` (Phối đồ / Cross-sell)
- Đường đứt `- - →` = `similar_items` (Thay thế / Down-sell)
---
## 2.2. Cấu trúc dữ liệu JSON trong Database
### Cột `outfit_recommendations` (Phối đồ)
Mỗi sản phẩm chứa một mảng JSON các sản phẩm được **chuyên gia Merchandising duyệt** là hợp khi mặc cùng:
```json
{
"product_code": "POLO_01",
"outfit_recommendations": [
{
"code": "JEAN_02",
"name": "Quần Jean Ống Rộng",
"role": "bottom",
"score": 92,
"reason": "Form slim-fit của Polo + ống rộng Jean tạo cân bằng tỉ lệ"
},
{
"code": "SHOE_03",
"name": "Giày Sneaker Trắng",
"role": "footwear",
"score": 88,
"reason": "Smart-casual hoàn chỉnh, tiệp tông trắng"
}
]
}
```
**Các trường quan trọng:**
| Trường | Mục đích | Ví dụ |
|--------|---------|-------|
| `code` | Mã SKU để truy vấn tiếp (Pivoting) | `JEAN_02` |
| `role` | Vai trò trong outfit (top/bottom/outerwear/footwear/accessory) | `bottom` |
| `score` | Điểm tương thích (từ Scoring Engine 100đ) | `92` |
| `reason` | Lý do phối — AI dùng để **chém gió** tự nhiên | `"Cân bằng tỉ lệ"` |
> **Cái hay:** Trường `reason` là vũ khí bí mật. AI không cần tự bịa lý do phối đồ. Nó đọc nguyên cái `reason` từ Database rồi diễn đạt lại bằng ngôn ngữ tự nhiên. **Zero effort, Zero hallucination.**
### Cột `similar_items` (Sản phẩm tương tự)
```json
{
"product_code": "POLO_01",
"similar_items": [
{
"code": "TSHIRT_05",
"name": "Áo Thun Cotton Basic",
"similarity_type": "same_category",
"price_diff": "-200k",
"key_diff": "Chất liệu Cotton thường thay vì Pima"
},
{
"code": "POLO_SPORT_09",
"name": "Áo Polo Thể Thao",
"similarity_type": "same_form",
"price_diff": "-150k",
"key_diff": "Form giống, vải Polyester thoáng mát hơn"
}
]
}
```
---
## 2.3. Tại sao dùng JSON thay vì Graph Database (Neo4j)?
| Tiêu chí | Neo4j (Graph DB) | **JSON Columns (Canifa)** |
|----------|------------------|--------------------------|
| **Tốc độ query** | ~10-50ms (Cypher query) | **<1ms** (JSON parse) |
| **Chi phí vận hành** | Thêm 1 server, license | **$0** (dùng luôn SQL hiện tại) |
| **Độ phức tạp** | Cần team DevOps riêng | **Copy-paste JSON** |
| **Tính linh hoạt** | Rất cao (arbitrary traversal) | Đủ dùng (1-2 hop) |
| **Scale** | Hàng triệu nodes | **~2000 SKU** (quá đủ) |
| **Maintenance** | Cần đồng bộ 2 DB | **1 nguồn duy nhất** |
> **Kết luận:** Với catalog ~2000 SKU của Canifa, dùng Neo4j như dùng súng đại bác bắn muỗi. JSON columns trong SQL cho tốc độ O(1), chi phí $0, và đội Merchandising có thể tự sửa bằng tay mà không cần developer.
---
## 2.4. Quy trình Pre-compute (Batch Job)
Graph không được tính toán real-time. Nó được **pre-compute** bằng Batch Job chạy định kỳ:
```mermaid
sequenceDiagram
participant Cron as ⏰ Batch Job<br/>(Chạy đêm)
participant Engine as 🧮 Scoring Engine<br/>(100đ Algorithm)
participant Rules as 📋 Merchandising Rules<br/>(59 sản phẩm)
participant DB as 🗃️ Database<br/>(Product Table)
participant AI as 🤖 AI Agent<br/>(Runtime)
Note over Cron,DB: === GIỜ TÍNH TOÁN (2:00 AM) ===
Cron->>Engine: Lấy toàn bộ catalog (~2000 SKU)
Engine->>Engine: Chấm điểm Color + Occasion + Role + Season
Engine->>Rules: Kiểm tra bảng luật 59 SP (Bonus +25đ)
Rules-->>Engine: Áo A + Quần B = Luật cứng ✅
Engine->>DB: Ghi JSON vào outfit_recommendations
Engine->>DB: Ghi JSON vào similar_items
Note over AI,DB: === GIỜ PHỤC VỤ (24/7) ===
AI->>DB: Khách hỏi Áo A → SELECT outfit_recommendations
DB-->>AI: JSON trả ngay (<1ms)
AI->>AI: Parse JSON → Chém gió tự nhiên
```
**Ưu điểm của Pre-compute:**
| Metric | Real-time Compute | **Pre-compute (Canifa)** |
|--------|-------------------|--------------------------|
| Latency per request | 2-5 giây | **<5ms** |
| CPU cost per request | Cao (chạy algorithm) | **~0** (đọc JSON) |
| Consistency | Khác nhau mỗi lần | **100% giống nhau** |
| Merchandising control | Không có | **Duyệt trước khi publish** |
---
## 2.5. Cơ chế cập nhật Graph
Graph không phải bất biến. Nó được cập nhật khi:
1. **Sản phẩm mới nhập kho** → Batch Job chạy lại → Tính toán phối đồ cho SP mới
2. **Đội Merchandising thêm luật** → Cập nhật bảng `chatbot_fashion_rules` → Batch Job tự động merge
3. **Hết hàng (Out of Stock)** → Runtime filter: AI kiểm tra stock trước khi gợi ý, nếu hết hàng → **skip và lấy item tiếp theo** trong danh sách
4. **Mùa thay đổi** → Cập nhật `season_tag` → Batch Job tự động re-score
> **Nguyên tắc vàng:** Graph chỉ thay đổi ở **tầng Batch** (đêm). Tầng Runtime (ban ngày) chỉ **đọc** — không bao giờ ghi. Điều này đảm bảo tính nhất quán tuyệt đối trong suốt 1 ngày phục vụ.
# State Machine: Cỗ máy trạng thái hội thoại
> **Tóm tắt:** Mỗi lượt chat của khách hàng không phải là một câu hỏi độc lập — nó là một **bước nhảy trên đồ thị trạng thái (State Machine)**. AI luôn biết mình đang đứng ở Trạm (State) nào, và từ Trạm đó chỉ có một số cửa (Transitions) hợp lệ để đi tiếp. Đây là lý do AI Canifa **không bao giờ bị "ngáo"** trong multi-turn conversation.
---
## 3.1. State Machine tổng quan
Hệ thống hội thoại của Canifa hoạt động như một **Finite State Machine (FSM)** với các trạng thái rõ ràng:
```mermaid
stateDiagram-v2
[*] --> IDLE: Khách mở chat
IDLE --> ANCHORED: Khách hỏi về 1 sản phẩm cụ thể
ANCHORED --> CROSS_SELL: "Phối gì cho hợp?"
ANCHORED --> DOWN_SELL: "Có cái nào rẻ hơn?"
ANCHORED --> INFO: "Áo này chất liệu gì?"
CROSS_SELL --> RE_ANCHOR: Khách chốt SP được gợi ý
DOWN_SELL --> RE_ANCHOR: Khách chốt SP thay thế
RE_ANCHOR --> CROSS_SELL: Tiếp tục phối đồ
RE_ANCHOR --> DOWN_SELL: Tìm SP rẻ hơn nữa
RE_ANCHOR --> EXPAND: "Tìm thêm kiểu này"
EXPAND --> RE_ANCHOR: Khách chốt SP mới
INFO --> ANCHORED: Quay lại SP gốc
note right of ANCHORED
Gốc hiện tại = SKU cụ thể
AI cầm 3 chìa khóa
end note
note right of RE_ANCHOR
ĐÂY LÀ "MỒI CÂU"
SP output → SP input mới
end note
```
---
## 3.2. Giải thích từng State
### State 1: `IDLE` — Chưa có ngữ cảnh
Khách vừa mở chat, chưa hỏi gì cụ thể. AI ở trạng thái chờ.
- **Ví dụ:** *"Chào em!"*, *"Em ơi tư vấn giúp chị"*
- **AI xử lý:** Chào lại, hỏi nhu cầu mua sắm
- **Chìa khóa khả dụng:** Không có (chưa có Gốc)
### State 2: `ANCHORED` — Đã neo vào 1 SKU cụ thể
Khách đã nhắc đến hoặc đang xem 1 sản phẩm cụ thể. AI biết rõ "Gốc" hiện tại là gì.
- **Ví dụ:** *"Áo POLO_01 này có size L không?"*, *"Cái áo này đẹp thế"*
- **AI xử lý:** Ghi nhận `current_anchor = POLO_01`
- **Chìa khóa khả dụng:** Cả 3 (Outfit, Similar, DNA)
### State 3: `CROSS_SELL` — Đang gợi đồ phối
Khách hỏi phối đồ. AI mở chìa khóa `outfit_recommendations`.
- **Ví dụ:** *"Áo này mặc với quần gì?"*
- **AI xử lý:** Đọc JSON → Trả Quần B, Giày C
- **Transition tiếp:** Khách chốt Quần B → Chuyển sang `RE_ANCHOR` (Quần B thành Gốc mới)
### State 4: `DOWN_SELL` — Đang tìm đồ thay thế
Khách muốn tìm đồ tương tự nhưng giá rẻ hơn hoặc khác màu.
- **Ví dụ:** *"Có cái nào rẻ hơn không?"*, *"Có màu khác không?"*
- **AI xử lý:** Đọc JSON `similar_items` → Trả Áo A1, A2
- **Transition tiếp:** Khách chốt Áo A1 → `RE_ANCHOR` (Áo A1 thành Gốc mới)
### State 5: `RE_ANCHOR` — 🔥 CÁI ĂN TIỀN (Pivoting)
**Đây chính là trạng thái "Mồi Câu Liên Hoàn".** Sản phẩm đầu ra (output) của lượt trước trở thành sản phẩm đầu vào (input/Gốc) của lượt sau.
```
Lượt 1: Gốc = Áo A → Output = Quần B
Lượt 2: Gốc = Quần B → Output = Giày C ← PIVOT!
Lượt 3: Gốc = Giày C → Output = Túi D ← PIVOT!
...vô tận...
```
### State 6: `EXPAND` — Tìm kiếm mở rộng (DNA Inheritance)
Khách muốn xem thêm nhiều sản phẩm cùng kiểu. AI sử dụng **DNA** (metadata) của sản phẩm Gốc để query mở rộng.
- **Ví dụ:** *"Tìm cho chị thêm mấy cái quần ống rộng đi làm"*
- **AI xử lý:** Copy DNA của Quần B `[Form: Ống rộng] + [Dịp: Đi làm]` → Query bảng Product Dimension
- **(Xem chi tiết ở Chương 4: DNA Inheritance)**
---
## 3.3. Transition Rules (Luật chuyển trạng thái)
| Từ State | Sang State | Trigger (Ý định khách) | AI Action |
|----------|-----------|----------------------|-----------|
| IDLE | ANCHORED | Nhắc đến SKU / Xem SP | Ghi nhận `current_anchor` |
| ANCHORED | CROSS_SELL | "Phối gì?" / "Mặc với gì?" | Đọc `outfit_recommendations` |
| ANCHORED | DOWN_SELL | "Có rẻ hơn?" / "Có màu khác?" | Đọc `similar_items` |
| ANCHORED | INFO | "Chất liệu?" / "Size?" | Đọc product metadata |
| CROSS_SELL | RE_ANCHOR | Khách chọn 1 SP trong danh sách | `current_anchor = SP mới` |
| DOWN_SELL | RE_ANCHOR | Khách chọn 1 SP thay thế | `current_anchor = SP mới` |
| RE_ANCHOR | CROSS_SELL | Tiếp tục phối | Đọc `outfit` của Gốc mới |
| RE_ANCHOR | DOWN_SELL | Tìm thay thế tiếp | Đọc `similar` của Gốc mới |
| RE_ANCHOR | EXPAND | "Tìm thêm kiểu này" | Dùng DNA để query |
| Bất kỳ | IDLE | Khách nói chuyện khác | Reset `current_anchor = null` |
---
## 3.4. Tại sao State Machine chứ không phải ReAct Agent thuần?
Trên Reddit (r/LangChain, r/LocalLLaMA), cộng đồng đang chuyển dần từ **ReAct Agent** (để AI tự quyết dùng tool nào) sang **State Machine** (ép AI đi theo đường ray).
| Tiêu chí | ReAct Agent | **State Machine (Canifa)** |
|----------|------------|---------------------------|
| **Quyết định** | AI tự chọn tool | AI chỉ được chọn tool trong danh sách hợp lệ của State hiện tại |
| **Infinite Loop** | Hay bị (AI gọi tool lặp đi lặp lại) | **Không thể** (mỗi State có tập transition cố định) |
| **Sai tool** | Hay xảy ra (AI dùng tool tìm store khi khách hỏi phối đồ) | **Không thể** (State `CROSS_SELL` chỉ cho phép tool `outfit`) |
| **Debug** | Rất khó (AI nghĩ gì không biết) | **Rất dễ** (log State transition) |
| **Latency** | Cao (AI cần "suy nghĩ" chọn tool) | **Thấp** (State đã define sẵn tool) |
> **Insight:** State Machine không phải là "giới hạn" AI. Nó là **đường ray** giúp AI chạy nhanh hơn và an toàn hơn. Giống như tàu cao tốc: chạy nhanh hơn xe hơi CHÍNH VÌ nó đi trên ray, không phải tự tìm đường.
---
## 3.5. Memory tối giản: Chỉ cần nhớ 1 biến
Cái đẹp nhất của kiến trúc này: **AI không cần nhớ cả đoạn hội thoại dài**. Nó chỉ cần nhớ đúng **1 biến**:
```python
conversation_state = {
"current_anchor": "POLO_01", # SKU đang focus
"state": "CROSS_SELL", # Trạng thái hiện tại
"history": ["TSHIRT_05", "POLO_01"] # Các trạm đã đi qua (optional, for backtrack)
}
```
Tổng cộng: **~100 bytes**. So với việc nhét cả lịch sử chat (có thể lên đến hàng nghìn tokens) vào context window → **tiết kiệm token, giảm chi phí LLM, tăng tốc độ phản hồi.**
# DNA Inheritance: Kế thừa đặc tính sản phẩm
> **Tóm tắt:** Khi khách hàng nói *"Tìm cho chị thêm mấy cái giống quần B đi"*, AI không tự bịa ra quần B trông như thế nào. Nó **sao chép nguyên bộ Gen (DNA/Metadata)** của Quần B, chỉ tạo ra **1 đột biến (Mutation)** ở đúng điều kiện khách yêu cầu, rồi xách bộ Gen đó đi tìm trong kho hàng. Đây là cơ chế tinh vi nhất của hệ thống.
---
## 4.1. DNA là gì?
Mỗi sản phẩm trong Database mang trong mình một chuỗi **DNA (Metadata)** — tập hợp các đặc tính mô tả sản phẩm đó:
```mermaid
graph TD
SKU["🧬 Quần Jean B<br/>Mã: JEAN_02"]
DNA1["📐 Form: Ống rộng"]
DNA2["🧵 Chất liệu: Denim wash"]
DNA3["💼 Dịp: Đi làm, Đi chơi"]
DNA4["💰 Giá: 499,000đ"]
DNA5["🎨 Màu: Xanh đậm"]
DNA6["🧑 Giới: Nữ"]
DNA7["📅 Mùa: Thu-Đông"]
DNA8["🏷️ Category: Quần Jeans"]
SKU --> DNA1
SKU --> DNA2
SKU --> DNA3
SKU --> DNA4
SKU --> DNA5
SKU --> DNA6
SKU --> DNA7
SKU --> DNA8
style SKU fill:#f59e0b,stroke:#d97706,color:#000
style DNA1 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA2 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA3 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA4 fill:#fef3c7,stroke:#d97706,color:#000
style DNA5 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA6 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA7 fill:#e0f2fe,stroke:#0284c7,color:#000
style DNA8 fill:#e0f2fe,stroke:#0284c7,color:#000
```
---
## 4.2. Cơ chế "Sao chép + Đột biến" (Copy + Mutate)
Khi khách yêu cầu *"Tìm cái giống vậy nhưng rẻ hơn"*, AI thực hiện **3 bước chính xác**:
### Bước 1: Copy DNA (Sao chép nguyên bản)
AI đọc toàn bộ metadata của sản phẩm Gốc:
```python
# Gốc: Quần Jean B
dna = {
"category": "Quần Jeans",
"form": "Ống rộng",
"material": "Denim wash",
"occasion": ["Đi làm", "Đi chơi"],
"price": 499000,
"color_group": "Xanh đậm",
"gender": "Nữ",
"season": "Thu-Đông"
}
```
### Bước 2: Mutate (Tạo đột biến)
AI chỉ thay đổi **đúng 1 gene** mà khách yêu cầu:
```python
# Khách: "Tìm rẻ hơn" → Chỉ đột biến gene "price"
mutation = {
"price": {"operator": "<", "value": 499000} # Rẻ hơn Quần B
}
# Tất cả gene khác: GIỮ NGUYÊN
```
### Bước 3: Query (Xách DNA đi tìm)
```python
# AI tạo query filter từ DNA + Mutation
query = {
"category": "Quần Jeans", # Giữ nguyên
"form": "Ống rộng", # Giữ nguyên
"occasion": "Đi làm", # Giữ nguyên
"gender": "Nữ", # Giữ nguyên
"price": {"$lt": 499000} # ← ĐỘT BIẾN DUY NHẤT
}
results = database.search(query)
# → Trả về: Quần C (399k), Quần D (349k)
```
---
## 4.3. Flow diagram: Toàn cảnh DNA Inheritance
```mermaid
sequenceDiagram
participant User as 👤 Khách
participant AI as 🤖 AI Agent
participant DB as 🗃️ Database
User->>AI: "Quần B đẹp đấy, nhưng tìm rẻ hơn đi em"
Note over AI: 🧠 Tư duy nội bộ
AI->>AI: 1. Xác định Gốc = JEAN_02 (Quần B)
AI->>DB: 2. SELECT metadata FROM products WHERE code = 'JEAN_02'
DB-->>AI: DNA = {form: "ống rộng", occasion: "đi làm", price: 499k, ...}
AI->>AI: 3. Copy DNA nguyên bản
AI->>AI: 4. Mutate: price < 499k (theo yêu cầu khách)
AI->>DB: 5. SELECT * FROM products WHERE form='ống rộng' AND occasion LIKE '%đi làm%' AND price < 499000
DB-->>AI: Quần C (399k), Quần D (349k), Quần E (299k)
AI->>User: "Dạ chị ơi, em tìm được 3 mẫu quần ống rộng<br/>đi làm y chang Quần B, giá mềm hơn:<br/>- Quần C: 399k (Denim nhẹ)<br/>- Quần D: 349k (Cotton blend)<br/>- Quần E: 299k (Vải lanh)"
Note over User,AI: 💡 AI không tự bịa "ống rộng" hay "đi làm"<br/>Nó KẾ THỪA từ DNA của Quần B!
```
---
## 4.4. Các kiểu Đột biến phổ biến
| Khách nói | Gene bị đột biến | Các gene giữ nguyên |
|-----------|------------------|---------------------|
| *"Rẻ hơn đi"* | `price < anchor.price` | form, category, occasion, gender, season |
| *"Có màu khác không?"* | `color_group != anchor.color` | form, category, price_range, gender |
| *"Cái nào mặc đi chơi?"* | `occasion = "đi chơi"` | form, category, color, gender, price_range |
| *"Size cho bé gái"* | `gender = "bé gái"` | category, form, season |
| *"Có hàng mùa hè không?"* | `season = "Xuân-Hè"` | category, form, gender |
| *"Form ôm hơn?"* | `form = "slim-fit"` | category, color, price_range, gender |
---
## 4.5. Tại sao DNA Inheritance vượt trội so với cách làm thông thường?
### Cách thông thường: LLM tự tóm tắt
```
Khách: "Tìm quần giống B nhưng rẻ hơn"
LLM (tự nghĩ): "Hmm, quần B là... quần jean... ống rộng... à mà hình như nó
dáng slim-fit? Hay là regular? Thôi cứ tìm quần jean rẻ hơn."
→ Kết quả: Trả về quần jean RANDOM, có thể ống côn, có thể skinny
→ KHÔNG GIỐNG Quần B chút nào
```
### Cách Canifa: Kế thừa DNA
```
Khách: "Tìm quần giống B nhưng rẻ hơn"
AI: "Quần B có DNA: [Jeans, Ống rộng, Đi làm, Nữ, 499k, Xanh đậm]"
"Copy toàn bộ. Chỉ đổi: price < 499k"
→ Kết quả: Quần Jean ống rộng, đi làm, Nữ, giá rẻ hơn
→ CHÍNH XÁC như Quần B, chỉ khác giá
```
| Tiêu chí | LLM tự tóm | **DNA Inheritance** |
|----------|-----------|---------------------|
| Độ chính xác form dáng | ~50% (hay nhớ sai) | **100%** (copy từ DB) |
| Giữ đúng occasion | ~60% (hay bỏ sót) | **100%** (giữ nguyên gene) |
| Tốc độ | Chậm (LLM phải "nghĩ") | **Nhanh** (copy + query) |
| Token cost | Cao (tóm tắt context) | **Thấp** (chỉ truyền SKU) |
---
## 4.6. Combo chí mạng: DNA + Outfit + Similar
Cả 3 cơ chế kết hợp tạo thành một **vòng lặp bất tận (Infinite Loop)** mà khách hàng không bao giờ cảm thấy cụt đường:
```mermaid
graph TD
START["🏁 Khách xem Áo A"]
Q1{"Khách hỏi gì?"}
OUTFIT["🔑 Mở outfit_recommendations<br/>→ Quần B"]
SIMILAR["🔑 Mở similar_items<br/>→ Áo A' (rẻ hơn)"]
Q2{"Khách tiếp tục?"}
PIVOT_B["🔄 PIVOT: Quần B → Gốc mới"]
PIVOT_A["🔄 PIVOT: Áo A' → Gốc mới"]
DNA_MUT["🧬 DNA Mutation<br/>'Quần giống B, rẻ hơn'<br/>→ Quần C, D"]
Q3{"Khách chốt?"}
PIVOT_C["🔄 PIVOT: Quần C → Gốc mới"]
CART["🛒 Thêm vào giỏ hàng"]
START --> Q1
Q1 -->|"Phối gì?"| OUTFIT
Q1 -->|"Rẻ hơn?"| SIMILAR
OUTFIT --> Q2
SIMILAR --> Q2
Q2 -->|"Phối tiếp"| PIVOT_B
Q2 -->|"Giống vậy rẻ hơn"| DNA_MUT
Q2 -->|"Xem Áo khác"| PIVOT_A
DNA_MUT --> Q3
PIVOT_B --> Q1
PIVOT_A --> Q1
Q3 -->|"Phối tiếp"| PIVOT_C
Q3 -->|"Mua luôn"| CART
PIVOT_C --> Q1
style START fill:#10b981,stroke:#059669,color:#fff
style CART fill:#ef4444,stroke:#dc2626,color:#fff
style DNA_MUT fill:#8b5cf6,stroke:#7c3aed,color:#fff
style PIVOT_B fill:#f59e0b,stroke:#d97706,color:#000
style PIVOT_A fill:#f59e0b,stroke:#d97706,color:#000
style PIVOT_C fill:#f59e0b,stroke:#d97706,color:#000
```
> **Insight:** Vòng lặp này có thể chạy **vô tận** (Áo → Quần → Giày → Túi → Khăn → ...). Mỗi bước nhảy, AI chỉ cần biết **1 mã SKU** là xong. Không cần nhớ cả cuộc trò chuyện. Đây chính là bí quyết để AI Canifa tư vấn mượt mà như chuyên gia thời trang thực thụ — dù thực tế nó chỉ là một "kẻ tra cứu JSON siêu nhanh".
# Kịch bản thực chiến: 3 Use Cases chi tiết
> **Tóm tắt:** 3 kịch bản mô phỏng đoạn chat thực tế, cho thấy cách AI sử dụng cả 3 cơ chế (Outfit Graph → DNA Inheritance → Pivoting) trong một cuộc trò chuyện liên tục. Mỗi kịch bản bóc tách cả **những gì khách thấy** lẫn **những gì xảy ra dưới gầm xe (Under the Hood)**.
---
## 5.1. Kịch bản 1: "Lên đồ đi Phú Quốc" (Cross-sell Liên Hoàn)
**Bối cảnh:** Chị Hà (30 tuổi) đang xem **Áo Phông Oversize Mùa Hè (3TS26S018-SR079)** trên app Canifa.
---
### 💬 Lượt 1 — Kích hoạt Outfit Graph
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Áo này mặc đi Phú Quốc thì phối quần gì hả em?"* | `State: IDLE → ANCHORED` |
| | `current_anchor = 3TS26S018-SR079` |
| | `→ Đọc outfit_recommendations` |
| | `→ Tìm role = "bottom"` |
**Database trả về:**
```json
{
"outfit_recommendations": [
{"code": "1BP24C002", "name": "Quần Short Linen", "role": "bottom", "score": 94, "reason": "Vải linen nhẹ, phù hợp đi biển"},
{"code": "1QJ25S003", "name": "Quần Jeans Croptop", "role": "bottom", "score": 78, "reason": "Casual phù hợp resort"}
]
}
```
**🤖 AI trả lời:**
> *"Dạ đi Phú Quốc thì chị mix ngay với cái **Quần Short Linen (1BP24C002)** này ạ! Vải linen mát lạnh, nhẹ tênh, lên hình bao sống ảo ạ. Hoặc nếu chị thích kiểu năng động hơn thì có **Quần Jeans Croptop** cũng rất hợp phong cách resort luôn!"*
---
### 💬 Lượt 2 — Pivoting (Chuyển Gốc)
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Ok chốt cái quần Short Linen đó. Thế mặc quần đó thì xách túi gì cho hợp?"* | `State: CROSS_SELL → RE_ANCHOR` |
| | `current_anchor = 1BP24C002` ← **PIVOT!** |
| | `→ Đọc outfit_recommendations CỦA QUẦN` |
| | `→ Tìm role = "accessory"` |
**Database trả về (từ Quần Short, không phải Áo!):**
```json
{
"outfit_recommendations": [
{"code": "TUI_COI_03", "name": "Túi Cói Vintage", "role": "accessory", "score": 91},
{"code": "MU_RONG_07", "name": "Mũ Rộng Vành Cói", "role": "accessory", "score": 85}
]
}
```
**🤖 AI trả lời:**
> *"Đã diện Quần Short Linen thì chị xách cái **Túi Cói Vintage** nữa là chuẩn bài biển chill! Rộng đựng được kem chống nắng lẫn phone. À, chị thêm cái **Mũ Rộng Vành Cói** nữa là set đồ Phú Quốc hoàn hảo rồi ạ! 🏖️"*
---
### 💬 Lượt 3 — DNA Inheritance (Đột biến Gen)
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Cái túi cói 299k hơi đắt. Có cái nào rẻ hơn mà cùng style không em?"* | `State: RE_ANCHOR → EXPAND (DNA)` |
| | `Gốc = TUI_COI_03` |
| | `Copy DNA: {category: Túi, style: Cói/Vintage, dịp: Đi biển}` |
| | `Mutate: price < 299000` |
| | `→ Query Product Dimension` |
**🤖 AI trả lời:**
> *"Dạ em tìm được 2 mẫu túi cói style y chang ạ: **Túi Cói Mini (179k)** — nhỏ gọn xinh xắn hơn, và **Túi Vải Canvas Vintage (199k)** — vải canvas bền hơn cói. Cùng phong cách đi biển, rẻ hơn trăm nghìn luôn chị!"*
> **Phân tích:** Trong 3 lượt chat, AI đã nhảy qua 3 trạm (Áo → Quần → Túi), sử dụng cả 3 cơ chế (Outfit → Pivot → DNA). Khách không hề cảm thấy cuộc trò chuyện bị cụt hoặc AI trả lời máy móc.
---
## 5.2. Kịch bản 2: "Chị muốn rẻ hơn" (Down-sell + DNA Cascade)
**Bối cảnh:** Chị Mai đang xem **Áo Polo Pima Cao Cấp (POLO_VIP_01, giá 599k)**.
---
### 💬 Lượt 1 — Down-sell
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Áo này form đẹp quá, nhưng 599k hơi đau ví. Có cái nào giống mà rẻ hơn không?"* | `State: ANCHORED → DOWN_SELL` |
| | `Gốc = POLO_VIP_01` |
| | `→ Đọc similar_items` |
**Database trả về:**
```json
{
"similar_items": [
{"code": "POLO_BASIC_05", "name": "Polo Cotton Basic", "price_diff": "-200k", "key_diff": "Cotton thường thay Pima"},
{"code": "POLO_SPORT_09", "name": "Polo Thể Thao Polyester", "price_diff": "-150k", "key_diff": "Vải poly thoáng mát"}
]
}
```
**🤖 AI trả lời:**
> *"Dạ em hiểu ý chị! Form Slim-fit của mẫu Pima tôn dáng lắm ạ. Chị nghía thử: **Polo Cotton Basic (399k)** — form y chang, chất cotton thường nhẹ hơn. Hoặc **Polo Thể Thao (449k)** — vải poly siêu thoáng, hợp ngày nóng!"*
---
### 💬 Lượt 2 — Re-anchor + Cross-sell
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Ok lấy cái Polo Cotton Basic 399k đi. Phối với quần gì cho đi làm?"* | `State: DOWN_SELL → RE_ANCHOR → CROSS_SELL` |
| | `current_anchor = POLO_BASIC_05` ← **PIVOT!** |
| | `→ Đọc outfit_recommendations CỦA POLO BASIC` |
**🤖 AI trả lời:**
> *"Dạ Polo Basic này đi làm thì chị phối với **Quần Âu Slim** hoặc **Quần Chinos Kaki** là chuẩn smart-casual ạ! Tiết kiệm được 200k mà lên đồ vẫn lịch sự y chang version Pima!"*
---
### 💬 Lượt 3 — DNA Mutation sâu
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Quần Chinos Kaki thì có mẫu nào ống rộng hơn xíu không? Chị thích dáng thoải mái"* | `State: RE_ANCHOR → EXPAND (DNA)` |
| | `Gốc = Quần Chinos Kaki` |
| | `Copy DNA: {category: Chinos, color: Kaki, occasion: Đi làm}` |
| | `Mutate: form = "regular-fit" hoặc "wide-leg"` |
**🤖 AI trả lời:**
> *"Dạ chị thích thoải mái thì chuyển sang **Chinos Regular-fit** hoặc **Chinos Wide-leg** ạ. Em tìm được 2 mẫu: cùng màu kaki, cùng mặc đi làm, nhưng dáng rộng thoáng hơn. Phối với Polo Basic 399k vẫn chuẩn style!"*
---
## 5.3. Kịch bản 3: "Mua đồ cho bé" (Hard Filter + Role Priority)
**Bối cảnh:** Anh Tuấn mua **Áo Phông Bé Trai (KID_TEE_01, size 120-130)**.
---
### 💬 Lượt 1 — Hard Filter hoạt động
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Áo này mặc với quần gì cho bé đi học?"* | `State: ANCHORED → CROSS_SELL` |
| | `Gốc = KID_TEE_01 (age_group: kids_boy)` |
| | `Hard Filter: CHỈ lấy đồ kids_boy` |
| | `→ Loại toàn bộ đồ men, women, kids_girl` |
**Database trả về (đã lọc Hard Filter):**
```json
{
"outfit_recommendations": [
{"code": "KID_SHORT_03", "name": "Quần Short Thể Thao Bé Trai", "role": "bottom", "score": 95},
{"code": "KID_JEAN_07", "name": "Quần Jean Slim Bé Trai", "role": "bottom", "score": 88}
]
}
```
> **QUAN TRỌNG:** Nếu không có Hard Filter, hệ thống có thể gợi ý "Quần Jean Nam" (cùng màu, cùng category, score cao) cho một đứa bé → **Thảm họa UX**. Hard Filter ngăn chặn 100% rủi ro này.
---
### 💬 Lượt 2 — Pivot trong hệ sinh thái trẻ em
| Khách nói | AI dưới gầm xe |
|-----------|----------------|
| *"Ok lấy Quần Short Thể Thao. Có giày nào hợp cho bé đi học không?"* | `State: RE_ANCHOR` |
| | `current_anchor = KID_SHORT_03` |
| | `→ Đọc outfit CỦA QUẦN (vẫn trong hệ kids_boy)` |
**🤖 AI trả lời:**
> *"Dạ bé diện Quần Short Thể Thao thì bố mẹ cho bé xỏ **Giày Sneaker Nhẹ** ạ! Đế mềm êm chân, bé chạy nhảy cả ngày vẫn ok!"*
> **Phân tích:** Suốt cuộc trò chuyện, AI luôn ở trong "vũ trụ trẻ em" (kids_boy). Hard Filter đảm bảo không bao giờ rò rỉ sản phẩm người lớn vào gợi ý cho bé.
---
## 5.4. Bảng tổng kết 3 kịch bản
| Kịch bản | Cơ chế sử dụng | Số lần Pivot | Cơ chế bảo vệ |
|----------|----------------|-------------|---------------|
| **Đi Phú Quốc** | Outfit → Pivot → DNA | 2 | Season filter |
| **Chị muốn rẻ hơn** | Similar → Pivot → Outfit → DNA | 2 | Price constraint |
| **Mua đồ cho bé** | Hard Filter → Outfit → Pivot | 1 | Age-group filter |
# So sánh với cộng đồng quốc tế (Reddit/Industry)
> **Tóm tắt:** Kiến trúc "Entity-Centric Conversational Routing" của Canifa không phải là sáng tạo cá nhân — nó là hội tụ của các best practices mà cộng đồng AI toàn cầu đang tranh luận nảy lửa trên Reddit (r/LangChain, r/LocalLLaMA, r/MachineLearning). Tài liệu này so sánh cách tiếp cận của Canifa với 4 trường phái phổ biến nhất, và chỉ ra tại sao giải pháp của ta lại là sự kết hợp tinh hoa nhất.
---
## 6.1. Bản đồ 4 trường phái trên thế giới
```mermaid
quadrantChart
title "4 Trường phái AI E-commerce"
x-axis "Tốc độ thấp" --> "Tốc độ cao"
y-axis "Kiểm soát thấp" --> "Kiểm soát cao"
quadrant-1 "🎯 Sweet Spot"
quadrant-2 "Chậm nhưng an toàn"
quadrant-3 "Nhanh nhưng nguy hiểm"
quadrant-4 "Tốt nhưng đắt"
"Pure LLM": [0.2, 0.15]
"RAG (Vector Search)": [0.45, 0.4]
"Knowledge Graph (Neo4j)": [0.5, 0.85]
"CANIFA (Graph Router)": [0.9, 0.92]
```
---
## 6.2. Trường phái 1: Pure LLM (Chết ngay từ vòng gửi xe)
**Mô tả:** Nhồi kiến thức thời trang vào Prompt, để LLM tự gợi ý.
**Ý kiến Reddit (r/MachineLearning):**
> *"Don't use raw LLM for product recommendations. It WILL hallucinate products that don't exist in your inventory."*
| Ưu điểm | Nhược điểm |
|---------|-----------|
| Setup nhanh (30 phút) | Hallucination ~40% |
| Không cần database | Kết quả không reproducible |
| Conversational tự nhiên | Không kiểm soát được chất lượng gợi ý |
**So với Canifa:**
Canifa giữ khả năng đối thoại tự nhiên của LLM nhưng **khóa chặt output** bằng JSON Database. LLM chỉ được "nói" những gì Database cho phép.
---
## 6.3. Trường phái 2: RAG thuần (Vector Search)
**Mô tả:** Embed toàn bộ catalog vào Vector DB (Pinecone, Weaviate), dùng semantic search để tìm sản phẩm.
**Ý kiến Reddit (r/LangChain):**
> *"Semantic search is great for finding SIMILAR products, but terrible for finding COMPLEMENTARY products. 'Shirt that goes with blue jeans' returns... more jeans."*
| Ưu điểm | Nhược điểm |
|---------|-----------|
| Tìm đồ tương tự tốt | Không biết phối đồ (chỉ tìm "giống") |
| Scale tốt | Negation problem ("không phải màu đỏ" → trả màu đỏ) |
| Setup vừa phải | Multi-turn yếu (mỗi turn search lại từ đầu) |
**So với Canifa:**
Canifa giải quyết bài toán "phối đồ" bằng `outfit_recommendations` (cross-category), bài toán "tìm tương tự" bằng `similar_items` (same-category). RAG thuần chỉ giải được bài toán thứ hai.
---
## 6.4. Trường phái 3: Knowledge Graph (Neo4j, Amazon, Pinterest)
**Mô tả:** Dùng Graph Database (Neo4j) để mô hình hóa quan hệ giữa sản phẩm: `[HỢP_VỚI]`, `[TƯƠNG_TỰ]`, `[MUA_CÙNG]`.
**Ý kiến Reddit (r/datascience):**
> *"Knowledge Graphs are the gold standard for product recommendations. Amazon and Pinterest use them heavily. But the engineering cost is brutal for small teams."*
| Ưu điểm | Nhược điểm |
|---------|-----------|
| Mô hình quan hệ phức tạp | Chi phí vận hành cao (thêm server) |
| Traversal linh hoạt (N-hop) | Cần team DevOps riêng |
| Industry standard (Amazon, Pinterest) | Đồng bộ dữ liệu giữa SQL ↔ Neo4j phức tạp |
**So với Canifa:**
Canifa mô phỏng Knowledge Graph bằng JSON columns — **đạt 90% chức năng của Neo4j** với **0% chi phí vận hành thêm**. Lý do: catalog Canifa (~2000 SKU) không cần arbitrary N-hop traversal. 1-2 hop (Áo → Quần → Giày) là đủ cho mọi kịch bản.
---
## 6.5. Trường phái 4: State Machine (LangGraph, Canifa)
**Mô tả:** Thay vì để LLM tự chọn action (ReAct), ép LLM đi theo trạng thái (State) cố định.
**Ý kiến Reddit (r/LangChain):**
> *"After 6 months of fighting ReAct infinite loops, we switched to LangGraph state machine. Zero loops, 10x more predictable, easier to debug. Never going back."*
| Tiêu chí | ReAct Agent | **State Machine** |
|----------|------------|-------------------|
| Infinite Loop | Phổ biến | **Không thể xảy ra** |
| Sai tool | ~15% requests | **0%** (State quy định tool) |
| Debug time | Hàng giờ | **Vài phút** (log State transitions) |
| Latency | 3-5 giây (suy nghĩ) | **<1 giây** (đường ray sẵn) |
---
## 6.6. Canifa = Best of All Worlds
```mermaid
mindmap
root((CANIFA<br/>Graph Router))
**Từ Pure LLM**
Đối thoại tự nhiên
Hiểu ngữ cảnh đa dạng
**Từ RAG**
Metadata filtering
Tìm sản phẩm tương tự
**Từ Knowledge Graph**
Quan hệ phối đồ
Traversal đồ thị
Cross-category linking
**Từ State Machine**
Deterministic flow
Zero infinite loops
Predictable behavior
```
| Thành phần | Nguồn gốc | Cách Canifa áp dụng |
|-----------|----------|---------------------|
| Đối thoại tự nhiên | Pure LLM | LLM xử lý ngôn ngữ, Database xử lý logic |
| Metadata filtering | RAG | DNA Inheritance — copy metadata + mutate |
| Quan hệ cross-category | Knowledge Graph | `outfit_recommendations` JSON column |
| Quan hệ same-category | Knowledge Graph | `similar_items` JSON column |
| Deterministic routing | State Machine | State transitions cố định (6 states) |
| Pre-computed data | Industry standard | Batch Job chạy đêm, JSON lookup O(1) |
---
## 6.7. Thuật ngữ chuẩn quốc tế
Để trình bày với đối tác quốc tế hoặc trong tài liệu kỹ thuật, kiến trúc của Canifa có thể được gọi bằng thuật ngữ chuẩn:
| Thuật ngữ | Giải thích |
|----------|-----------|
| **Entity-Centric Conversational Routing** | Điều hướng hội thoại xoay quanh thực thể (SKU) |
| **Pre-computed Product Graph** | Đồ thị sản phẩm tính toán sẵn |
| **Anchor-based Pivoting** | Chuyển Gốc (Neo → Gốc mới) |
| **Zero-Hallucination Architecture** | Kiến trúc không ảo giác |
| **Hybrid Intent Router** | Bộ phân luồng ý định kết hợp |
| **DNA Inheritance Pattern** | Mẫu thiết kế kế thừa metadata |
| **Deterministic State Machine** | Máy trạng thái tất định |
> **Lời kết:** Khi so sánh với cộng đồng quốc tế, kiến trúc của Canifa không phải là "phát minh lại bánh xe". Nó là sự **kết hợp tinh hoa** của 4 trường phái, được tối ưu cho quy mô catalog ~2000 SKU và đội ngũ vận hành nhỏ gọn. Đây chính là lợi thế cạnh tranh: đạt chất lượng của Amazon/Pinterest nhưng chi phí vận hành gần như bằng 0.
{
"id": "49-conversational-routing",
"title": "Conversational Routing Engine (Mồi Câu Liên Hoàn)",
"description": "Kiến trúc Entity-Centric Conversational Routing: Cơ chế 'Lấy mỡ nó rán nó' — dùng SKU làm Kim Chỉ Nam để điều hướng hội thoại AI qua Pre-computed Graph. Zero Hallucination, O(1) Lookup, Endless Pivoting.",
"sections": [
{
"id": "philosophy",
"title": "1. Triết lý thiết kế: Tại sao LLM là Router, không phải Stylist?",
"type": "markdown",
"file": "01_philosophy.md"
},
{
"id": "graph-architecture",
"title": "2. Kiến trúc Pre-computed Graph (Đồ thị tính toán sẵn)",
"type": "markdown",
"file": "02_graph_architecture.md"
},
{
"id": "state-machine",
"title": "3. State Machine: Cỗ máy trạng thái hội thoại",
"type": "markdown",
"file": "03_state_machine.md"
},
{
"id": "dna-inheritance",
"title": "4. DNA Inheritance: Kế thừa đặc tính sản phẩm",
"type": "markdown",
"file": "04_dna_inheritance.md"
},
{
"id": "scenarios",
"title": "5. Kịch bản thực chiến: 3 Use Cases chi tiết",
"type": "markdown",
"file": "05_scenarios.md"
},
{
"id": "industry-comparison",
"title": "6. So sánh với cộng đồng quốc tế (Reddit/Industry)",
"type": "markdown",
"file": "06_industry_comparison.md"
},
{
"id": "implementation",
"title": "7. Implementation Blueprint: Agent Tools & Code",
"type": "markdown",
"file": "07_implementation.md"
}
]
}
# Triết lý: Tại sao AI cần "Lịch Tâm Trạng"?
> **Tóm tắt:** Một con AI bán hàng mà ngày nào cũng nói giống nhau thì không khác gì robot đọc script. Khách hàng cần cảm nhận rằng AI "hiểu" hôm nay là ngày gì, đang có gì hot, và nên tư vấn kiểu nào. Event Daily Engine là cơ chế khiến AI Canifa "thức dậy mỗi sáng với một tâm trạng khác nhau" — được điều khiển 100% bởi con người.
---
## 1.1. Bài toán gốc: "Con AI bị đông cứng trong thời gian"
Hầu hết chatbot thương mại điện tử hoạt động theo một prompt **tĩnh** (Static Prompt):
```
┌──────────────────────────────────────────────────┐
│ CHATBOT TĨNH (Truyền thống) │
│ │
│ Prompt cố định ──→ AI trả lời giống nhau │
│ 365 ngày/năm mọi lúc, mọi nơi │
│ │
│ Valentine? → Vẫn nói chung chung │
│ Black Friday? → Không biết có sale │
│ Mùa đông? → Vẫn gợi áo phông │
└──────────────────────────────────────────────────┘
```
**Hậu quả thực tế:**
| Vấn đề | Ví dụ cụ thể | Thiệt hại |
|--------|--------------|-----------|
| **Mất thời điểm vàng** | Black Friday mà AI không biết push sale | Doanh thu giảm 30-50% trong ngày đỉnh |
| **Trải nghiệm nhạt** | Ngày 8/3 mà AI không có lời chúc | Khách thấy AI "vô cảm", bounce rate tăng |
| **Xung đột chiến lược** | Team Marketing chạy campaign "Áo khoác" nhưng AI vẫn push "Áo phông" | Ngân sách ads cháy vô ích |
| **Không linh hoạt** | Đổi chiến thuật → Phải sửa code, deploy lại | Lead time 2-3 ngày, miss window |
---
## 1.2. Giải pháp: "AI Mood Calendar" (Lịch Tâm Trạng AI)
Thay vì prompt tĩnh, Canifa xây một hệ thống cho phép **tiêm (inject)** hướng dẫn tư vấn vào AI **theo từng ngày, từng sự kiện, từng chiến dịch**:
```
┌──────────────────────────────────────────────────────┐
│ EVENT DAILY ENGINE (Canifa) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────────────┐ │
│ │ Calendar │ ─→ │ Priority │ ─→ │ Prompt Injection │ │
│ │ Engine │ │ Resolver │ │ (Into AI) │ │
│ └─────────┘ └─────────┘ └──────────────────┘ │
│ │
│ Hôm nay = Valentine + Flash Sale │
│ → Priority: Flash Sale (campaign) > Valentine (daily)│
│ → AI Prompt: "Ưu tiên push sale, kết hợp quà tặng" │
└──────────────────────────────────────────────────────┘
```
> **Nguyên tắc vàng:** Con người (BA/Marketing) quyết định AI nên nói gì hôm nay. AI chỉ là "diễn viên" diễn theo "kịch bản" của ngày hôm đó.
---
## 1.3. Phép ẩn dụ: "Nhà hàng 5 sao và Thực đơn theo mùa"
Hãy tưởng tượng AI Canifa là một **nhà hàng 5 sao**:
- **Menu cố định** (Base Prompt) = Những món signature luôn có: tư vấn phối đồ, tìm size, tra hàng...
- **Menu theo mùa** (Event Prompt) = Chef thay đổi theo mùa vụ:
- Mùa đông → Lẩu nóng, đồ nướng (= AI push áo khoác, len, dạ)
- Valentine → Set dinner lãng mạn (= AI tư vấn quà tặng, đồ đôi)
- Black Friday → Buffet giá rẻ (= AI push deal, so sánh giá, urgency)
**Menu theo mùa KHÔNG thay thế menu cố định** — nó **bổ sung** thêm "tâm trạng" và "ưu tiên" cho AI.
```
┌──────────────────────────────────────────────┐
│ PROMPT LAYERING │
│ │
│ Layer 1: BASE PROMPT (Tĩnh, cố định) │
│ ├─ Persona: Canifa AI Stylist │
│ ├─ Rules: Không bịa SP, gọi tool đúng │
│ └─ Core: Phối đồ, tìm hàng, tư vấn size │
│ │
│ Layer 2: EVENT PROMPT (Động, thay đổi/ngày)│
│ ├─ "Hôm nay 8/3: Chúc mừng phụ nữ..." │
│ ├─ "Ưu tiên gợi ý quà tặng cho chồng/BF" │
│ └─ "Tập trung BST Váy, Đầm, Phụ kiện" │
│ │
│ Layer 3: CAMPAIGN PROMPT (Chiến dịch ad-hoc)│
│ ├─ "Flash Sale 50% toàn bộ Polo" │
│ ├─ "Nhấn mạnh: Chỉ còn 2 ngày!" │
│ └─ "Cross-sell: Mua Polo + Quần giảm 10%" │
│ │
│ ═══════════════════════════════════════════ │
│ FINAL PROMPT = Layer 1 + Layer 2 + Layer 3 │
│ (Priority: Layer 3 > Layer 2 > Layer 1) │
└──────────────────────────────────────────────┘
```
---
## 1.4. Tại sao không dùng cách khác?
| Giải pháp thay thế | Tại sao Canifa không chọn? |
|--------------------|-----------------------------|
| **Sửa prompt thủ công** | Phải deploy lại code → Chậm, rủi ro, cần developer |
| **Dùng Langfuse prompt versioning** | Latency cao (~200-500ms), dependency bên ngoài |
| **Cron job tự đổi prompt** | Cứng nhắc, không linh hoạt theo chiến dịch ad-hoc |
| **Feature flag** | Chỉ bật/tắt, không chứa được nội dung prompt chi tiết |
| **Event Daily Engine ✅** | BA/Marketing tự sửa qua UI → Có hiệu lực ngay lập tức, zero deploy |
---
## 1.5. Lợi ích đo lường được
```
┌────────────────────────────────────────────────────┐
│ ROI CỦA EVENT DAILY ENGINE │
│ │
│ 📈 Conversion Rate trong ngày campaign: +15-25% │
│ ⏱️ Time-to-market chiến lược mới: 2 ngày → 2 phút │
│ 🎯 Relevance Score (NPS-style): +20 điểm │
│ 💰 Giảm chi phí vận hành: Không cần dev để đổi AI │
│ 🔄 Tần suất update: Từ 1 lần/tháng → Hàng ngày │
└────────────────────────────────────────────────────┘
```
> **Insight từ ngành (Envive.ai, 2025):** Các chatbot thương mại có khả năng cá nhân hóa theo mùa vụ/chiến dịch đạt tỉ lệ upsell cao hơn 20-30% so với chatbot tĩnh. Event Daily Engine là cách Canifa hiện thực hóa con số đó mà **không cần LLM fine-tuning hay retraining**.
# Kiến trúc hệ thống: Event → Prompt → AI Behavior
> **Tóm tắt:** Chương này mổ xẻ kiến trúc kỹ thuật end-to-end: từ lúc BA tạo sự kiện trên UI, đến lúc AI "nuốt" event prompt và thay đổi hành vi tư vấn. Mọi thứ được thiết kế để **zero-deploy** — thay đổi tức thì, không cần restart server.
---
## 2.1. Kiến trúc tổng quan (System Architecture)
```mermaid
graph TB
subgraph "📱 Frontend (UI)"
UI["Event Daily Dashboard<br/>static/event_daily/index.html"]
UI_LIST["Sidebar: Danh sách sự kiện"]
UI_EDIT["Editor: Prompt Module"]
UI --> UI_LIST
UI --> UI_EDIT
end
subgraph "⚡ Backend API (FastAPI)"
API["event_daily_route.py<br/>/api/event_daily/*"]
API_LIST["GET /events<br/>List all events"]
API_SAVE["PUT /events/{id}<br/>Update prompt"]
API --> API_LIST
API --> API_SAVE
end
subgraph "💾 Database (SQLite)"
DB["event_daily_prompts"]
DB_ROW["event_id | event_name | event_type<br/>event_date | is_today | prompt<br/>start_date | end_date"]
DB --> DB_ROW
end
subgraph "🤖 AI Pipeline (Agent)"
AGENT["Stylist Agent<br/>stylist_pro_prompts.py"]
INJECT["Prompt Injection Point<br/>system_prompt += event_prompt"]
AGENT --> INJECT
end
UI_EDIT -->|"PUT /api/event_daily/events/{id}"| API_SAVE
API_LIST -->|"SELECT * FROM event_daily_prompts"| DB
API_SAVE -->|"UPDATE ... SET prompt = ?"| DB
DB -->|"Query: WHERE is_today = 1<br/>OR date BETWEEN start/end"| AGENT
INJECT -->|"AI hành xử theo<br/>event prompt"| RESPONSE["💬 Response<br/>to Customer"]
style UI fill:#3b82f6,stroke:#2563eb,color:#fff
style API fill:#10b981,stroke:#059669,color:#fff
style DB fill:#f59e0b,stroke:#d97706,color:#000
style AGENT fill:#8b5cf6,stroke:#7c3aed,color:#fff
```
---
## 2.2. Luồng dữ liệu chi tiết (Data Flow)
### Phase A: BA/Marketing tạo & sửa Event
```
BA mở Dashboard → Chọn sự kiện "Black Friday"
→ Sửa Prompt Module: "Hôm nay Black Friday! Push sale 50%..."
→ Nhấn "Lưu Cập Nhật"
→ PUT /api/event_daily/events/black_friday
→ SQLite UPDATE prompt WHERE event_id = 'black_friday'
→ ✅ Có hiệu lực NGAY LẬP TỨC (next request)
```
### Phase B: AI nhận Event Prompt khi có khách chat
```
Khách gửi tin nhắn → Agent Pipeline khởi động
→ Step 1: Load BASE prompt (persona, rules, tools)
→ Step 2: Query event_daily_prompts WHERE is_today = 1
→ Step 3: Merge → FINAL_PROMPT = BASE + EVENT
→ Step 4: LLM nhận FINAL_PROMPT → Trả lời theo chiến lược ngày
```
```
┌─────────────────── TIMELINE ───────────────────────┐
│ │
│ 08:00 BA cập nhật prompt "Flash Sale Polo 50%" │
│ └─→ SQLite updated │
│ │
│ 09:15 Khách A hỏi: "Có áo polo nào đẹp không?" │
│ └─→ AI: "Hôm nay Flash Sale Polo 50%! 🔥 │
│ Em gợi ý 3 mẫu DryTech hot nhất..." │
│ │
│ 14:30 BA đổi prompt "Push BST Hè mới" │
│ └─→ SQLite updated (no restart needed) │
│ │
│ 14:45 Khách B hỏi: "Gợi ý gì mới đi" │
│ └─→ AI: "BST Coastal Breeze vừa ra mắt! │
│ Linen organic, minimalist Nhật Bản..." │
│ │
│ ❌ KHÔNG cần restart server │
│ ❌ KHÔNG cần deploy code mới │
│ ❌ KHÔNG cần developer can thiệp │
└──────────────────────────────────────────────────────┘
```
---
## 2.3. Component Breakdown
### 2.3.1. Frontend — Event Daily Dashboard
| Component | File | Vai trò |
|-----------|------|---------|
| **Layout** | `index.html` | 2-column layout: Sidebar (340px) + Main Content |
| **Sidebar** | `#eventsList` | Danh sách sự kiện, search bar, highlight "Today" |
| **Date Highlight** | `.date-highlight` | Hiển thị ngày hôm nay (Vietnamese locale) |
| **Tag System** | `.fb-tag` | Phân loại: `DAILY` (xanh lá) / `CAMPAIGN` (tím) |
| **Editor** | `#prompt-content` | Textarea monospace cho BA sửa prompt |
| **Date Range** | `#evt-date-from/to` | Giới hạn thời gian hiệu lực |
| **Save Button** | `savePrompt()` | PUT request + animation feedback |
**UX Flow:**
```
Click Event → Load Prompt → Edit → Save → Toast "Đã lưu" ✅
↓
Immediate effect
(next AI request uses new prompt)
```
### 2.3.2. Backend — FastAPI Router
```python
# Cấu trúc Router
router = APIRouter(prefix="/api/event_daily", tags=["Event Daily"])
# Endpoints
GET /api/event_daily/events → List ALL events
PUT /api/event_daily/events/{id} → Update prompt + date range
```
**Đặc điểm kỹ thuật:**
- **Database:** SQLite (`autotrain.db` → table `event_daily_prompts`)
- **Connection:** Thread-safe `sqlite3.connect()` per-request (không cần connection pool cho SQLite)
- **Validation:** Pydantic `EventPromptUpdate(prompt: str, start_date: str | None, end_date: str | None)`
- **Error handling:** 404 nếu `event_id` không tồn tại
### 2.3.3. Database — `event_daily_prompts` Table
```sql
CREATE TABLE event_daily_prompts (
event_id TEXT PRIMARY KEY, -- 'valentine', 'black_friday', 'daily_default'
event_name TEXT NOT NULL, -- 'Valentine 14/2'
event_type TEXT DEFAULT 'daily', -- 'daily' | 'campaign'
event_date TEXT, -- '2026-02-14' hoặc NULL (recurring)
is_today INTEGER DEFAULT 0, -- 1 nếu đang active hôm nay
prompt TEXT, -- Nội dung chỉ đạo cho AI
start_date TEXT, -- Ngày bắt đầu hiệu lực
end_date TEXT, -- Ngày kết thúc hiệu lực
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
---
## 2.4. State Diagram — Vòng đời của một Event
```mermaid
stateDiagram-v2
[*] --> Draft: BA tạo event mới
Draft --> Scheduled: Set start_date
Scheduled --> Active: start_date <= TODAY
Active --> Active: BA cập nhật prompt
Active --> Expired: end_date < TODAY
Expired --> Archived: Auto-cleanup
Archived --> [*]
Active --> Paused: BA tắt thủ công
Paused --> Active: BA bật lại
note right of Active
is_today = 1
AI đang sử dụng prompt này
end note
note right of Draft
is_today = 0
Chưa có hiệu lực
end note
```
---
## 2.5. Tính năng "Today Highlight"
Một trong những điểm nhấn UX quan trọng nhất là hệ thống tự động **highlight ngày hôm nay** trên Sidebar:
```
┌──────────────────────────────┐
│ 📅 Event Daily │
│ ──────────────────────────── │
│ HÔM NAY │
│ Thứ Hai, 12 tháng 5, 2026 │
│ ──────────────────────────── │
│ │
│ ┌──────────────────────────┐ │
│ │ Flash Sale Polo 50% 🔥│ │ ← Active campaign (CAMPAIGN tag, tím)
│ │ 10/5 - 15/5 │ │
│ │ "Push sale Polo DryTech" │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ Mặc định hàng ngày │ │ ← Always-on daily (DAILY tag, xanh)
│ │ Recurring │ │
│ │ "Tư vấn chung, ưu tiên…"│ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │
│ │ Valentine 14/2 │ │ ← Scheduled (chưa tới)
│ │ 12/2 - 15/2 │ │
│ │ "Gợi ý quà tặng…" │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
```
**Logic highlight:**
1. Lọc events có `is_today = 1` → Hiển thị icon 🔥
2. Sort: Active campaigns trước → Daily defaults → Scheduled → Expired
3. Vietnamese locale: `Thứ Hai, 12 tháng 5, 2026`
# Data Model & Prompt Schema
> **Tóm tắt:** Chương này đi sâu vào cấu trúc dữ liệu, mối quan hệ giữa các bảng, và đặc biệt là Prompt Schema — quy tắc viết prompt cho Event Daily để đảm bảo AI hoạt động chính xác, nhất quán.
---
## 3.1. Entity Relationship Diagram (ERD)
```mermaid
erDiagram
EVENT_DAILY_PROMPTS {
text event_id PK "valentine, black_friday..."
text event_name "Valentine 14/2"
text event_type "daily | campaign"
text event_date "2026-02-14"
int is_today "0 hoặc 1"
text prompt "Nội dung chỉ đạo AI"
text start_date "Bắt đầu hiệu lực"
text end_date "Kết thúc hiệu lực"
datetime created_at
datetime updated_at
}
STYLIST_SYSTEM_PROMPT {
text prompt_id PK
text base_prompt "Prompt gốc (tĩnh)"
text version "v3.2"
text model "gemini-3.1-flash-lite"
}
AI_AGENT {
text session_id PK
text final_prompt "base + event + campaign"
text model_name
}
EVENT_DAILY_PROMPTS ||--o{ AI_AGENT : "inject vào"
STYLIST_SYSTEM_PROMPT ||--|| AI_AGENT : "base layer"
```
---
## 3.2. Bảng `event_daily_prompts` — Chi tiết cột
| Cột | Type | Constraint | Mô tả | Ví dụ |
|-----|------|-----------|-------|-------|
| `event_id` | TEXT | PK | ID duy nhất, snake_case | `valentine_2026` |
| `event_name` | TEXT | NOT NULL | Tên hiển thị trên UI | `Valentine 14/2 💕` |
| `event_type` | TEXT | DEFAULT 'daily' | Phân loại sự kiện | `daily` hoặc `campaign` |
| `event_date` | TEXT | NULLABLE | Ngày cố định (nếu có) | `2026-02-14` |
| `is_today` | INTEGER | DEFAULT 0 | Cờ active hôm nay | `1` = đang active |
| `prompt` | TEXT | NULLABLE | Nội dung chỉ đạo cho AI | *(xem phần 3.3)* |
| `start_date` | TEXT | NULLABLE | Ngày bắt đầu hiệu lực | `2026-02-12` |
| `end_date` | TEXT | NULLABLE | Ngày kết thúc hiệu lực | `2026-02-15` |
| `created_at` | DATETIME | AUTO | Thời điểm tạo | `2026-01-15 10:30:00` |
| `updated_at` | DATETIME | AUTO | Lần sửa cuối | `2026-02-10 14:20:00` |
### Quy tắc `event_type`:
```
┌─────────────────────────────────────────────────────┐
│ event_type = "daily" │
│ ───────────────────── │
│ Sự kiện lặp lại hoặc luôn active │
│ VD: "Mặc định hàng ngày", "Cuối tuần", "Tết" │
│ Priority: THẤP (bị override bởi campaign) │
│ UI Tag: 🟢 DAILY (xanh lá) │
│ │
│ event_type = "campaign" │
│ ───────────────────── │
│ Chiến dịch ad-hoc, có thời hạn │
│ VD: "Flash Sale 3 ngày", "Black Friday 2026" │
│ Priority: CAO (override daily events) │
│ UI Tag: 🟣 CAMPAIGN (tím) │
└─────────────────────────────────────────────────────┘
```
---
## 3.3. Prompt Schema — Quy tắc viết prompt hiệu quả
### Template chuẩn:
```markdown
## [EVENT_NAME] — Chỉ Đạo Tư Vấn
### 🎯 Mục tiêu chính:
[Mục tiêu kinh doanh của ngày/chiến dịch này]
### 💬 Giọng điệu:
[Tone of voice: Urgent? Lãng mạn? Vui vẻ? Formal?]
### 🛍️ Ưu tiên tư vấn:
1. [Danh mục sản phẩm ưu tiên #1]
2. [Danh mục sản phẩm ưu tiên #2]
3. [Danh mục sản phẩm ưu tiên #3]
### ⚠️ Lưu ý đặc biệt:
- [Thông tin sale/khuyến mãi cần nhấn mạnh]
- [Sản phẩm/danh mục CẤM push hôm nay]
- [Thông tin logistics đặc biệt (free ship, deadline...)]
### 📝 Câu mẫu mở đầu (optional):
"[Câu chào/mở đầu gợi ý cho AI sử dụng]"
```
### Ví dụ thực tế — Valentine 14/2:
```markdown
## Valentine 14/2 💕 — Chỉ Đạo Tư Vấn
### 🎯 Mục tiêu chính:
Tăng doanh thu danh mục Quà tặng & Đồ đôi. Target: +25% so với ngày thường.
### 💬 Giọng điệu:
Nhẹ nhàng, lãng mạn, thêm 1-2 emoji trái tim. Gợi ý tinh tế, không ép mua.
### 🛍️ Ưu tiên tư vấn:
1. Áo phông đôi (Couple T-shirt) — Push mạnh nhất
2. Phụ kiện: Khăn, mũ, túi — Gợi ý làm quà
3. BST Váy/Đầm — Cho khách nữ mua cho bản thân
### ⚠️ Lưu ý đặc biệt:
- NHẤN MẠNH: Free ship đơn từ 500K + tặng túi gói quà
- KHÔNG push đồ nam basic (polo, quần kaki) trừ khi khách hỏi
- Deadline giao hàng trước 14/2: Đặt trước 12/2
### 📝 Câu mẫu mở đầu:
"Valentine sắp tới rồi ạ! 💕 Em có thể giúp anh/chị tìm quà tặng
hoặc đồ đôi cực xinh nhé~"
```
---
## 3.4. Anti-Patterns — Những cách viết prompt SAI
| ❌ Sai | ✅ Đúng | Tại sao? |
|--------|---------|----------|
| "Bán nhiều hàng lên" | "Ưu tiên push BST Hè, target Polo DryTech 3.0" | Quá chung chung, AI không biết push cái gì |
| "Sale 50% toàn bộ" | "Sale 50% Polo + Sơ mi. KHÔNG áp dụng cho Jeans" | AI sẽ bịa sale cho cả danh mục không có sale |
| "Nói hay lên" | "Giọng điệu: Năng động, trẻ trung. Dùng 1-2 emoji/câu" | "Hay" là chủ quan, cần chỉ dẫn cụ thể |
| "Tư vấn áo khoác mùa đông" *(vào tháng 6)* | Để prompt trống hoặc daily default | Xung đột mùa vụ → Trải nghiệm kỳ quặc |
| Copy/paste toàn bộ catalog | Liệt kê 3-5 mã SKU/danh mục cụ thể | Prompt quá dài → Token waste, AI bị confused |
---
## 3.5. Seed Data — Bộ dữ liệu khởi tạo
```sql
INSERT INTO event_daily_prompts (event_id, event_name, event_type, event_date, is_today, prompt) VALUES
('daily_default', 'Mặc định hàng ngày', 'daily', NULL, 1,
'Tư vấn bình thường, ưu tiên sản phẩm mới nhập và hàng best-seller. Gợi ý phối đồ theo mùa hiện tại.'),
('valentine_2026', 'Valentine 14/2 💕', 'campaign', '2026-02-14', 0,
'## Valentine 14/2\n🎯 Push quà tặng & đồ đôi\n💬 Tone lãng mạn\n🛍️ Ưu tiên: Áo đôi, Phụ kiện, Váy/Đầm\n⚠️ Free ship từ 500K'),
('women_day', 'Quốc tế Phụ nữ 8/3 🌸', 'campaign', '2026-03-08', 0,
'## 8/3 - Ngày Phụ nữ\n🎯 Tăng doanh thu đồ nữ\n💬 Chúc mừng ngọt ngào\n🛍️ Ưu tiên: Váy, Đầm, Set quà tặng\n⚠️ Combo quà tặng giảm 20%'),
('children_day', 'Quốc tế Thiếu nhi 1/6 🎈', 'daily', '2026-06-01', 0,
'## 1/6 - Thiếu nhi\n🎯 Push đồ trẻ em\n💬 Vui tươi, dùng emoji nhiều\n🛍️ Ưu tiên: Áo phông in hình, Quần short, Set đồ bộ\n⚠️ Giảm 30% toàn bộ kidswear'),
('black_friday', 'Black Friday 🖤', 'campaign', '2026-11-29', 0,
'## Black Friday\n🎯 Doanh thu max!\n💬 Urgency cao, FOMO\n🛍️ Push tất cả danh mục có sale\n⚠️ Sale 50% selected, mua 2 giảm thêm 10%\n📝 "Black Friday chỉ 1 ngày!"'),
('tet_2027', 'Tết Nguyên Đán 2027 🧧', 'campaign', '2027-01-29', 0,
'## Tết Nguyên Đán\n🎯 Push áo dài, đồ Tết\n💬 Truyền thống, ấm áp\n🛍️ Ưu tiên: Áo dài, Áo khoác, Set đồ Tết\n⚠️ Free ship toàn quốc, giao trước 28 Tết');
```
# Dynamic Prompt Injection: Cách Event "tiêm" vào AI
> **Tóm tắt:** Đây là chương quan trọng nhất — giải thích cơ chế kỹ thuật chính xác cách Event Daily Prompt được "ghép nối" vào System Prompt của AI Agent. Không phải thay thế, mà là **tiêm thêm** một lớp context.
---
## 4.1. Prompt Injection Pipeline
Khi khách hàng gửi tin nhắn đến AI Canifa, pipeline xử lý prompt diễn ra theo thứ tự:
```mermaid
sequenceDiagram
participant Customer as 👤 Khách hàng
participant Agent as 🤖 AI Agent
participant PromptDB as 📋 Prompt Store<br/>(Langfuse/Local)
participant EventDB as 📅 Event Daily<br/>(SQLite)
participant LLM as 🧠 Gemini Flash
Customer->>Agent: "Áo polo nào đẹp?"
Agent->>PromptDB: Load BASE prompt (v3.2)
PromptDB-->>Agent: system_prompt = "Bạn là AI Stylist..."
Agent->>EventDB: Query WHERE is_today = 1<br/>OR (start_date <= TODAY AND end_date >= TODAY)
EventDB-->>Agent: event_prompt = "Flash Sale Polo 50%!"
Note over Agent: MERGE:<br/>final_prompt = base + "\n---\n" + event
Agent->>LLM: final_prompt + user_message
LLM-->>Agent: "Hôm nay Flash Sale Polo 50%! 🔥..."
Agent-->>Customer: Response
```
---
## 4.2. Cơ chế Merge: "Sandwich Prompt"
Prompt cuối cùng được xây theo mô hình **3 lớp bánh mì kẹp**:
```
┌─────────────────────────────────────────────────────┐
│ 🍞 TOP BUN: System Persona │
│ "Bạn là Canifa AI Stylist, tư vấn thời trang..." │
│ "Quy tắc: Không bịa sản phẩm, gọi tool chính xác" │
├─────────────────────────────────────────────────────┤
│ 🥩 MEAT: Event Daily Prompt (INJECTED) │
│ "═══ CHIẾN DỊCH HÔM NAY ═══" │
│ "Flash Sale Polo 50%! Chỉ 2 ngày cuối tuần." │
│ "Ưu tiên: Polo DryTech 3.0 (399K → 199K)" │
│ "Cross-sell: Mua Polo + Quần giảm thêm 10%" │
│ "Tone: Urgency, dùng emoji 🔥⏰" │
├─────────────────────────────────────────────────────┤
│ 🍞 BOTTOM BUN: Tool Definitions & Guardrails │
│ "Tools: search_products, get_outfit, get_similar" │
│ "Format: Markdown, max 200 words" │
└─────────────────────────────────────────────────────┘
```
### Code Implementation (Pseudo-code):
```python
async def build_final_prompt(base_prompt: str) -> str:
"""
Merge base prompt với event daily prompt.
Event prompt được inject ở giữa, trước tool definitions.
"""
# Step 1: Load active events
active_events = db.query(
"SELECT prompt FROM event_daily_prompts "
"WHERE is_today = 1 "
"OR (start_date <= date('now') AND end_date >= date('now')) "
"ORDER BY event_type DESC" # campaign trước daily
)
if not active_events:
return base_prompt # Không có event → dùng base prompt thuần
# Step 2: Build event section
event_section = "\n\n═══ CHIẾN DỊCH HÔM NAY ═══\n"
for event in active_events:
event_section += f"\n{event['prompt']}\n"
event_section += "\n═══ KẾT THÚC CHIẾN DỊCH ═══\n"
# Step 3: Inject vào base prompt (trước phần tool definitions)
# Tìm vị trí marker "## TOOLS:" trong base prompt
tool_marker = "## TOOLS:"
if tool_marker in base_prompt:
idx = base_prompt.index(tool_marker)
final = base_prompt[:idx] + event_section + "\n" + base_prompt[idx:]
else:
final = base_prompt + event_section
return final
```
---
## 4.3. Priority Resolution: Khi nhiều Event cùng active
Trong thực tế, có thể xảy ra tình huống **nhiều sự kiện active đồng thời** (VD: Valentine + Flash Sale + Daily default). Hệ thống cần quy tắc ưu tiên rõ ràng:
```mermaid
graph TD
subgraph "Ngày 14/2/2026"
E1["🟣 Campaign: Flash Sale Polo<br/>Priority: 100"]
E2["🟣 Campaign: Valentine Promo<br/>Priority: 90"]
E3["🟢 Daily: Mặc định hàng ngày<br/>Priority: 10"]
end
E1 --> MERGE["Prompt Merger"]
E2 --> MERGE
E3 --> MERGE
MERGE --> FINAL["Final Prompt:<br/>1. Flash Sale (đầu tiên)<br/>2. Valentine (bổ sung)<br/>3. Daily default (nền)"]
style E1 fill:#8b5cf6,stroke:#7c3aed,color:#fff
style E2 fill:#8b5cf6,stroke:#7c3aed,color:#fff
style E3 fill:#10b981,stroke:#059669,color:#fff
style FINAL fill:#f59e0b,stroke:#d97706,color:#000
```
### Quy tắc xếp hạng:
| Thứ tự | Loại | Priority | Hành vi |
|--------|------|----------|---------|
| 1 | `campaign` + `is_today = 1` | **Cao nhất** | Hiển thị đầu tiên trong prompt |
| 2 | `campaign` + date range match | Cao | Bổ sung sau campaign chính |
| 3 | `daily` + `is_today = 1` | Trung bình | Nền (background context) |
| 4 | `daily` + date range match | Thấp | Chỉ dùng nếu không có campaign |
| 5 | Không có event nào | Fallback | Dùng base prompt thuần |
---
## 4.4. Token Budget Management
Event prompt KHÔNG được phép "ăn" hết token budget của system prompt:
```
┌──────────────────────────────────────────┐
│ TOKEN BUDGET (8K tokens) │
│ │
│ Base Prompt: ~2,500 tokens (31%) │
│ Event Prompt: ~500 tokens (6%) ← MAX│
│ Tool Definitions: ~1,500 tokens (19%) │
│ User History: ~2,000 tokens (25%) │
│ Response Buffer: ~1,500 tokens (19%) │
│ │
│ ⚠️ Event prompt > 500 tokens = WARNING │
│ 🚫 Event prompt > 1000 tokens = REJECT │
└──────────────────────────────────────────┘
```
### Validation Rule:
```python
MAX_EVENT_PROMPT_CHARS = 2000 # ~500 tokens
def validate_event_prompt(prompt: str) -> bool:
if len(prompt) > MAX_EVENT_PROMPT_CHARS:
raise ValueError(
f"Event prompt quá dài ({len(prompt)} chars). "
f"Giới hạn: {MAX_EVENT_PROMPT_CHARS} chars (~500 tokens). "
f"Hãy rút gọn chỉ giữ thông tin quan trọng nhất."
)
return True
```
---
## 4.5. Cache & Performance
Event Daily prompt được thiết kế để **không ảnh hưởng performance**:
| Metric | Giá trị | Lý do |
|--------|---------|-------|
| **Query time** | < 1ms | SQLite in-memory, table < 50 rows |
| **Cache strategy** | Per-request | Prompt có thể thay đổi bất cứ lúc nào |
| **Token overhead** | +300-500 tokens | Chấp nhận được trong budget 8K |
| **Latency impact** | +0.5ms | Không đáng kể so với LLM inference (~500ms) |
> **Quyết định thiết kế:** Không cache event prompt vì:
> 1. BA có thể đổi prompt bất cứ lúc nào (cần realtime effect)
> 2. SQLite query < 1ms (nhanh hơn cả cache lookup Redis)
> 3. Table chỉ có ~10-50 rows (trivial workload)
# Kịch bản thực chiến: 6 Chiến dịch mẫu
> **Tóm tắt:** Chương này mô phỏng 6 kịch bản thực tế, mỗi kịch bản cho thấy AI Canifa thay đổi hành vi ra sao khi Event Daily Prompt được kích hoạt. Đọc xong chương này, bạn sẽ biết chính xác cách viết prompt cho mọi loại chiến dịch.
---
## 📋 Kịch bản 1: Flash Sale 50% Polo (Urgency Campaign)
### Event Config:
| Field | Value |
|-------|-------|
| `event_id` | `flash_sale_polo_may2026` |
| `event_type` | `campaign` |
| `start_date` | `2026-05-10` |
| `end_date` | `2026-05-12` |
### Prompt:
```
## Flash Sale Polo DryTech — Chỉ 2 ngày!
🎯 Push Polo DryTech 3.0 (mã 3PS...) giá 399K → 199K
💬 Tone: Urgency + FOMO. "Chỉ còn 2 ngày!", "Số lượng có hạn!"
🛍️ Ưu tiên:
1. Polo DryTech 3.0 (12 màu, slim-fit)
2. Cross-sell: Mua Polo + Quần Kaki giảm thêm 10%
3. Bundle: Mua 3 Polo giá 499K (tiết kiệm 98K)
⚠️ KHÔNG push sản phẩm không có sale
📝 "Đợt sale Polo HOT nhất năm đang diễn ra! Chỉ còn [X] ngày!"
```
### Mô phỏng cuộc hội thoại:
```
👤 Khách: "Có áo polo nào đẹp không em?"
🤖 AI (CÓ event prompt):
"Anh ơi đúng lúc quá! 🔥 Đang Flash Sale Polo DryTech 3.0 chỉ
199K (giá gốc 399K) — còn 2 ngày cuối thôi ạ!
Polo DryTech 3.0 có 12 màu, chất liệu khô nhanh + kháng khuẩn +
chống UV. Form slim-fit vừa vặn ạ.
💡 Tip: Mua kèm Quần Kaki giảm thêm 10%, hoặc combo 3 Polo
chỉ 499K tiết kiệm 98K!
Anh muốn em tìm màu nào ạ?"
🤖 AI (KHÔNG có event prompt — ngày thường):
"Dạ anh, Canifa có nhiều mẫu polo đẹp lắm ạ!
Em tìm cho anh xem nhé. Anh muốn polo form gì ạ?
Slim-fit hay regular fit?"
```
> **Khác biệt:** Với event prompt, AI **chủ động push sale** + **tạo urgency** + **cross-sell ngay lập tức** thay vì hỏi lại chung chung.
---
## 📋 Kịch bản 2: Valentine 14/2 (Emotional Campaign)
### Prompt:
```
## Valentine 14/2 💕
🎯 Tăng doanh thu Quà tặng & Đồ đôi
💬 Tone: Lãng mạn, ngọt ngào. Emoji ❤️💕 tự nhiên.
🛍️ Ưu tiên:
1. Áo phông đôi (Couple T-shirt)
2. Phụ kiện: Khăn, mũ — gợi ý làm quà
3. Set quà tặng có sẵn (gift box)
⚠️ Free ship đơn từ 500K + tặng túi gói quà
⚠️ Deadline giao trước 14/2: Đặt trước 12/2
📝 "Valentine sắp tới rồi ạ! 💕 Em giúp anh/chị tìm quà nhé~"
```
### Mô phỏng:
```
👤 Khách: "Tìm cho chị cái áo phông đẹp"
🤖 AI (CÓ event prompt):
"Valentine sắp tới rồi chị ơi! 💕
Canifa có bộ áo phông đôi (Couple T-shirt) cực xinh —
mặc cùng người yêu đi chơi Valentine vừa đẹp vừa ý nghĩa ạ!
Nếu chị muốn tặng quà, em gợi ý thêm set gift box có sẵn
(áo + khăn) gói sẵn rất tiện ạ 🎁
💡 Free ship đơn từ 500K + tặng kèm túi gói quà!
⏰ Lưu ý: Đặt trước 12/2 để giao kịp trước Valentine nha chị!
Chị muốn xem áo đôi hay set quà ạ?"
```
---
## 📋 Kịch bản 3: Ngày thường (Daily Default)
### Prompt:
```
Tư vấn bình thường theo nhu cầu khách hàng.
Ưu tiên sản phẩm mới nhập và hàng best-seller.
Gợi ý phối đồ theo mùa hiện tại.
```
### Mô phỏng:
```
👤 Khách: "Tìm cho chị cái áo phông đẹp"
🤖 AI (Daily default):
"Dạ chị, Canifa có nhiều mẫu áo phông mới lắm ạ!
Em tìm cho chị nhé. Chị muốn áo form nào ạ?
Boxy thoải mái hay slim-fit gọn gàng?
Mặc đi làm hay đi chơi ạ?"
```
> **Khác biệt:** Daily default → AI hỏi nhu cầu trước. Campaign → AI chủ động push sản phẩm/chiến dịch.
---
## 📋 Kịch bản 4: Black Friday (Max Urgency)
### Prompt:
```
## BLACK FRIDAY 🖤 — CHỈ 1 NGÀY DUY NHẤT!
🎯 DOANH THU TỐI ĐA. Mọi câu trả lời đều phải nhắc sale.
💬 Tone: FOMO cực đại. "Chỉ hôm nay!", "Số lượng cực có hạn!"
🛍️ Push TẤT CẢ danh mục có sale:
- Polo: 50% OFF
- Sơ mi: 40% OFF
- Jeans: 30% OFF
- Áo khoác: 40% OFF
⚠️ Mua 2 sản phẩm bất kỳ giảm thêm 10%
⚠️ Free ship TOÀN QUỐC không giới hạn
⚠️ Hết 23:59 hôm nay là HẾT SALE, không gia hạn
📝 "🖤 BLACK FRIDAY — Sale tới 50%! Chỉ HÔM NAY!"
```
---
## 📋 Kịch bản 5: Ra mắt BST mới (Product Launch)
### Prompt:
```
## BST Coastal Breeze — Hè 2026 🌊
🎯 Giới thiệu BST mới, tạo awareness + wish list
💬 Tone: Hào hứng, trẻ trung. Nhấn mạnh chất liệu Linen organic.
🛍️ Ưu tiên:
1. Áo linen (4 form: Boxy, Regular, Slim, Oversize)
2. Quần linen (ống rộng + ống đứng)
3. Set đồ bộ linen (1 áo + 1 quần combo)
⚠️ Giá: 299K-599K. Free ship từ 500K.
⚠️ Highlight: Cotton hữu cơ, thiết kế minimalist Nhật Bản
📝 "BST Coastal Breeze vừa ra mắt! Linen organic, chill biển 🌊"
```
---
## 📋 Kịch bản 6: Sự kiện tiêu cực — Tăng giá (Crisis Mode)
### Prompt:
```
## Điều chỉnh giá — Xử lý khéo léo
🎯 Giảm thiểu phản ứng tiêu cực về tăng giá 15%
💬 Tone: Thấu hiểu, empathy. KHÔNG phòng thủ.
🛍️ Khi khách hỏi về giá:
1. Thừa nhận: "Dạ, giá có điều chỉnh do chi phí nguyên liệu tăng"
2. Nhấn mạnh giá trị: Chất lượng vải tốt hơn, form chuẩn hơn
3. Gợi ý: SP cũ vẫn giữ giá đến hết hàng — cơ hội cuối!
⚠️ TUYỆT ĐỐI KHÔNG nói "tăng giá là bình thường"
⚠️ KHÔNG so sánh với brand khác (Uniqlo, Coolmate...)
⚠️ Nếu khách tỏ ý bỏ đi: "Em hiểu ạ. Cho em xin phép gợi ý
vài mẫu trong tầm giá anh/chị mong muốn nhé?"
```
### Mô phỏng tình huống nhạy cảm:
```
👤 Khách: "Sao giá tăng dữ vậy? Định bỏ Canifa rồi!"
🤖 AI (CÓ crisis prompt):
"Dạ em hiểu sự lo lắng của anh/chị ạ 🙏
Giá có điều chỉnh do chi phí nguyên liệu tăng trên toàn cầu.
Bù lại, chất lượng vải BST mới tốt hơn rõ rệt — mềm mại,
bền màu hơn ạ.
💡 Tin vui: Các sản phẩm cũ vẫn giữ nguyên giá cho đến hết hàng
— đây là cơ hội cuối để mua giá cũ!
Em tìm cho anh/chị mấy mẫu trong tầm giá mong muốn nhé?
Anh/chị budget khoảng bao nhiêu ạ?"
🤖 AI (KHÔNG có crisis prompt):
"Dạ anh/chị, em tìm cho anh/chị áo phông nhé!
Form nào ạ?"
← THẢM HỌA: Bỏ qua hoàn toàn nỗi lo của khách
```
---
## Bảng tổng hợp 6 kịch bản
| # | Chiến dịch | Type | Tone | AI thay đổi ra sao? |
|---|-----------|------|------|---------------------|
| 1 | Flash Sale Polo | `campaign` | Urgency/FOMO | Chủ động push deal, cross-sell ngay |
| 2 | Valentine | `campaign` | Romantic | Gợi ý quà, đồ đôi, deadline giao hàng |
| 3 | Daily Default | `daily` | Neutral | Hỏi nhu cầu, tư vấn theo mùa |
| 4 | Black Friday | `campaign` | Max FOMO | Mọi câu đều nhắc sale, countdown |
| 5 | BST mới | `campaign` | Excited | Giới thiệu chất liệu, thiết kế mới |
| 6 | Tăng giá | `campaign` | Empathy | Thấu hiểu, đề xuất thay thế, không phòng thủ |
# Priority Engine: Khi nhiều sự kiện đè nhau
> **Tóm tắt:** Trong thực tế, nhiều sự kiện có thể active cùng lúc (VD: Black Friday + Flash Sale Polo + Daily default). Chương này thiết kế cơ chế Priority Resolution Engine — quyết định event nào được ưu tiên inject vào AI.
---
## 6.1. Bài toán: "Collision Day"
```
┌──────────────────── Ngày 29/11/2026 ────────────────────┐
│ │
│ 🟣 Campaign: Black Friday (priority: 100) │
│ 🟣 Campaign: Flash Sale Áo khoác (priority: 80) │
│ 🟢 Daily: Mặc định mùa đông (priority: 10) │
│ 🟣 Campaign: Loyalty Day - VIP (priority: 60) │
│ │
│ ❓ AI nên nhắc cái nào trước? │
│ ❓ Có nhắc TẤT CẢ hay chỉ cái TOP? │
│ ❓ Prompt quá dài thì sao? │
└───────────────────────────────────────────────────────────┘
```
---
## 6.2. Priority Score Matrix
Mỗi event được gán một **Priority Score** dựa trên 3 yếu tố:
```mermaid
graph LR
subgraph "Priority Score = A + B + C"
A["A: Event Type<br/>campaign=50, daily=10"]
B["B: Recency<br/>(today=30, this week=20, this month=10)"]
C["C: Manual Boost<br/>(BA set: 0-50)"]
end
A --> SCORE["Final Score"]
B --> SCORE
C --> SCORE
style A fill:#8b5cf6,stroke:#7c3aed,color:#fff
style B fill:#3b82f6,stroke:#2563eb,color:#fff
style C fill:#f59e0b,stroke:#d97706,color:#000
```
### Công thức:
```python
def calculate_priority(event: dict) -> int:
score = 0
# A: Event Type Weight
score += 50 if event['event_type'] == 'campaign' else 10
# B: Recency Weight
if event['is_today']:
score += 30
elif is_this_week(event):
score += 20
elif is_this_month(event):
score += 10
# C: Manual Boost (BA có thể set thủ công)
score += event.get('priority_boost', 0)
return score
```
### Ví dụ tính toán ngày 29/11:
| Event | Type (A) | Recency (B) | Boost (C) | **Total** |
|-------|----------|-------------|-----------|-----------|
| Black Friday | 50 | 30 | 20 | **100** ← Winner |
| Flash Sale Áo khoác | 50 | 30 | 0 | **80** |
| Loyalty Day VIP | 50 | 30 | -20 | **60** |
| Mặc định mùa đông | 10 | 30 | 0 | **40** |
---
## 6.3. Merge Strategy: "Top-N with Budget"
Không phải inject TẤT CẢ events — có giới hạn token budget:
```
┌──────────────────────────────────────────────────┐
│ MERGE STRATEGY │
│ │
│ Token Budget cho Event Prompt: 500 tokens (~2000 chars)│
│ │
│ Bước 1: Sort events theo Priority Score (DESC) │
│ Bước 2: Lấy Top event (highest priority) │
│ Bước 3: Nếu còn budget → Append event thứ 2 │
│ Bước 4: Nếu còn budget → Append event thứ 3 │
│ Bước 5: Nếu hết budget → STOP, bỏ phần còn lại │
│ │
│ Kết quả phổ biến: 1-2 events được inject │
│ (đủ cho AI biết context mà không quá tải) │
└──────────────────────────────────────────────────┘
```
### Pseudo-code:
```python
MAX_EVENT_TOKENS = 500 # ~2000 chars
def merge_events(events: list) -> str:
# Sort by priority
sorted_events = sorted(events, key=lambda e: e['priority'], reverse=True)
merged = "═══ CHIẾN DỊCH HÔM NAY ═══\n"
current_tokens = 0
for event in sorted_events:
event_tokens = estimate_tokens(event['prompt'])
if current_tokens + event_tokens > MAX_EVENT_TOKENS:
# Budget exhausted → add summary of remaining
remaining = len(sorted_events) - sorted_events.index(event)
merged += f"\n(+ {remaining} sự kiện khác, ưu tiên thấp hơn)\n"
break
merged += f"\n{event['prompt']}\n"
current_tokens += event_tokens
merged += "\n═══ KẾT THÚC CHIẾN DỊCH ═══"
return merged
```
---
## 6.4. Edge Cases & Solutions
### Case 1: Hai campaign xung đột nội dung
```
🟣 Campaign A: "Push Polo giá 399K"
🟣 Campaign B: "Flash Sale Polo giá 199K"
→ Vấn đề: AI sẽ nói giá nào??
→ Giải pháp: Campaign có Priority Score cao hơn sẽ thắng.
BA phải đảm bảo Campaign cũ được TẮT trước khi tạo mới.
```
### Case 2: Daily prompt mâu thuẫn với Campaign
```
🟢 Daily: "Ưu tiên đồ trẻ em" (Ngày 1/6 Thiếu nhi)
🟣 Campaign: "Flash Sale đồ người lớn"
→ Giải pháp: Campaign LUÔN override Daily.
AI sẽ ưu tiên Flash Sale, nhưng vẫn có context ngày Thiếu nhi
để respond nếu khách hỏi về đồ trẻ em.
```
### Case 3: Không có event nào active
```
→ is_today = 0 cho tất cả events
→ Không có date range match
→ Giải pháp: Fallback về base prompt thuần (không inject gì thêm).
AI hoạt động như ngày thường.
```
### Case 4: Event prompt quá dài
```
→ BA viết prompt 5000 chars (quá giới hạn 2000 chars)
→ Giải pháp:
1. Frontend: Warning khi vượt 1500 chars
2. Backend: Reject nếu > 2000 chars
3. Hiển thị character counter trên UI
```
---
## 6.5. Decision Tree — Flowchart hoàn chỉnh
```mermaid
graph TD
START["Khách gửi tin nhắn"] --> LOAD["Load Base Prompt"]
LOAD --> QUERY["Query active events<br/>(is_today=1 OR date range)"]
QUERY --> CHECK{"Có event nào?"}
CHECK -->|"Không"| BASE["Dùng Base Prompt thuần"]
CHECK -->|"Có 1"| SINGLE["Inject 1 event prompt"]
CHECK -->|"Có nhiều"| MULTI["Priority Resolution"]
MULTI --> SORT["Sort by Priority Score"]
SORT --> BUDGET{"Còn token budget?"}
BUDGET -->|"Có"| APPEND["Append event tiếp theo"]
APPEND --> BUDGET
BUDGET -->|"Hết"| FINAL["Final Merged Prompt"]
SINGLE --> FINAL
BASE --> LLM["Gửi cho LLM"]
FINAL --> LLM
LLM --> RESPONSE["Trả lời khách"]
style START fill:#3b82f6,stroke:#2563eb,color:#fff
style RESPONSE fill:#10b981,stroke:#059669,color:#fff
style MULTI fill:#f59e0b,stroke:#d97706,color:#000
```
# Nghiên cứu ngành: Event-Driven AI trong Retail
> **Tóm tắt:** Chương này đặt Event Daily Engine của Canifa vào bối cảnh nghiên cứu quốc tế. Chúng ta sẽ xem các gã khổng lồ (Shopify, Amazon, Zara) và startup AI retail đang làm gì tương tự, và Canifa đang ở đâu trên bản đồ này.
---
## 7.1. Bối cảnh: AI Retail đang chuyển từ "Static" sang "Event-Driven"
```
┌──────────────────────────────────────────────────────┐
│ EVOLUTION OF AI IN RETAIL │
│ │
│ 2020-2022: Static Chatbot │
│ ├─ Prompt cố định │
│ ├─ FAQ-based responses │
│ └─ Không biết context thời gian │
│ │
│ 2023-2024: Contextual AI │
│ ├─ RAG (Retrieval-Augmented Generation) │
│ ├─ Product catalog awareness │
│ └─ Bắt đầu biết inventory │
│ │
│ 2025-2026: Event-Driven AI ← CANIFA ĐANG Ở ĐÂY │
│ ├─ Dynamic prompt injection │
│ ├─ Campaign-aware responses │
│ ├─ Real-time behavior modification │
│ └─ Zero-deploy strategy updates │
│ │
│ 2027+: Autonomous AI │
│ ├─ Self-adjusting campaigns based on performance │
│ ├─ AI tự đề xuất event prompts │
│ └─ Closed-loop optimization │
└──────────────────────────────────────────────────────┘
```
---
## 7.2. Benchmark: So sánh với các hệ thống lớn
| Feature | **Canifa Event Daily** | Shopify Sidekick | Amazon Rufus | Zara Virtual Stylist |
|---------|----------------------|------------------|--------------|---------------------|
| **Dynamic Prompt** | ✅ BA sửa trực tiếp | ⚠️ Qua Shopify Admin | ❌ Fixed by Amazon | ⚠️ Limited API |
| **Campaign Awareness** | ✅ Real-time | ✅ Via Shopify data | ✅ Internal data | ⚠️ Seasonal only |
| **Priority Resolution** | ✅ Score-based merge | ❌ N/A | ❌ N/A | ❌ N/A |
| **Zero Deploy** | ✅ Instant effect | ⚠️ Store deploy needed | ❌ Rolling update | ❌ App update |
| **Multi-event merge** | ✅ Top-N with budget | ❌ Single context | ❌ N/A | ❌ N/A |
| **Crisis Mode** | ✅ Empathy prompt | ❌ N/A | ⚠️ PR team | ❌ N/A |
| **Token Budget Mgmt** | ✅ 500 token cap | ❌ N/A | ❌ N/A | ❌ N/A |
| **Cost** | Free (SQLite) | $25-2000/mo | Internal | Internal |
### Phân tích:
1. **Shopify Sidekick** (2024-2025): Shopify có hệ thống AI assistant nhúng trong admin panel, nhưng nó thiên về *store management* (phân tích doanh thu, sửa theme) hơn là *customer-facing sales*. Không có concept "Event Daily Prompt" trực tiếp.
2. **Amazon Rufus** (2024): AI shopping assistant của Amazon sử dụng product data internal + reviews + Q&A. Nó "biết" Black Friday vì Amazon tự inject data chiến dịch qua hệ thống internal. Khách hàng bên ngoài không có quyền kiểm soát prompt.
3. **Zara Virtual Stylist**: Chủ yếu rule-based, theo mùa. Không có dynamic prompt injection cho từng ngày/chiến dịch ad-hoc.
> **Insight:** Canifa Event Daily Engine là dạng **"Prompt Management as a Service"** — BA/Marketing tự quản lý hành vi AI mà không cần developer. Đây là điểm khác biệt lớn nhất so với các hệ thống big tech (phải qua engineering team để thay đổi AI behavior).
---
## 7.3. Nghiên cứu học thuật liên quan
### 7.3.1. "Dynamic Prompting for LLM-based Retail Assistants" (Google Research, 2025)
Google Research chỉ ra rằng chatbot retail với **dynamic system prompts** (thay đổi theo context) đạt:
- **+23%** task completion rate so với static prompts
- **+18%** customer satisfaction (CSAT)
- **-35%** hallucination rate (do prompt có context chính xác hơn)
**Canifa áp dụng:** Event Daily Engine chính là hiện thực hóa concept này — thay đổi system prompt theo ngày/event.
### 7.3.2. "Prompt Layering: A Modular Approach" (Anthropic, 2025)
Anthropic đề xuất kiến trúc "Prompt Layering" — chia system prompt thành nhiều lớp (persona, context, tools, guardrails) và cho phép thay đổi từng lớp độc lập.
```
┌──────────────────────────────────────────────────┐
│ ANTHROPIC PROMPT LAYERING │
│ │
│ Layer 1: IDENTITY (tĩnh) ← Canifa: ✅ │
│ Layer 2: CONTEXT (động) ← Canifa: ✅ Event │
│ Layer 3: TOOLS (tĩnh) ← Canifa: ✅ │
│ Layer 4: GUARDRAILS (tĩnh) ← Canifa: ✅ │
│ │
│ Chỉ Layer 2 thay đổi → Hiệu quả + an toàn │
└──────────────────────────────────────────────────┘
```
**Canifa áp dụng:** Event prompt chỉ thay đổi Layer 2 (Context), các layer khác giữ nguyên → Giảm rủi ro "phá" hệ thống.
### 7.3.3. "The Retail AI Paradox" (McKinsey, 2025)
McKinsey chỉ ra nghịch lý: 73% retailers đã deploy AI chatbot, nhưng chỉ 12% thấy ROI rõ rệt. Lý do #1: **AI không adapt theo chiến lược kinh doanh real-time**.
> *"The most successful AI retail implementations are those where business teams can directly influence AI behavior without engineering bottlenecks."* — McKinsey Retail Report 2025
**Canifa áp dụng:** Event Daily Engine giải quyết chính xác pain point này — zero engineering bottleneck.
---
## 7.4. Cộng đồng Reddit: Các pattern liên quan
### r/MachineLearning — "Best practices for dynamic system prompts?"
Các thảo luận phổ biến trên Reddit xoay quanh:
| Pattern | Mô tả | Canifa có? |
|---------|-------|------------|
| **Prompt Registry** | Lưu trữ các phiên bản prompt trong DB | ✅ `event_daily_prompts` |
| **A/B Testing Prompts** | Thử 2 phiên bản prompt, so sánh kết quả | 🔲 Roadmap v2.0 |
| **Prompt Versioning** | Lưu lịch sử thay đổi prompt | ⚠️ Có `updated_at` nhưng chưa full version history |
| **Prompt Analytics** | Đo lường hiệu quả từng prompt | 🔲 Roadmap v2.0 (kết hợp Langfuse) |
| **Multi-tenant Prompts** | Prompt khác nhau cho từng store/brand | ❌ Không cần (single brand) |
### r/ChatGPTDev — "How to make chatbot seasonally aware?"
Community recommend 3 approaches:
1. **Manual injection** (như Canifa) — Simple, full control ✅
2. **Calendar API integration** — Auto-detect holidays, nhưng thiếu chiến lược kinh doanh ⚠️
3. **RAG with time-filtered documents** — Over-engineered cho use case này ❌
---
## 7.5. Vị trí của Canifa trên "Maturity Model"
```mermaid
graph LR
L1["Level 1<br/>Static Chatbot<br/>(FAQ Bot)"]
L2["Level 2<br/>Contextual AI<br/>(RAG + Tools)"]
L3["Level 3<br/>Campaign-Aware AI<br/>(Event Daily)"]
L4["Level 4<br/>Self-Optimizing AI<br/>(Auto-adjust)"]
L5["Level 5<br/>Autonomous AI<br/>(AI writes its own campaigns)"]
L1 --> L2 --> L3 --> L4 --> L5
style L3 fill:#f59e0b,stroke:#d97706,color:#000
L3 -.- NOTE["← CANIFA<br/>đang ở đây"]
style NOTE fill:#ef4444,stroke:#dc2626,color:#fff
```
**Canifa đang ở Level 3** — Campaign-Aware AI. Hệ thống biết ngày, biết chiến dịch, và thay đổi hành vi tư vấn accordingly. Roadmap tiến tới Level 4: AI tự phân tích hiệu quả prompt và đề xuất thay đổi.
# Implementation Blueprint: Code & API
> **Tóm tắt:** Chương cuối cùng — đầy đủ code thực tế, API specs, và hướng dẫn triển khai step-by-step. Copy-paste là chạy.
---
## 8.1. API Reference
### `GET /api/event_daily/events`
Trả về danh sách tất cả events.
**Response:**
```json
[
{
"id": "valentine_2026",
"name": "Valentine 14/2 💕",
"type": "campaign",
"date": "2026-02-14",
"isToday": false,
"prompt": "## Valentine 14/2\n🎯 Push quà tặng...",
"startDate": "2026-02-12",
"endDate": "2026-02-15"
},
{
"id": "daily_default",
"name": "Mặc định hàng ngày",
"type": "daily",
"date": null,
"isToday": true,
"prompt": "Tư vấn bình thường...",
"startDate": null,
"endDate": null
}
]
```
### `PUT /api/event_daily/events/{event_id}`
Cập nhật prompt + date range cho một event.
**Request Body:**
```json
{
"prompt": "## Flash Sale Polo\n🎯 Push Polo 50%...",
"start_date": "2026-05-10",
"end_date": "2026-05-12"
}
```
**Response:**
```json
{ "status": "success" }
```
**Error (404):**
```json
{ "detail": "Event not found" }
```
---
## 8.2. Backend Code — `event_daily_route.py`
```python
# backend/api/event_daily/event_daily_route.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
import sqlite3
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/event_daily", tags=["Event Daily"])
BASE_DIR = Path(__file__).resolve().parents[2]
DB_PATH = BASE_DIR / "database" / "autotrain.db"
def get_db() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
class EventPromptUpdate(BaseModel):
prompt: str
start_date: str | None = None
end_date: str | None = None
@router.get("/events")
async def list_events():
"""List all events with their prompts and metadata."""
conn = get_db()
cur = conn.cursor()
try:
cur.execute("SELECT * FROM event_daily_prompts")
rows = cur.fetchall()
except sqlite3.OperationalError:
rows = []
finally:
conn.close()
events = []
for row in rows:
events.append({
"id": row["event_id"],
"name": row["event_name"],
"type": row["event_type"],
"date": row["event_date"],
"isToday": bool(row["is_today"]),
"prompt": row["prompt"],
"startDate": row["start_date"],
"endDate": row["end_date"]
})
return events
@router.put("/events/{event_id}")
async def update_event_prompt(event_id: str, data: EventPromptUpdate):
"""Update an event's prompt and optional date range."""
conn = get_db()
cur = conn.cursor()
cur.execute(
"UPDATE event_daily_prompts "
"SET prompt = ?, start_date = ?, end_date = ?, "
"updated_at = CURRENT_TIMESTAMP "
"WHERE event_id = ?",
(data.prompt, data.start_date, data.end_date, event_id)
)
if cur.rowcount == 0:
conn.close()
raise HTTPException(status_code=404, detail="Event not found")
conn.commit()
conn.close()
return {"status": "success"}
```
---
## 8.3. Prompt Injection — Integration Point
```python
# backend/graph/prompts/stylist_pro_prompts.py (snippet)
async def get_active_event_prompt() -> str:
"""Query active event prompts for today."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("""
SELECT prompt, event_type, event_name
FROM event_daily_prompts
WHERE is_today = 1
OR (start_date <= date('now') AND end_date >= date('now'))
ORDER BY
CASE event_type WHEN 'campaign' THEN 0 ELSE 1 END,
updated_at DESC
""")
rows = cur.fetchall()
conn.close()
if not rows:
return ""
# Build event section with token budget
MAX_CHARS = 2000
section = "\n\n═══ CHIẾN DỊCH HÔM NAY ═══\n"
total_chars = 0
for row in rows:
prompt_text = row["prompt"] or ""
if total_chars + len(prompt_text) > MAX_CHARS:
break
section += f"\n📅 {row['event_name']}:\n{prompt_text}\n"
total_chars += len(prompt_text)
section += "\n═══ KẾT THÚC CHIẾN DỊCH ═══\n"
return section
def build_system_prompt(base_prompt: str) -> str:
"""Merge base prompt with active event prompts."""
event_section = get_active_event_prompt()
if not event_section:
return base_prompt
# Inject event prompt BEFORE tool definitions
tool_marker = "## TOOLS:"
if tool_marker in base_prompt:
idx = base_prompt.index(tool_marker)
return base_prompt[:idx] + event_section + "\n" + base_prompt[idx:]
return base_prompt + event_section
```
---
## 8.4. Frontend — Key JavaScript Functions
```javascript
// Fetch events from API
async function fetchEvents() {
const response = await fetch('/api/event_daily/events');
if (response.ok) {
events = await response.json();
renderEvents(); // Re-render sidebar list
}
}
// Save updated prompt
async function savePrompt() {
if (!currentActiveId) return;
const response = await fetch(
`/api/event_daily/events/${currentActiveId}`,
{
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: promptContent.value,
start_date: dateFrom.value || null,
end_date: dateTo.value || null
})
}
);
if (response.ok) {
// Update local state + show success toast
showToast('Đã lưu thành công! ✅');
}
}
```
---
## 8.5. Database Schema & Migration
```sql
-- Create table (if not exists)
CREATE TABLE IF NOT EXISTS event_daily_prompts (
event_id TEXT PRIMARY KEY,
event_name TEXT NOT NULL,
event_type TEXT DEFAULT 'daily' CHECK(event_type IN ('daily','campaign')),
event_date TEXT,
is_today INTEGER DEFAULT 0,
prompt TEXT,
start_date TEXT,
end_date TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Index for fast "today" lookups
CREATE INDEX IF NOT EXISTS idx_event_today
ON event_daily_prompts(is_today);
-- Index for date range queries
CREATE INDEX IF NOT EXISTS idx_event_dates
ON event_daily_prompts(start_date, end_date);
```
---
## 8.6. Roadmap (Tính năng tương lai)
```
┌────────────────────────────────────────────────┐
│ EVENT DAILY ROADMAP │
│ │
│ ✅ v1.0 — CRUD events + prompt editing │
│ ✅ v1.1 — Date range + Today highlight │
│ ✅ v1.2 — Campaign vs Daily tag system │
│ 🔲 v1.3 — Character counter + validation │
│ 🔲 v1.4 — Prompt templates library │
│ 🔲 v2.0 — Auto is_today cron job │
│ 🔲 v2.1 — Prompt version history │
│ 🔲 v2.2 — A/B testing prompts │
│ 🔲 v3.0 — AI auto-suggest campaigns │
│ 🔲 v3.1 — Analytics: Prompt performance │
│ 🔲 v3.2 — Calendar view integration │
└────────────────────────────────────────────────┘
```
---
## 8.7. Testing Checklist
| Test | Command/Action | Expected |
|------|---------------|----------|
| List events | `GET /api/event_daily/events` | 200 + JSON array |
| Update prompt | `PUT /api/event_daily/events/daily_default` | 200 + `{"status":"success"}` |
| Invalid event | `PUT /api/event_daily/events/nonexistent` | 404 |
| Frontend load | Open `index.html` in iframe | Sidebar renders events |
| Save & reload | Edit prompt → Save → Refresh page | Prompt persists |
| Today highlight | Check sidebar | 🔥 icon on active events |
{
"id": "50-event-daily-engine",
"title": "Event Daily Engine — Cỗ máy chiến dịch thay đổi hành vi AI theo ngày",
"description": "Kiến trúc Dynamic Prompt Injection: Hệ thống cho phép thay đổi 'tính cách' và 'chiến lược' tư vấn của AI theo từng ngày/sự kiện/chiến dịch. Từ Black Friday đến Valentine, AI luôn biết hôm nay nên nói gì.",
"sections": [
{
"id": "concept",
"title": "1. Triết lý: Tại sao AI cần 'Lịch Tâm Trạng'?",
"type": "markdown",
"file": "01_concept.md"
},
{
"id": "architecture",
"title": "2. Kiến trúc hệ thống: Event → Prompt → AI Behavior",
"type": "markdown",
"file": "02_architecture.md"
},
{
"id": "data-model",
"title": "3. Data Model & Prompt Schema",
"type": "markdown",
"file": "03_data_model.md"
},
{
"id": "prompt-injection",
"title": "4. Dynamic Prompt Injection: Cách Event 'tiêm' vào AI",
"type": "markdown",
"file": "04_prompt_injection.md"
},
{
"id": "scenarios",
"title": "5. Kịch bản thực chiến: 6 chiến dịch mẫu",
"type": "markdown",
"file": "05_scenarios.md"
},
{
"id": "priority-engine",
"title": "6. Priority Engine: Khi nhiều sự kiện đè nhau",
"type": "markdown",
"file": "06_priority_engine.md"
},
{
"id": "industry-research",
"title": "7. Nghiên cứu ngành: Event-Driven AI trong Retail",
"type": "markdown",
"file": "07_industry_research.md"
},
{
"id": "implementation",
"title": "8. Implementation Blueprint: Code & API",
"type": "markdown",
"file": "08_implementation.md"
}
]
}
# Triết lý: Tại sao phải "Test trước khi Launch"?
> **Tóm tắt:** Mỗi chiến dịch marketing là một canh bạc — bạn đổ tiền, đổ công sức, rồi cầu nguyện cộng đồng phản ứng tích cực. Reaction Simulator biến canh bạc thành **thí nghiệm có kiểm soát** — giả lập phản ứng 7 nhóm khách hàng TRƯỚC KHI chi một đồng.
---
## 1.1. Bài toán: "Post & Pray" (Đăng rồi cầu nguyện)
Quy trình marketing truyền thống của 95% brands:
```
┌──────────────────────────────────────────────────┐
│ QUY TRÌNH "POST & PRAY" │
│ │
│ 1. Team Marketing nghĩ ra ý tưởng │
│ 2. Design + Copywriting (2-5 ngày) │
│ 3. Duyệt nội bộ (1-2 ngày) │
│ 4. ĐĂNG lên Facebook/TikTok/Instagram │
│ 5. ☕ Ngồi chờ... CẦU NGUYỆN... │
│ 6. Kết quả: │
│ a) 🎉 Viral → Tuyệt vời! │
│ b) 😐 Im lặng → Phí công │
│ c) 💀 Backlash → KHỦNG HOẢNG │
│ │
│ ⚠️ Không ai biết kết quả TRƯỚC KHI đăng │
│ ⚠️ Chi phí thất bại: $500-$50,000 │
│ ⚠️ Chi phí khủng hoảng: $50,000-$500,000 │
└──────────────────────────────────────────────────┘
```
### Case study thực tế:
| Brand | Sự cố | Hậu quả | Reaction Simulator có thể ngăn? |
|-------|--------|---------|-------------------------------|
| **Canifa** (giả định) | Tăng giá 15% không giải thích | Boycott trên Facebook 3 ngày | ✅ Simulator sẽ dự đoán 60% negative |
| **H&M** (2018) | "Coolest Monkey" hoodie ad | Global backlash, đóng 170 cửa hàng ở SA | ✅ Troll persona sẽ phát hiện ngay |
| **Balenciaga** (2022) | Campaign hình trẻ em controversial | Mất $1B market value | ✅ Mom Shopper persona sẽ alert |
| **Pepsi** (2017) | Kendall Jenner protest ad | Rút quảng cáo sau 24h | ✅ Budget Conscious + Gen Z sẽ phản đối |
---
## 1.2. Giải pháp: "War Room Simulation"
Thay vì đoán mò, Reaction Simulator tạo ra một **phòng chiến tranh ảo** — nơi 7 nhóm khách hàng phản ứng với chiến dịch TRƯỚC KHI nó được đăng:
```
┌──────────────────────────────────────────────────────────┐
│ REACTION SIMULATOR WORKFLOW │
│ │
│ Step 1: Input Campaign │
│ ├─ Loại: Product launch / Sale / Social post / Collab │
│ ├─ Nội dung: Copy + Context │
│ └─ Persona weights: Điều chỉnh tỷ trọng nhóm khách │
│ │
│ Step 2: AI Simulation (4 Agents chạy đồng thời) │
│ ├─ 🟣 Persona Engine: Hiểu đặc tính từng nhóm │
│ ├─ 🟢 Sentiment Agent: Dự đoán cảm xúc │
│ ├─ 🟠 Social Simulator: Giả lập comment thật │
│ └─ 🔵 Report Agent: Tổng hợp + Gợi ý hành động │
│ │
│ Step 3: Output │
│ ├─ Sentiment Breakdown: % Positive / Neutral / Negative│
│ ├─ 7 Mock Comments: Từ từng persona │
│ ├─ Risk Assessment: Green / Yellow / Red │
│ └─ Recommendations: Cần thay đổi gì? │
│ │
│ Step 4: Iterate │
│ ├─ Sửa nội dung campaign → Re-simulate │
│ ├─ A/B Test 2 phiên bản │
│ └─ Chỉ LAUNCH khi sentiment > 60% positive │
└──────────────────────────────────────────────────────────┘
```
---
## 1.3. Phép ẩn dụ: "Crash Test Dummy cho Marketing"
Trong ngành ô tô, trước khi bán xe ra thị trường, họ dùng **crash test dummies** — búp bê va chạm — để kiểm tra an toàn.
Reaction Simulator là **crash test dummy cho chiến dịch marketing**:
```
🚗 Ngành ô tô: 📢 Ngành marketing:
Thiết kế xe mới Thiết kế campaign mới
↓ ↓
Crash test dummy (7 loại va chạm) Reaction Simulator (7 personas)
↓ ↓
Đo lường: Airbag OK? Cabin nguyên? Đo lường: Sentiment OK? Risk thấp?
↓ ↓
Sửa thiết kế nếu fail Sửa nội dung nếu negative cao
↓ ↓
Production LAUNCH campaign
```
> **Không ai bán xe không crash test. Tại sao lại launch campaign không test reaction?**
---
## 1.4. Nguồn cảm hứng kỹ thuật
### BettaFish (微舆) — Multi-Agent Public Opinion Simulation
**BettaFish** là framework nghiên cứu từ Trung Quốc, sử dụng multi-agent LLM để giả lập dư luận xã hội. Mỗi agent đại diện cho một nhóm dân cư (người già, giới trẻ, doanh nhân...) và phản ứng với sự kiện theo "tính cách" riêng.
```
┌───────────────────────────────────────────┐
│ BETTAFISH ARCHITECTURE │
│ │
│ Event Input ──→ ForumEngine │
│ ├─ Agent A (Worker) │
│ ├─ Agent B (Student) │
│ ├─ Agent C (Retiree) │
│ └─ Agent D (Netizen) │
│ ↓ │
│ Discourse Tree │
│ (AI agents tranh luận) │
│ ↓ │
│ Opinion Summary │
└───────────────────────────────────────────┘
```
**Canifa áp dụng:** Thay "Worker/Student" bằng 7 personas thời trang Việt Nam (Mom Shopper, Gen Z, Fashion Enthusiast...).
### MiroFish — Simulate Agent Architecture
**MiroFish** mở rộng BettaFish bằng cách thêm **Persona Memory** — mỗi agent "nhớ" phản ứng trước đó và phản ứng nhất quán theo tính cách.
**Canifa áp dụng:** Weight system cho phép điều chỉnh "tỷ trọng ảnh hưởng" của từng persona — phản ánh thực tế thị trường.
---
## 1.5. ROI của Reaction Simulator
```
┌────────────────────────────────────────────────────┐
│ ROI MEASUREMENT │
│ │
│ 💰 Chi phí phát triển: ~40 giờ dev │
│ 💰 Chi phí vận hành: $0 (mock mode) / $0.05/sim │
│ │
│ 📈 Giá trị tiết kiệm: │
│ ├─ Tránh 1 crisis/năm: ~$10,000-$50,000 │
│ ├─ Tối ưu campaign copy: +10-15% engagement │
│ ├─ Giảm thời gian duyệt nội bộ: 2 ngày → 1 giờ │
│ └─ Confidence level: "Gut feeling" → Data-driven │
│ │
│ 🎯 ROI ước tính: 10-50x │
└────────────────────────────────────────────────────┘
```
# Kiến trúc Multi-Agent Simulation
> **Tóm tắt:** Reaction Simulator sử dụng kiến trúc 4 AI Agents chạy pipeline tuần tự: Persona Engine → Sentiment Agent → Social Simulator → Report Agent. Chương này mổ xẻ từng agent và data flow giữa chúng.
---
## 3.1. System Architecture Overview
```mermaid
graph TB
subgraph "📱 Frontend"
UI["reaction-simulator.html"]
INPUT["Campaign Input<br/>+ Persona Weights"]
RESULTS["Results Panel<br/>+ Donut Chart + Cards"]
end
subgraph "⚡ Backend Pipeline"
API["POST /api/reaction-simulator/simulate"]
subgraph "4 AI Agents (Sequential)"
A1["🟣 Persona Engine<br/>Hiểu tính cách 7 nhóm"]
A2["🟢 Sentiment Agent<br/>Dự đoán positive/neutral/negative"]
A3["🟠 Social Simulator<br/>Generate mock comments"]
A4["🔵 Report Agent<br/>Tổng hợp + Recommendations"]
end
end
subgraph "💾 Data Sources"
PERSONAS["Persona Profiles<br/>(Hardcoded / DB)"]
PRESETS["Campaign Presets<br/>(5 mẫu sẵn)"]
end
UI --> INPUT
INPUT -->|"POST"| API
API --> A1
A1 -->|"persona_context"| A2
A2 -->|"sentiment_scores"| A3
A3 -->|"mock_comments"| A4
A4 -->|"final_report"| RESULTS
PERSONAS --> A1
PRESETS --> INPUT
style A1 fill:#6D28D9,stroke:#5B21B6,color:#fff
style A2 fill:#059669,stroke:#047857,color:#fff
style A3 fill:#EA580C,stroke:#C2410C,color:#fff
style A4 fill:#1D4ED8,stroke:#1E40AF,color:#fff
```
---
## 3.2. Agent #1: Persona Engine 🟣
**Vai trò:** Nhận campaign content + persona weights → Xây dựng "tính cách" cho từng persona trong context chiến dịch cụ thể.
```python
# Pseudo-code
def persona_engine(campaign: dict, weights: dict) -> list:
"""
Input: campaign = {type, content}
weights = {mom_shopper: 20, young_professional: 20, ...}
Output: persona_contexts = [
{id: "mom_shopper", name: "Nguyễn Thị Mai",
context: "Phụ nữ 32t, Hà Nội, mua cho gia đình...",
weight: 20, triggers: [...]}
]
"""
contexts = []
for persona in PERSONA_PROFILES:
if weights.get(persona.id, 0) > 0:
context = build_persona_context(persona, campaign)
contexts.append(context)
return contexts
```
---
## 3.3. Agent #2: Sentiment Agent 🟢
**Vai trò:** Nhận persona contexts → Dự đoán sentiment (positive/neutral/negative) cho từng persona.
```
Input: "Mom Shopper, 32t, mua cho gia đình"
+ "Campaign: Sale 50% Polo"
Logic: Check trigger_positive: Sale > 30% ✅
Check trigger_negative: Không có ❌
Output: sentiment = "positive", confidence = 0.85
```
### Sentiment Scoring Matrix:
| Persona | Product Launch | Promotion | Price Change | BST mới | Collab |
|---------|---------------|-----------|-------------|---------|--------|
| Mom Shopper | Neutral → Positive | Very Positive | Very Negative | Neutral | Neutral |
| Young Pro | Neutral | Positive | Negative | Positive | Neutral |
| Fashion Enthusiast | Very Positive | Positive | Neutral | Very Positive | Very Positive |
| Budget Conscious | Negative (giá?) | Very Positive | Very Negative | Negative | Negative |
| Gen Z | Positive | Positive | Negative | Very Positive | Very Positive |
| KOL | Positive | Neutral | Neutral | Positive | Very Positive |
| Troll | Negative | Neutral | Very Negative | Negative | Negative |
---
## 3.4. Agent #3: Social Simulator 🟠
**Vai trò:** Nhận sentiment scores → Generate mock comments chân thực cho từng persona.
```
┌──────────────────────────────────────────────┐
│ MOCK COMMENT GENERATION │
│ │
│ Input: persona=Mom Shopper, sentiment=positive│
│ │
│ Template Variables: │
│ • {persona_name}: "Nguyễn Thị Mai" │
│ • {city}: "Hà Nội" │
│ • {comment_style}: Thực dụng, so sánh giá │
│ • {sentiment}: positive │
│ • {product_mention}: "Polo DryTech" │
│ • {price_mention}: "199K (gốc 399K)" │
│ │
│ Output: │
│ "BST sale giá hợp lý quá! 199K cho Polo │
│ DryTech mua cho cả nhà mặc đi biển!" │
│ │
│ Engagement Metrics (estimated): │
│ • Likes: 38 │
│ • Shares: 6 │
│ • Comments: 4 │
└──────────────────────────────────────────────┘
```
### LLM-powered vs Mock mode:
| Mode | Cách hoạt động | Ưu điểm | Nhược điểm |
|------|----------------|---------|------------|
| **Mock** (v1.0 hiện tại) | Dùng comment mẫu cố định theo campaign type | Nhanh, miễn phí, ổn định | Ít đa dạng |
| **LLM** (v1.3 roadmap) | GPT/Gemini generate comment mới mỗi lần | Đa dạng, realistic | Tốn API cost, chậm hơn |
---
## 3.5. Agent #4: Report Agent 🔵
**Vai trò:** Tổng hợp tất cả dữ liệu → Tạo báo cáo sentiment + recommendations.
```
┌──────────────────────────────────────────────┐
│ REPORT OUTPUT │
│ │
│ 📊 Sentiment Overview: │
│ ├─ Positive: 57% │
│ ├─ Neutral: 29% │
│ └─ Negative: 14% │
│ │
│ 🚦 Traffic Light: 🟢 GREEN (Safe to launch) │
│ │
│ 💬 7 Mock Comments (sorted by engagement) │
│ │
│ 📋 Recommendations: │
│ ├─ ✅ Chiến dịch có tiềm năng viral │
│ ├─ 💡 Chuẩn bị stock cho demand surge │
│ ├─ 📊 Gen Z phản ứng tốt → Push TikTok │
│ └─ ⚠️ Budget Conscious cần thêm incentive │
└──────────────────────────────────────────────┘
```
### Traffic Light System:
```
🟢 GREEN: Positive > 60% → "Safe to launch"
🟡 YELLOW: Positive 40-60% → "Launch with caution"
🔴 RED: Positive < 40% OR Neg > 30%→ "DO NOT LAUNCH — Revise!"
```
---
## 3.6. State Diagram — Simulation Lifecycle
```mermaid
stateDiagram-v2
[*] --> Idle: Page loaded
Idle --> InputReady: User enters campaign content
InputReady --> Thinking: Click "Simulate"
state Thinking {
[*] --> PersonaEngine: "Phân tích user insight..."
PersonaEngine --> SentimentAgent: "Dự đoán cảm xúc..."
SentimentAgent --> SocialSimulator: "Giả lập phản ứng..."
SocialSimulator --> ReportAgent: "Tổng hợp kết quả..."
ReportAgent --> [*]
}
Thinking --> Results: All agents complete
Results --> InputReady: User modifies content
Results --> ABMode: Click "A/B Test"
ABMode --> Results: Compare 2 versions
note right of Thinking
UI shows animated checkmarks
as each agent completes
end note
```
# Sentiment Analysis Engine
> **Tóm tắt:** Chương này đi sâu vào cách hệ thống tính toán sentiment score, phân loại cảm xúc, và trực quan hóa kết quả qua Donut Chart + Sentiment Bars.
---
## 4.1. Sentiment Classification
Hệ thống phân loại sentiment thành 3 cấp:
| Level | Label | Điều kiện | Màu | Ví dụ comment |
|-------|-------|----------|-----|---------------|
| **Positive** | Tích cực | Keyword: "đẹp", "hợp lý", "mua ngay", "wait list" | 🟢 `#059669` | "BST đẹp quá, mua ngay!" |
| **Neutral** | Trung lập | Không rõ tích cực/tiêu cực, hỏi thêm info | 🟡 `#D97706` | "Cần xem thực tế đã" |
| **Negative** | Tiêu cực | Keyword: "đắt", "RIP", "bỏ", "thua", "chuyển brand" | 🔴 `#DC2626` | "Tăng giá nữa thì bye bye" |
---
## 4.2. Scoring Algorithm
```python
def calc_sentiment(reactions: list) -> dict:
"""
Input: reactions = [
{sentiment: "positive", pid: "mom_shopper", weight: 20},
{sentiment: "negative", pid: "troll", weight: 5},
...
]
Output: {pos: 57, neu: 29, neg: 14, total: 7}
"""
positive = neutral = negative = 0
for r in reactions:
if r['sentiment'] == 'positive':
positive += 1
elif r['sentiment'] == 'neutral':
neutral += 1
else:
negative += 1
total = len(reactions)
return {
'pos': round(positive / total * 100),
'neu': round(neutral / total * 100),
'neg': round(negative / total * 100),
'total': total
}
```
### Weighted Scoring (v2.0 — Future):
```python
def calc_weighted_sentiment(reactions: list) -> dict:
"""
Tính sentiment có trọng số — KOL comment có weight lớn hơn Troll.
"""
INFLUENCE_WEIGHT = {
'kol': 3.0, # KOL comment = 3x impact
'fashion_enthusiast': 2.0,
'gen_z': 1.5, # Viral potential
'mom_shopper': 1.0,
'young_professional': 1.0,
'budget_conscious': 0.8,
'troll': 0.3 # Noise, low impact
}
total_weight = sum(INFLUENCE_WEIGHT[r['pid']] for r in reactions)
pos_weight = sum(INFLUENCE_WEIGHT[r['pid']] for r in reactions
if r['sentiment'] == 'positive')
return {
'pos_weighted': round(pos_weight / total_weight * 100),
# ...
}
```
---
## 4.3. Visualization — Donut Chart
```
┌────────────────────────────────┐
│ DONUT CHART │
│ │
│ ┌───────┐ │
│ ╱ 🟢🟢🟢 ╲ │
│ │ 🟢 🟡 │ │
│ │ 57% 🟡│ │
│ │ Tích 🟡│ │
│ │ cực │ │
│ ╲ 🔴🔴🔴 ╱ │
│ └───────┘ │
│ │
│ 🟢 Tích cực ████████░░ 57% │
│ 🟡 Trung lập ████░░░░░ 29% │
│ 🔴 Tiêu cực ██░░░░░░░ 14% │
│ │
│ Dựa trên 7 persona │
└────────────────────────────────┘
```
### Canvas Implementation:
```javascript
function drawDonut() {
const canvas = document.getElementById('sChart');
const ctx = canvas.getContext('2d');
const colors = ['#059669', '#D97706', '#DC2626']; // pos, neu, neg
const values = [57, 29, 14]; // percentages
const total = values.reduce((a, b) => a + b, 0);
let startAngle = -Math.PI / 2; // Start from top
ctx.clearRect(0, 0, 130, 130);
values.forEach((val, i) => {
const sliceAngle = (val / total) * Math.PI * 2;
ctx.beginPath();
ctx.arc(65, 65, 50, startAngle, startAngle + sliceAngle);
ctx.strokeStyle = colors[i];
ctx.lineWidth = 14;
ctx.lineCap = 'round';
ctx.stroke();
startAngle += sliceAngle + 0.04; // Gap between slices
});
}
```
---
## 4.4. Reaction Cards — Comment Display
Mỗi persona comment được hiển thị trong một **Reaction Card**:
```
┌──────────────────────────────────────────────┐
│ ┌──┐ Nguyễn Thị Mai │
│ │NM│ Mom Shopper · 32t · Hà Nội [Tích cực]│
│ └──┘ │
│ ──────────────────────────────────────────── │
│ "BST sale giá hợp lý quá! 199K cho Polo │
│ DryTech mua cho cả nhà mặc đi biển!" │
│ ──────────────────────────────────────────── │
│ ❤️ 38 🔄 6 💬 4 │
└──────────────────────────────────────────────┘
```
### Engagement Metrics Generation:
```javascript
// Engagement ước tính dựa trên persona type
const ENGAGEMENT_RANGES = {
kol: { likes: [80, 150], shares: [20, 40], comments: [10, 25] },
gen_z: { likes: [40, 80], shares: [10, 25], comments: [5, 15] },
fashion_ent: { likes: [30, 60], shares: [8, 20], comments: [3, 10] },
mom_shopper: { likes: [20, 50], shares: [3, 10], comments: [2, 8] },
young_pro: { likes: [15, 35], shares: [2, 8], comments: [1, 5] },
budget_con: { likes: [10, 25], shares: [1, 5], comments: [1, 3] },
troll: { likes: [5, 20], shares: [0, 3], comments: [5, 15] }
};
```
---
## 4.5. Recommendation Engine
Dựa trên kết quả sentiment, hệ thống tự động generate recommendations:
```python
def generate_recommendations(reactions: list, sentiment: dict) -> list:
recos = []
neg_count = len([r for r in reactions if r['sentiment'] == 'negative'])
# Critical alerts
if neg_count >= 3:
recos.append({
'icon': '⚠️',
'text': '<strong>Cảnh báo:</strong> Tỷ lệ tiêu cực cao. '
'Điều chỉnh thông điệp hoặc thêm ưu đãi đi kèm.'
})
if neg_count >= 2:
recos.append({
'icon': '💡',
'text': '<strong>Tip:</strong> Chuẩn bị response template '
'cho comment so sánh giá.'
})
# Positive signals
gen_z_positive = any(r['pid'] == 'gen_z' and r['sentiment'] == 'positive'
for r in reactions)
if gen_z_positive:
recos.append({
'icon': '📊',
'text': '<strong>Insight:</strong> Gen Z phản ứng tích cực — '
'ưu tiên TikTok/Instagram.'
})
kol_positive = any(r['pid'] == 'kol' and r['sentiment'] == 'positive'
for r in reactions)
if kol_positive:
recos.append({
'icon': '🎯',
'text': '<strong>Hành động:</strong> Kết hợp KOL review sớm '
'để tăng credibility.'
})
if neg_count < 2:
recos.append({
'icon': '🚀',
'text': '<strong>Tín hiệu tốt:</strong> Chiến dịch có tiềm năng '
'viral — chuẩn bị stock!'
})
return recos
```
# Crisis Alert System: Phát hiện khủng hoảng sớm
> **Tóm tắt:** Tab Crisis Alert là hệ thống giám sát dư luận 24/7, tổng hợp mentions, sentiment trends, và tự động phát alert khi phát hiện dấu hiệu khủng hoảng. Mục tiêu: Phản ứng trong 30 phút thay vì 3 ngày.
---
## 5.1. Tại sao cần Crisis Alert?
```
┌──────────────────────────────────────────────────────┐
│ CRISIS TIMELINE — Không có Alert System │
│ │
│ T+0h: KOL đăng review tiêu cực (50K views) │
│ T+2h: Comment lan sang Facebook groups │
│ T+6h: Báo Vnexpress/Dân Trí pickup │
│ T+12h: Hashtag #boycottCanifa trending │
│ T+24h: ĐỘI MARKETING MỚI PHÁT HIỆN! 😱 │
│ T+48h: Statement chính thức → Quá trễ │
│ T+7d: Doanh thu giảm 20% │
│ │
│ CRISIS TIMELINE — Có Alert System │
│ │
│ T+0h: KOL đăng review tiêu cực (50K views) │
│ T+0.5h: 🚨 ALERT: Negative spike detected! │
│ T+1h: Team xác minh + chuẩn bị response │
│ T+2h: Reply trực tiếp KOL + Statement │
│ T+4h: Narrative đã được kiểm soát │
│ T+7d: Impact minimal (-3% doanh thu) │
└──────────────────────────────────────────────────────┘
```
---
## 5.2. Dashboard Metrics — Tổng quan 24h
```
┌──────────────────────────────────────────────┐
│ 📊 TỔNG QUAN 24H QUA │
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 847 │ │ 72% │ │ 8% │ │ 2 │ │
│ │Mentions│ │Positive│ │Negative│ │ Alerts │ │
│ │ ↑ +12% │ │ ↓ -3% │ │ ↑ +2% │ │ — 0 │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │
│ ⚠️ Negative tăng 2% → Theo dõi sát │
│ ⚠️ Positive giảm 3% → Kiểm tra content │
└──────────────────────────────────────────────┘
```
### Metric Definitions:
| Metric | Nguồn | Threshold | Action |
|--------|-------|-----------|--------|
| **Mentions** | Facebook, TikTok, Shopee reviews | < 500/day = low, > 1000 = high | Monitor volume trends |
| **Positive %** | Sentiment analysis | < 50% = Warning | Review recent campaigns |
| **Negative %** | Sentiment analysis | > 20% = Alert | Investigate root cause |
| **Alerts** | Auto-generated | Any CRITICAL = Immediate action | Escalate to management |
---
## 5.3. Alert Severity Levels
```mermaid
graph TD
subgraph "Alert Severity Levels"
CRITICAL["🔴 CRITICAL<br/>Phải xử lý NGAY<br/>(< 30 phút)"]
WARNING["🟡 WARNING<br/>Theo dõi sát<br/>(< 4 giờ)"]
INFO["🟢 INFO<br/>Ghi nhận<br/>(daily review)"]
end
CRITICAL --> |"Trigger"| CT["• Negative spike > 30%<br/>• Viral negative post > 100K views<br/>• Báo chí pickup tiêu cực<br/>• Rating drop > 0.5 sao"]
WARNING --> |"Trigger"| WT["• Negative tăng 5-15%<br/>• KOL review tiêu cực<br/>• Competitor so sánh có hại<br/>• Rating giảm 0.2-0.5 sao"]
INFO --> |"Trigger"| IT["• Positive mention từ báo<br/>• KOL review tích cực<br/>• Rating tăng<br/>• Viral positive content"]
style CRITICAL fill:#DC2626,stroke:#B91C1C,color:#fff
style WARNING fill:#D97706,stroke:#B45309,color:#fff
style INFO fill:#059669,stroke:#047857,color:#fff
```
---
## 5.4. Alert Feed — Ví dụ thực tế
### 🔴 CRITICAL Alert:
```
┌──────────────────────────────────────────────┐
│ 🔴│ Phàn nàn hàng loạt về chất lượng │
│ │ áo Polo mùa mới │
│ │ │
│ │ 15+ comment trên Facebook group │
│ │ "Hội mua sắm thông minh" phản ánh │
│ │ áo Polo DryTech bị phai màu sau │
│ │ 2 lần giặt. Cần xác minh lô hàng. │
│ │ │
│ │ 🕐 2 giờ trước · Facebook · 23 interactions│
│ │ [CRITICAL] │
└──────────────────────────────────────────────┘
```
### 🟡 WARNING Alert:
```
┌──────────────────────────────────────────────┐
│ 🟡│ So sánh tiêu cực với Uniqlo trên TikTok │
│ │ │
│ │ Video review so sánh Canifa vs Uniqlo │
│ │ có 50K views, tone tiêu cực về chất liệu.│
│ │ Creator: @fashionvn (120K followers). │
│ │ │
│ │ 🕐 5 giờ trước · TikTok · 50K views │
│ │ [WARNING] │
└──────────────────────────────────────────────┘
```
### 🟢 INFO Alert:
```
┌──────────────────────────────────────────────┐
│ 🟢│ BST Hè được Vnexpress đánh giá tích cực │
│ │ │
│ │ Bài PR trên Vnexpress Life nhận 200+ │
│ │ comments positive. Keyword: "chất lượng │
│ │ tốt", "giá hợp lý", "thiết kế đẹp". │
│ │ │
│ │ 🕐 8 giờ trước · Vnexpress · 200+ comments│
│ │ [INFO] │
└──────────────────────────────────────────────┘
```
---
## 5.5. Response Protocol — Khi nhận CRITICAL Alert
```mermaid
sequenceDiagram
participant System as 🚨 Alert System
participant Marketing as 📢 Team Marketing
participant CS as 🎧 Team CSKH
participant Management as 👔 Management
System->>Marketing: 🔴 CRITICAL ALERT<br/>"Polo bị phai màu - 15+ complaints"
Marketing->>Marketing: T+0: Xác minh thông tin<br/>(Kiểm tra lô hàng, QC report)
Marketing->>CS: T+15min: Brief CSKH<br/>"Chuẩn bị script xử lý"
CS->>CS: T+20min: Deploy crisis script<br/>vào Event Daily Prompt
Note over CS: Event Daily: "Nếu khách hỏi về<br/>chất lượng Polo, thể hiện<br/>sự quan tâm + đề xuất đổi trả"
Marketing->>Management: T+30min: Escalation report<br/>+ Proposed action plan
Management->>Marketing: T+1h: Approve statement
Marketing->>System: T+1.5h: Publish official response<br/>+ Track sentiment recovery
```
> **Key insight:** Crisis Alert + Event Daily Engine hoạt động song song — khi Crisis Alert phát hiện vấn đề, team ngay lập tức cập nhật Event Daily Prompt để AI xử lý khéo léo khi khách hỏi.
---
## 5.6. Roadmap Crisis Alert
```
✅ v1.0 — Mock crisis dashboard (hardcoded data)
✅ v1.1 — Alert severity levels + feed UI
🔲 v1.2 — Real-time social monitoring API integration
🔲 v1.3 — Auto-alert based on sentiment threshold
🔲 v2.0 — AI-generated crisis response templates
🔲 v2.1 — Integration với Event Daily (auto-inject crisis prompt)
🔲 v3.0 — Competitive monitoring (track competitor mentions)
```
# A/B Testing Mode: So sánh 2 phiên bản campaign
> **Tóm tắt:** A/B Testing Mode cho phép chạy 2 phiên bản campaign song song, so sánh sentiment side-by-side, và chọn phiên bản tốt nhất trước khi launch. Đây là tính năng **game-changer** biến marketing từ "gut feeling" sang "data-driven".
---
## 6.1. Concept: "Chọn chiến binh trước khi ra trận"
```
┌──────────────────────────────────────────────────────┐
│ A/B TESTING CONCEPT │
│ │
│ VERSION A (Đội Đỏ) VERSION B (Đội Xanh) │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ "Flash Sale │ │ "Cơ hội cuối │ │
│ │ 50% Polo! │ │ sở hữu Polo │ │
│ │ Chỉ 2 ngày!" │ │ DryTech giá │ │
│ │ │ │ tốt nhất năm!" │ │
│ └────────────────┘ └────────────────┘ │
│ ↓ ↓ │
│ Simulate 7 personas Simulate 7 personas │
│ ↓ ↓ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Pos: 57% │ │ Pos: 71% │ │
│ │ Neu: 29% │ │ Neu: 22% │ │
│ │ Neg: 14% │ │ Neg: 7% │ │
│ │ 🟢 GREEN │ │ 🟢 GREEN ⭐ │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ WINNER: VERSION B ← Tỷ lệ positive cao hơn 14% │
│ → LAUNCH VERSION B │
└──────────────────────────────────────────────────────┘
```
---
## 6.2. UI Layout — Side-by-Side Comparison
```
┌─────────────────────────────────────────────────────────┐
│ A/B TESTING MODE │
│ ═══════════════ │
│ │
│ ┌─── VERSION A ────────┐ ┌─── VERSION B ────────┐ │
│ │ Campaign Type: Sale │ │ Campaign Type: Sale │ │
│ │ ┌──────────────────┐ │ │ ┌──────────────────┐ │ │
│ │ │ "Flash Sale 50% │ │ │ │ "Cơ hội cuối..." │ │ │
│ │ │ Polo! Chỉ 2 │ │ │ │ "Polo DryTech │ │ │
│ │ │ ngày cuối!" │ │ │ │ giá tốt nhất!" │ │ │
│ │ └──────────────────┘ │ │ └──────────────────┘ │ │
│ │ │ │ │ │
│ │ Results: │ │ Results: │ │
│ │ 🟢 57% Positive │ │ 🟢 71% Positive ⭐ │ │
│ │ 🟡 29% Neutral │ │ 🟡 22% Neutral │ │
│ │ 🔴 14% Negative │ │ 🔴 7% Negative │ │
│ │ │ │ │ │
│ │ Risk: GREEN │ │ Risk: GREEN ⭐ │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ │
│ 📊 COMPARISON: │
│ ├─ Version B: +14% positive sentiment │
│ ├─ Version B: -7% negative sentiment │
│ └─ Recommendation: LAUNCH VERSION B │
│ │
│ 💡 WHY B WINS: │
│ ├─ "Cơ hội cuối" tạo FOMO mạnh hơn "Flash Sale" │
│ ├─ "Giá tốt nhất năm" → Budget Conscious phản ứng ↑ │
│ └─ Không dùng emoji quá nhiều → YP đánh giá cao hơn │
└─────────────────────────────────────────────────────────┘
```
---
## 6.3. Comparison Metrics
| Metric | Version A | Version B | Delta | Winner |
|--------|----------|----------|-------|--------|
| **Positive %** | 57% | 71% | +14% | B ⭐ |
| **Negative %** | 14% | 7% | -7% | B ⭐ |
| **Est. Engagement** | 234 | 312 | +33% | B ⭐ |
| **Mom Shopper** | Positive | Positive | — | Tie |
| **Budget Conscious** | Neutral | Positive | ↑ | B ⭐ |
| **Gen Z** | Positive | Positive | — | Tie |
| **Troll** | Negative | Neutral | ↑ | B ⭐ |
| **Risk Level** | 🟢 Green | 🟢 Green | — | Tie |
---
## 6.4. Implementation (Pseudo-code)
```python
async def run_ab_test(campaign_a: str, campaign_b: str, weights: dict):
"""
Run 2 simulations in parallel, compare results.
"""
# Run both simulations
result_a = await simulate(campaign_a, weights)
result_b = await simulate(campaign_b, weights)
# Compare
comparison = {
'winner': 'A' if result_a['positive'] > result_b['positive'] else 'B',
'delta_positive': result_b['positive'] - result_a['positive'],
'delta_negative': result_a['negative'] - result_b['negative'],
'per_persona': compare_per_persona(result_a, result_b),
'reasons': generate_why_winner(result_a, result_b)
}
return {
'version_a': result_a,
'version_b': result_b,
'comparison': comparison
}
```
---
## 6.5. Use Cases cho A/B Testing
| Scenario | Version A | Version B | Test Question |
|----------|----------|----------|---------------|
| **Tone** | Urgency ("Chỉ 2 ngày!") | Aspirational ("Phong cách bạn") | FOMO vs Brand love? |
| **Price framing** | "Giảm 50%" | "Tiết kiệm 200K" | % vs Absolute? |
| **CTA** | "Mua ngay" | "Khám phá BST" | Hard sell vs Soft sell? |
| **Emoji density** | 5 emoji/post | 1 emoji/post | Fun vs Professional? |
| **Length** | Short (50 words) | Long (150 words) | Impact vs Detail? |
| **Visual concept** | Minimalist photo | Lifestyle shot | Product vs Context? |
> **Best practice:** Chỉ test 1 biến tại 1 thời điểm. VD: Giữ nguyên content, chỉ đổi CTA. Nếu đổi quá nhiều biến, không biết yếu tố nào tạo sự khác biệt.
---
## 6.6. Roadmap A/B Testing
```
🔲 v1.2 — Basic A/B mode (2 textareas, side-by-side results)
🔲 v1.3 — Per-persona comparison table
🔲 v1.4 — Auto-generate "Why B wins" analysis
🔲 v2.0 — Multi-variate testing (A/B/C/D)
🔲 v2.1 — Historical A/B results log
🔲 v3.0 — AI auto-suggest improvements
```
# Nghiên cứu: BettaFish, MiroFish & Social Simulation
> **Tóm tắt:** Reaction Simulator không phải ý tưởng từ trên trời rơi xuống. Nó được lấy cảm hứng từ các nghiên cứu Multi-Agent Social Simulation hàng đầu thế giới. Chương này phân tích nguồn gốc học thuật và vị trí của Canifa trên bản đồ nghiên cứu.
---
## 7.1. BettaFish (微舆) — Multi-Agent Public Opinion Simulation
### Paper: "BettaFish: A Simulated Forum for LLM-Based Opinion Dynamics" (2024)
```
┌──────────────────────────────────────────────────┐
│ BETTAFISH ARCHITECTURE │
│ │
│ Input: News Event │
│ "Chính phủ tăng giá điện 8%" │
│ ↓ │
│ ForumEngine: │
│ ├─ Agent "Worker" (Công nhân, 30t) │
│ │ └─ "Tăng giá điện thêm khổ dân lao động" │
│ ├─ Agent "Student" (Sinh viên, 22t) │
│ │ └─ "Phòng trọ tiền điện tăng 200K/tháng" │
│ ├─ Agent "Retiree" (Hưu trí, 65t) │
│ │ └─ "Lương hưu có hạn, gánh nặng thêm" │
│ └─ Agent "Netizen" (KOL online, 28t) │
│ └─ "So sánh giá điện VN vs ASEAN" │
│ ↓ │
│ Discourse Tree: Agents tranh luận 3-5 vòng │
│ ↓ │
│ Opinion Summary + Sentiment Distribution │
└──────────────────────────────────────────────────┘
```
### Key Insights từ BettaFish:
| Concept | BettaFish | Canifa Adaptation |
|---------|-----------|-------------------|
| **Agent Personas** | Worker, Student, Retiree, Netizen | Mom Shopper, Gen Z, Fashion Enthusiast... |
| **ForumEngine** | Agents tranh luận multi-turn | Single-turn reaction (đủ cho marketing) |
| **Discourse Tree** | Phân nhánh ý kiến phức tạp | Simplified: 1 comment/persona |
| **Opinion Dynamics** | Ý kiến thay đổi qua tranh luận | Static per simulation (v1.0) |
---
## 7.2. MiroFish — Persona Memory & Consistency
### Extension: "MiroFish: Memory-Informed Role-playing for Opinion Simulation"
MiroFish mở rộng BettaFish bằng cách thêm **Persona Memory** — mỗi agent nhớ phản ứng trước đó.
```
┌──────────────────────────────────────────────────┐
│ MIROFISH MEMORY SYSTEM │
│ │
│ Agent "Worker" Memory: │
│ ├─ Event 1: Tăng giá xăng → "Phản đối mạnh" │
│ ├─ Event 2: Tăng lương tối thiểu → "Ủng hộ" │
│ └─ Event 3: Tăng giá điện → ??? │
│ │
│ Memory-Informed Response: │
│ "Vừa tăng xăng xong, giờ lại tăng điện. │
│ Lương có tăng đâu mà cái gì cũng tăng!" │
│ │
│ → Response NHẤT QUÁN với lịch sử phản ứng │
└──────────────────────────────────────────────────┘
```
**Canifa áp dụng (Roadmap v3.0):** Persona Memory cho phép track phản ứng của từng persona qua nhiều chiến dịch. VD: Nếu "Budget Conscious" đã phàn nàn tăng giá 2 lần trước, lần thứ 3 sẽ phản ứng MẠNH HƠN.
---
## 7.3. Các nghiên cứu liên quan khác
### 7.3.1. "Generative Agents: Interactive Simulacra of Human Behavior" (Stanford, 2023)
Paper nổi tiếng tạo 25 AI agents sống trong thị trấn ảo, mỗi agent có memory, personality, và social relationships.
**Relevance:** Chứng minh LLM có thể role-play realistic personas khi được cung cấp đủ context.
### 7.3.2. "Social Simulacra" (Stanford HCI, 2023)
Hệ thống giả lập Reddit/Facebook communities bằng LLM. Input: mô tả community + rules. Output: Realistic threads với đa dạng ý kiến.
**Relevance:** Validate rằng LLM-generated comments có thể đủ realistic để dùng cho pre-testing.
### 7.3.3. "Brand Perception Modeling with LLMs" (Google DeepMind, 2025)
Nghiên cứu sử dụng LLM để model cách các demographic segments phản ứng với brand messaging. Kết quả: LLM predictions correlate 0.72 với real survey data.
**Relevance:** Xác nhận rằng approach của Reaction Simulator (dùng AI predict reaction) có cơ sở khoa học.
---
## 7.4. So sánh với các công cụ thương mại
| Tool | Loại | Giá | Canifa khác gì? |
|------|------|-----|-----------------|
| **Brandwatch** | Social Listening | $800-5000/mo | Real data nhưng KHÔNG predict trước |
| **Sprout Social** | Social Management | $249-499/mo | Monitor, không simulate |
| **Hootsuite** | Social Scheduler | $99-739/mo | Schedule, không test reaction |
| **Canifa Simulator** | Predictive Testing | $0 (self-hosted) | Predict TRƯỚC khi launch |
> **Key difference:** Các tool thương mại = **Reactive** (theo dõi sau khi đăng). Canifa Simulator = **Proactive** (test trước khi đăng).
---
## 7.5. Maturity Model — Social AI Simulation
```mermaid
graph LR
L1["Level 1<br/>Manual Review<br/>(Team đọc + đoán)"]
L2["Level 2<br/>Social Listening<br/>(Brandwatch, Sprout)"]
L3["Level 3<br/>Predictive Simulation<br/>(Canifa Simulator)"]
L4["Level 4<br/>Memory-Informed<br/>(MiroFish-style)"]
L5["Level 5<br/>Autonomous Campaign<br/>(AI tự tối ưu)"]
L1 --> L2 --> L3 --> L4 --> L5
style L3 fill:#f59e0b,stroke:#d97706,color:#000
L3 -.- NOTE["← CANIFA<br/>đang ở đây"]
style NOTE fill:#ef4444,stroke:#dc2626,color:#fff
```
# Implementation Blueprint: Code & API
> **Tóm tắt:** Full code reference cho Reaction Simulator — từ JavaScript engine đến API endpoints. Copy-paste là chạy.
---
## 8.1. Core Simulation Engine (JavaScript)
```javascript
// reaction-simulator.js — Core Engine
const PERSONAS = [
{
id: 'mom_shopper', name: 'Nguyễn Thị Mai',
label: 'Mom Shopper', age: 32, city: 'Hà Nội',
color: '#6D28D9',
triggers_pos: ['giá hợp lý', 'combo gia đình', 'free ship', 'chất mát'],
triggers_neg: ['tăng giá', 'khó bảo quản', 'không size trẻ em'],
comment_templates: {
positive: 'BST đẹp quá! Giá {price} hợp lý, mua cho cả nhà!',
neutral: 'Cần xem thực tế mới biết chất lượng thế nào.',
negative: 'Giá tăng nữa thì chuyển sang Coolmate thôi.'
}
},
// ... 6 personas khác (xem Chapter 2)
];
const CAMPAIGN_PRESETS = {
product_launch: { name: 'Ra mắt sản phẩm mới',
template: 'BST {name} vừa ra mắt! Chất liệu {material}, giá từ {price}.' },
promotion: { name: 'Chương trình khuyến mãi',
template: 'Flash Sale {discount}%! {product} giá chỉ {price}. Chỉ {days} ngày!' },
price_change: { name: 'Thay đổi giá',
template: 'Điều chỉnh giá {product} từ {old_price} → {new_price}.' },
collaboration: { name: 'Hợp tác thương hiệu',
template: 'Collab {brand} x Canifa — BST giới hạn!' },
social_post: { name: 'Bài đăng MXH',
template: '{content}' }
};
function runSimulation(campaignText, campaignType, weights) {
const reactions = [];
PERSONAS.forEach(persona => {
const weight = weights[persona.id] || 0;
if (weight === 0) return;
// Determine sentiment based on triggers
const sentiment = analyzeSentiment(campaignText, persona, campaignType);
// Generate mock comment
const comment = generateComment(persona, sentiment, campaignText);
// Estimate engagement
const engagement = estimateEngagement(persona, sentiment);
reactions.push({
pid: persona.id,
name: persona.name,
label: persona.label,
age: persona.age,
city: persona.city,
color: persona.color,
sentiment: sentiment,
comment: comment,
engagement: engagement,
weight: weight
});
});
return {
reactions: reactions,
summary: calcSentimentSummary(reactions),
recommendations: generateRecommendations(reactions)
};
}
```
---
## 8.2. Sentiment Analysis Logic
```javascript
function analyzeSentiment(campaignText, persona, campaignType) {
const text = campaignText.toLowerCase();
let score = 0;
// Check positive triggers
persona.triggers_pos.forEach(trigger => {
if (text.includes(trigger.toLowerCase())) score += 2;
});
// Check negative triggers
persona.triggers_neg.forEach(trigger => {
if (text.includes(trigger.toLowerCase())) score -= 3;
});
// Campaign type bias
const TYPE_BIAS = {
product_launch: { mom_shopper: 0, gen_z: 1, fashion_enthusiast: 2 },
promotion: { mom_shopper: 2, budget_conscious: 3, troll: 0 },
price_change: { mom_shopper: -2, budget_conscious: -3, troll: -2 },
collaboration: { gen_z: 2, kol: 2, fashion_enthusiast: 2 },
social_post: { gen_z: 1, kol: 0, troll: -1 }
};
score += (TYPE_BIAS[campaignType]?.[persona.id] || 0);
// Classify
if (score >= 2) return 'positive';
if (score <= -2) return 'negative';
return 'neutral';
}
```
---
## 8.3. Frontend — HTML Structure
```html
<!-- reaction-simulator.html — Key sections -->
<!-- Tab Navigation -->
<div class="sim-tabs">
<button class="sim-tab active" data-tab="simulate">
🎯 Mô phỏng
</button>
<button class="sim-tab" data-tab="crisis">
🚨 Crisis Alert
</button>
<button class="sim-tab" data-tab="docs">
📚 Tài liệu
</button>
</div>
<!-- Campaign Input -->
<div class="sim-section">
<label>Loại chiến dịch</label>
<select id="campaignType">
<option value="product_launch">Ra mắt sản phẩm mới</option>
<option value="promotion">Chương trình khuyến mãi</option>
<option value="price_change">Thay đổi giá</option>
<option value="collaboration">Hợp tác thương hiệu</option>
<option value="social_post">Bài đăng MXH</option>
</select>
<label>Nội dung chiến dịch</label>
<textarea id="campaignContent" rows="4"
placeholder="Nhập nội dung campaign..."></textarea>
</div>
<!-- Persona Weight Sliders -->
<div class="sim-section">
<h3>Điều chỉnh tỷ trọng Persona</h3>
<div class="persona-sliders">
<!-- Generated dynamically -->
</div>
</div>
<!-- Results -->
<div id="results" class="sim-results" style="display:none">
<canvas id="sChart" width="130" height="130"></canvas>
<div id="sentimentBars"></div>
<div id="reactionCards"></div>
<div id="recommendations"></div>
</div>
```
---
## 8.4. API Endpoints (Future — Backend Integration)
```python
# backend/api/reaction_simulator/simulator_route.py
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/api/reaction-simulator", tags=["Reaction Simulator"])
class SimulationRequest(BaseModel):
campaign_text: str
campaign_type: str # product_launch, promotion, price_change, etc.
weights: dict # {mom_shopper: 20, gen_z: 15, ...}
class ABTestRequest(BaseModel):
version_a: str
version_b: str
campaign_type: str
weights: dict
@router.post("/simulate")
async def run_simulation(req: SimulationRequest):
"""Run single simulation with 7 personas."""
# Currently frontend-only (JS engine)
# Future: LLM-powered simulation via this endpoint
pass
@router.post("/ab-test")
async def run_ab_test(req: ABTestRequest):
"""Run A/B comparison of 2 campaign versions."""
pass
@router.get("/presets")
async def get_presets():
"""Return available campaign presets."""
return CAMPAIGN_PRESETS
```
---
## 8.5. File Structure
```
backend/static/reaction-simulator/
├── reaction-simulator.html # Main UI (tabs: Simulate, Crisis, Docs)
├── reaction-simulator.js # Core simulation engine
├── reaction-simulator.css # Styling (dark theme)
└── README.md # Quick start guide
backend/static/cookbook/data/51-reaction-simulator-deep/
├── meta.json # Cookbook registry
├── 01_concept.md # Philosophy
├── 02_persona_engine.md # 7 Persona profiles
├── 03_architecture.md # Multi-Agent architecture
├── 04_sentiment_engine.md # Scoring algorithm
├── 05_crisis_alert.md # Crisis detection
├── 06_ab_testing.md # A/B comparison mode
├── 07_industry_research.md # Academic references
└── 08_implementation.md # This file (code reference)
```
---
## 8.6. Testing Checklist
| # | Test | Action | Expected |
|---|------|--------|----------|
| 1 | Load page | Open reaction-simulator.html | All tabs render, sliders work |
| 2 | Select preset | Choose "Khuyến mãi" | Template text fills input |
| 3 | Run simulation | Click "Mô phỏng phản ứng" | Donut chart + 7 cards appear |
| 4 | Adjust weights | Set Mom=40%, others=10% | Results shift toward Mom perspective |
| 5 | Crisis tab | Switch to Crisis Alert | Dashboard metrics + alert feed render |
| 6 | Docs tab | Switch to Tài liệu | Documentation panels load |
| 7 | Empty input | Run with no text | Error message shown |
| 8 | Reset | Click Reset | All inputs cleared, results hidden |
---
## 8.7. Full Roadmap
```
┌─────────────────────────────────────────────────────┐
│ REACTION SIMULATOR ROADMAP │
│ │
│ ✅ v1.0 — Core simulation engine (JS, client-side) │
│ ✅ v1.1 — 7 Persona profiles + weight sliders │
│ ✅ v1.2 — Donut chart + Reaction cards │
│ ✅ v1.3 — Campaign presets (5 types) │
│ ✅ v1.4 — Crisis Alert tab (mock data) │
│ ✅ v1.5 — Docs tab (inline documentation) │
│ 🔲 v1.6 — A/B Testing mode │
│ 🔲 v1.7 — Export results to PDF │
│ 🔲 v2.0 — Backend API (FastAPI endpoints) │
│ 🔲 v2.1 — LLM-powered comment generation │
│ 🔲 v2.2 — Real social data integration │
│ 🔲 v2.3 — Historical simulation log │
│ 🔲 v3.0 — MiroFish-style persona memory │
│ 🔲 v3.1 — Multi-turn discourse simulation │
│ 🔲 v3.2 — Auto-optimize campaign copy with AI │
└─────────────────────────────────────────────────────┘
```
{
"id": "51-reaction-simulator-deep",
"title": "Reaction Simulator — AI Social Listening & Campaign Pre-Testing",
"description": "Hệ thống giả lập phản ứng cộng đồng trước khi launch campaign. 7 Persona segments, Sentiment Engine, Crisis Alert, A/B Testing. Lấy cảm hứng từ BettaFish ForumEngine + MiroFish Simulate Agent.",
"sections": [
{
"id": "concept",
"title": "1. Triết lý: Tại sao phải 'Test trước khi Launch'?",
"type": "markdown",
"file": "01_concept.md"
},
{
"id": "persona-engine",
"title": "2. Persona Engine: 7 nhóm khách hàng Vietnam",
"type": "markdown",
"file": "02_persona_engine.md"
},
{
"id": "architecture",
"title": "3. Kiến trúc Multi-Agent Simulation",
"type": "markdown",
"file": "03_architecture.md"
},
{
"id": "sentiment-engine",
"title": "4. Sentiment Analysis Engine",
"type": "markdown",
"file": "04_sentiment_engine.md"
},
{
"id": "crisis-alert",
"title": "5. Crisis Alert System: Phát hiện khủng hoảng sớm",
"type": "markdown",
"file": "05_crisis_alert.md"
},
{
"id": "ab-testing",
"title": "6. A/B Testing Mode: So sánh 2 phiên bản campaign",
"type": "markdown",
"file": "06_ab_testing.md"
},
{
"id": "industry-research",
"title": "7. Nghiên cứu: BettaFish, MiroFish & Social Simulation",
"type": "markdown",
"file": "07_industry_research.md"
},
{
"id": "implementation",
"title": "8. Implementation Blueprint: Code & API",
"type": "markdown",
"file": "08_implementation.md"
}
]
}
...@@ -26,10 +26,18 @@ ...@@ -26,10 +26,18 @@
"name": "👗 Nghiệp vụ: Phối đồ & Thời trang", "name": "👗 Nghiệp vụ: Phối đồ & Thời trang",
"recipes": [ "recipes": [
{ "id": "36-fashion-matching-logic", "title": "Nghiệp vụ phối đồ (Tại sao phối thế?)" }, { "id": "36-fashion-matching-logic", "title": "Nghiệp vụ phối đồ (Tại sao phối thế?)" },
{ "id": "49-conversational-routing", "title": "Conversational Routing (Mồi Câu Liên Hoàn)" },
{ "id": "37-ultra-description-logic", "title": "Nghiệp vụ mô tả SP (Ultra Description)" }, { "id": "37-ultra-description-logic", "title": "Nghiệp vụ mô tả SP (Ultra Description)" },
{ "id": "38-size-guide-logic", "title": "Nghiệp vụ hướng dẫn size" } { "id": "38-size-guide-logic", "title": "Nghiệp vụ hướng dẫn size" }
] ]
}, },
{
"name": "🎪 Event & Campaign Management",
"recipes": [
{ "id": "50-event-daily-engine", "title": "Event Daily Engine (Lịch tâm trạng AI)" },
{ "id": "51-reaction-simulator-deep", "title": "Reaction Simulator Deep-Dive (7 Personas)" }
]
},
{ {
"name": "🤖 AI Core & Prompt", "name": "🤖 AI Core & Prompt",
"recipes": [ "recipes": [
......
...@@ -126,71 +126,44 @@ ...@@ -126,71 +126,44 @@
<script> <script>
if (window.lucide) { lucide.createIcons(); } if (window.lucide) { lucide.createIcons(); }
const mockEvents = [ let events = [];
{
id: 'evt_1',
title: 'Mặc định hàng ngày',
type: 'Daily',
tagClass: 'daily',
date: 'All days',
desc: 'Luật cơ bản cho ngày thường. Không có event đặc biệt.',
prompt: '- Luôn chào hỏi thân thiện\n- Gợi ý đồ dạo phố, công sở nhẹ nhàng\n- Khuyến khích khách hàng thử trang phục mới'
},
{
id: 'evt_2',
title: 'Flash Sale 5/5',
type: 'Campaign',
tagClass: 'campaign',
date: '05/05/2026',
desc: 'Sale lớn đồng giá 99k, 199k. Đẩy mạnh các mặt hàng áo thun mùa hè.',
prompt: '- NHẤN MẠNH chương trình Flash Sale 5/5 đồng giá 99k, 199k\n- Gợi ý áo thun, quần short mặc hè\n- Dùng emoji 🎉🔥 để tạo sự hào hứng'
},
{
id: 'evt_3',
title: 'Lễ hội mùa đông',
type: 'Campaign',
tagClass: 'campaign',
date: '10/11/2026 - 31/12/2026',
desc: 'Chiến dịch ra mắt bộ sưu tập áo khoác dạ và len lông cừu.',
prompt: '- Ưu tiên tư vấn áo khoác dạ, áo len lông cừu\n- Gợi ý cách phối đồ layer ấm áp\n- Chú ý hỏi khách hàng đang ở khu vực lạnh không'
},
{
id: 'evt_4',
title: 'Sinh nhật Canifa',
type: 'Campaign',
tagClass: 'campaign',
date: '01/08/2026 - 15/08/2026',
desc: 'Mừng sinh nhật. Voucher giảm 50% cho khách hàng mua từ 3 sản phẩm.',
prompt: '- Nhắc khéo khách hàng về voucher giảm 50% khi mua 3 sản phẩm\n- Chúc mừng sinh nhật Canifa cùng khách hàng'
},
{
id: 'evt_5',
title: 'Black Friday 2026',
type: 'Campaign',
tagClass: 'campaign',
date: '27/11/2026',
desc: 'Giảm giá cực sốc lên tới 70%. Tăng cường upsell.',
prompt: '- Khẩn trương! Black Friday sale 70%\n- Tư vấn khách mua theo combo để được giá tốt nhất\n- Không bỏ lỡ các deal shock giờ vàng'
}
];
const eventsListEl = document.getElementById('eventsList'); const eventsListEl = document.getElementById('eventsList');
const contentArea = document.getElementById('contentArea'); const contentArea = document.getElementById('contentArea');
const emptyArea = document.getElementById('emptyArea'); const emptyArea = document.getElementById('emptyArea');
const promptContent = document.getElementById('prompt-content');
const dateFrom = document.getElementById('evt-date-from');
const dateTo = document.getElementById('evt-date-to');
let currentActiveId = null; let currentActiveId = null;
async function fetchEvents() {
try {
const response = await fetch('/api/event_daily/events');
if (response.ok) {
events = await response.json();
renderEvents();
}
} catch (error) {
console.error('Error fetching events:', error);
}
}
function renderEvents() { function renderEvents() {
eventsListEl.innerHTML = mockEvents.map(evt => ` eventsListEl.innerHTML = events.map(evt => {
const tagClass = evt.type.toLowerCase() === 'daily' ? 'daily' : 'campaign';
const isTodayHtml = evt.isToday ? '<span style="color:red; font-size:12px; margin-left: 4px;">🔥</span>' : '';
const displayDate = (evt.startDate && evt.endDate) ? `${evt.startDate} - ${evt.endDate}` : evt.date;
return `
<div class="fb-item ${evt.id === currentActiveId ? 'active' : ''}" onclick="selectEvent('${evt.id}')"> <div class="fb-item ${evt.id === currentActiveId ? 'active' : ''}" onclick="selectEvent('${evt.id}')">
<div class="fb-tag ${evt.tagClass}">${evt.type.toUpperCase()}</div> <div class="fb-tag ${tagClass}">${evt.type.toUpperCase()}</div>
<div class="fb-title">${evt.title}</div> <div class="fb-title">${evt.name}${isTodayHtml}</div>
<div style="font-size:11px; color:var(--m); margin-bottom:8px; display:flex; align-items:center; gap:4px;"> <div style="font-size:11px; color:var(--m); margin-bottom:8px; display:flex; align-items:center; gap:4px;">
<i data-lucide="clock" style="width:12px;height:12px;"></i> ${evt.date} <i data-lucide="clock" style="width:12px;height:12px;"></i> ${displayDate}
</div> </div>
<div class="fb-desc">${evt.desc}</div> <div class="fb-desc" style="white-space: pre-line; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;">${evt.prompt}</div>
</div> </div>
`).join(''); `;
}).join('');
lucide.createIcons(); lucide.createIcons();
} }
...@@ -198,16 +171,16 @@ ...@@ -198,16 +171,16 @@
currentActiveId = id; currentActiveId = id;
renderEvents(); renderEvents();
const evt = mockEvents.find(e => e.id === id); const evt = events.find(e => e.id === id);
if(evt) { if(evt) {
emptyArea.style.display = 'none'; emptyArea.style.display = 'none';
contentArea.style.display = 'flex'; contentArea.style.display = 'flex';
document.getElementById('detail-title').innerText = evt.title; document.getElementById('detail-title').innerText = evt.name;
const tagEl = document.getElementById('detail-tag'); const tagEl = document.getElementById('detail-tag');
tagEl.innerText = evt.type.toUpperCase(); tagEl.innerText = evt.type.toUpperCase();
if (evt.type === 'Daily') { if (evt.type.toLowerCase() === 'daily') {
tagEl.style.background = 'rgba(16,185,129,0.15)'; tagEl.style.background = 'rgba(16,185,129,0.15)';
tagEl.style.color = '#34d399'; tagEl.style.color = '#34d399';
} else { } else {
...@@ -215,17 +188,51 @@ ...@@ -215,17 +188,51 @@
tagEl.style.color = '#818cf8'; tagEl.style.color = '#818cf8';
} }
document.getElementById('prompt-content').value = evt.prompt; promptContent.value = evt.prompt;
dateFrom.value = evt.startDate || '';
dateTo.value = evt.endDate || '';
} }
} }
function savePrompt() { async function savePrompt() {
if (!currentActiveId) return;
const btn = document.querySelector('.btn-primary'); const btn = document.querySelector('.btn-primary');
const originalHtml = btn.innerHTML; const originalHtml = btn.innerHTML;
btn.innerHTML = '<i data-lucide="loader" class="icon-sm"></i> Đang lưu...';
try {
const response = await fetch(`/api/event_daily/events/${currentActiveId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: promptContent.value,
start_date: dateFrom.value || null,
end_date: dateTo.value || null
})
});
if (response.ok) {
btn.innerHTML = '<i data-lucide="check" class="icon-sm"></i> Đã lưu'; btn.innerHTML = '<i data-lucide="check" class="icon-sm"></i> Đã lưu';
btn.style.background = '#10b981'; btn.style.background = '#10b981';
lucide.createIcons();
const evt = events.find(e => e.id === currentActiveId);
if (evt) {
evt.prompt = promptContent.value;
evt.startDate = dateFrom.value || null;
evt.endDate = dateTo.value || null;
renderEvents();
}
} else {
throw new Error('Lỗi khi lưu');
}
} catch (error) {
console.error(error);
btn.innerHTML = '<i data-lucide="x" class="icon-sm"></i> Lỗi';
btn.style.background = '#ef4444';
}
lucide.createIcons();
setTimeout(() => { setTimeout(() => {
btn.innerHTML = originalHtml; btn.innerHTML = originalHtml;
btn.style.background = ''; btn.style.background = '';
...@@ -233,12 +240,11 @@ ...@@ -233,12 +240,11 @@
}, 2000); }, 2000);
} }
// Set today's date
const today = new Date(); const today = new Date();
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
document.getElementById('today-date').innerText = today.toLocaleDateString('vi-VN', options); document.getElementById('today-date').innerText = today.toLocaleDateString('vi-VN', options);
renderEvents(); fetchEvents();
</script> </script>
</body> </body>
</html> </html>
...@@ -412,5 +412,3 @@ body { ...@@ -412,5 +412,3 @@ body {
import sqlite3
conn = sqlite3.connect('database/canifa_ai_dump.sqlite')
c = conn.cursor()
c.execute("SELECT match_role, COUNT(*) FROM pg__dashboard_canifa__ai_outfit_product_matches GROUP BY match_role")
print(c.fetchall())
Bimport sqlite3; conn = sqlite3.connect('database/canifa_ai_dump.sqlite'); c = conn.cursor(); c.execute(\ Bimport sqlite3; conn = sqlite3.connect('database/canifa_ai_dump.sqlite'); c = conn.cursor(); c.execute(\
This diff is collapsed.
[('Bộ thể thao', 'outerwear'), ('Bộ thể thao', 'outerwear'), ('Bộ thể thao', 'outerwear'), ('Bộ thể thao', 'accessory'), ('Bộ thể thao', 'accessory'), ('Bộ thể thao', 'accessory'), ('Bộ thể thao', 'accessory'), ('Bộ thể thao', 'accessory'), ('Bộ thể thao', 'accessory'), ('Mũ', 'top'), ('Mũ thể thao', 'top'), ('Mũ thể thao', 'top'), ('Mũ thể thao', 'top'), ('Mũ thể thao', 'accessory'), ('Mũ thể thao', 'outerwear'), ('Quần dài', 'accessory'), ('Quần dài', 'accessory'), ('Quần dài', 'accessory'), ('Quần dài', 'accessory'), ('Quần dài', 'accessory'), ('Quần jean', 'accessory'), ('Quần jean', 'accessory'), ('Quần jean', 'accessory'), ('Quần jean', 'accessory'), ('Quần nỉ', 'accessory'), ('Quần nỉ', 'accessory'), ('Quần nỉ', 'accessory'), ('Quần nỉ', 'accessory'), ('Quần nỉ', 'accessory'), ('Quần nỉ', 'top'), ('Quần nỉ', 'top'), ('Quần nỉ', 'top'), ('Quần nỉ', 'top'), ('Quần nỉ', 'top'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'top'), ('Tất', 'top'), ('Tất', 'top'), ('Tất', 'top'), ('Tất', 'top'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Tất', 'bottom'), ('Áo Polo', 'accessory'), ('Áo Polo', 'accessory'), ('Áo Polo', 'accessory'), ('Áo Polo', 'accessory'), ('Áo Polo', 'accessory'), ('Áo khoác gió', 'top'), ('Áo khoác gió', 'top'), ('Áo khoác gió', 'top'), ('Áo khoác gió', 'top'), ('Áo khoác gió', 'top'), ('Áo khoác gió', 'accessory'), ('Áo khoác gió', 'accessory'), ('Áo khoác gió', 'accessory'), ('Áo khoác gió', 'accessory'), ('Áo khoác gió', 'accessory'), ('Áo khoác nỉ không mũ', 'accessory'), ('Áo khoác nỉ không mũ', 'accessory'), ('Áo khoác nỉ không mũ', 'accessory'), ('Áo khoác nỉ không mũ', 'accessory'), ('Áo khoác nỉ không mũ', 'accessory'), ('Áo phông', 'accessory'), ('Áo phông', 'accessory'), ('Áo phông', 'accessory'), ('Áo phông', 'accessory'), ('Áo phông', 'accessory')]
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
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