package validation import ( "fmt" "time" "materialanalyzer/internal/model" ) func ValidateAsset(asset model.MaterialAsset, hist model.ColorHistogram) model.ValidationReport { report := model.ValidationReport{ Status: "valid", CheckedAt: time.Now().UTC().Format(time.RFC3339), } checkRange(&report, "visual.brightness", asset.Visual.Brightness, 0, 1) checkRange(&report, "visual.contrast", asset.Visual.Contrast, 0, 1) checkRange(&report, "visual.saturation", asset.Visual.Saturation, 0, 1) checkRange(&report, "texture.orientation", asset.Texture.PrimaryOrientationDeg, 0, 180) if asset.Texture.Entropy <= 0 { report.Errors = append(report.Errors, "texture.entropy must be > 0") } if len(hist.Values) != 256 { report.Errors = append(report.Errors, fmt.Sprintf("histogram length = %d, want 256", len(hist.Values))) } for _, vector := range asset.Embeddings.Vectors { if vector.Dimensions <= 0 { report.Errors = append(report.Errors, "embedding "+vector.Name+" has invalid dimension") } if vector.ByteLength != int64(vector.Dimensions*4) { report.Errors = append(report.Errors, fmt.Sprintf("embedding %s byte length = %d, want %d", vector.Name, vector.ByteLength, vector.Dimensions*4)) } if vector.Model == "dinov2-large" && vector.Dimensions != 1024 { report.Errors = append(report.Errors, "dinov2-large dimension must be 1024") } if vector.Model == "ViT-L/14" && vector.Dimensions != 768 { report.Errors = append(report.Errors, "ViT-L/14 dimension must be 768") } } if asset.Semantic.Description == "" { report.Errors = append(report.Errors, "semantic.description is required") } if asset.Semantic.MaterialType == "" { report.Errors = append(report.Errors, "semantic.material_type is required") } if asset.Semantic.SurfaceFinish == "" { report.Errors = append(report.Errors, "semantic.surface_finish is required") } if asset.Semantic.ColorFamily == "" { report.Errors = append(report.Errors, "semantic.color_family is required") } if asset.Semantic.Confidence <= 0 || asset.Semantic.Confidence > 1 { report.Errors = append(report.Errors, "semantic.confidence must be in (0,1]") } if len(asset.Canonical.BrightnessDistribution) != asset.Canonical.BrightnessHistogramBins { report.Warnings = append(report.Warnings, "canonical brightness histogram bin count mismatch") } if len(asset.Canonical.GradientHistogram) != asset.Canonical.GradientHistogramBins { report.Warnings = append(report.Warnings, "canonical gradient histogram bin count mismatch") } if len(report.Errors) > 0 { report.Status = "invalid" } return report } func checkRange(report *model.ValidationReport, name string, value, min, max float64) { if value < min || value > max { report.Errors = append(report.Errors, fmt.Sprintf("%s = %.4f, want [%.2f,%.2f]", name, value, min, max)) } }