# 搜索引擎通用化开发进度

## 项目概述

对后端搜索技术 做通用化。
通用化的本质 是 对于各种业务数据、各种检索需求，都可以  用少量定制+配置化 来实现效果。


**通用化的本质**：对于各种业务数据、各种检索需求，都可以用少量定制+配置化来实现效果。

---

## 1. 原始数据层的约定

### 1.1 店匠主表

所有租户共用以下主表：
- `shoplazza_product_sku` - SKU级别商品数据
- `shoplazza_product_spu` - SPU级别商品数据

### 1.2 索引结构（SPU维度）

**统一索引架构**：
- 所有客户共享同一个Elasticsearch索引：`search_products`
- 索引粒度：SPU级别（每个文档代表一个SPU）
- 数据隔离：通过`tenant_id`字段实现租户隔离
- 嵌套结构：每个SPU文档包含嵌套的`skus`数组

**索引文档结构**：
```json
{
  "tenant_id": "1",
  "spu_id": "123",
  "title_zh": "蓝牙耳机",
  "title_en": "Bluetooth Headphones",
  "brief_zh": "高品质蓝牙耳机",
  "brief_en": "High-quality Bluetooth headphones",
  "category_name": "电子产品",
  "category_path_zh": "电子产品/音频设备/耳机",
  "category_path_en": "Electronics/Audio/Headphones",
  "category1_name": "电子产品",
  "category2_name": "音频设备",
  "category3_name": "耳机",
  "vendor_zh": "品牌A",
  "vendor_en": "Brand A",
  "min_price": 199.99,
  "max_price": 299.99,
  "option1_name": "color",
  "option2_name": "size",
  "specifications": [
    {
      "sku_id": "456",
      "name": "color",
      "value": "black"
    },
    {
      "sku_id": "456",
      "name": "size",
      "value": "large"
    }
  ],
  "skus": [
    {
      "sku_id": "456",
      "price": 199.99,
      "compare_at_price": 249.99,
      "sku_code": "SKU-123-1",
      "stock": 50,
      "weight": 0.2,
      "weight_unit": "kg",
      "option1_value": "black",
      "option2_value": "large",
      "option3_value": null,
      "image_src": "https://example.com/image.jpg"
    }
  ],
  "title_embedding": [0.1, 0.2, ...],  // 1024维向量
  "image_embedding": [
    {
      "vector": [0.1, 0.2, ...],  // 1024维向量
      "url": "https://example.com/image.jpg"
    }
  ]
}
```

### 1.3 索引结构简化方案

**简化原则**：
- **硬编码映射**：ES mapping 结构直接定义在 JSON 文件中（`mappings/search_products.json`）
- **统一索引结构**：所有租户共享相同的索引结构，通过 `tenant_id` 隔离数据
- **数据源统一**：所有租户使用相同的 MySQL 表结构（店匠标准表）
- **查询配置硬编码**：查询相关配置（字段 boost、查询域等）硬编码在 `search/query_config.py`

**索引结构特点**：
1. **多语言字段**：所有文本字段支持中英文（`title_zh/en`, `brief_zh/en`, `description_zh/en`, `vendor_zh/en`, `category_path_zh/en`, `category_name_zh/en`）
2. **嵌套字段**：
   - `skus`: SKU 嵌套数组（包含价格、库存、选项值等）
   - `specifications`: 规格嵌套数组（包含 name、value、sku_id）
   - `image_embedding`: 图片向量嵌套数组
3. **扁平化字段**：`sku_prices`, `sku_weights`, `total_inventory` 等用于过滤和排序
4. **向量字段**：`title_embedding`（1024维）用于语义搜索

**实现文件**：
- `mappings/search_products.json` - ES mapping 定义（硬编码）
- `search/query_config.py` - 查询配置（硬编码）
- `indexer/mapping_generator.py` - 加载 JSON mapping 并创建索引

---

## 2. 索引结构实现

