This commit is contained in:
2026-08-16 23:42:00 +02:00
parent 6e77154228
commit 92f046b542
32 changed files with 1443 additions and 105 deletions
+45 -13
View File
@@ -296,15 +296,24 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
_ = json.Unmarshal(rawJSON, &rawData)
main, more := catalog.ExtractProductImages(mapped, rawData)
plainDesc := ""
// Preserve formula HTML from processed_description; meta synthesis strips tags.
descOut := ""
if descTxt != nil {
plainDesc = v1PlainDescription(*descTxt)
descOut = v1PreserveDescription(*descTxt)
}
eprelVal := extractEPRELFromAttrs(attrs)
attrs = SanitizeV1ProcessAttributesAllowed(attrs, allowedAttrs)
titleStr := derefStringPtr(title)
// Prefer full product name from mapped/raw when processed title is brand-only
// (e.g. LLM returned "ANKER" while feed has "Anker Soundcore Space One Pro").
titleStr = preferredProductTitle(ean, titleStr,
stringFromAny(mapped["name"]),
stringFromAny(mapped["title"]),
stringFromAny(rawData["name"]),
stringFromAny(rawData["title"]),
)
catStr := derefStringPtr(category)
catNameStr := derefStringPtr(categoryName)
// Projection gap: when processed.category is empty but mapped/raw carries a
@@ -363,14 +372,14 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
if catLabel == "" {
catLabel = catStr
}
if (metaTitleOut == nil || metaDescOut == nil) && (titleStr != "" || plainDesc != "" || catLabel != "") {
if (metaTitleOut == nil || metaDescOut == nil) && (titleStr != "" || descOut != "" || catLabel != "") {
synthTitle, synthDesc := fillMetaFromResult(StepResult{
Name: titleStr,
ProcessedName: titleStr,
Category: catStr,
CategoryName: catNameStr,
Description: plainDesc,
ProcessedDescription: plainDesc,
Description: descOut,
ProcessedDescription: descOut,
Attributes: attrs,
ProcessedAttributes: attrs,
})
@@ -384,8 +393,8 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
if metaDescOut == nil {
if synthDesc != "" {
metaDescOut = synthDesc
} else if plainDesc != "" {
metaDescOut = truncateMetaDescription(plainDesc, v1MetaDescriptionMaxChars)
} else if descOut != "" {
metaDescOut = truncateMetaDescription(v1PlainDescription(descOut), v1MetaDescriptionMaxChars)
}
}
} else if metaTitleOut == nil {
@@ -393,8 +402,8 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
}
var description any
if plainDesc != "" {
description = plainDesc
if descOut != "" {
description = descOut
} else {
description = nil
}
@@ -406,7 +415,10 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
catNameOut = catNameStr
}
titleOut := nullIfEmptyPtr(title)
var titleOut any
if titleStr != "" {
titleOut = titleStr
}
item := V1ProcessJobItem{
"ean": ean,
"status": MapV1JobItemStatus(itemStatus, true),
@@ -518,10 +530,30 @@ func loadCompanyCategoryNameMap(ctx context.Context, p *Pipeline, companyID uuid
return out
}
// v1PreserveDescription unwraps legacy JSON-encoded description payloads and
// normalizes entities, but keeps formula HTML tags (h1/h2/p/ul/…) for the V1
// process poll and DB-facing normalize. Prefer this over v1PlainDescription when
// enhance / category description_template markup must reach API clients.
func v1PreserveDescription(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
s = unwrapLegacyDescriptionStored(s)
s = strings.TrimSpace(s)
if s == "" {
return ""
}
s = html.UnescapeString(s)
s = strings.ReplaceAll(s, "\u00a0", " ")
s = strings.ReplaceAll(s, `\u00a0`, " ")
return strings.TrimSpace(s)
}
// v1PlainDescription normalizes legacy stored descriptions into a single plain-text
// string for the process poll (description is a string, not a one-element array).
// Handles JSON array/string encodings (e.g. mapped_data->>'description' when the
// feed value was an array) and HTML/<br>/entity markup.
// string (meta/SEO, feed normalize). Handles JSON array/string encodings and
// strips HTML/<br>/entity markup. Do not use for V1 item.description when formula
// HTML must be preserved — use v1PreserveDescription / DescriptionFromAny.
func v1PlainDescription(s string) string {
s = strings.TrimSpace(s)
if s == "" {