Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user