fixes
This commit is contained in:
@@ -13,6 +13,8 @@ var (
|
||||
specsHTMLTagRe = regexp.MustCompile(`(?is)<[^>]+>`)
|
||||
csvLikeRe = regexp.MustCompile(`(?m)^\s*([^:=\n\r]{1,120})\s*[:=]\s*(.+?)\s*$`)
|
||||
bulletLineRe = regexp.MustCompile(`(?m)^\s*[-•*]\s*([^:=\n\r]{1,120})\s*[:=]\s*(.+?)\s*$`)
|
||||
// GTIN/EAN/UPC masquerading as product_model (feed mapping mistakes).
|
||||
barcodeLikeModelRe = regexp.MustCompile(`^\d{8,14}$`)
|
||||
)
|
||||
|
||||
// Caps for locale/spec parsing — unbounded FindAll on huge CDATA can OOM the worker.
|
||||
@@ -173,6 +175,73 @@ func SanitizeV1ProcessAttributesAllowed(attrs map[string]any, allowed map[string
|
||||
return FilterAttributesByAllowed(sanitizeProductAttributes(attrs, true), allowed)
|
||||
}
|
||||
|
||||
// AttrsForEnhance prepares attributes for AI enhance prompts / hashes.
|
||||
// Always drops reserved and invalid keys. When allowed is non-nil (including empty),
|
||||
// keeps only coreCharacteristicAttrKeys plus the allowlist (category_attributes).
|
||||
// When allowed is nil, returns sanitized attrs unchanged (unit-test fallback).
|
||||
func AttrsForEnhance(attrs map[string]any, allowed map[string]struct{}) map[string]any {
|
||||
cleaned := sanitizeProductAttributes(attrs, true)
|
||||
if allowed == nil {
|
||||
return cleaned
|
||||
}
|
||||
cleaned = MapAttrsOntoAllowedKeys(cleaned, allowed)
|
||||
return FilterAttributesByAllowed(cleaned, allowed)
|
||||
}
|
||||
|
||||
// ensureEnergyClassFromEPREL copies eprel_energy_class → energy_class when the
|
||||
// core energy_class key is empty so AttrsForEnhance (which dropEPREL strips
|
||||
// eprel_*) still surfaces the label class to the model.
|
||||
func ensureEnergyClassFromEPREL(attrs map[string]any) {
|
||||
if attrs == nil {
|
||||
return
|
||||
}
|
||||
if v, ok := attrs["energy_class"]; ok {
|
||||
if s := strings.TrimSpace(fmt.Sprint(v)); s != "" && s != "<nil>" {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, k := range []string{"eprel_energy_class", "eprelEnergyClass"} {
|
||||
if v, ok := attrs[k]; ok {
|
||||
s := strings.TrimSpace(fmt.Sprint(v))
|
||||
if s != "" && s != "<nil>" {
|
||||
attrs["energy_class"] = s
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AttrsForPersist prepares attributes for DB storage (attributes / processed_attributes).
|
||||
// Same category allowlist semantics as AttrsForEnhance, but uses SanitizeProductAttributes
|
||||
// (keeps eprel_* for poll extractEPRELFromAttrs) then FilterAttributesByAllowed.
|
||||
// Feed/spec keys are remapped onto category_attributes (formula) keys when possible so
|
||||
// values under near-miss labels (velikost-zaslona → diagonala_zaslona) are kept.
|
||||
// When allowed is nil, returns sanitized attrs unchanged (unit-test fallback).
|
||||
func AttrsForPersist(attrs map[string]any, allowed map[string]struct{}) map[string]any {
|
||||
cleaned := SanitizeProductAttributes(attrs)
|
||||
if allowed == nil {
|
||||
return cleaned
|
||||
}
|
||||
cleaned = MapAttrsOntoAllowedKeys(cleaned, allowed)
|
||||
filtered := FilterAttributesByAllowed(cleaned, allowed)
|
||||
// Preserve eprel_* that SanitizeProductAttributes kept (not on category allowlists).
|
||||
for k, v := range cleaned {
|
||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(k)), "eprel") {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// enhanceAllowedAttrKeys prefers category_attributes for the resolved category;
|
||||
// falls back to AllowedAttrKeys (nil = sanitize-only for unit tests).
|
||||
func enhanceAllowedAttrKeys(in ProductInput, categoryUID string) map[string]struct{} {
|
||||
if in.CategoryAttrKeys != nil {
|
||||
return allowedAttrKeysFromSets(in.CategoryAttrKeys, categoryUID)
|
||||
}
|
||||
return in.AllowedAttrKeys
|
||||
}
|
||||
|
||||
// FilterAttributesByAllowed keeps coreCharacteristicAttrKeys plus keys present in
|
||||
// allowed (after canonicalizeAttrKey). When allowed is nil, returns attrs unchanged.
|
||||
func FilterAttributesByAllowed(attrs map[string]any, allowed map[string]struct{}) map[string]any {
|
||||
@@ -216,6 +285,31 @@ func sanitizeProductAttributes(attrs map[string]any, dropEPREL bool) map[string]
|
||||
if dropEPREL && strings.HasPrefix(strings.ToLower(key), "eprel") {
|
||||
continue
|
||||
}
|
||||
// Keep nested eprel object for storage / extractEPRELFromAttrs (stringify would drop maps).
|
||||
if !dropEPREL && strings.EqualFold(key, "eprel") {
|
||||
if m, ok := v.(map[string]any); ok && len(m) > 0 {
|
||||
nested := make(map[string]any, len(m))
|
||||
for nk, nv := range m {
|
||||
nk = strings.TrimSpace(nk)
|
||||
if nk == "" || nv == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := nv.(string); ok {
|
||||
s = strings.TrimSpace(SanitizeOutput(s))
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
nested[nk] = s
|
||||
continue
|
||||
}
|
||||
nested[nk] = nv
|
||||
}
|
||||
if len(nested) > 0 {
|
||||
out["eprel"] = nested
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
s := stringifySpecValue(v)
|
||||
if s == "" || s == "<nil>" {
|
||||
continue
|
||||
@@ -231,6 +325,10 @@ func sanitizeProductAttributes(attrs map[string]any, dropEPREL bool) map[string]
|
||||
if isDimensionKey(canon) && isZeroishString(s) {
|
||||
continue
|
||||
}
|
||||
// Drop barcode-as-model (e.g. mapped product_model = GTIN).
|
||||
if canon == "product_model" && barcodeLikeModelRe.MatchString(strings.TrimSpace(s)) {
|
||||
continue
|
||||
}
|
||||
if _, exists := out[canon]; exists && (canon != key) {
|
||||
continue
|
||||
}
|
||||
@@ -257,6 +355,8 @@ func canonicalizeAttrKey(k string) string {
|
||||
return "product_model"
|
||||
case "energijskirazred", "energyclass":
|
||||
return "energy_class"
|
||||
case "eprelid", "eprel":
|
||||
return "eprel_id"
|
||||
default:
|
||||
return compact
|
||||
}
|
||||
@@ -459,4 +559,4 @@ func attributeKeyFromLabel(label string) string {
|
||||
return ""
|
||||
}
|
||||
return slug
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user