197 lines
7.0 KiB
Go
197 lines
7.0 KiB
Go
|
|
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)
|
||
|
|
}
|