[cache] cache XIRR computation

refer #127
This commit is contained in:
Anantha Kumaran 2024-02-02 19:57:27 +05:30
parent 4551a854bf
commit 798154bdf0
6 changed files with 91 additions and 10 deletions

2
go.mod
View File

@ -47,6 +47,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kelindar/binary v1.0.18 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/labstack/echo/v4 v4.11.1 // indirect
github.com/labstack/gommon v0.4.0 // indirect
@ -57,6 +58,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect

4
go.sum
View File

@ -97,6 +97,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kelindar/binary v1.0.18 h1:xGKmvb6Q3bpvvhKULfB0rG5vLOjtpoZn/FgPpnTl+aI=
github.com/kelindar/binary v1.0.18/go.mod h1:/twdz8gRLNMffx0U4UOgqm1LywPs6nd9YK2TX52MDh8=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@ -136,6 +138,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

58
internal/model/cache/cache.go vendored Normal file
View File

@ -0,0 +1,58 @@
package cache
import (
"fmt"
"time"
"github.com/kelindar/binary"
"github.com/mitchellh/hashstructure/v2"
"gorm.io/gorm"
)
type Cache struct {
ID uint `gorm:"primaryKey" json:"id"`
ExpiresAt time.Time `json:"expires_at"`
HashKey string `json:"hash_key"`
Value []byte `gorm:"type:BLOB" json:"item"`
}
func DeleteExpired(db *gorm.DB) error {
err := db.Exec("DELETE FROM caches WHERE expires_at < ?", time.Now()).Error
if err != nil {
return err
}
return nil
}
func Lookup[I any, K any](db *gorm.DB, key K, fallback func() I) I {
var item I
var cache Cache
hash, err := hashstructure.Hash(key, hashstructure.FormatV2, nil)
hashKey := fmt.Sprintf("%d", hash)
if err == nil {
err := db.Where("hash_key = ?", hashKey).First(&cache).Error
if err == nil {
if time.Now().Before(cache.ExpiresAt) {
err := binary.Unmarshal(cache.Value, &item)
if err == nil {
return item
}
} else {
DeleteExpired(db)
}
}
}
item = fallback()
bytes, err := binary.Marshal(item)
if err == nil {
cache = Cache{
ExpiresAt: time.Now().Add(24 * time.Hour),
HashKey: hashKey,
Value: bytes,
}
db.Save(&cache)
}
return item
}

View File

@ -6,6 +6,7 @@ import (
"github.com/ananthakumaran/paisa/internal/config"
"github.com/ananthakumaran/paisa/internal/ledger"
"github.com/ananthakumaran/paisa/internal/model/cache"
"github.com/ananthakumaran/paisa/internal/model/cii"
"github.com/ananthakumaran/paisa/internal/model/commodity"
mutualfundModel "github.com/ananthakumaran/paisa/internal/model/mutualfund/scheme"
@ -29,6 +30,7 @@ func AutoMigrate(db *gorm.DB) {
db.AutoMigrate(&portfolio.Portfolio{})
db.AutoMigrate(&price.Price{})
db.AutoMigrate(&cii.CII{})
db.AutoMigrate(&cache.Cache{})
}
func SyncJournal(db *gorm.DB) (string, error) {

View File

@ -1,6 +1,7 @@
package service
import (
"github.com/ananthakumaran/paisa/internal/model/cache"
"github.com/ananthakumaran/paisa/internal/model/posting"
"github.com/ananthakumaran/paisa/internal/utils"
"github.com/ananthakumaran/paisa/internal/xirr"
@ -26,7 +27,9 @@ func XIRR(db *gorm.DB, ps []posting.Posting) decimal.Decimal {
}))
cashflows = append(cashflows, xirr.Cashflow{Date: today, Amount: marketAmount.Round(4).InexactFloat64()})
return xirr.XIRR(cashflows)
return cache.Lookup(db, cashflows, func() decimal.Decimal {
return xirr.XIRR(cashflows)
})
}
func APR(db *gorm.DB, ps []posting.Posting) decimal.Decimal {
@ -38,5 +41,8 @@ func APR(db *gorm.DB, ps []posting.Posting) decimal.Decimal {
return xirr.Cashflow{Date: p.Date, Amount: p.Amount.Round(4).InexactFloat64()}
})
cashflows = append(cashflows, xirr.Cashflow{Date: today, Amount: marketAmount.Neg().Round(4).InexactFloat64()})
return xirr.XIRR(cashflows)
return cache.Lookup(db, cashflows, func() decimal.Decimal {
return xirr.XIRR(cashflows)
})
}

View File

@ -159,7 +159,7 @@
<div class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/assets/networth">Assets</a>
<a class="secondary-link has-text-grey" href="/assets/networth">Assets</a>
</p>
<div class="content">
<div>
@ -201,7 +201,8 @@
<article class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/assets/balance">Checking Balance</a>
<a class="secondary-link has-text-grey" href="/assets/balance">Checking Balance</a
>
</p>
<div class="content">
<UntypedMasonryGrid gap={10} maxStretchColumnSize={400} align="stretch">
@ -220,7 +221,7 @@
<div class="tile is-parent">
<article class="tile is-child min-w-0">
<p class="subtitle">
<a class="secondary-link" href="/cash_flow/monthly">Cash Flow</a>
<a class="secondary-link has-text-grey" href="/cash_flow/monthly">Cash Flow</a>
</p>
<div class="content box px-2 pb-0">
<ZeroState item={cashFlows}>
@ -243,7 +244,7 @@
<div class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/expense/budget">Budget</a>
<a class="secondary-link has-text-grey" href="/expense/budget">Budget</a>
</p>
<div class="content">
<div>
@ -262,7 +263,7 @@
<article class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/more/goals">Goals</a>
<a class="secondary-link has-text-grey" href="/more/goals">Goals</a>
</p>
<div class="content">
{#each goalSummaries as goal}
@ -280,7 +281,7 @@
<article class="tile is-child">
<p class="subtitle is-flex is-justify-content-space-between is-align-items-end">
<span
><a class="secondary-link" href="/expense/monthly">Expenses</a>
><a class="secondary-link has-text-grey" href="/expense/monthly">Expenses</a>
<span class="is-size-5 has-text-weight-bold px-2" style="color: {COLORS.expenses}"
>{formatCurrency(totalExpense)}</span
></span
@ -301,7 +302,8 @@
<article class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/cash_flow/recurring">Recurring</a>
<a class="secondary-link has-text-grey" href="/cash_flow/recurring">Recurring</a
>
</p>
<div class="content box">
<div
@ -324,7 +326,9 @@
<article class="tile is-child">
<div class="content">
<p class="subtitle">
<a class="secondary-link" href="/ledger/transaction">Recent Transactions</a>
<a class="secondary-link has-text-grey" href="/ledger/transaction"
>Recent Transactions</a
>
</p>
<div>
<UntypedMasonryGrid gap={10} maxStretchColumnSize={500} align="stretch">
@ -349,4 +353,9 @@
p.subtitle {
margin-bottom: 0.5rem !important;
}
p.subtitle a.secondary-link {
text-transform: uppercase;
font-size: 1rem;
}
</style>