FloorMaterialAnalyzer/internal/intelligence/builders.go
2026-07-27 11:03:51 +08:00

125 lines
4.6 KiB
Go

package intelligence
import (
"fmt"
"math"
"strings"
"materialanalyzer/internal/model"
)
func buildFingerprint(asset model.MaterialAsset) MaterialFingerprint {
variation := variationScore(asset.Semantic.Variation, asset.Visual.Contrast, asset.Texture.EdgeDensity)
fp := MaterialFingerprint{
SKU: asset.SKU,
Brightness: round4(asset.Visual.Brightness),
Contrast: round4(asset.Visual.Contrast),
Saturation: round4(asset.Visual.Saturation),
Variation: round4(variation),
TextureEntropy: round4(asset.Texture.Entropy / 8.0),
TextureFrequency: round4(asset.Texture.TextureFrequency),
Orientation: round4(asset.Texture.PrimaryOrientationDeg),
DominantLAB: []float64{
round4(asset.Visual.DominantLAB.L),
round4(asset.Visual.DominantLAB.A),
round4(asset.Visual.DominantLAB.B),
},
ColorVariance: round4(asset.Canonical.ColorVariance),
TextureVariance: round4(asset.Canonical.TextureVariance),
MaterialType: firstNonEmpty(asset.Semantic.MaterialType, familyFromSpecific(asset.MaterialSpecific), asset.Category),
ColorFamily: asset.Semantic.ColorFamily,
SurfaceFinish: asset.Semantic.SurfaceFinish,
GlossLevel: asset.Semantic.GlossLevel,
}
fp.ReadableSignature = fmt.Sprintf("%s %s, %s finish, %s gloss, brightness %.2f, contrast %.2f, entropy %.2f, orientation %.0f",
fp.ColorFamily, fp.MaterialType, fp.SurfaceFinish, fp.GlossLevel, fp.Brightness, fp.Contrast, fp.TextureEntropy, fp.Orientation)
return fp
}
func buildGroundTruth(asset model.MaterialAsset, fp MaterialFingerprint) GroundTruth {
return GroundTruth{
SKU: asset.SKU,
Brand: asset.Product.Brand,
Category: asset.Category,
Material: asset.Material,
MaterialType: fp.MaterialType,
CanonicalColor: firstNonEmpty(asset.Semantic.ColorFamily, asset.Product.ColorTone),
SurfaceFinish: asset.Semantic.SurfaceFinish,
GlossLevel: asset.Semantic.GlossLevel,
GrainType: firstNonEmpty(asset.Semantic.GrainType, asset.Semantic.Grain),
VisualStyle: asset.Semantic.VisualStyle,
Variation: asset.Semantic.Variation,
RenderingConstraints: []string{
"Keep material color, species, finish, grain, gloss, and variation identical to the reference texture.",
"Use the reference image as material ground truth, not as loose style inspiration.",
"Only adjust plank or tile scale when size instructions require it.",
},
DoNotAlter: []string{"species", "finish", "grain_type", "gloss_level", "color_family", "variation"},
Fingerprint: fp,
MaterialSpecific: asset.MaterialSpecific,
}
}
func buildPromptRecord(asset model.MaterialAsset, gt GroundTruth) PromptRecord {
system := "You are a material-constrained image rendering pipeline. Preserve flooring material identity exactly."
material := strings.Join([]string{
"Material Constraints",
"",
"SKU: " + asset.SKU,
"Material Type: " + gt.MaterialType,
"Material: " + firstNonEmpty(asset.Material, gt.MaterialType),
"Color Family: " + gt.CanonicalColor,
"Surface: " + gt.SurfaceFinish,
"Gloss: " + gt.GlossLevel,
"Grain: " + gt.GrainType,
"Visual Style: " + gt.VisualStyle,
"Variation: " + gt.Variation,
"",
"Keep all material properties identical to the reference texture.",
"Do not alter species, finish, grain, gloss, color temperature, or variation.",
"The reference texture is ground truth for the floor material.",
}, "\n")
negative := "Do not reinterpret the material. Do not change wood species, stone type, grain direction, surface finish, gloss level, color family, plank texture, tile texture, or material variation."
return PromptRecord{SKU: asset.SKU, SystemPrompt: system, MaterialPrompt: material, NegativePrompt: negative}
}
func variationScore(label string, contrast, edgeDensity float64) float64 {
switch strings.ToLower(strings.TrimSpace(label)) {
case "high":
return math.Max(0.70, math.Min(1, contrast*2.5+edgeDensity*2.0))
case "medium":
return math.Max(0.35, math.Min(0.75, contrast*2.0+edgeDensity*1.6))
case "low":
return math.Min(0.35, contrast*1.8+edgeDensity*1.2)
default:
return math.Min(1, contrast*2.0+edgeDensity*1.5)
}
}
func familyFromSpecific(values map[string]interface{}) string {
if values == nil {
return ""
}
if v, ok := values["family"].(string); ok {
return v
}
return ""
}
func firstNonEmpty(values ...string) string {
for _, value := range values {
if strings.TrimSpace(value) != "" {
return value
}
}
return ""
}
func round4(v float64) float64 {
return math.Round(v*10000) / 10000
}
func round6(v float64) float64 {
return math.Round(v*1000000) / 1000000
}