# Deep Dive: Thuật toán Hybrid Search (Simulate Match)

Mặc dù được thiết kế với tư tưởng của BM25 (theo các mô tả ban đầu), việc triển khai thực tế trong file `common/match_algo.py` sử dụng một phương pháp tiếp cận tối ưu hóa, kết hợp **Word Overlap** và **Fuzzy Match** (khoảng cách tương tự chuỗi) để tính toán điểm số nhanh chóng trực tiếp trên bộ nhớ mà không cần dùng vector database hoặc cấu trúc TF-IDF phức tạp.

## Luồng xử lý (Sequence Diagram)

```mermaid
sequenceDiagram
    participant U as User (Frontend)
    participant API as FAQ Route (/simulate-match)
    participant DB as SQLite (faqs table)
    participant Algo as Match Algorithm (match_algo.py)
    
    U->>API: POST /api/faqs/simulate-match {query}
    API->>DB: SELECT id, question, answer, category, is_variant FROM faqs
    DB-->>API: List of all FAQs & Variants
    API->>Algo: find_best_matches(query, faq_list, top_k=3)
    
    loop For each FAQ
        Algo->>Algo: normalize_text(query & faq.question)
        Note right of Algo: Bỏ dấu TV, chữ thường, ký tự đặc biệt
        Algo->>Algo: calculate_similarity()
        Note right of Algo: Score = (Overlap * 0.7) + (FuzzyRatio * 0.3)
    end
    
    Algo-->>API: Top 3 FAQs (score > 5.0)
    API-->>U: Trả về kết quả (kèm elapsed_ms)
```

## 1. Chuẩn hóa dữ liệu (`normalize_text`)
Trước khi tính điểm, cả câu truy vấn (query) và câu hỏi trong DB (document) đều được đi qua hàm `normalize_text(text: str)`:
- Chuyển toàn bộ về chữ thường bằng `.lower()`.
- Xóa dấu tiếng Việt bằng `unicodedata.normalize('NFD', text)`.
- Lọc bỏ các ký tự đặc biệt (chỉ giữ lại chữ cái, số và khoảng trắng thông qua `isalnum()`).

## 2. Tính điểm tương đồng (`calculate_similarity`)
Thay vì tính IDF toàn cục như thuật toán BM25 chuẩn, thuật toán trong hệ thống hiện tại sử dụng công thức gộp:
* **Fuzzy Ratio (30% trọng số):** Sử dụng hàm `difflib.SequenceMatcher(None, norm_q, norm_d).ratio()` để tính độ giống nhau của chuỗi. Xử lý tốt các trường hợp sai lỗi chính tả nhẹ (vd: `quan jean` vs `quần jean`).
* **Word Overlap (70% trọng số):** Tập hợp từ vựng độc nhất (set) của câu query và document được giao nhau (`intersection`). Điểm số là `%` từ vựng của query xuất hiện trong document (`len(intersection) / len(q_words)`).

**Công thức tính điểm chung:**
```python
final_score = (overlap_score * 0.7) + (fuzzy_ratio * 0.3)
return round(final_score * 100, 2)
```

Hệ thống lọc bỏ tất cả các câu hỏi có điểm số `<= 5.0` (ngưỡng tối thiểu) và sắp xếp kết quả giảm dần. Top K kết quả tốt nhất sẽ được trả về cho người dùng.