### 2.1 硬编码映射方案

**实现方式**：
- ES mapping 直接定义在 `mappings/search_products.json` 文件中
- 所有租户共享相同的索引结构
- 查询配置硬编码在 `search/query_config.py`

**索引字段结构**：

#### 基础字段
- `tenant_id` (keyword): 租户ID，用于数据隔离
- `spu_id` (keyword): SPU唯一标识
- `create_time`, `update_time` (date): 创建和更新时间

#### 多语言文本字段
- `title_zh/en` (text): 标题（中英文）
- `brief_zh/en` (text): 短描述（中英文）
- `description_zh/en` (text): 详细描述（中英文）
- `vendor_zh/en` (text): 供应商/品牌（中英文）
- `category_path_zh/en` (text): 类目路径（中英文）
- `category_name_zh/en` (text): 类目名称（中英文）

**分析器配置**：
- 中文字段：`hanlp_index`（索引时）/ `hanlp_standard`（查询时）
- 英文字段：`english`
- `vendor` 字段包含 `keyword` 子字段（normalizer: lowercase）

#### 分类字段
- `category_id` (keyword): 类目ID
- `category_name` (keyword): 类目名称
- `category_level` (integer): 类目层级
- `category1_name`, `category2_name`, `category3_name` (keyword): 多级类目名称

#### 规格字段（Specifications）
- `specifications` (nested): 规格嵌套数组
  - `sku_id` (keyword): SKU ID
  - `name` (keyword): 规格名称（如 "color", "size"）
  - `value` (keyword): 规格值（如 "white", "256GB"）

**用途**：
- 支持按规格过滤：`{"specifications": {"name": "color", "value": "white"}}`
- 支持规格分面：`["specifications"]` 或 `["specifications.color"]`

#### SKU嵌套字段
- `skus` (nested): SKU嵌套数组
  - `sku_id`, `price`, `compare_at_price`, `sku_code`
  - `stock`, `weight`, `weight_unit`
  - `option1_value`, `option2_value`, `option3_value`
  - `image_src` (index: false)

#### 选项名称字段
- `option1_name`, `option2_name`, `option3_name` (keyword): 选项名称（如 "color", "size"）

#### 扁平化字段
- `min_price`, `max_price`, `compare_at_price` (float): 价格字段
- `sku_prices` (float[]): 所有SKU价格数组
- `sku_weights` (long[]): 所有SKU重量数组
- `total_inventory` (long): 总库存

#### 向量字段
- `title_embedding` (dense_vector, 1024维): 标题向量，用于语义搜索
- `image_embedding` (nested): 图片向量数组
  - `vector` (dense_vector, 1024维)
  - `url` (text)

**实现模块**：
- `mappings/search_products.json` - ES mapping 定义
- `indexer/mapping_generator.py` - 加载 JSON mapping 并创建索引
- `search/query_config.py` - 查询配置（字段 boost、查询域等）

### 2.2 索引结构配置（查询域配置）

**配置内容**：定义了 ES 的字段索引 mapping 配置，支持各个域的查询，包括默认域的查询。

**实现情况**：

#### 域（Domain）配置
每个域定义了：
- 域名称（如 `default`, `title`, `category`, `brand`）
- 域标签（中文描述）
- 搜索字段列表
- 默认分析器
- 权重（boost）
- **多语言字段映射**（`language_field_mapping`）

#### 多语言字段映射

支持将不同语言的查询路由到对应的字段：

```yaml
indexes:
  - name: "default"
    label: "默认索引"
    fields:
      - "name"
      - "enSpuName"
      - "ruSkuName"
      - "categoryName"
      - "brandName"
    analyzer: "chinese_ecommerce"
    boost: 1.0
    language_field_mapping:
      zh:
        - "name"
        - "categoryName"
        - "brandName"
      en:
        - "enSpuName"
      ru:
        - "ruSkuName"

  - name: "title"
    label: "标题索引"
    fields:
      - "name"
      - "enSpuName"
      - "ruSkuName"
    analyzer: "chinese_ecommerce"
    boost: 2.0
    language_field_mapping:
      zh:
        - "name"
      en:
        - "enSpuName"
      ru:
        - "ruSkuName"
```

