fix: anchor growth forecasts to real Tehran date

This commit is contained in:
2026-08-21 18:27:40 +08:00
parent 7140213857
commit 8507b5d017
9 changed files with 306 additions and 6 deletions

View File

@@ -47,10 +47,13 @@ type Builder interface {
Build(ctx context.Context, merchantID int64, terminalID string) (Analysis, error)
}
type PostgreSQLBuilder struct{ pool *pgxpool.Pool }
type PostgreSQLBuilder struct {
pool *pgxpool.Pool
now func() time.Time
}
func NewPostgreSQLBuilder(pool *pgxpool.Pool) *PostgreSQLBuilder {
return &PostgreSQLBuilder{pool: pool}
return &PostgreSQLBuilder{pool: pool, now: time.Now}
}
func (b *PostgreSQLBuilder) Build(ctx context.Context, merchantID int64, terminalID string) (Analysis, error) {
@@ -88,7 +91,7 @@ func (b *PostgreSQLBuilder) Build(ctx context.Context, merchantID int64, termina
if err := b.pool.QueryRow(ctx, `SELECT max(created_at AT TIME ZONE 'Asia/Tehran')::date FROM payment_sessions`).Scan(&latest); err != nil {
return Analysis{}, fmt.Errorf("loading dataset watermark date: %w", err)
}
forecastStart := latest.AddDate(0, 0, 1)
forecastStart := tehranToday(b.now())
peer, err := b.loadPeer(ctx, merchantID, terminalID)
if err != nil {
return Analysis{}, err

View File

@@ -46,10 +46,11 @@ func (s *Service) Create(ctx context.Context, userID, merchantID int64, request
return Record{}, false, err
}
}
watermark, start, err := s.repository.DatasetMetadata(ctx)
watermark, _, err := s.repository.DatasetMetadata(ctx)
if err != nil {
return Record{}, false, err
}
start := tehranToday(now)
fingerprint := s.fingerprint(merchantID, request.TerminalID, watermark, start)
var refreshDay *time.Time
if request.Refresh {
@@ -91,10 +92,11 @@ func (s *Service) Latest(ctx context.Context, merchantID int64, terminalID strin
record, err := s.repository.Latest(ctx, merchantID, terminalID, growth.AnalyticsVersion)
if err == nil {
record.Cached = true
watermark, start, metadataErr := s.repository.DatasetMetadata(ctx)
watermark, _, metadataErr := s.repository.DatasetMetadata(ctx)
if metadataErr != nil {
record.Stale = true
} else {
start := tehranToday(s.now())
record.Stale = record.Fingerprint != s.fingerprint(merchantID, terminalID, watermark, start)
}
record.NextRefreshAt, _ = s.repository.NextRefreshAt(ctx, merchantID, terminalID, growth.AnalyticsVersion, s.now())

View File

@@ -295,7 +295,7 @@ func (r *Repository) DatasetMetadata(ctx context.Context) (string, time.Time, er
if err != nil {
return "", time.Time{}, fmt.Errorf("loading growth dataset metadata: %w", err)
}
return sha + ":" + latest.Format(time.DateOnly), latest.AddDate(0, 0, 1), nil
return sha + ":" + latest.Format(time.DateOnly), latest, nil
}
func (r *Repository) CleanupSuperseded(ctx context.Context, cutoff time.Time) (int64, error) {

View File

@@ -27,6 +27,16 @@ func TestNextTehranRefreshUsesCalendarBoundary(t *testing.T) {
}
}
func TestTehranTodayUsesClockInsteadOfDatasetWatermark(t *testing.T) {
t.Parallel()
now := time.Date(2026, time.August, 21, 8, 30, 0, 0, time.UTC)
got := tehranToday(now)
if got.Format(time.DateOnly) != "2026-08-21" {
t.Fatalf("tehranToday() = %s", got.Format(time.DateOnly))
}
}
func TestEstimatedTokenReservationCoversEveryRetryAndTurn(t *testing.T) {
t.Parallel()
small := EstimatedTokenReservation([]byte("{}"))

View File

@@ -2,6 +2,15 @@ package growthreport
import "time"
func tehranToday(now time.Time) time.Time {
location, err := time.LoadLocation("Asia/Tehran")
if err != nil {
location = time.FixedZone("Asia/Tehran", 12600)
}
local := now.In(location)
return time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, location)
}
func NextTehranRefresh(now time.Time) time.Time {
location, err := time.LoadLocation("Asia/Tehran")
if err != nil {