FloorMaterialAnalyzer/internal/intelligence/similarity.go

80 lines
2.0 KiB
Go
Raw Permalink Normal View History

2026-07-27 03:03:51 +00:00
package intelligence
import (
"math"
"sort"
"time"
)
func buildSimilarityGraph(assets []loadedAsset, fingerprints map[string]MaterialFingerprint, k int) SimilarityGraph {
if k <= 0 {
k = 5
}
graph := SimilarityGraph{
Version: "3.0",
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
K: k,
Nodes: len(assets),
}
for _, source := range assets {
candidates := make([]SimilarityEdge, 0, len(assets)-1)
sourceVec, sourceMethod := comparableVector(source, fingerprints[source.Asset.SKU])
for _, target := range assets {
if source.Asset.SKU == target.Asset.SKU {
continue
}
targetVec, targetMethod := comparableVector(target, fingerprints[target.Asset.SKU])
score := cosine(sourceVec, targetVec)
method := sourceMethod
if sourceMethod != targetMethod {
method = "mixed"
}
candidates = append(candidates, SimilarityEdge{
SourceSKU: source.Asset.SKU, TargetSKU: target.Asset.SKU,
Score: round6(score), Method: method,
})
}
sort.Slice(candidates, func(i, j int) bool { return candidates[i].Score > candidates[j].Score })
if len(candidates) > k {
candidates = candidates[:k]
}
graph.Edges = append(graph.Edges, candidates...)
}
return graph
}
func comparableVector(asset loadedAsset, fp MaterialFingerprint) ([]float64, string) {
if len(asset.Embeddings) > 0 && len(asset.Embeddings[0].Values) > 0 {
vec := make([]float64, len(asset.Embeddings[0].Values))
for i, v := range asset.Embeddings[0].Values {
vec[i] = float64(v)
}
return vec, "embedding_cosine"
}
return []float64{
fp.Brightness, fp.Contrast, fp.Saturation, fp.Variation,
fp.TextureEntropy, fp.TextureFrequency, fp.Orientation / 180,
fp.ColorVariance, fp.TextureVariance,
}, "fingerprint_cosine"
}
func cosine(a, b []float64) float64 {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
var dot, na, nb float64
for i := 0; i < n; i++ {
dot += a[i] * b[i]
na += a[i] * a[i]
nb += b[i] * b[i]
}
if na == 0 || nb == 0 {
return 0
}
return dot / (math.Sqrt(na) * math.Sqrt(nb))
}