ifixkart-backend/app/services/migration_engine/column_mapper.py

86 lines
4.5 KiB
Python

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", "product_description"],
"seo_title": ["seo_title", "seo title", "meta_title", "page_title", "seo_name"],
"seo_description": ["seo_description", "seo description", "meta_description", "seo_desc"],
"meta_keywords": ["meta_keywords", "meta keywords", "seo_keywords", "keywords"],
"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"],
"warranty_type": ["warranty_type", "warranty type", "warranty_mode", "guarantee_type"],
"warranty_summary": ["warranty_summary", "warranty summary", "warranty_details", "warranty_desc", "warranty_info", "guarantee_details"]
}
@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