diff --git a/api/models.py b/api/models.py index 375d2e9..3f403f0 100644 --- a/api/models.py +++ b/api/models.py @@ -146,9 +146,14 @@ class SearchRequest(BaseModel): debug: bool = Field(False, description="是否返回调试信息") # SKU筛选参数 - sku_filter_dimension: Optional[str] = Field( + sku_filter_dimension: Optional[List[str]] = Field( None, - description="子SKU筛选维度(店铺配置)。指定后,每个SPU下的SKU将按该维度分组,每组选择第一个SKU返回。例如:'color'表示按颜色分组,每种颜色选一款。支持的值:'option1'、'option2'、'option3'或specifications中的name(如'color'、'size')" + description=( + "子SKU筛选维度(店铺配置),为字符串列表。" + "指定后,每个SPU下的SKU将按这些维度的组合进行分组,每个维度组合只保留一个SKU返回。" + "例如:['color'] 表示按颜色分组,每种颜色选一款;['color', 'size'] 表示按颜色+尺码组合分组。" + "支持的值:'option1'、'option2'、'option3' 或选项名称(如 'color'、'size',将通过 option1_name/2_name/3_name 匹配)。" + ) ) # 个性化参数(预留) diff --git a/api/result_formatter.py b/api/result_formatter.py index 936bb0a..6fdf947 100644 --- a/api/result_formatter.py +++ b/api/result_formatter.py @@ -14,7 +14,7 @@ class ResultFormatter: es_hits: List[Dict[str, Any]], max_score: float = 1.0, language: str = "zh", - sku_filter_dimension: Optional[str] = None + sku_filter_dimension: Optional[List[str]] = None ) -> List[SpuResult]: """ Convert ES hits to SpuResult list. @@ -85,10 +85,10 @@ class ResultFormatter: ) skus.append(sku) - # Apply SKU filtering if dimension is specified + # Apply SKU filtering if dimension list is specified if sku_filter_dimension and skus: - skus = ResultFormatter._filter_skus_by_dimension( - skus, + skus = ResultFormatter._filter_skus_by_dimensions( + skus, sku_filter_dimension, source.get('option1_name'), source.get('option2_name'), @@ -138,22 +138,22 @@ class ResultFormatter: return results @staticmethod - def _filter_skus_by_dimension( + def _filter_skus_by_dimensions( skus: List[SkuResult], - dimension: str, + dimensions: List[str], option1_name: Optional[str] = None, option2_name: Optional[str] = None, option3_name: Optional[str] = None, specifications: Optional[List[Dict[str, Any]]] = None ) -> List[SkuResult]: """ - Filter SKUs by dimension, keeping only one SKU per dimension value. + Filter SKUs by one or more dimensions, keeping only one SKU per dimension value combination. Args: skus: List of SKU results to filter - dimension: Filter dimension, can be: + dimensions: Filter dimensions, each dimension can be: - 'option1', 'option2', 'option3': Direct option field - - A specification name (e.g., 'color', 'size'): Match by option name + - A specification/option name (e.g., 'color', 'size'): Match by option name option1_name: Name of option1 (e.g., 'color') option2_name: Name of option2 (e.g., 'size') option3_name: Name of option3 @@ -162,54 +162,59 @@ class ResultFormatter: Returns: Filtered list of SKUs (one per dimension value) """ - if not skus: + if not skus or not dimensions: return skus - - # Determine which field to use for filtering - filter_field = None - - # Direct option field (option1, option2, option3) - if dimension.lower() == 'option1': - filter_field = 'option1_value' - elif dimension.lower() == 'option2': - filter_field = 'option2_value' - elif dimension.lower() == 'option3': - filter_field = 'option3_value' - else: - # Try to match by option name - dimension_lower = dimension.lower() - if option1_name and option1_name.lower() == dimension_lower: - filter_field = 'option1_value' - elif option2_name and option2_name.lower() == dimension_lower: - filter_field = 'option2_value' - elif option3_name and option3_name.lower() == dimension_lower: - filter_field = 'option3_value' - - # If no matching field found, return all SKUs (no filtering) - if not filter_field: - return skus - - # Group SKUs by dimension value and select first one from each group - dimension_groups: Dict[str, SkuResult] = {} - + + # Resolve each dimension to an underlying SKU field (option1_value / option2_value / option3_value) + filter_fields: List[str] = [] + + for dim in dimensions: + if not dim: + continue + dim_lower = dim.lower() + + field_name: Optional[str] = None + # Direct option field (option1, option2, option3) + if dim_lower == 'option1': + field_name = 'option1_value' + elif dim_lower == 'option2': + field_name = 'option2_value' + elif dim_lower == 'option3': + field_name = 'option3_value' + else: + # Try to match by option name + if option1_name and option1_name.lower() == dim_lower: + field_name = 'option1_value' + elif option2_name and option2_name.lower() == dim_lower: + field_name = 'option2_value' + elif option3_name and option3_name.lower() == dim_lower: + field_name = 'option3_value' + + if field_name and field_name not in filter_fields: + filter_fields.append(field_name) + + # If no matching field found for all dimensions, do not return any child SKUs + if not filter_fields: + return [] + + # Group SKUs by dimension value combination and select first one from each group + dimension_groups: Dict[tuple, SkuResult] = {} + for sku in skus: - # Get dimension value from the determined field - dimension_value = None - if filter_field == 'option1_value': - dimension_value = sku.option1_value - elif filter_field == 'option2_value': - dimension_value = sku.option2_value - elif filter_field == 'option3_value': - dimension_value = sku.option3_value - - # Use empty string as key for None values - key = str(dimension_value) if dimension_value is not None else '' - - # Keep first SKU for each dimension value + # Build key as combination of all dimension values + key_values: List[str] = [] + for field in filter_fields: + dimension_value = getattr(sku, field, None) + # Use empty string as key part for None values + key_values.append(str(dimension_value) if dimension_value is not None else '') + + key = tuple(key_values) + + # Keep first SKU for each dimension combination if key not in dimension_groups: dimension_groups[key] = sku - - # Return filtered SKUs (one per dimension value) + + # Return filtered SKUs (one per dimension combination) return list(dimension_groups.values()) @staticmethod diff --git a/docs/搜索API对接指南.md b/docs/搜索API对接指南.md index f0ddca5..545a953 100644 --- a/docs/搜索API对接指南.md +++ b/docs/搜索API对接指南.md @@ -104,7 +104,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ "sort_by": "string", "sort_order": "desc", "min_score": 0.0, - "sku_filter_dimension": "string", + "sku_filter_dimension": ["string"], "debug": false, "user_id": "string", "session_id": "string" @@ -127,7 +127,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ | `sort_by` | string | N | null | 排序字段名(如 `min_price`, `max_price`) | | `sort_order` | string | N | "desc" | 排序方向:`asc`(升序)或 `desc`(降序) | | `min_score` | float | N | null | 最小相关性分数阈值 | -| `sku_filter_dimension` | string | N | null | 子SKU筛选维度(店铺配置)。指定后,每个SPU下的SKU将按该维度分组,每组选择第一个SKU返回。支持的值:`option1`、`option2`、`option3` 或 specifications 中的 name(如 `color`、`size`)。详见下文说明 | +| `sku_filter_dimension` | array[string] | N | null | 子SKU筛选维度列表(店铺配置)。指定后,每个SPU下的SKU将按这些维度的组合进行分组,每个组合只返回第一个SKU。支持的值:`option1`、`option2`、`option3` 或选项名称(如 `color`、`size`)。详见下文说明 | | `debug` | boolean | N | false | 是否返回调试信息 | | `user_id` | string | N | null | 用户ID(用于个性化,预留) | | `session_id` | string | N | null | 会话ID(用于分析,预留) | @@ -349,7 +349,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ ### SKU筛选维度 (sku_filter_dimension) **功能说明**: -`sku_filter_dimension` 用于控制每个SPU下返回的SKU数量。当指定此参数后,系统会按指定维度对SKU进行分组,每个分组只返回第一个SKU(从简实现,选择该维度下的第一款)。 +`sku_filter_dimension` 用于控制每个SPU下返回的SKU数量,为字符串列表。当指定此参数后,系统会按指定维度**组合**对SKU进行分组,每个维度组合只返回第一个SKU(从简实现,选择该组合下的第一款)。 **使用场景**: - 店铺配置了SKU筛选维度(如 `color`),希望每个SPU下每种颜色只显示一个SKU @@ -360,8 +360,8 @@ curl -X POST "http://120.76.41.98:6002/search/" \ 1. **直接选项字段**: `option1`、`option2`、`option3` - 直接使用对应的 `option1_value`、`option2_value`、`option3_value` 字段进行分组 -2. **规格名称**: 通过 `option1_name`、`option2_name`、`option3_name` 匹配 - - 例如:如果 `option1_name` 为 `"color"`,则可以使用 `sku_filter_dimension: "color"` 来按颜色分组 +2. **规格/选项名称**: 通过 `option1_name`、`option2_name`、`option3_name` 匹配 + - 例如:如果 `option1_name` 为 `"color"`,则可以使用 `sku_filter_dimension: ["color"]` 来按颜色分组 **示例**: @@ -369,7 +369,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ ```json { "query": "芭比娃娃", - "sku_filter_dimension": "color" + "sku_filter_dimension": ["color"] } ``` @@ -377,7 +377,7 @@ curl -X POST "http://120.76.41.98:6002/search/" \ ```json { "query": "芭比娃娃", - "sku_filter_dimension": "option1" + "sku_filter_dimension": ["option1"] } ``` @@ -385,7 +385,15 @@ curl -X POST "http://120.76.41.98:6002/search/" \ ```json { "query": "芭比娃娃", - "sku_filter_dimension": "option2" + "sku_filter_dimension": ["option2"] +} +``` + +**按颜色 + 尺寸组合筛选(假设 option1_name = "color", option2_name = "size")**: +```json +{ + "query": "芭比娃娃", + "sku_filter_dimension": ["color", "size"] } ``` diff --git a/frontend/index.html b/frontend/index.html index 5d579f7..ac0e3c7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -135,6 +135,6 @@
SearchEngine © 2025 | API: Loading...
- +