**工作原理**：
1. 检测查询语言（中文、英文、俄文等）
2. 如果查询语言在 `language_field_mapping` 中，使用原始查询搜索对应语言的字段
3. 将查询翻译到其他支持的语言，分别搜索对应语言的字段
4. 组合多个语言查询的结果，提高召回率

**实现模块**：
- `search/es_query_builder.py` - ES 查询构建器（单层架构）
- `query/query_parser.py` - 查询解析器（支持语言检测和翻译）
- `search/query_config.py` - 查询配置（字段 boost、查询域等）

---

## 3. 数据导入流程

### 3.1 数据源

**店匠标准表**（Base配置使用）：
- `shoplazza_product_spu` - SPU级别商品数据
- `shoplazza_product_sku` - SKU级别商品数据

**其他客户表**（tenant1等）：
- 使用各自的数据源表和扩展表

### 3.2 数据导入方式

**数据源统一**：
- 所有租户使用相同的MySQL表结构（店匠标准表）
- 数据转换逻辑写死在转换器代码中
- 索引结构硬编码，不依赖配置

#### 数据导入流程（店匠通用）

**脚本**：`scripts/ingest_shoplazza.py`

**数据流程**：
1. **数据加载**：
   - 从MySQL读取`shoplazza_product_spu`表（SPU数据）
   - 从MySQL读取`shoplazza_product_sku`表（SKU数据）
   - 从MySQL读取`shoplazza_product_option`表（选项定义）

2. **数据转换**（`indexer/spu_transformer.py`）：
   - 按`spu_id`和`tenant_id`关联SPU和SKU数据
   - **多语言字段映射**：
     - MySQL的`title` → ES的`title_zh`（英文字段设为空）
     - 其他文本字段类似处理
   - **分类字段映射**：
     - 从SPU表的`category_path`解析多级类目（`category1_name`, `category2_name`, `category3_name`）
     - 映射`category_id`, `category_name`, `category_level`
   - **规格字段构建**（`specifications`）：
     - 从`shoplazza_product_option`表获取选项名称（`name`）
     - 从SKU的`option1/2/3`字段获取选项值（`value`）
     - 构建嵌套数组：`[{"sku_id": "...", "name": "color", "value": "white"}, ...]`
   - **选项名称映射**：
     - 从`shoplazza_product_option`表获取`option1_name`, `option2_name`, `option3_name`
   - **SKU嵌套数组构建**：
     - 包含所有SKU字段（价格、库存、选项值、图片等）
   - **扁平化字段计算**：
     - `min_price`, `max_price`: 从所有SKU价格计算
     - `sku_prices`: 所有SKU价格数组
     - `total_inventory`: SKU库存总和
   - 注入`tenant_id`字段

3. **索引创建**：
   - 从`mappings/search_products.json`加载ES mapping
   - 创建或更新`search_products`索引

4. **批量入库**：
   - 批量写入ES（默认每批500条）
   - 错误处理和重试机制

**命令行工具**：
```bash
python scripts/ingest_shoplazza.py \
    --db-host localhost \
    --db-port 3306 \
    --db-database saas \
    --db-username root \
    --db-password password \
    --tenant-id "1" \
    --config base \
    --es-host http://localhost:9200 \
    --recreate \
    --batch-size 500
```

#### 其他客户数据导入

- 使用各自的数据转换器（如`indexer/data_transformer.py`）
- 数据源映射逻辑写死在各自的转换器中
- 共享`search_products`索引，通过`tenant_id`隔离

**实现模块**：
- `indexer/spu_transformer.py` - SPU数据转换器（Base配置）
- `indexer/data_transformer.py` - 通用数据转换器（其他客户）
- `indexer/bulk_indexer.py` - 批量索引器
- `scripts/ingest_shoplazza.py` - 店匠数据导入脚本

