package feeds import "strings" // MappingSuggestion is a suggested source→target pair from schema extraction. type MappingSuggestion struct { Source string `json:"source"` Target string `json:"target"` Confidence string `json:"confidence"` Score float64 `json:"score"` } var sourceAliases = map[string][]string{ "gtin": {"gtin"}, "ean": {"gtin"}, "upc": {"gtin"}, "barcode": {"gtin"}, "title": {"title"}, "name": {"title"}, "productname": {"title"}, "brand": {"brand"}, "manufacturer": {"brand"}, "description": {"description"}, "desc": {"description"}, "price": {"price"}, "regularprice": {"price"}, "saleprice": {"sale_price", "price"}, "currency": {"currency"}, "image": {"image_url", "main_image", "image"}, "imageurl": {"image_url", "main_image", "image"}, "imagelink": {"image_url", "main_image", "image"}, "mainimage": {"image_url", "main_image", "image"}, "mainimageurl": {"image_url", "main_image", "image"}, "link": {"product_url"}, "url": {"product_url"}, "producturl": {"product_url"}, "sku": {"sku"}, "mpn": {"mpn"}, "category": {"category"}, "availability": {"availability"}, "stockstatus": {"availability", "stock_status"}, "stock": {"stock"}, "quantity": {"stock"}, "qty": {"stock"}, "color": {"color"}, "size": {"size"}, "material": {"material"}, "weight": {"weight"}, "netmass": {"weight"}, "purchaseprice": {"purchase_price", "price"}, "buyprice": {"purchase_price"}, "cost": {"purchase_price"}, "costprice": {"purchase_price"}, "officiallink": {"official_link"}, "warranty": {"warranty"}, "garancija": {"warranty"}, "service": {"service"}, "servis": {"service"}, "productmodel": {"product_model"}, "model": {"product_model"}, "modelnumber": {"product_model"}, "eprelid": {"eprel_id"}, "eprel": {"eprel_id"}, "specifications": {"specs", "specifications"}, "specs": {"specs", "specifications"}, "specification": {"specs", "specifications"}, "techspecs": {"specs", "specifications"}, } func normalizeSuggestKey(raw string) string { s := strings.ToLower(strings.TrimSpace(raw)) var b strings.Builder for _, r := range s { if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { b.WriteRune(r) } } return b.String() } func leafSuggestKey(path string) string { leaf := path if i := strings.LastIndex(path, "/"); i >= 0 { leaf = path[i+1:] } if i := strings.LastIndex(leaf, ":"); i >= 0 { leaf = leaf[i+1:] } return normalizeSuggestKey(leaf) } // SuggestTarget returns the best canonical target key for a single source name/path, // or "" when no alias matches. Used during schema extract to annotate fields. func SuggestTarget(source string) string { keys := []string{normalizeSuggestKey(source), leafSuggestKey(source)} for _, key := range keys { if key == "" { continue } if aliases, ok := sourceAliases[key]; ok && len(aliases) > 0 { return aliases[0] } // Exact identity for known-looking keys already in alias map as targets. for _, aliases := range sourceAliases { for _, a := range aliases { if a == key { return a } } } } return "" } // SuggestMappings fuzzy-matches schema fields onto enabled target keys (1:1). func SuggestMappings(schema []SchemaField, enabledTargets []string) []MappingSuggestion { enabled := make(map[string]struct{}, len(enabledTargets)) for _, t := range enabledTargets { t = strings.TrimSpace(t) if t != "" { enabled[t] = struct{}{} } } if len(schema) == 0 || len(enabled) == 0 { return nil } used := map[string]struct{}{} type scored struct { MappingSuggestion order int } var candidates []scored for i, f := range schema { keys := []string{ normalizeSuggestKey(f.FieldName), leafSuggestKey(f.Path), normalizeSuggestKey(f.Path), } var hit *MappingSuggestion for _, key := range keys { if key == "" { continue } if _, ok := enabled[key]; ok { if _, taken := used[key]; !taken { hit = &MappingSuggestion{Source: f.Path, Target: key, Confidence: "exact", Score: 1} break } } if aliases, ok := sourceAliases[key]; ok { for _, a := range aliases { if _, ok := enabled[a]; !ok { continue } if _, taken := used[a]; taken { continue } hit = &MappingSuggestion{Source: f.Path, Target: a, Confidence: "alias", Score: 0.95} break } if hit != nil { break } } } if hit != nil { candidates = append(candidates, scored{MappingSuggestion: *hit, order: i}) } } // Prefer higher score, then earlier schema order. for i := 0; i < len(candidates); i++ { for j := i + 1; j < len(candidates); j++ { if candidates[j].Score > candidates[i].Score || (candidates[j].Score == candidates[i].Score && candidates[j].order < candidates[i].order) { candidates[i], candidates[j] = candidates[j], candidates[i] } } } out := make([]MappingSuggestion, 0, len(candidates)) for _, c := range candidates { if _, taken := used[c.Target]; taken { continue } used[c.Target] = struct{}{} out = append(out, c.MappingSuggestion) } return out }