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 }