FloorMaterialAnalyzer/internal/intelligence/statistics.go

83 lines
2.7 KiB
Go
Raw Normal View History

2026-07-27 03:03:51 +00:00
package intelligence
import (
"sort"
"time"
)
func buildStatistics(assets []loadedAsset, fingerprints map[string]MaterialFingerprint) StatisticsCenter {
stats := StatisticsCenter{
Version: "3.0",
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
TotalAssets: len(assets),
MaterialDistribution: map[string]int{},
MaterialTypeDistribution: map[string]int{},
SpeciesDistribution: map[string]int{},
ColorDistribution: map[string]int{},
GlossDistribution: map[string]int{},
BrightnessDistribution: make([]float64, 10),
}
if len(assets) == 0 {
return stats
}
var sumBrightness, sumContrast, sumSaturation, sumEntropy float64
for _, item := range assets {
asset := item.Asset
fp := fingerprints[asset.SKU]
stats.MaterialDistribution[firstNonEmpty(asset.Material, "unknown")]++
stats.MaterialTypeDistribution[firstNonEmpty(fp.MaterialType, "unknown")]++
stats.SpeciesDistribution[firstNonEmpty(asset.Semantic.GrainType, asset.Semantic.Grain, "unknown")]++
stats.ColorDistribution[firstNonEmpty(fp.ColorFamily, "unknown")]++
stats.GlossDistribution[firstNonEmpty(fp.GlossLevel, "unknown")]++
bin := int(fp.Brightness * float64(len(stats.BrightnessDistribution)))
if bin >= len(stats.BrightnessDistribution) {
bin = len(stats.BrightnessDistribution) - 1
}
if bin < 0 {
bin = 0
}
stats.BrightnessDistribution[bin]++
sumBrightness += fp.Brightness
sumContrast += fp.Contrast
sumSaturation += fp.Saturation
sumEntropy += fp.TextureEntropy
stats.EmbeddingPCA = append(stats.EmbeddingPCA, embeddingProjection(item, fp))
}
total := float64(len(assets))
for i := range stats.BrightnessDistribution {
stats.BrightnessDistribution[i] = round6(stats.BrightnessDistribution[i] / total)
}
stats.AverageBrightness = round4(sumBrightness / total)
stats.AverageContrast = round4(sumContrast / total)
stats.AverageSaturation = round4(sumSaturation / total)
stats.AverageTextureEntropy = round4(sumEntropy / total)
stats.ClusterStatistics = buildClusterStats(stats.MaterialTypeDistribution)
return stats
}
func embeddingProjection(item loadedAsset, fp MaterialFingerprint) PCAPoint {
vec, _ := comparableVector(item, fp)
x, y := 0.0, 0.0
if len(vec) > 0 {
x = vec[0]
}
if len(vec) > 1 {
y = vec[1]
}
return PCAPoint{SKU: item.Asset.SKU, X: round6(x), Y: round6(y)}
}
func buildClusterStats(dist map[string]int) []ClusterStat {
out := make([]ClusterStat, 0, len(dist))
for name, count := range dist {
out = append(out, ClusterStat{Name: name, Count: count})
}
sort.Slice(out, func(i, j int) bool {
if out[i].Count != out[j].Count {
return out[i].Count > out[j].Count
}
return out[i].Name < out[j].Name
})
return out
}