fix: anchor growth forecasts to real Tehran date
This commit is contained in:
49
internal/insight/time.go
Normal file
49
internal/insight/time.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package insight
|
||||
|
||||
import "time"
|
||||
|
||||
type Freshness string
|
||||
|
||||
const (
|
||||
FreshnessFresh Freshness = "FRESH"
|
||||
FreshnessLagging Freshness = "LAGGING"
|
||||
FreshnessStale Freshness = "STALE"
|
||||
)
|
||||
|
||||
type TimeContext struct {
|
||||
AsOfDate string `json:"asOfDate"`
|
||||
DataThroughDate string `json:"dataThroughDate"`
|
||||
ForecastStartDate string `json:"forecastStartDate"`
|
||||
Freshness Freshness `json:"freshness"`
|
||||
DataLagDays int `json:"dataLagDays"`
|
||||
}
|
||||
|
||||
func BuildTimeContext(now, dataThrough time.Time) TimeContext {
|
||||
location, err := time.LoadLocation("Asia/Tehran")
|
||||
if err != nil {
|
||||
location = time.FixedZone("Asia/Tehran", 3*60*60+30*60)
|
||||
}
|
||||
today := dateOnly(now.In(location), location)
|
||||
through := dateOnly(dataThrough.In(location), location)
|
||||
lag := max(0, int(today.Sub(through).Hours()/24))
|
||||
return TimeContext{
|
||||
AsOfDate: today.Format(time.DateOnly), DataThroughDate: through.Format(time.DateOnly),
|
||||
ForecastStartDate: today.Format(time.DateOnly), Freshness: ClassifyFreshness(lag), DataLagDays: lag,
|
||||
}
|
||||
}
|
||||
|
||||
func ClassifyFreshness(lagDays int) Freshness {
|
||||
switch {
|
||||
case lagDays <= 1:
|
||||
return FreshnessFresh
|
||||
case lagDays <= 7:
|
||||
return FreshnessLagging
|
||||
default:
|
||||
return FreshnessStale
|
||||
}
|
||||
}
|
||||
|
||||
func dateOnly(value time.Time, location *time.Location) time.Time {
|
||||
year, month, day := value.In(location).Date()
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, location)
|
||||
}
|
||||
51
internal/insight/time_test.go
Normal file
51
internal/insight/time_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package insight
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeContextUsesRealTehranTodayAndReportsDatasetLag(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Date(2026, time.August, 21, 8, 30, 0, 0, time.UTC)
|
||||
dataThrough := time.Date(2026, time.June, 30, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
got := BuildTimeContext(now, dataThrough)
|
||||
|
||||
if got.AsOfDate != "2026-08-21" {
|
||||
t.Fatalf("AsOfDate = %q, want real Tehran date", got.AsOfDate)
|
||||
}
|
||||
if got.DataThroughDate != "2026-06-30" {
|
||||
t.Fatalf("DataThroughDate = %q", got.DataThroughDate)
|
||||
}
|
||||
if got.ForecastStartDate != got.AsOfDate {
|
||||
t.Fatalf("ForecastStartDate = %q, want %q", got.ForecastStartDate, got.AsOfDate)
|
||||
}
|
||||
if got.Freshness != FreshnessStale || got.DataLagDays != 52 {
|
||||
t.Fatalf("freshness = %s, lag = %d", got.Freshness, got.DataLagDays)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyFreshnessUsesProductionBoundaries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
lag int
|
||||
want Freshness
|
||||
}{
|
||||
{name: "same day", lag: 0, want: FreshnessFresh},
|
||||
{name: "yesterday", lag: 1, want: FreshnessFresh},
|
||||
{name: "two days", lag: 2, want: FreshnessLagging},
|
||||
{name: "seven days", lag: 7, want: FreshnessLagging},
|
||||
{name: "eight days", lag: 8, want: FreshnessStale},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ClassifyFreshness(tt.lag); got != tt.want {
|
||||
t.Fatalf("ClassifyFreshness(%d) = %s, want %s", tt.lag, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user