FloorMaterialAnalyzer/internal/intelligence/run.go

197 lines
7.0 KiB
Go
Raw Permalink Normal View History

2026-07-27 03:03:51 +00:00
package intelligence
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"sort"
"time"
"materialanalyzer/internal/model"
"materialanalyzer/internal/output"
"materialanalyzer/internal/service"
)
func Run(args []string) int {
fs := flag.NewFlagSet("material-intelligence", flag.ContinueOnError)
assetsDir := fs.String("assets-dir", "MaterialAssets", "material asset library directory")
outputRoot := fs.String("output-root", ".", "root directory for v3 deliverables")
k := fs.Int("k", 5, "nearest neighbors per material in similarity graph")
baselineDir := fs.String("baseline-dir", "", "optional old MaterialAssets directory for regression comparison")
includeVectors := fs.Bool("include-vectors", true, "include embedding float values in knowledge.db and Embeddings/embeddings.json")
if err := fs.Parse(args); err != nil {
return 2
}
assets, err := loadAssets(*assetsDir)
if err != nil {
fmt.Fprintf(os.Stderr, "load assets: %v\n", err)
return 1
}
if len(assets) == 0 {
fmt.Fprintln(os.Stderr, "no material assets found")
return 1
}
if err := buildAll(assets, *assetsDir, *outputRoot, *baselineDir, *k, *includeVectors); err != nil {
fmt.Fprintf(os.Stderr, "build intelligence: %v\n", err)
return 1
}
fmt.Printf("Material Intelligence v3 built: assets=%d output=%s\n", len(assets), *outputRoot)
return 0
}
func buildAll(assets []loadedAsset, assetsDir, outputRoot, baselineDir string, k int, includeVectors bool) error {
now := time.Now().UTC().Format(time.RFC3339)
dirs := map[string]string{
"knowledge": filepath.Join(outputRoot, "KnowledgeBase"),
"prompts": filepath.Join(outputRoot, "PromptDataset"),
"statistics": filepath.Join(outputRoot, "Statistics"),
"embeddings": filepath.Join(outputRoot, "Embeddings"),
"fingerprint": filepath.Join(outputRoot, "Fingerprints"),
"groundtruth": filepath.Join(outputRoot, "GroundTruth"),
"regression": filepath.Join(outputRoot, "Regression"),
"benchmarks": filepath.Join(outputRoot, "Benchmarks"),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
}
fingerprints := map[string]MaterialFingerprint{}
groundTruths := make([]GroundTruth, 0, len(assets))
prompts := make([]PromptRecord, 0, len(assets))
knowledge := KnowledgeDB{Version: "3.0", GeneratedAt: now}
embeddingExports := make([]KnowledgeEntry, 0, len(assets))
for _, item := range assets {
fp := buildFingerprint(item.Asset)
gt := buildGroundTruth(item.Asset, fp)
prompt := buildPromptRecord(item.Asset, gt)
embeddings := item.Embeddings
if !includeVectors {
embeddings = stripEmbeddingValues(embeddings)
}
entry := KnowledgeEntry{
SKU: item.Asset.SKU, Brand: item.Asset.Product.Brand,
Category: item.Asset.Category, Material: item.Asset.Material,
Semantic: item.Asset.Semantic, Fingerprint: fp, Embeddings: embeddings,
PreviewImage: filepath.Join(item.AssetDir, "preview.jpg"), AssetDir: item.AssetDir,
}
fingerprints[item.Asset.SKU] = fp
groundTruths = append(groundTruths, gt)
prompts = append(prompts, prompt)
knowledge.Entries = append(knowledge.Entries, entry)
embeddingExports = append(embeddingExports, entry)
safeName := output.SafePathName(item.Asset.SKU)
if err := writeJSON(filepath.Join(dirs["fingerprint"], safeName+".json"), fp); err != nil {
return err
}
if err := writeJSON(filepath.Join(dirs["groundtruth"], safeName+".json"), gt); err != nil {
return err
}
if err := writeSemanticJSON(item.AssetDir, item.Asset); err != nil {
return err
}
if err := updateAssetManifest(item.AssetDir, item.Asset); err != nil {
return err
}
}
if err := writeJSON(filepath.Join(dirs["fingerprint"], "fingerprints.json"), mapValues(fingerprints)); err != nil {
return err
}
if err := writeJSON(filepath.Join(dirs["groundtruth"], "ground_truth.json"), groundTruths); err != nil {
return err
}
if err := writeJSON(filepath.Join(dirs["prompts"], "prompt_dataset.json"), prompts); err != nil {
return err
}
if err := writeJSON(filepath.Join(dirs["knowledge"], "knowledge.db"), knowledge); err != nil {
return err
}
similarity := buildSimilarityGraph(assets, fingerprints, k)
if err := writeJSON(filepath.Join(dirs["knowledge"], "similarity.json"), similarity); err != nil {
return err
}
if err := writeJSON(filepath.Join(dirs["embeddings"], "embeddings.json"), embeddingExports); err != nil {
return err
}
stats := buildStatistics(assets, fingerprints)
if err := writeJSON(filepath.Join(dirs["statistics"], "statistics.json"), stats); err != nil {
return err
}
regression := buildRegressionReport(assets, baselineDir)
if err := writeJSON(filepath.Join(dirs["regression"], "regression_report.json"), regression); err != nil {
return err
}
visionBench := buildVisionBenchmark(assets)
if err := writeJSON(filepath.Join(dirs["benchmarks"], "benchmark_vision.json"), visionBench); err != nil {
return err
}
summary := BuildSummary{
Assets: len(assets), OutputRoot: outputRoot,
KnowledgeDB: filepath.Join(dirs["knowledge"], "knowledge.db"),
PromptDataset: filepath.Join(dirs["prompts"], "prompt_dataset.json"),
Statistics: filepath.Join(dirs["statistics"], "statistics.json"),
}
return writeJSON(filepath.Join(outputRoot, "material_intelligence_summary.json"), summary)
}
func writeSemanticJSON(assetDir string, asset model.MaterialAsset) error {
payload := map[string]interface{}{
"sku": asset.SKU,
"strategy": "single_provider_passthrough",
"fusion": "not_applicable",
"final_semantic": asset.Semantic,
"providers": []map[string]interface{}{
{"provider": asset.Semantic.Provider, "model": asset.Semantic.Model, "confidence": asset.Semantic.Confidence},
},
}
return writeJSON(filepath.Join(assetDir, "semantic.json"), payload)
}
func updateAssetManifest(assetDir string, asset model.MaterialAsset) error {
files := []string{"preview.jpg", "thumbnail.jpg", "material.json", "histogram.json", "embedding.bin", "semantic.json", "manifest.json"}
return output.WriteManifest(assetDir, model.AssetManifest{
SKU: asset.SKU, AnalyzerVersion: firstNonEmpty(asset.AnalyzerVersion, service.AnalyzerVersion),
FeatureSchema: firstNonEmpty(asset.FeatureSchema, service.FeatureSchema),
GeneratedAt: time.Now().UTC().Format(time.RFC3339), Files: files,
Status: "complete", Validation: asset.Validation, Warnings: asset.Warnings,
})
}
func stripEmbeddingValues(records []EmbeddingRecord) []EmbeddingRecord {
out := make([]EmbeddingRecord, len(records))
for i, record := range records {
record.Values = nil
out[i] = record
}
return out
}
func mapValues(values map[string]MaterialFingerprint) []MaterialFingerprint {
out := make([]MaterialFingerprint, 0, len(values))
for _, value := range values {
out = append(out, value)
}
sort.Slice(out, func(i, j int) bool { return out[i].SKU < out[j].SKU })
return out
}
func writeJSON(path string, v interface{}) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
enc := json.NewEncoder(file)
enc.SetIndent("", " ")
enc.SetEscapeHTML(false)
return enc.Encode(v)
}