136 lines
4.5 KiB
Go
136 lines
4.5 KiB
Go
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)
|
||
|
|
}
|