<!-- 2bec2252-690d-478e-9ce8-bc9073ec23ae c3f51467-b81b-47b7-869f-3da700299e26 -->
# API响应格式优化与SPU索引重构

## 概述

重构搜索系统以实现：

1. 外部接口友好的API响应格式（移除ES内部格式 `_hits`, `_source`, `_score`）
2. SPU维度的索引结构（包含嵌套variants数组）
3. 所有客户共用同一索引（使用tenant_id隔离）
4. 配置简化（完全移除MySQL相关配置，只保留ES搜索配置）
5. 创建base配置（店匠通用配置）和SPU数据导入流程

## 核心设计原则

- **配置只关注ES搜索**：配置文件只包含ES字段定义、查询域、排序规则等搜索相关配置
- **数据灌入流程写死**：数据来源和转换逻辑由Pipeline层（脚本）决定，不在配置中体现
- **代码简洁无冗余**：不做向后兼容，直接删除不需要的代码

## Phase 1: 配置系统重构

### 1.1 创建BASE配置文件

**文件**: [`config/schema/base/config.yaml`](config/schema/base/config.yaml) (NEW)

创建店匠通用配置文件：

- **不包含**：`mysql_config`, `main_table`, `extension_table`, `source_table`, `source_column`
- **固定索引名称**：`search_products`
- **SPU级别字段定义**：
- `tenant_id` (KEYWORD, required)
- `product_id` (对应SPU的id)
- `title_zh`, `title_en` (多语言标题)
- `description_zh`, `description_en` (多语言描述)
- `min_price`, `max_price`, `compare_at_price` (扁平化价格)
- `vendor`, `product_type`, `tags` 等
- **嵌套variants字段定义**：
- `variants` (nested type)
- variants包含：`variant_id`, `title`, `price`, `sku`, `stock`, `options` 等

### 1.2 移除配置中的MySQL相关字段

**文件**: [`config/config_loader.py`](config/config_loader.py)

修改：

- **删除** `TenantConfig` 中的 `mysql_config`, `main_table`, `extension_table` 字段定义
- **删除** `_parse_config` 中解析这些字段的代码
- **删除** `_parse_field_config` 中解析 `source_table`, `source_column` 的代码
- 保持其他配置解析逻辑不变

**文件**: [`config/field_types.py`](config/field_types.py)

修改：

- **删除** `FieldConfig` 中的 `source_table`, `source_column` 字段定义
- 保持其他字段类型定义不变

### 1.3 更新配置验证

**文件**: [`config/config_loader.py`](config/config_loader.py)

修改：

- 添加 `tenant_id` 字段验证（必需字段）
- 移除所有与MySQL/数据源相关的验证逻辑

## Phase 2: 索引结构重构（SPU维度）

### 2.1 更新Mapping生成器

**文件**: [`indexer/mapping_generator.py`](indexer/mapping_generator.py)

修改：

- 在 `_generate_mappings` 中：
- 添加 `tenant_id` 字段（KEYWORD, required）
- 支持嵌套 `variants` 字段（nested type）
- 支持扁平化价格字段（`min_price`, `max_price`, `compare_at_price`）
- 确保所有字段映射正确生成

### 2.2 创建SPU数据转换器

**文件**: [`indexer/spu_transformer.py`](indexer/spu_transformer.py) (NEW)

创建SPU数据转换器（**不依赖配置中的source_table/source_column**）：

- **数据读取**：直接从MySQL读取 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表（写死在代码中）
- **数据聚合**：按 `spu_id` 和 `tenant_id` 关联，将SKU数据聚合为variants数组
- **字段映射**：将SPU和SKU表的字段映射到ES文档字段（写死在代码中）
- **价格计算**：计算 `min_price`, `max_price`, `compare_at_price`
- **向量生成**：支持文本和图片向量生成（复用现有encoder）
- **文档生成**：生成SPU级别的ES文档，包含嵌套variants数组
- **tenant_id注入**：自动注入 `tenant_id` 字段

### 2.3 创建店匠数据导入脚本

