package main import ( "context" "fmt" "log" "os" "path/filepath" "strings" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // resolveMySQLDumpPath picks an explicit path, else the first readable candidate // under common local locations documented in scripts/seed/README.txt. func resolveMySQLDumpPath(explicit string) string { if p := strings.TrimSpace(explicit); p != "" { if st, err := os.Stat(p); err == nil && !st.IsDir() { return p } log.Printf("warning: mysql dump not found at %q — trying auto-detect", p) } for _, c := range mysqlDumpCandidates() { if strings.TrimSpace(explicit) != "" && filepath.Clean(c) == filepath.Clean(strings.TrimSpace(explicit)) { continue } if st, err := os.Stat(c); err == nil && !st.IsDir() { return c } } return "" } func mysqlDumpCandidates() []string { var out []string if v := strings.TrimSpace(os.Getenv("SEED_A1_MYSQL_DUMP")); v != "" { out = append(out, v) } home, _ := os.UserHomeDir() names := []string{ "descrybe_new (1).sql", "descrybe_new.sql", "descrybe_new(1).sql", } if home != "" { for _, n := range names { out = append(out, filepath.Join(home, "Downloads", n)) out = append(out, filepath.Join(home, "downloads", n)) } } // Repo-relative guesses (cwd may be apps/api or repo root). for _, n := range names { out = append(out, n, filepath.Join("..", "..", n), filepath.Join("scripts", "seed", n), filepath.Join("..", "..", "scripts", "seed", n), ) } return out } type mappedCoverage struct { Total int WithDesc int WithCat int WithAttrs int Processed int Jobs int } func (c mappedCoverage) pct(n int) float64 { if c.Total == 0 { return 0 } return 100 * float64(n) / float64(c.Total) } // feedAttrsSQL matches catalog.processedHasFeedAttributesSQL (mapped_data aliases). const feedAttrsSQL = `( CASE jsonb_typeof(mapped_data->'specifications') WHEN 'string' THEN length(trim(mapped_data->>'specifications')) > 0 WHEN 'object' THEN mapped_data->'specifications' <> '{}'::jsonb WHEN 'array' THEN jsonb_array_length(mapped_data->'specifications') > 0 ELSE false END OR CASE jsonb_typeof(mapped_data->'specs') WHEN 'string' THEN length(trim(mapped_data->>'specs')) > 0 WHEN 'object' THEN mapped_data->'specs' <> '{}'::jsonb WHEN 'array' THEN jsonb_array_length(mapped_data->'specs') > 0 ELSE false END OR COALESCE(NULLIF(trim(mapped_data->>'eprel_id'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'eprel'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'netwidth'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'net_width'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'netheight'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'net_height'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'netdepth'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'net_depth'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'netmass'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'net_mass'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'warranty'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'productmodel'), ''), '') <> '' OR COALESCE(NULLIF(trim(mapped_data->>'product_model'), ''), '') <> '' )` func measureMappedCoverage(ctx context.Context, pg *pgxpool.Pool, companyID uuid.UUID) (mappedCoverage, error) { var c mappedCoverage err := pg.QueryRow(ctx, fmt.Sprintf(` SELECT count(*)::int, count(*) FILTER (WHERE COALESCE(NULLIF(trim(mapped_data->>'description'), ''), '') <> '')::int, count(*) FILTER ( WHERE COALESCE(NULLIF(trim(mapped_data->>'category'), ''), '') <> '' AND lower(trim(mapped_data->>'category')) <> 'none' )::int, count(*) FILTER (WHERE %s)::int FROM raw_products WHERE company_id = $1`, feedAttrsSQL), companyID).Scan( &c.Total, &c.WithDesc, &c.WithCat, &c.WithAttrs, ) if err != nil { return c, fmt.Errorf("measure mapped coverage: %w", err) } _ = pg.QueryRow(ctx, `SELECT count(*)::int FROM processed_products WHERE company_id = $1`, companyID).Scan(&c.Processed) _ = pg.QueryRow(ctx, `SELECT count(*)::int FROM processing_jobs WHERE company_id = $1`, companyID).Scan(&c.Jobs) return c, nil } func logMappedCoverage(c mappedCoverage, label string) { log.Printf("A1 mapped coverage (%s): total=%d desc=%.1f%% (%d) category=%.1f%% (%d) feed_attrs=%.1f%% (%d) processed=%d jobs=%d", label, c.Total, c.pct(c.WithDesc), c.WithDesc, c.pct(c.WithCat), c.WithCat, c.pct(c.WithAttrs), c.WithAttrs, c.Processed, c.Jobs) }