fix
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// Command backfill-ai-usage recovers AI cost history for products that were
|
||||
// processed before prompt/usage capture existed.
|
||||
//
|
||||
// It reads the provider usage blocks still stored in processed_products.gpt_response
|
||||
// and rolls them into ai_usage_daily with source='backfill', so /admin/ai-costs
|
||||
// shows spend from before the feature shipped. See internal/aiaudit/backfill.go for
|
||||
// what is and is not recoverable.
|
||||
//
|
||||
// go run ./cmd/backfill-ai-usage # dry run, reports what it would write
|
||||
// go run ./cmd/backfill-ai-usage -apply
|
||||
// go run ./cmd/backfill-ai-usage -apply -before 2026-08-23
|
||||
//
|
||||
// Safe to re-run: each pass replaces its own rows and never touches live capture.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/aiaudit"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func main() {
|
||||
apply := flag.Bool("apply", false, "write rows (default is a dry run)")
|
||||
before := flag.String("before", "", "only products last updated before this date (YYYY-MM-DD); default: the day live capture started")
|
||||
dsn := flag.String("postgres", os.Getenv("DATABASE_URL"), "Postgres URL (defaults to DATABASE_URL / repo .env)")
|
||||
flag.Parse()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
url := strings.TrimSpace(*dsn)
|
||||
if url == "" {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("config: %v", err)
|
||||
}
|
||||
url = cfg.DatabaseURL
|
||||
}
|
||||
pool, err := pgxpool.New(ctx, url)
|
||||
if err != nil {
|
||||
log.Fatalf("db: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
cutoff, err := resolveCutoff(ctx, pool, *before)
|
||||
if err != nil {
|
||||
log.Fatalf("cutoff: %v", err)
|
||||
}
|
||||
|
||||
res, err := aiaudit.BackfillFromProcessedProducts(ctx, pool, cutoff, !*apply)
|
||||
if err != nil {
|
||||
log.Fatalf("backfill: %v", err)
|
||||
}
|
||||
|
||||
b, _ := json.MarshalIndent(res, "", " ")
|
||||
fmt.Println(string(b))
|
||||
fmt.Printf("\nrecovered %s across %d product(s); %d product(s) had no stored usage block\n",
|
||||
usd(res.CostUSD()), res.ProductsUsed, res.ProductsNoUsage)
|
||||
if !*apply {
|
||||
fmt.Println("dry run — re-run with -apply to write these rows")
|
||||
}
|
||||
}
|
||||
|
||||
// resolveCutoff keeps the backfill from double-counting calls the live recorder
|
||||
// already logged: by default it stops at the first day live capture wrote a row.
|
||||
func resolveCutoff(ctx context.Context, pool *pgxpool.Pool, before string) (time.Time, error) {
|
||||
if s := strings.TrimSpace(before); s != "" {
|
||||
t, err := time.Parse("2006-01-02", s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("invalid -before date (YYYY-MM-DD): %w", err)
|
||||
}
|
||||
return t.UTC(), nil
|
||||
}
|
||||
start, err := aiaudit.LiveCaptureStart(ctx, pool)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if start.IsZero() {
|
||||
log.Println("no live capture recorded yet — scanning all history")
|
||||
return time.Time{}, nil
|
||||
}
|
||||
log.Printf("live capture starts %s — backfilling products updated before that", start.Format("2006-01-02"))
|
||||
return start, nil
|
||||
}
|
||||
|
||||
func usd(v float64) string {
|
||||
if v != 0 && v < 0.01 {
|
||||
return fmt.Sprintf("$%.6f", v)
|
||||
}
|
||||
return fmt.Sprintf("$%.2f", v)
|
||||
}
|
||||
Reference in New Issue
Block a user