---

## 4. QueryParser 实现


### 4.1 查询改写（Query Rewriting）

配置词典的key是query，value是改写后的查询表达式，比如。比如品牌词 改写为在brand|query OR name|query，类别词、标签词等都可以放进去。纠错、规范化、查询改写等 都可以通过这个词典来配置。
**实现情况**：

#### 配置方式
在 `query_config.rewrite_dictionary` 中配置查询改写规则：

```yaml
query_config:
  enable_query_rewrite: true
  rewrite_dictionary:
    "芭比": "brand:芭比 OR name:芭比娃娃"
    "玩具": "category:玩具"
    "消防": "category:消防 OR name:消防"
```

#### 功能特性
- **精确匹配**：查询完全匹配词典 key 时，替换为 value
- **部分匹配**：查询包含词典 key 时，替换该部分
- **支持布尔表达式**：value 可以是复杂的布尔表达式（AND, OR, 域查询等）

#### 实现模块
- `query/query_rewriter.py` - 查询改写器
- `query/query_parser.py` - 查询解析器（集成改写功能）

### 4.2 翻译（Translation）

**实现情况**：

#### 配置方式
```yaml
query_config:
  supported_languages:
    - "zh"
    - "en"
    - "ru"
  default_language: "zh"
  enable_translation: true
  translation_service: "deepl"
  translation_api_key: null  # 通过环境变量设置
```

#### 功能特性
1. **语言检测**：自动检测查询语言
2. **智能翻译**：
   - 如果查询是中文，翻译为英文、俄文
   - 如果查询是英文，翻译为中文、俄文
   - 如果查询是其他语言，翻译为所有支持的语言
3. **域感知翻译**：
   - 如果域有 `language_field_mapping`，只翻译到映射中存在的语言
   - 避免不必要的翻译，提高效率
4. **翻译缓存**：缓存翻译结果，避免重复调用 API

#### 工作流程
```
查询输入 → 语言检测 → 翻译 → 查询构建（filters and (text_recall or embedding_recall)）
```

#### 实现模块
- `query/language_detector.py` - 语言检测器
- `query/translator.py` - 翻译器（DeepL API）
- `query/query_parser.py` - 查询解析器（集成翻译功能）

### 4.3 文本向量化（Text Embedding）

如果配置打开了text_embedding查询，并且query 包含了default域的查询，那么要把default域的查询词转向量，后面searcher会用这个向量参与查询。

**实现情况**：

#### 配置方式
```yaml
query_config:
  enable_text_embedding: true
```

#### 功能特性
1. **条件生成**：
   - 仅当 `enable_text_embedding=true` 时生成向量
   - 仅对 `default` 域查询生成向量
2. **向量模型**：BGE-M3 模型（1024维向量）
3. **用途**：用于语义搜索（KNN 检索）

#### 实现模块
- `embeddings/bge_encoder.py` - BGE 文本编码器
- `query/query_parser.py` - 查询解析器（集成向量生成）

---

## 5. Searcher 实现

参考opensearch，他们自己定义的一套索引结构配置、支持自定义的一套检索表达式、排序表达式，这是各个客户进行配置化的基础，包括索引结构配置、排序策略配置。
比如各种业务过滤策略 可以简单的通过表达式满足，比如brand|耐克 AND cate2|xxx。指定字段排序可以通过排序的表达式实现。

查询默认在default域，相也会对这个域的查询做一些相关性的重点优化，包括融合语义相关性、多语言相关性（可以基于配置 将查询翻译到指定语言并在对应的语言的字段进行查询）来弥补传统查询分析手段（比如查询改写 纠错 词权重等）的不足，也支持通过配置一些词表转为泛查询模式来优化相关性。

### 5.1 布尔表达式解析

**实现情况**：

