fixes
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
package processing
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// categoryDisplayLabel returns the human category name for meta / synthesize /
|
||||
// {{category}}. Prefers CategoryName; never falls back to a digits-only unique_id.
|
||||
func categoryDisplayLabel(result StepResult) string {
|
||||
if n := strings.TrimSpace(result.CategoryName); n != "" {
|
||||
return n
|
||||
}
|
||||
cat := strings.TrimSpace(result.Category)
|
||||
if cat == "" || isDigitsOnlyCategoryID(cat) {
|
||||
return ""
|
||||
}
|
||||
return cat
|
||||
}
|
||||
|
||||
// resolveCategoryDisplayName looks up unique_id → name; returns "" when unknown
|
||||
// so callers do not treat "50" as a human label.
|
||||
func resolveCategoryDisplayName(uid string, namesByUID map[string]string) string {
|
||||
uid = strings.TrimSpace(uid)
|
||||
if uid == "" || len(namesByUID) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(namesByUID[uid])
|
||||
}
|
||||
|
||||
func syncCategoryName(out *StepResult, namesByUID map[string]string) {
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
if n := resolveCategoryDisplayName(out.Category, namesByUID); n != "" {
|
||||
out.CategoryName = n
|
||||
}
|
||||
}
|
||||
|
||||
func isDigitsOnlyCategoryID(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if !unicode.IsDigit(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// categoryUniqueIDFromAny extracts a company category unique_id from feed/mapped
|
||||
// shapes: plain string/number, nested {unique_id|category_unique_id|#text|…}, or
|
||||
// a one-element array of those. Returns "" for empty/"none"/unusable values.
|
||||
func categoryUniqueIDFromAny(v any) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
return normalizeCategoryUniqueID(t)
|
||||
case float64:
|
||||
if t == float64(int64(t)) {
|
||||
return normalizeCategoryUniqueID(strconv.FormatInt(int64(t), 10))
|
||||
}
|
||||
return normalizeCategoryUniqueID(strconv.FormatFloat(t, 'f', -1, 64))
|
||||
case float32:
|
||||
return categoryUniqueIDFromAny(float64(t))
|
||||
case int:
|
||||
return normalizeCategoryUniqueID(strconv.Itoa(t))
|
||||
case int64:
|
||||
return normalizeCategoryUniqueID(strconv.FormatInt(t, 10))
|
||||
case int32:
|
||||
return normalizeCategoryUniqueID(strconv.FormatInt(int64(t), 10))
|
||||
case jsonNumberStringer:
|
||||
return normalizeCategoryUniqueID(t.String())
|
||||
case map[string]any:
|
||||
for _, k := range []string{
|
||||
"unique_id", "category_unique_id", "categoryUniqueId",
|
||||
"#text", "text", "id", "value", "code",
|
||||
} {
|
||||
if s := categoryUniqueIDFromAny(t[k]); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
case []any:
|
||||
for _, item := range t {
|
||||
if s := categoryUniqueIDFromAny(item); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// jsonNumberStringer matches encoding/json.Number without importing encoding/json here.
|
||||
type jsonNumberStringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func normalizeCategoryUniqueID(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "<nil>" || strings.EqualFold(s, "none") {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// categoryUniqueIDFromMaps returns the first usable category unique_id from maps
|
||||
// (normalized/mapped/raw). Prefer canonical keys; mapped should win by call order.
|
||||
func categoryUniqueIDFromMaps(maps ...map[string]any) string {
|
||||
for _, m := range maps {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
for _, k := range []string{
|
||||
"category", "category_unique_id", "categoryUniqueId",
|
||||
"Category", "product_type", "productType",
|
||||
} {
|
||||
if s := categoryUniqueIDFromAny(m[k]); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// coerceMappedCategoryUniqueID writes a string unique_id onto m["category"] when
|
||||
// category (or category_unique_id) can be resolved — so RunSteps string paths stay deterministic.
|
||||
func coerceMappedCategoryUniqueID(m map[string]any) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
cat := categoryUniqueIDFromMaps(m)
|
||||
if cat == "" {
|
||||
return ""
|
||||
}
|
||||
m["category"] = cat
|
||||
if _, ok := m["category_unique_id"]; !ok {
|
||||
m["category_unique_id"] = cat
|
||||
}
|
||||
return cat
|
||||
}
|
||||
|
||||
// applyCategoryFromMapped sets StepResult.Category from feed/mapped unique_id when present.
|
||||
// Does not clear an existing Category when maps lack a usable value (caller may preserve prior).
|
||||
func applyCategoryFromMapped(out *StepResult, maps ...map[string]any) {
|
||||
if out == nil {
|
||||
return
|
||||
}
|
||||
cat := categoryUniqueIDFromMaps(maps...)
|
||||
if cat == "" {
|
||||
return
|
||||
}
|
||||
out.Category = SanitizeText(cat)
|
||||
if out.FieldSources == nil {
|
||||
out.FieldSources = map[string]any{}
|
||||
}
|
||||
out.FieldSources["category"] = "mapped"
|
||||
}
|
||||
|
||||
// filterCategoryIfInvalid clears Category when the company has a known unique_id set
|
||||
// and the value is not in that set. Empty valid set means "skip validation" (tests / no taxonomy).
|
||||
func filterCategoryIfInvalid(out *StepResult, valid map[string]struct{}) {
|
||||
if out == nil || len(valid) == 0 {
|
||||
return
|
||||
}
|
||||
cat := strings.TrimSpace(out.Category)
|
||||
if cat == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := valid[cat]; ok {
|
||||
return
|
||||
}
|
||||
out.Category = ""
|
||||
if out.FieldSources != nil {
|
||||
delete(out.FieldSources, "category")
|
||||
}
|
||||
out.Notes = append(out.Notes, "category: ignored (unknown unique_id for company)")
|
||||
}
|
||||
Reference in New Issue
Block a user