""" Result formatter for converting ES internal format to external-friendly format. """ from typing import List, Dict, Any, Optional from .models import ProductResult, VariantResult, FacetResult, FacetValue class ResultFormatter: """Formats ES search results to external-friendly format.""" @staticmethod def format_search_results( es_hits: List[Dict[str, Any]], max_score: float = 1.0 ) -> List[ProductResult]: """ Convert ES hits to ProductResult list. Args: es_hits: List of ES hit dictionaries (with _id, _score, _source) max_score: Maximum score (unused, kept for compatibility) Returns: List of ProductResult objects """ results = [] for hit in es_hits: source = hit.get('_source', {}) score = hit.get('_score', 0.0) # Use original ES score directly (no normalization) relevance_score = score # Extract variants variants = [] variants_data = source.get('variants', []) if isinstance(variants_data, list): for variant_data in variants_data: variant = VariantResult( variant_id=str(variant_data.get('variant_id', '')), title=variant_data.get('title'), price=variant_data.get('price'), compare_at_price=variant_data.get('compare_at_price'), sku=variant_data.get('sku'), stock=variant_data.get('stock', 0), options=variant_data.get('options') ) variants.append(variant) # Determine in_stock (any variant has stock > 0) in_stock = any(v.stock > 0 for v in variants) if variants else True # Build ProductResult product = ProductResult( product_id=str(source.get('product_id', '')), title=source.get('title'), handle=source.get('handle'), description=source.get('description'), vendor=source.get('vendor'), product_type=source.get('product_type'), tags=source.get('tags'), price=source.get('min_price'), compare_at_price=source.get('compare_at_price'), currency="USD", # Default currency image_url=source.get('image_url'), in_stock=in_stock, variants=variants, relevance_score=relevance_score ) results.append(product) return results @staticmethod def format_facets( es_aggregations: Dict[str, Any], facet_configs: Optional[List[Any]] = None ) -> List[FacetResult]: """ Format ES aggregations to FacetResult list. Args: es_aggregations: ES aggregations response facet_configs: Facet configurations (optional) Returns: List of FacetResult objects """ facets = [] for field_name, agg_data in es_aggregations.items(): # Handle terms aggregation if 'buckets' in agg_data: values = [] for bucket in agg_data['buckets']: value = FacetValue( value=bucket['key'], label=bucket.get('key_as_string', str(bucket['key'])), count=bucket['doc_count'], selected=False ) values.append(value) facet = FacetResult( field=field_name, label=field_name, # Can be enhanced with field labels type="terms", values=values, total_count=agg_data.get('sum_other_doc_count', 0) + len(values) ) facets.append(facet) # Handle range aggregation elif 'buckets' in agg_data and any('from' in b or 'to' in b for b in agg_data['buckets']): values = [] for bucket in agg_data['buckets']: range_key = bucket.get('key', '') value = FacetValue( value=range_key, label=range_key, count=bucket['doc_count'], selected=False ) values.append(value) facet = FacetResult( field=field_name, label=field_name, type="range", values=values ) facets.append(facet) return facets @staticmethod def generate_suggestions( query: str, results: List[ProductResult] ) -> List[str]: """ Generate search suggestions. Args: query: Original search query results: Search results Returns: List of suggestion strings (currently returns empty list) """ # TODO: Implement suggestion generation logic return [] @staticmethod def generate_related_searches( query: str, results: List[ProductResult] ) -> List[str]: """ Generate related searches. Args: query: Original search query results: Search results Returns: List of related search strings (currently returns empty list) """ # TODO: Implement related search generation logic return []