**文件**: [`scripts/ingest_shoplazza.py`](scripts/ingest_shoplazza.py) (NEW)

创建店匠数据导入脚本：

- 连接MySQL数据库（连接信息通过参数或环境变量传入）
- 读取 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表
- 使用SPU转换器转换数据
- 批量导入到ES索引 `search_products`
- 支持 `--tenant-id` 参数（必需）
- 支持 `--recreate` 参数（重建索引）
- 支持 `--batch-size` 参数

## Phase 3: API响应格式重构

### 3.1 更新响应模型

**文件**: [`api/models.py`](api/models.py)

修改：

- **创建** `VariantResult` 模型：
- `variant_id`, `title`, `price`, `sku`, `stock`, `options` 等
- **创建** `ProductResult` 模型：
- `product_id`, `title`, `handle`, `description`, `vendor`, `product_type`, `tags`
- `price`, `compare_at_price`, `currency`, `image_url`, `in_stock`
- `variants` (List[VariantResult])
- `relevance_score` (float, 0-1)
- **修改** `SearchResponse` 模型：
- 将 `hits` 改为 `results` (List[ProductResult])
- 添加 `suggestions` (List[str])
- 添加 `related_searches` (List[str])
- 保持 `facets`, `total`, `took_ms` 等字段

### 3.2 创建结果格式化器

**文件**: [`api/result_formatter.py`](api/result_formatter.py) (NEW)

创建结果格式化器：

- **方法** `format_search_results(es_hits, max_score) -> List[ProductResult]`：
- 将ES返回的 `_hits` 格式转换为 `ProductResult` 列表
- 提取SPU级别字段（从 `_source` 中提取）
- 提取嵌套variants数组（从 `_source.variants` 中提取）
- 计算 `relevance_score`（从 `_score` 归一化到0-1，基于max_score）
- 处理缺失字段（提供默认值）
- **方法** `format_facets(es_aggregations, facet_configs) -> List[FacetResult]`：
- 格式化facets结果（保持现有逻辑）
- **方法** `generate_suggestions(query, results) -> List[str]`：
- 生成搜索建议（暂时返回空数组）
- **方法** `generate_related_searches(query, results) -> List[str]`：
- 生成相关搜索（暂时返回空数组）

### 3.3 更新搜索器

**文件**: [`search/searcher.py`](search/searcher.py)

修改：

- **添加** `tenant_id` 参数到 `search` 方法（必需）
- **修改** 查询构建：在filter中添加 `tenant_id` 过滤（必需）
- **修改** 结果处理：
- 使用 `ResultFormatter` 格式化结果
- 返回 `results` 而不是 `hits`
- 移除ES内部格式字段（`_id`, `_score`, `_source`）
- **修改** `SearchResult` 类：
- 将 `hits` 改为 `results` (List[ProductResult])
- 添加 `suggestions` 和 `related_searches` 字段

### 3.4 更新API路由

**文件**: [`api/routes/search.py`](api/routes/search.py)

修改：

- **添加** `tenant_id` 参数获取：
- 优先从请求头 `X-Tenant-ID` 获取
- 其次从查询参数 `tenant_id` 获取
- 如果都没有，返回400错误
- **修改** 搜索调用：
- 传递 `tenant_id` 给 `searcher.search()`
- **修改** 响应构建：
- 使用格式化后的 `results`
- 添加 `suggestions` 和 `related_searches`（暂时返回空数组）

## Phase 4: DataTransformer重构

### 4.1 重构DataTransformer（向后兼容，用于tenant1）

**文件**: [`indexer/data_transformer.py`](indexer/data_transformer.py)

修改：

- **保持** 现有逻辑（用于tenant1等旧配置）
- **添加** 检查：如果 `field.source_column` 不存在，跳过该字段（向后兼容）
- **注意**：base配置不使用DataTransformer，使用SPU转换器

## Phase 5: 测试数据生成

### 5.1 创建测试数据生成脚本

**文件**: [`scripts/generate_test_data.py`](scripts/generate_test_data.py) (NEW)