#### 支持的运算符
- **AND**：所有项必须匹配
- **OR**：任意项匹配
- **RANK**：排序增强（类似 OR 但影响排序）
- **ANDNOT**：排除（第一项匹配，第二项不匹配）
- **()**：括号分组

#### 优先级（从高到低）
1. `()` - 括号
2. `ANDNOT` - 排除
3. `AND` - 与
4. `OR` - 或
5. `RANK` - 排序

#### 示例
```
laptop AND (gaming OR professional) ANDNOT cheap
```

#### 实现模块
- `search/boolean_parser.py` - 布尔表达式解析器
- `search/searcher.py` - 搜索器（集成布尔解析）

### 5.2 多语言搜索

**实现情况**：

#### 工作原理
1. **查询解析**：
   - 提取域（如 `title:查询` → 域=`title`，查询=`查询`）
   - 检测查询语言
   - 生成翻译
2. **查询构建**（简化架构）：
   - **结构**: `filters AND (text_recall OR embedding_recall)`
   - **filters**: 前端传递的过滤条件（永远起作用，放在 `filter` 中）
     - 普通字段过滤：`{"category_name": "手机"}`
     - 范围过滤：`{"min_price": {"gte": 50, "lte": 200}}`
     - **Specifications嵌套过滤**：
       - 单个规格：`{"specifications": {"name": "color", "value": "white"}}`
       - 多个规格：`{"specifications": [{"name": "color", "value": "white"}, {"name": "size", "value": "256GB"}]}`
       - 过滤逻辑：不同维度（不同name）是AND关系，相同维度（相同name）的多个值是OR关系
       - 使用ES的`nested`查询实现
   - **text_recall**: 文本相关性召回
     - 同时搜索中英文字段（`title_zh/en`, `brief_zh/en`, `description_zh/en`, `vendor_zh/en`, `category_path_zh/en`, `category_name_zh/en`, `tags`）
     - 使用 `multi_match` 查询，支持字段 boost
     - 中文字段使用中文分词器，英文字段使用英文分析器
   - **embedding_recall**: 向量召回（KNN）
     - 使用 `title_embedding` 字段进行 KNN 搜索
     - ES 自动与文本召回合并（OR逻辑）
   - **function_score**: 包装召回部分，支持提权字段（新鲜度、销量等）

#### 查询结构示例
```json
{
  "query": {
    "bool": {
      "must": [
        {
          "function_score": {
            "query": {
              "multi_match": {
                "query": "手机",
                "fields": [
                  "title_zh^3.0", "title_en^3.0",
                  "brief_zh^1.5", "brief_en^1.5",
                  ...
                ]
              }
            },
            "functions": [...]
          }
        }
      ],
      "filter": [
        {"term": {"tenant_id": "2"}},
        {"term": {"category_name": "手机"}}
      ]
    }
  },
  "knn": {
    "field": "title_embedding",
    "query_vector": [...],
    "k": 50,
    "boost": 0.2
  }
}
```

#### 实现模块
- `search/es_query_builder.py` - ES 查询构建器（单层架构，`build_query` 方法）
- `search/searcher.py` - 搜索器（使用 `ESQueryBuilder`）

### 5.3 相关性计算（Ranking）

**实现情况**：

#### 当前实现
**公式**：`bm25() + 0.2 * text_embedding_relevance()`

- **bm25()**：BM25 文本相关性得分
  - 包括多语言打分
  - 内部通过配置翻译为多种语言
  - 分别到对应的字段搜索
  - 中文字段使用中文分词器，英文字段使用英文分词器
- **text_embedding_relevance()**：文本向量相关性得分（KNN 检索的打分）
  - 权重：0.2

#### 配置方式
```yaml
ranking:
  expression: "bm25() + 0.2*text_embedding_relevance()"
  description: "BM25 text relevance combined with semantic embedding similarity"
```

#### 扩展性
- 支持表达式配置（未来可扩展）
- 支持自定义函数（如 `timeliness()`, `field_value()`）

