Compare commits

..

3 Commits

Author SHA1 Message Date
bel
6df7171057 /api/trends has PIES
All checks were successful
cicd / ci (push) Successful in 1m36s
2024-07-21 17:18:56 -06:00
bel
6be00d6423 /api/trends has 1 pie of median 2024-07-21 17:12:52 -06:00
bel
b8c78fe55e /api/trends prints pie of each of last N months 2024-07-21 17:03:44 -06:00
4 changed files with 112 additions and 0 deletions

View File

@@ -205,6 +205,10 @@
<summary><i>Look at this graph</i></summary>
<iframe style="background: white; width: 100%;" src="/api/reg?x=y&mode=reg&likeName=Withdrawal:[0123]&chart=stack&predictionMonths=6&prediction=autoContributions=&bpi=true&zoomStart=YYYY-MM"></iframe>
</details>
<details>
<summary><i>Where did the money go</i></summary>
<iframe style="background: white; width: 100%;" src="/api/trends"></iframe>
</details>
</details>
<details open style="display: none;">
<summary>Edit</summary>

View File

@@ -74,6 +74,8 @@ func (router Router) API(w http.ResponseWriter, r *http.Request) {
router.APITransactions(w, r)
case "/api/amend":
router.APIAmend(w, r)
case "/api/trends":
router.APITrends(w, r)
case "/api/reg", "/api/bal":
router.APIReg(w, r)
default:
@@ -110,6 +112,71 @@ func (router Router) APITransactions(w http.ResponseWriter, r *http.Request) {
})
}
func (router Router) APITrends(w http.ResponseWriter, r *http.Request) {
bpis, err := router.bpis()
if err != nil {
panic(err)
}
deltas, err := router.files.Deltas()
if err != nil {
panic(err)
}
recent := time.Hour * 24 * 365 / 2
pie := func(title, groupName string, min int) {
recentHouseRelatedDeltas := deltas.
Like(ledger.LikeTransactions(
deltas.Like(ledger.LikeName(`^House`))...,
)).
Like(ledger.LikeAfter(time.Now().Add(-1 * recent).Format("2006-01"))).
Group(ledger.GroupName(groupName)).
Group(ledger.GroupDate(`^[0-9]*-[0-9]*`)).
Like(ledger.LikeNotName(`^$`))
monthsToDeltas := map[string]ledger.Deltas{}
for _, delta := range recentHouseRelatedDeltas {
monthsToDeltas[delta.Date] = append(monthsToDeltas[delta.Date], delta)
}
months := []string{}
for k := range monthsToDeltas {
months = append(months, k)
}
slices.Sort(months)
catToMonth := map[string][]int{}
for _, month := range months {
balances := monthsToDeltas[month].Balances().WithBPIs(bpis)
for category, balance := range balances {
catToMonth[category] = append(catToMonth[category], int(balance[ledger.USD]))
}
}
chart := view.NewChart("pie")
ttl := 0
for cat, month := range catToMonth {
for i := 0; i < len(months)-len(month); i++ {
month = append(month, 0)
}
slices.Sort(month)
median := month[len(month)/2]
ttl += median
if median > min {
chart.AddY(cat, []int{median})
}
}
fmt.Fprintln(w, "<!DOCTYPE html><h2>", title, "($", ttl, ")</h2>")
if err := chart.Render(w); err != nil {
panic(err)
}
}
pie("Median Spending", `Withdrawal:[0-9]*`, 50)
pie("Median Spending (detailed)", `Withdrawal:[0-9]*:[^:]*`, 25)
pie("Median Spending (MORE detailed)", `Withdrawal:[0-9]*:[^:]*:[^:]*`, 10)
}
func (router Router) APIAmend(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)

View File

@@ -35,6 +35,12 @@ func LikeAfter(date string) Like {
}
}
func LikeNotName(pattern string) Like {
return func(d Delta) bool {
return !like(pattern, d.Name)
}
}
func LikeName(pattern string) Like {
return func(d Delta) bool {
return like(pattern, d.Name)

View File

@@ -23,6 +23,8 @@ func NewChart(name string) Chart {
return NewBar()
case "stack":
return NewStack()
case "pie":
return NewPie()
default:
panic("bad chart name " + name)
}
@@ -107,3 +109,36 @@ func (stack Stack) AddY(name string, v []int) {
Stack: "stackA",
}))
}
type Pie struct {
*charts.Pie
series []opts.PieData
}
func NewPie() *Pie {
return &Pie{Pie: charts.NewPie()}
}
func (pie *Pie) AddX(v interface{}) {
}
func (pie *Pie) Render(w io.Writer) error {
pie.AddSeries("", pie.series)
pie.SetGlobalOptions(charts.WithLegendOpts(opts.Legend{
Show: false,
}))
return pie.Pie.Render(w)
}
func (pie *Pie) AddY(name string, v []int) {
for _, v := range v {
pie.series = append(pie.series, opts.PieData{
Name: fmt.Sprintf("%s ($%d)", name, v),
Value: v,
})
}
}
func (pie *Pie) Overlap(other Chart) {
panic("nope")
}