This commit is contained in:
2026-08-16 19:21:49 +02:00
parent d6fe7c38d5
commit 106f602232
8 changed files with 212 additions and 15 deletions
+88 -10
View File
@@ -326,19 +326,24 @@ func (e *Engine) RunSteps(ctx context.Context, companyID string, in ProductInput
// Prefer title-aware selection + synthesize before deciding hash persistence.
name = preferredProductTitle(in.GTIN, name, out.Name, priorName, in.Name)
desc = preferredProductDescription(name, desc, out.Description, priorDesc)
outerSynth := false
if isWeakPriorEnhanceDescription(desc, name) {
if synth := synthesizeDescriptionFromTitle(name, displayCat, lang, enhanceAttrs); synth != "" {
if synth := synthesizeProductDescription(name, displayCat, lang, enhanceAttrs, descTpl); synth != "" {
desc = synth
outerSynth = true
}
}
weakDesc := isWeakPriorEnhanceDescription(desc, name)
// Only persist enhance_input_hash for quality ok / hash-skip unchanged.
// Never copy input_hash from error/passthrough/thin meta into localized.
// Never copy input_hash from error/passthrough/thin/synthesized meta.
persistHash := ""
rawHash := enhanceHashFromMeta(raw)
switch {
case err != nil:
persistHash = ""
case status == "synthesized" || outerSynth:
persistHash = ""
allUnchanged = false
case status == "unchanged" && !weakDesc:
persistHash = rawHash
case status == "ok" && !weakDesc:
@@ -402,7 +407,8 @@ func (e *Engine) RunSteps(ctx context.Context, companyID string, in ProductInput
// Timeout/error/empty: always synthesize a factual fallback when a title exists.
if out.ProcessedName != "" && (out.ProcessedDescription == "" ||
isWeakPriorEnhanceDescription(out.ProcessedDescription, out.ProcessedName, out.Name)) {
if synth := synthesizeDescriptionFromTitle(out.ProcessedName, displayCat, primary, enhanceAttrs); synth != "" {
_, failDescTpl := categoryFormulasFor(in, out.Category)
if synth := synthesizeProductDescription(out.ProcessedName, displayCat, primary, enhanceAttrs, failDescTpl); synth != "" {
out.ProcessedDescription = synth
if out.Description == "" || isWeakPriorEnhanceDescription(out.Description, out.Name, out.ProcessedName) {
out.Description = synth
@@ -477,15 +483,16 @@ func (e *Engine) RunSteps(ctx context.Context, companyID string, in ProductInput
if out.ProcessedName == "" {
out.ProcessedName = out.Name
}
_, finalDescTpl := categoryFormulasFor(in, out.Category)
if out.ProcessedDescription == "" || isWeakPriorEnhanceDescription(out.ProcessedDescription, out.ProcessedName, out.Name) {
if synth := synthesizeDescriptionFromTitle(out.ProcessedName, displayCat, in.Language, out.Attributes); synth != "" {
if synth := synthesizeProductDescription(out.ProcessedName, displayCat, in.Language, out.Attributes, finalDescTpl); synth != "" {
out.ProcessedDescription = synth
}
}
if out.Description == "" || isWeakPriorEnhanceDescription(out.Description, out.Name, out.ProcessedName) {
if out.ProcessedDescription != "" && !isWeakPriorEnhanceDescription(out.ProcessedDescription, out.Name, out.ProcessedName) {
out.Description = out.ProcessedDescription
} else if synth := synthesizeDescriptionFromTitle(out.Name, displayCat, in.Language, out.Attributes); synth != "" {
} else if synth := synthesizeProductDescription(out.Name, displayCat, in.Language, out.Attributes, finalDescTpl); synth != "" {
out.Description = synth
if out.ProcessedDescription == "" || isWeakPriorEnhanceDescription(out.ProcessedDescription, out.ProcessedName, out.Name) {
out.ProcessedDescription = synth
@@ -657,7 +664,7 @@ func (e *Engine) enhance(ctx context.Context, in ProductInput, category string,
name := preferredProductTitle(in.GTIN, in.Name, in.PriorProcessedName)
desc := preferredProductDescription(name, in.Description, in.PriorProcessedDescription)
if isWeakPriorEnhanceDescription(desc, name) {
if synth := synthesizeDescriptionFromTitle(name, category, in.Language, attrs); synth != "" {
if synth := synthesizeProductDescription(name, category, in.Language, attrs, in.DescriptionTemplate); synth != "" {
desc = synth
}
}
@@ -676,18 +683,22 @@ func (e *Engine) enhance(ctx context.Context, in ProductInput, category string,
}
name := preferredProductTitle(in.GTIN, SanitizeOutput(fmt.Sprint(obj["name"])), in.Name, in.PriorProcessedName)
desc := preferredProductDescription(name, SanitizeOutput(fmt.Sprint(obj["description"])), in.Description, in.PriorProcessedDescription)
synthesized := false
if isWeakPriorEnhanceDescription(desc, name) {
if synth := synthesizeDescriptionFromTitle(name, category, in.Language, attrs); synth != "" {
if synth := synthesizeProductDescription(name, category, in.Language, attrs, in.DescriptionTemplate); synth != "" {
desc = synth
synthesized = true
}
}
meta := map[string]any{
"status": "ok",
"raw": comp.Raw,
}
// Only attach input_hash when output description is quality-worthy; thin
// title-echo / heuristic filler must not poison field_sources / localized skip hashes.
if !isWeakPriorEnhanceDescription(desc, name) {
// Heuristic synthesize must not poison enhance_input_hash (would hash-skip
// formula-aware LLM copy on reprocess). Only persist hash for real LLM quality.
if synthesized {
meta["status"] = "synthesized"
} else if !isWeakPriorEnhanceDescription(desc, name) {
meta["input_hash"] = hash
}
return name, desc, comp.TotalTokens, meta, nil
@@ -948,6 +959,73 @@ func formatAttrDimParts(attrs map[string]any, maxParts int) []string {
return parts
}
// synthesizeProductDescription prefers a category description_template skeleton
// (HTML section types) when present; otherwise falls back to plain title synthesize.
func synthesizeProductDescription(title, category, language string, attrs map[string]any, descriptionTemplate any) string {
if sections, ok := parseDescriptionFormulaSections(descriptionTemplate); ok && len(sections) > 0 {
if out := synthesizeDescriptionFromFormula(title, category, language, attrs, sections); out != "" {
return out
}
}
return synthesizeDescriptionFromTitle(title, category, language, attrs)
}
// synthesizeDescriptionFromFormula builds a minimal HTML description matching
// category description_template section types so timeout/fallback still respects
// A1 category structure (unlike plain title synthesize).
func synthesizeDescriptionFromFormula(title, category, language string, attrs map[string]any, sections []descriptionFormulaSection) string {
title = strings.TrimSpace(title)
if title == "" || title == "<nil>" || isPromptLabelTitle(title) {
return ""
}
base := synthesizeDescriptionFromTitle(title, category, language, attrs)
if base == "" {
return ""
}
dims := formatAttrDimParts(attrs, 6)
var b strings.Builder
paraUsed := false
listUsed := false
for _, s := range sections {
typ := strings.ToLower(strings.TrimSpace(s.Type))
switch typ {
case "h1", "h2", "h3", "h4":
heading := title
if typ != "h1" {
if isSlovenianContentLanguage(language) {
heading = "Ključne lastnosti"
} else {
heading = "Key features"
}
}
fmt.Fprintf(&b, "<%s>%s</%s>", typ, SanitizeOutput(heading), typ)
case "ul":
b.WriteString("<ul>")
items := dims
if len(items) == 0 {
items = []string{base}
}
for _, it := range items {
fmt.Fprintf(&b, "<li>%s</li>", SanitizeOutput(it))
}
b.WriteString("</ul>")
listUsed = true
default: // p and unknown → paragraph
body := base
if paraUsed && len(dims) > 0 && !listUsed {
if isSlovenianContentLanguage(language) {
body = "Ključne specifikacije: " + strings.Join(dims, ", ") + "."
} else {
body = "Key specs: " + strings.Join(dims, ", ") + "."
}
}
fmt.Fprintf(&b, "<p>%s</p>", SanitizeOutput(body))
paraUsed = true
}
}
return SanitizeOutput(b.String())
}
// synthesizeDescriptionFromTitle builds a short factual fallback when enhance
// returns empty/title-echo/filler copy. Uses title, category, brand, model, and
// key dims. language is a content-language code (en/sl/…) or English label.