#### 实现模块
- `search/ranking_engine.py` - 排序引擎
- `search/searcher.py` - 搜索器（集成排序功能）

---

## 6. 已完成功能总结

### 6.1 配置系统
- ✅ 字段定义配置（类型、分析器、来源表/列）
- ✅ 索引域配置（多域查询、多语言映射）
- ✅ 查询配置（改写词典、翻译配置）
- ✅ 排序配置（表达式配置）
- ✅ 配置验证（字段存在性、类型检查、分析器匹配）

### 6.2 数据索引
- ✅ 数据转换（字段映射、类型转换）
- ✅ 向量生成（文本向量、图片向量）
- ✅ 向量缓存（避免重复计算）
- ✅ 批量索引（错误处理、重试机制）
- ✅ ES mapping 自动生成

### 6.3 查询处理
- ✅ 查询改写（词典配置）
- ✅ 语言检测
- ✅ 多语言翻译（DeepL API）
- ✅ 文本向量化（BGE-M3）
- ✅ 域提取（支持 `domain:query` 语法）

### 6.4 搜索功能
- ✅ 布尔表达式解析（AND, OR, RANK, ANDNOT, 括号）
- ✅ 多语言查询构建（同时搜索中英文字段）
- ✅ 语义搜索（KNN 检索）
- ✅ 相关性排序（BM25 + 向量相似度）
- ✅ 结果聚合（Faceted Search）
- ✅ Specifications嵌套过滤（单个和多个规格，按维度分组：不同维度AND，相同维度OR）
- ✅ Specifications嵌套分面（所有规格名称和指定规格名称）
- ✅ SKU筛选（按维度过滤，应用层实现）

### 6.5 API 服务
- ✅ RESTful API（FastAPI）
- ✅ 搜索接口（文本搜索、图片搜索）
- ✅ 文档查询接口
- ✅ 前端界面（HTML + JavaScript）
- ✅ 租户隔离（tenant_id过滤）

### 6.6 索引结构（店匠通用）
- ✅ SPU级别索引结构
- ✅ 多语言字段支持（中英文）
- ✅ 嵌套字段（skus, specifications, image_embedding）
- ✅ 规格字段（specifications）支持过滤和分面
- ✅ 扁平化字段（价格、库存等）用于过滤和排序
- ✅ 统一索引（search_products）
- ✅ 租户隔离（tenant_id）
- ✅ 硬编码映射（mappings/search_products.json）
- ✅ 硬编码查询配置（search/query_config.py）

---

## 7. 技术栈

- **后端**：Python 3.6+
- **搜索引擎**：Elasticsearch
- **数据库**：MySQL（Shoplazza）
- **向量模型**：BGE-M3（文本）、CN-CLIP（图片）
- **翻译服务**：DeepL API
- **API 框架**：FastAPI
- **前端**：HTML + JavaScript

---

## 8. API响应格式

### 8.1 外部友好格式

API返回格式不包含ES内部字段（`_id`, `_score`, `_source`），使用外部友好的格式：

**响应结构**：
```json
{
  "results": [
    {
      "spu_id": "123",
      "title": "蓝牙耳机",
      "skus": [
        {
          "sku_id": "456",
          "price": 199.99,
          "sku": "SKU-123-1",
          "stock": 50
        }
      ],
      "relevance_score": 0.95
    }
  ],
  "total": 10,
  "facets": [...],
  "suggestions": [],
  "related_searches": []
}
```

**主要变化**：
- 结构化结果（`SpuResult`和`SkuResult`）
- 嵌套skus数组
- 无ES内部字段

### 8.2 租户隔离

所有API请求必须提供`tenant_id`：
- 请求头：`X-Tenant-ID: 1`
- 或查询参数：`?tenant_id=1`

搜索时自动添加`tenant_id`过滤，确保数据隔离。

### 8.3 数据接口约定