创建测试数据生成脚本：

- 生成100条SPU测试数据（符合 `shoplazza_product_spu` 表结构）
- 为每个SPU生成1-5个SKU变体（符合 `shoplazza_product_sku` 表结构）
- 包含中文和英文标题
- 包含价格、库存、图片等字段
- 输出为MySQL INSERT语句或CSV文件
- 设置 `tenant_id` 为指定值

### 5.2 创建数据导入脚本

**文件**: [`scripts/import_test_data.py`](scripts/import_test_data.py) (NEW)

创建数据导入脚本：

- 连接MySQL数据库
- 导入测试数据到 `shoplazza_product_spu` 和 `shoplazza_product_sku` 表
- 设置 `tenant_id` 为base客户的ID（如 "base" 或 "1"）
- 验证数据导入结果

## Phase 6: 测试脚本和文档

### 6.1 创建测试脚本

**文件**: [`scripts/test_base.py`](scripts/test_base.py) (NEW)

创建base配置测试脚本：

- 测试数据导入（使用ingest_shoplazza.py）
- 测试搜索API（验证tenant_id过滤）
- 测试多语言搜索
- 测试facets聚合
- **验证响应格式**：
- 确认返回 `results` 而不是 `hits`
- 确认每个result包含 `product_id`, `title`, `variants`, `relevance_score`
- 确认variants数组格式正确
- 确认没有ES内部格式字段（`_id`, `_score`, `_source`）

### 6.2 创建说明文档

**文件**: [`docs/BASE_CONFIG_GUIDE.md`](docs/BASE_CONFIG_GUIDE.md) (NEW)

创建base配置测试指南：

- 数据导入步骤
- 配置说明（强调不包含MySQL配置）
- API测试示例（包含tenant_id参数）
- 响应格式说明
- 常见问题解答

## Phase 7: 更新设计文档

### 7.1 更新设计文档

**文件**: [`设计文档.md`](设计文档.md)

修改：

- 更新索引结构说明（SPU维度，所有客户共用 `search_products` 索引）
- 更新配置说明（完全移除MySQL相关配置，只保留ES搜索配置）
- 更新API响应格式说明（results格式，非hits格式）
- 更新数据导入流程说明（Pipeline层决定数据源，配置不包含数据源信息）
- 添加base配置说明
- 添加tenant_id隔离说明

## 关键修改点总结

1. **配置系统**：

- 完全移除 `mysql_config`, `main_table`, `extension_table`, `source_table`, `source_column`
- 配置只包含ES搜索相关配置
- 代码简洁，无冗余，无向后兼容逻辑

2. **数据转换**：

- SPU转换器直接从MySQL读取，不依赖配置
- 数据映射逻辑写死在代码中
- 支持嵌套variants数组

3. **索引结构**：

- SPU级别索引
- 嵌套variants字段
- tenant_id字段（必需）

4. **API响应**：

- 从 `hits` 改为 `results`
- 从 `_id`, `_score`, `_source` 改为 `product_id`, `relevance_score`, 结构化字段
- 包含 `variants` 数组

5. **租户隔离**：

- 所有客户共用 `search_products` 索引
- 使用 `tenant_id` 字段过滤
- API必须提供 `tenant_id` 参数

### To-dos

- [ ] Create ResponseTransformer to convert ES hits to Shoplazza format (results, facets, suggestions, related_searches)
- [ ] Update API models: add VariantOption, ProductVariant, ProductResult, update SearchResponse with new format
- [ ] Update search route to use ResponseTransformer and return Shoplazza format
- [ ] Create script to generate 100 SPU+SKU test records for tenant2 in Shoplazza tables
- [ ] Create tenant2 config.yaml with search-only fields (no pipeline details)
- [ ] Create or update SPUDataTransformer to join SPU+SKU and create nested variants structure
- [ ] Create tenant2 ingestion script that loads from MySQL and uses SPU transformer
- [ ] Create test script and documentation for tenant2 setup and testing
- [ ] Update design document: SPU-level indexing, unified index, config separation, pipeline decisions