// Temporary slim LIVE verify — 2 products, short timeouts. Do not commit. package main import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "os" "path/filepath" "strings" "time" "github.com/descrybe/descrybe-v2/apps/api/internal/aiprompts" "github.com/descrybe/descrybe-v2/apps/api/internal/aiprovider" "github.com/descrybe/descrybe-v2/apps/api/internal/billing" "github.com/descrybe/descrybe-v2/apps/api/internal/config" "github.com/descrybe/descrybe-v2/apps/api/internal/db" "github.com/descrybe/descrybe-v2/apps/api/internal/eprel" "github.com/descrybe/descrybe-v2/apps/api/internal/logredact" "github.com/descrybe/descrybe-v2/apps/api/internal/platformsettings" "github.com/descrybe/descrybe-v2/apps/api/internal/processing" "github.com/google/uuid" ) func main() { out := `F:\laragon\www\_MY\descrybe-v2\.codehelper\_final_verify_20260817` _ = os.MkdirAll(out, 0o755) logPath := filepath.Join(out, "inproc_run_quick.log") logFile, err := os.Create(logPath) if err != nil { log.Fatalf("log: %v", err) } defer logFile.Close() log.SetOutput(logredact.Writer(io.MultiWriter(os.Stderr, logFile))) repoRoot := `F:\laragon\www\_MY\descrybe-v2` wpPath := filepath.Join(repoRoot, "scripts", "seed", "wp_product_categories.sql") wpBytes, err := os.ReadFile(wpPath) if err != nil { failReport(out, "FAIL", "repo seed missing: "+err.Error(), nil) } _ = os.Setenv("SEED_A1_WP_CATEGORIES", wpPath) eans := []string{ "4548736132597", // Slušalke "195348253666", // Gaming monitorji } companyID := uuid.MustParse("604f23a8-b66e-4b21-8b45-0d72b68f4790") userID := uuid.MustParse("6bf00877-a693-4d77-b28e-8c8292adac98") cfg, err := config.Load() if err != nil { failReport(out, "FAIL", "config: "+err.Error(), nil) } ctx := context.Background() pool, err := db.NewPool(ctx, cfg.DatabaseURL, db.PoolOptions{ MaxConns: int32(cfg.DBMaxConns), MinConns: int32(cfg.DBMinConns), MaxConnLifetime: cfg.DBMaxConnLifetime, MaxConnLifetimeJitter: cfg.DBMaxConnLifetimeJitter, MaxConnIdleTime: cfg.DBMaxConnIdleTime, HealthCheckPeriod: cfg.DBHealthCheckPeriod, StatementTimeout: cfg.DBStatementTimeout, }) if err != nil { failReport(out, "FAIL", "db: "+err.Error(), nil) } defer pool.Close() // --- Sync A1 from repo seed only --- var legacy string _ = pool.QueryRow(ctx, `SELECT COALESCE(legacy_company_id,'') FROM companies WHERE id=$1`, companyID).Scan(&legacy) a1Cohort := billing.IsA1CohortCompany(legacy, "A1 Slovenija") syncRes, serr := processing.SyncCompanyA1(ctx, pool, companyID, "A1 Slovenija", a1Cohort, processing.SyncCompanyA1Opts{ SkipDumpBackfill: true, FixCompanyCatalogOpts: processing.FixCompanyCatalogOpts{ BackfillCategories: true, WPCategoriesSQL: wpBytes, AIPrompts: aiprompts.NewService(pool), }, }) syncInfo := map[string]any{"wp_path": wpPath, "wp_bytes": len(wpBytes), "error": nil, "result": syncRes} if serr != nil { syncInfo["error"] = serr.Error() writeJSON(out, "sync_quick.json", syncInfo) failReport(out, "FAIL", "SyncCompanyA1: "+serr.Error(), syncInfo) } writeJSON(out, "sync_quick.json", syncInfo) log.Printf("sync ok prompts_already_ok=%d wp_entries=%d", syncRes.CategoryPromptsAlreadyOK, syncRes.WPCategoriesEntries) platEnv := platformsettings.EnvConfig{ AppEncryptionKey: cfg.AppEncryptionKey, CredentialsEncryptionKey: cfg.CredentialsEncryptionKey, TokenSigningSecret: cfg.TokenSigningSecret, DatabaseURL: cfg.DatabaseURL, OpenAIAPIKey: cfg.OpenAIAPIKey, OpenAIBaseURL: cfg.OpenAIBaseURL, OpenAIModel: cfg.OpenAIModel, } plat := platformsettings.NewService(pool, platEnv) aiSvc := aiprovider.NewService(pool, aiprovider.EnvConfig{ AppEncryptionKey: cfg.AppEncryptionKey, CredentialsEncryptionKey: cfg.CredentialsEncryptionKey, TokenSigningSecret: cfg.TokenSigningSecret, DatabaseURL: cfg.DatabaseURL, OpenAIAPIKey: cfg.OpenAIAPIKey, OpenAIBaseURL: cfg.OpenAIBaseURL, OpenAIModel: cfg.OpenAIModel, ProcessingRPM: cfg.ProcessingRPM, ProcessingMaxRetries: cfg.ProcessingMaxRetries, }) aiSvc.Platform = plat roleCfg, rerr := plat.ResolveAIConfig(ctx, platformsettings.AIRoleProcessing) if rerr != nil { failReport(out, "FAIL", "ResolveAIConfig: "+rerr.Error(), nil) } completer, modeLabel, byok, cerr := aiSvc.ResolveCompleterForRole(ctx, companyID, aiprovider.RoleProcessing) if cerr != nil { failReport(out, "FAIL", "ResolveCompleter: "+cerr.Error(), nil) } client, ok := completer.(*processing.OpenAIClient) if !ok || client == nil { failReport(out, "FAIL", fmt.Sprintf("completer type %T", completer), nil) } base := strings.TrimSpace(client.BaseURL) model := strings.TrimSpace(client.Model) resolve := map[string]any{ "role_source": roleCfg.Source, "role_provider": roleCfg.Provider, "completer_base": base, "completer_model": model, "mode": modeLabel, "byok": byok, "env_ignored": cfg.OpenAIBaseURL, } if processing.IsMockOrLoopbackBaseURL(base) || strings.Contains(base, "18767") { resolve["fail_reason"] = "mock/loopback refused" writeJSON(out, "resolve_quick.json", resolve) failReport(out, "FAIL", "mock/loopback refused — live OverloadedBot only", resolve) } // --- /models probe (hard stop on 502; 15s) --- modelsURL := strings.TrimRight(base, "/") + "/models" modelsCtx, modelsCancel := context.WithTimeout(ctx, 15*time.Second) defer modelsCancel() req, _ := http.NewRequestWithContext(modelsCtx, http.MethodGet, modelsURL, nil) req.Header.Set("Authorization", "Bearer "+client.APIKey) req.Header.Set("Accept", "application/json") t0 := time.Now() resp, merr := http.DefaultClient.Do(req) elapsed := time.Since(t0).Round(time.Millisecond) if merr != nil { resolve["models_error"] = merr.Error() writeJSON(out, "resolve_quick.json", resolve) failReport(out, "FAIL", "GET /models network: "+merr.Error(), resolve) } body, _ := io.ReadAll(io.LimitReader(resp.Body, 512)) _ = resp.Body.Close() resolve["models_http_status"] = resp.StatusCode resolve["models_elapsed"] = elapsed.String() resolve["models_snippet"] = trunc(string(body), 180) if resp.StatusCode == http.StatusBadGateway { writeJSON(out, "resolve_quick.json", resolve) failReport(out, "FAIL", "FAIL 502: GET /v1/models — OverloadedBot unavailable (no mock, no wait)", resolve) } if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized { writeJSON(out, "resolve_quick.json", resolve) failReport(out, "FAIL", fmt.Sprintf("GET /models HTTP %d", resp.StatusCode), resolve) } // Prefer advertised green/code-fast if DB model is bare code-fast (gateway list). if model == "code-fast" && strings.Contains(string(body), `"green/code-fast"`) { client.Model = "green/code-fast" model = client.Model resolve["completer_model_adjusted"] = model log.Printf("adjusted model code-fast -> green/code-fast from /models list") } // Cap per-call wait (~2.5m) and retries so we never sit on 4m×N. if client.HTTPClient != nil { client.HTTPClient.Timeout = 150 * time.Second } client.MaxRetries = 1 resolve["client_http_timeout"] = "150s" resolve["client_max_retries"] = 1 writeJSON(out, "resolve_quick.json", resolve) log.Printf("LIVE OK models=%d base=%s model=%s", resp.StatusCode, base, model) pipeline := processing.NewPipeline(pool) pipeline.BatchSize = cfg.ProcessingBatchSize pipeline.AI = aiSvc pipeline.Prompts = aiprompts.NewService(pool) pipeline.Engine = &processing.Engine{ Completer: nil, Vector: processing.NoopVectorCategorizer{}, EPREL: eprel.NewClient(eprel.Options{Enabled: true, Timeout: 15 * time.Second}), ProviderMode: processing.AIProviderInternal, } rawIDs := make([]uuid.UUID, 0, len(eans)) for _, ean := range eans { var id uuid.UUID if err := pool.QueryRow(ctx, `SELECT id FROM raw_products WHERE company_id=$1 AND gtin=$2`, companyID, ean).Scan(&id); err != nil { failReport(out, "FAIL", fmt.Sprintf("raw %s: %v", ean, err), resolve) } rawIDs = append(rawIDs, id) } _, _ = pool.Exec(ctx, ` UPDATE processed_products pp SET field_sources = COALESCE(field_sources, '{}'::jsonb) - 'enhance_input_hash', updated_at=now() FROM raw_products rp WHERE pp.company_id=$1 AND pp.raw_product_id=rp.id AND rp.gtin=ANY($2::text[])`, companyID, eans) _, _ = pool.Exec(ctx, ` UPDATE processing_jobs SET status='failed', error='yielded to quick final verify', updated_at=now() WHERE company_id=$1 AND status IN ('pending','running','processing')`, companyID) jobs, err := pipeline.StartJob(ctx, companyID, userID, rawIDs, "full") if err != nil || len(jobs) == 0 { failReport(out, "FAIL", fmt.Sprintf("StartJob: %v", err), resolve) } jobID := jobs[0].ID var dbStatus string var processedCount int _ = pool.QueryRow(ctx, `SELECT lower(status), COALESCE(processed_count,0) FROM processing_jobs WHERE id=$1`, jobID).Scan(&dbStatus, &processedCount) startOK := (dbStatus == "pending" || dbStatus == "queued") && processedCount == 0 startSample := map[string]any{ "process_id": jobID.String(), "status": dbStatus, "processed_items": processedCount, "total_items": len(rawIDs), "start_ok": startOK, } writeJSON(out, "start_sample.json", startSample) _, _ = pool.Exec(ctx, `UPDATE processing_jobs SET status='running', started_at=COALESCE(started_at,now()), updated_at=now() WHERE id=$1`, jobID) // Hard wall: 2 products × ~2.5–3 min ≈ 8 min max runCtx, runCancel := context.WithTimeout(ctx, 8*time.Minute) defer runCancel() procErr := pipeline.ProcessJob(runCtx, jobID) logBytes, _ := os.ReadFile(logPath) logText := string(logBytes) aiEnhance := strings.Contains(logText, "ai_enhance") items, loadErr := pipeline.LoadV1ProcessJobItems(ctx, companyID, jobID, "full") if loadErr != nil { items = nil } cards := []map[string]any{} allPass := startOK && aiEnhance && procErr == nil for _, item := range items { ean := str(item["ean"]) if ean == "" { ean = str(item["product_id"]) } title := first(str(item["name"]), str(item["title"])) desc := str(item["description"]) catName := str(item["category_name"]) if catName == "" { if m, ok := item["category"].(map[string]any); ok { catName = str(m["name"]) } } _, hasMetaT := item["meta_title"] _, hasMetaD := item["meta_description"] _, hasID := item["id"] _, hasPP := item["processed_product_id"] _, hasRaw := item["raw_product_id"] hasHTML := strings.Contains(strings.ToLower(desc), "") checks := map[string]bool{ "name_present": strings.TrimSpace(title) != "", "category_name_present": strings.TrimSpace(catName) != "", "no_meta_title": !hasMetaT || item["meta_title"] == nil, "no_meta_description": !hasMetaD || item["meta_description"] == nil, "no_id": !hasID, "no_processed_product_id": !hasPP, "no_raw_product_id": !hasRaw, "formula_html_or_empty": hasHTML || strings.TrimSpace(desc) == "" || strings.Contains(strings.ToLower(logText), "empty_or_provider_error") || strings.Contains(strings.ToLower(logText), "refuse"), } pass := true for _, v := range checks { if !v { pass = false break } } if !pass { allPass = false } cards = append(cards, map[string]any{ "ean": ean, "title": title, "category": catName, "desc_len": len(desc), "has_html": hasHTML, "checks": checks, "pass": pass, }) } if procErr != nil { allPass = false } overall := "PASS" reason := "" if !allPass { overall = "FAIL" if procErr != nil { reason = "ProcessJob: " + processing.TruncateError(procErr) } else if !aiEnhance { reason = "no ai_enhance log lines" } else if !startOK { reason = "start semantics not pending/0" } else { reason = "scorecard assertion failed" } } report := map[string]any{ "generated_at": time.Now().UTC().Format(time.RFC3339), "overall": overall, "blocking_reason": reason, "sync_pass": true, "live_llm_pass": true, "process_run": true, "job_id": jobID.String(), "eans": eans, "start_ok": startOK, "ai_enhance_seen": aiEnhance, "process_error": nil, "scorecards": cards, "llm": resolve, "sync": syncInfo, "note": "slim verify: 2 products, 150s/call, 8m wall; repo seed only", } if procErr != nil { report["process_error"] = processing.TruncateError(procErr) } writeJSON(out, "report.json", report) writeMD(out, report, startSample, cards, logText) fmt.Printf("OVERALL %s job=%s ai_enhance=%v err=%v\n", overall, jobID, aiEnhance, procErr) if overall != "PASS" { os.Exit(1) } } func failReport(out, overall, reason string, extra map[string]any) { report := map[string]any{ "generated_at": time.Now().UTC().Format(time.RFC3339), "overall": overall, "blocking_reason": reason, "process_run": false, "extra": extra, } writeJSON(out, "report.json", report) var b strings.Builder b.WriteString("# Final verify 2026-08-17 (quick LIVE)\n\n") b.WriteString(fmt.Sprintf("**Overall: %s**\n\n", overall)) b.WriteString(fmt.Sprintf("Blocking: %s\n\n", reason)) b.WriteString("Live OverloadedBot only. No mock.\n") _ = os.WriteFile(filepath.Join(out, "REPORT.md"), []byte(b.String()), 0o644) log.Print(reason) os.Exit(1) } func writeMD(out string, report map[string]any, start map[string]any, cards []map[string]any, logText string) { var b strings.Builder b.WriteString("# Final verify 2026-08-17 (quick LIVE)\n\n") b.WriteString(fmt.Sprintf("**Overall: %v**\n\n", report["overall"])) if r := str(report["blocking_reason"]); r != "" { b.WriteString(fmt.Sprintf("Blocking: %s\n\n", r)) } b.WriteString("## Progress note\n\n") b.WriteString("Prior hung run killed (4m chat waits on 6 EANs). This pass: live OverloadedBot only, repo seed sync, 2 products, 150s/call, 8m wall.\n\n") b.WriteString("## 1. Sync A1 (repo seed only)\n\n") b.WriteString("- Source: `scripts/seed/wp_product_categories.sql` (no upload, SkipDumpBackfill)\n") b.WriteString("- See `sync_quick.json`\n\n") b.WriteString("## 2. Live LLM\n\n") if llm, ok := report["llm"].(map[string]any); ok { b.WriteString(fmt.Sprintf("- base: `%v`\n- model: `%v`\n- models HTTP: `%v`\n- source: `%v`\n\n", llm["completer_base"], llm["completer_model"], llm["models_http_status"], llm["role_source"])) } b.WriteString("## 3. Start semantics\n\n") b.WriteString(fmt.Sprintf("- process_id: `%v`\n- status: `%v`\n- processed_items: `%v`\n- start_ok: `%v`\n\n", start["process_id"], start["status"], start["processed_items"], start["start_ok"])) b.WriteString(fmt.Sprintf("## 4. ai_enhance\n\n- seen: `%v`\n\n", report["ai_enhance_seen"])) b.WriteString("## 5. Scorecards\n\n") for _, c := range cards { b.WriteString(fmt.Sprintf("- **%v** pass=%v cat=%v html=%v title=%q\n", c["ean"], c["pass"], c["category"], c["has_html"], trunc(str(c["title"]), 60))) } b.WriteString("\n## Log excerpt (ai_enhance)\n\n```\n") n := 0 for _, line := range strings.Split(logText, "\n") { if strings.Contains(line, "ai_enhance") || strings.Contains(line, "LIVE") || strings.Contains(line, "OVERALL") { b.WriteString(trunc(line, 240) + "\n") n++ if n >= 12 { break } } } b.WriteString("```\n") _ = os.WriteFile(filepath.Join(out, "REPORT.md"), []byte(b.String()), 0o644) } func writeJSON(out, name string, v any) { raw, _ := json.MarshalIndent(v, "", " ") _ = os.WriteFile(filepath.Join(out, name), raw, 0o644) } func str(v any) string { if v == nil { return "" } if s, ok := v.(string); ok { return s } return strings.TrimSpace(fmt.Sprint(v)) } func first(vals ...string) string { for _, v := range vals { if strings.TrimSpace(v) != "" { return v } } return "" } func trunc(s string, n int) string { s = strings.TrimSpace(s) if len(s) <= n { return s } return s[:n] + "…" }