diff --git a/ana/prediction.go b/ana/prediction.go index 2872af8..ef32d79 100644 --- a/ana/prediction.go +++ b/ana/prediction.go @@ -1,3 +1,44 @@ package ana +import ( + "maps" + "time" + + "gogs.inhome.blapointe.com/ana-ledger/ledger" +) + type Prediction []Predictor + +func NewPrediction(predictor ...Predictor) Prediction { + return Prediction(predictor) +} + +func (prediction Prediction) Predict(register ledger.Register, window time.Duration) ledger.Register { + return prediction.predict(register.Latest(), time.Now(), predictionTimes(window)) +} + +func (prediction Prediction) predict(latest ledger.Balances, from time.Time, these []time.Time) ledger.Register { + latestT := from + result := make(ledger.Register) + for i := range these { + k := these[i].Format("2006-01") + result[k] = make(ledger.Balances) + for k2, v2 := range result[k] { + result[k][k2] = maps.Clone(v2) + } + + elapsed := these[i].Sub(latestT) + result[k] = prediction.predictOne(result[k], elapsed) + + latest = result[k] + latestT = these[i] + } + return result +} + +func (prediction Prediction) predictOne(balances ledger.Balances, elapsed time.Duration) ledger.Balances { + for i := range prediction { + balances = prediction[i](balances, elapsed) + } + return balances +} diff --git a/ana/prediction_test.go b/ana/prediction_test.go new file mode 100644 index 0000000..d5b7b04 --- /dev/null +++ b/ana/prediction_test.go @@ -0,0 +1,54 @@ +package ana + +import ( + "fmt" + "maps" + "testing" + "time" + + "gogs.inhome.blapointe.com/ana-ledger/ledger" +) + +func TestPredictionPredict(t *testing.T) { + inc := func(b ledger.Balances, _ time.Duration) ledger.Balances { + result := make(ledger.Balances) + for k, v := range b { + result[k] = maps.Clone(v) + for k2, v2 := range result[k] { + result[k][k2] = v2 + 1 + } + } + return result + } + double := func(b ledger.Balances, _ time.Duration) ledger.Balances { + result := make(ledger.Balances) + for k, v := range b { + result[k] = maps.Clone(v) + for k2, v2 := range result[k] { + result[k][k2] = v2 * 2 + } + } + return result + } + from := time.Now() + these := []time.Time{time.Now().Add(time.Hour * 24 * 365 / 12)} + _, _ = inc, double + + cases := map[string]struct { + prediction Prediction + given ledger.Balances + want ledger.Register + }{ + "empty": {}, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + got := c.prediction.predict(c.given, from, these) + if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", c.want) { + t.Errorf("want\n\t%+v, got\n\t%+v", c.want, got) + } + }) + } +} diff --git a/ledger/register.go b/ledger/register.go index 9dc66c0..b223c7d 100644 --- a/ledger/register.go +++ b/ledger/register.go @@ -9,6 +9,15 @@ import ( type Register map[string]Balances +func (register Register) Latest() Balances { + dates := register.Dates() + if len(dates) == 0 { + return nil + } + date := dates[len(dates)-1] + return register[date] +} + func (register Register) WithBPIs(bpis BPIs) Register { result := make(Register) for d := range register {