**统一的数据约定格式**：所有API接口使用 Pydantic 模型进行数据验证和序列化。

#### 8.3.1 数据流模式

系统采用统一的数据流模式，确保数据在各层之间的一致性：

**数据流转路径**：
```
API Request (JSON)
    ↓
Pydantic 验证 → 结构化模型（RangeFilter, FacetConfig 等）
    ↓
Searcher（透传）
    ↓
ES Query Builder → model_dump() 转换为字典
    ↓
ES Query (字典)
    ↓
Elasticsearch
```

#### 8.3.2 Facets 配置数据流

**输入格式**：`List[FacetConfig]`

**配置对象列表**：所有分面配置必须使用 FacetConfig 对象
```json
[
  {
    "field": "category1_name",
    "size": 15,
    "type": "terms"
  },
  {
    "field": "specifications.color",
    "size": 20,
    "type": "terms"
  },
  {
    "field": "min_price",
    "type": "range",
    "ranges": [
      {"key": "0-50", "to": 50},
      {"key": "50-100", "from": 50, "to": 100}
    ]
  }
]
```

**Specifications 分面支持**：
- 所有规格名称：`field: "specifications"` - 返回所有 name 及其 value 列表
- 指定规格名称：`field: "specifications.color"` - 只返回指定 name 的 value 列表

**数据流**：
1. API 层：接收 `List[FacetConfig]`，Pydantic 验证参数
2. Searcher 层：透传 FacetConfig 对象列表
3. ES Query Builder：解析 FacetConfig 对象
   - 检测 `"specifications"` 或 `"specifications.{name}"` 格式
   - 构建对应的嵌套聚合查询或普通聚合查询
4. 输出：转换为 ES 聚合查询（包括 specifications 嵌套聚合）
5. Result Formatter：格式化 ES 聚合结果，处理 specifications 嵌套结构

#### 8.3.3 Range Filters 数据流

**输入格式**：`Dict[str, RangeFilter]`

**RangeFilter 模型**：
```python
class RangeFilter(BaseModel):
    gte: Optional[Union[float, str]]  # 大于等于
    gt: Optional[Union[float, str]]   # 大于
    lte: Optional[Union[float, str]]  # 小于等于
    lt: Optional[Union[float, str]]   # 小于
```

**示例**：
```json
{
  "min_price": {"gte": 50, "lte": 200},
  "create_time": {"gte": "2023-01-01T00:00:00Z"}
}
```

**数据流**：
1. API 层：接收 `Dict[str, RangeFilter]`，Pydantic 自动验证
2. Searcher 层：透传 `Dict[str, RangeFilter]`
3. ES Query Builder：调用 `range_filter.model_dump()` 转换为字典
4. 输出：ES range 查询（支持数值和日期）

**特性**：
- 自动验证：确保至少指定一个边界值（gte, gt, lte, lt）
- 类型支持：支持数值（float）和日期时间字符串（ISO 格式）
- 统一约定：所有范围过滤都使用 RangeFilter 模型

#### 8.3.3.1 Specifications 过滤数据流

**输入格式**：`Dict[str, Union[Dict[str, str], List[Dict[str, str]]]]`

**单个规格过滤**：
```json
{
  "specifications": {
    "name": "color",
    "value": "white"
  }
}
```

**多个规格过滤（按维度分组）**：
```json
{
  "specifications": [
    {"name": "color", "value": "white"},
    {"name": "size", "value": "256GB"}
  ]
}
```

**数据流**：
1. API 层：接收 `filters` 字典，检测 `specifications` 键
2. Searcher 层：透传 `filters` 字典
3. ES Query Builder：检测 `specifications` 键，构建ES `nested` 查询
   - 单个规格：构建单个 `nested` 查询
   - 多个规格：按 name 维度分组，相同维度内使用 `should` 组合（OR逻辑），不同维度之间使用 `must` 组合（AND逻辑）
4. 输出：ES nested 查询（`nested.path=specifications` + `bool.must=[term(name), term(value)]`）

