from typing import Dict, List, Any class ColumnMapper: """ Maps dynamic source column names (e.g., 'Item Code', 'Selling Price') to standard system target fields (e.g., 'sku', 'price'). Provides heuristic / AI auto-mapping suggestions. """ STANDARD_FIELDS = { "sku": ["sku", "item_code", "item code", "product_code", "part_number", "model_number"], "name": ["product_name", "name", "title", "product name", "item_name"], "parent_name": ["parent_name", "base_product", "product_group", "parent_product", "model_series", "parent_title"], "price": ["price", "selling_price", "mrp", "unit_price", "rate"], "cost_price": ["cost_price", "purchase_price", "buying_price", "cost price"], "stock": ["stock", "quantity", "qty", "inventory", "stock_count", "balance"], "brand": ["brand", "brand_name", "manufacturer", "make"], "category": ["category", "category_name", "department", "group"], "parent_category": ["parent_category", "parent_category_name", "parent category"], "is_parent_feature": ["is_parent_feature", "parent_feature", "is parent feature", "featured_category"], "parent_media_key": ["parent_media_key", "parent_image_key", "parent media key"], "media_key": ["media_key", "media key", "image_key", "folder_key", "media_group", "variant_media_key"], "barcode": ["barcode", "upc", "ean", "isbn"], "description": ["description", "details", "specifications", "summary"], "device_series": ["device_series", "series", "device series", "series_name"], "device_model": ["device_model", "model", "device model", "target_model", "compatibility_model"], "device_type": ["device_type", "device type", "type", "device_category"] } @classmethod def suggest_mappings(cls, headers: List[str]) -> Dict[str, str]: """ Suggests mapping of source header -> standard target field. """ suggestions = {} for header in headers: normalized = header.lower().strip().replace("-", "_") matched = False for target_field, aliases in cls.STANDARD_FIELDS.items(): if normalized == target_field or normalized in aliases: suggestions[header] = target_field matched = True break if not matched: # Fuzzy keyword match fallback for target_field, aliases in cls.STANDARD_FIELDS.items(): for alias in aliases: if alias in normalized: # Avoid matching 'rate' in non-price contexts like 'refresh_rate' or 'heart_rate' if alias == 'rate' and ('refresh' in normalized or 'heart' in normalized): continue suggestions[header] = target_field matched = True break if matched: break if not matched: suggestions[header] = header # Keep as custom dynamic attribute return suggestions @classmethod def apply_mapping(cls, raw_row: Dict[str, Any], column_maps: Dict[str, str]) -> Dict[str, Any]: """ Transforms a raw row using the given column_maps dictionary. Auto-generates mapping suggestions if column_maps is empty or missing. """ if not column_maps or column_maps == {}: column_maps = cls.suggest_mappings(list(raw_row.keys())) mapped_row = {} attributes = {} for source_col, val in raw_row.items(): target_field = column_maps.get(source_col, source_col) if target_field in cls.STANDARD_FIELDS: mapped_row[target_field] = val else: attributes[target_field] = val if attributes: mapped_row["attributes"] = attributes return mapped_row