#### 8.3.4 响应 Facets 数据流

**输出格式**：`List[FacetResult]`

**FacetResult 模型**：
```python
class FacetResult(BaseModel):
    field: str                    # 字段名
    label: str                    # 显示标签
    type: Literal["terms", "range"]  # 分面类型
    values: List[FacetValue]      # 分面值列表
    total_count: Optional[int]    # 总文档数
```

**数据流**：
1. ES Response：返回聚合结果（字典格式，包括specifications嵌套聚合）
2. Result Formatter：格式化ES聚合结果
   - 处理普通terms聚合
   - 处理range聚合
   - **处理specifications嵌套聚合**：
     - 所有规格名称：解析 `by_name` 聚合结构
     - 指定规格名称：解析 `filter_by_name` 聚合结构
3. Searcher 层：构建 `List[FacetResult]` 对象
4. API 层：直接返回 `List[FacetResult]`（Pydantic 自动序列化为 JSON）

**优势**：
- 类型安全：使用 Pydantic 模型确保数据结构一致性
- 自动序列化：模型自动转换为 JSON，无需手动处理
- 统一约定：所有响应都使用标准化的 Pydantic 模型

#### 8.3.5 SKU筛选数据流

**输入格式**：`Optional[str]`

**支持的维度值**：
- `option1`, `option2`, `option3`: 直接使用选项字段
- 规格名称（如 `color`, `size`）: 通过 `option1_name`、`option2_name`、`option3_name` 匹配

**示例**：
```json
{
  "query": "手机",
  "sku_filter_dimension": "color"
}
```

**数据流**：
1. API 层：接收 `sku_filter_dimension` 字符串参数
2. Searcher 层：透传到 Result Formatter
3. Result Formatter：在格式化结果时，按指定维度对SKU进行分组
   - 如果维度是 `option1/2/3`，直接使用对应的 `option1_value/2/3` 字段
   - 如果维度是规格名称，通过 `option1_name/2/3` 匹配找到对应的 `option1_value/2/3`
   - 每个分组选择第一个SKU返回
4. 输出：过滤后的SKU列表（每个维度值一个SKU）

**工作原理**：
1. 系统从ES返回所有SKU（不改变ES查询，保持性能）
2. 在结果格式化阶段，按指定维度对SKU进行分组
3. 每个分组选择第一个SKU返回
4. 如果维度不匹配或未找到，返回所有SKU（不进行过滤）

**性能说明**：
- ✅ **推荐方案**: 在应用层过滤（当前实现）
  - ES查询简单，不需要nested查询和join
  - 只对返回的结果（通常10-20个SPU）进行过滤，数据量小
  - 实现简单，性能开销小
- ❌ **不推荐**: 在ES查询时过滤
  - 需要nested查询和join，性能开销大
  - 实现复杂
  - 只对返回的结果需要过滤，不需要在ES层面过滤

#### 8.3.6 统一约定的好处

1. **类型安全**：使用 Pydantic 模型提供运行时类型检查和验证
2. **代码一致性**：所有层使用相同的数据模型，减少转换错误
3. **自动文档**：FastAPI 自动生成 API 文档（基于 Pydantic 模型）
4. **易于维护**：修改数据结构只需更新模型定义
5. **数据验证**：自动验证输入数据，减少错误处理代码

**实现模块**：
- `api/models.py` - 所有 Pydantic 模型定义（包括 `SearchRequest`, `FacetConfig`, `RangeFilter` 等）
- `api/result_formatter.py` - 结果格式化器（ES 响应 → Pydantic 模型，包括specifications分面处理和SKU筛选）
- `search/es_query_builder.py` - ES 查询构建器（Pydantic 模型 → ES 查询，包括specifications过滤和分面）

## 9. 索引结构文件

**硬编码映射**（店匠通用）：`mappings/search_products.json`

**查询配置**（硬编码）：`search/query_config.py`

---
