From 7262f5f69b58b2b8aaee83519dd46fe373b790c0 Mon Sep 17 00:00:00 2001 From: Bel LaPointe <153096461+breel-render@users.noreply.github.com> Date: Sat, 31 Jan 2026 12:14:08 -0700 Subject: [PATCH] find unique to ledger --- cmd/cli/main.go | 90 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 34c5da3..c241694 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -13,6 +13,7 @@ import ( "os" "os/signal" "slices" + "strconv" "strings" "syscall" "time" @@ -376,6 +377,7 @@ func Main() { return strings.Contains(strings.ToLower(field), "amount") }) + csvDeltas := make(ledger.Deltas, 0) for { line, err := reader.Read() if err == io.EOF { @@ -393,29 +395,77 @@ func Main() { desc := line[descIdx] amount := line[amountIdx] - matches := deltas.Like( - func(delta ledger.Delta) bool { - if delta.Date < slices.Min(dates) { - return false - } - if delta.Date > slices.Max(dates) { - return false - } - if delta.Currency != ledger.USD { - return false - } - if fmt.Sprintf("%.2f", -1*delta.Value) != amount { - return false - } - return true - }, - ) - if len(matches) == 0 { - fmt.Printf("%s %s %s\n", strings.Join(dates, "="), desc, amount) + amountF, err := strconv.ParseFloat(amount, 32) + if err != nil { + log.Fatalf("non-float amount %q: %v", amount, err) } + csvDeltas = append(csvDeltas, ledger.Delta{ + Date: dates[0], + OtherDates: dates[1:], + Name: desc, + Value: amountF, + Currency: ledger.USD, + Description: desc, + Payee: true, + }) } - log.Fatalf("not impl: for all accounts matched, any unique deltas to those accounts not in csv within dates?") + matchies := func(deltas ledger.Deltas, delta ledger.Delta) (ledger.Deltas, []string) { + deltas = slices.Clone(deltas) + dates := append([]string{delta.Date}, delta.OtherDates...) + matches := deltas.Like(func(delta ledger.Delta) bool { + return delta.Date >= slices.Min(dates) + }) + matches = matches.Like(func(delta ledger.Delta) bool { + return delta.Date <= slices.Max(dates) + }) + matches = matches.Like(func(delta ledger.Delta) bool { + return delta.Currency == ledger.USD + }) + return matches.Like(func(d2 ledger.Delta) bool { + return fmt.Sprintf("%.2f", d2.Value) == fmt.Sprintf("%.2f", delta.Value) + }), dates + } + + datesMatched := []string{} + namesMatched := []string{} + for _, csvDelta := range csvDeltas { + matches, dates := matchies(deltas, csvDelta) + if len(matches) == 0 { + fmt.Printf("unique to csv | %s %s %.2f\n", strings.Join(dates, "="), csvDelta.Name, csvDelta.Value) + } + for _, match := range matches { + datesMatched = append(datesMatched, match.Date) + datesMatched = append(datesMatched, match.OtherDates...) + namesMatched = append(namesMatched, match.Name) + } + } + datesMatched = slices.DeleteFunc(datesMatched, func(a string) bool { return strings.TrimSpace(a) == "" }) + datesMatched = slices.Compact(datesMatched) + namesMatched = slices.Compact(namesMatched) + + deltas = deltas.Like(func(delta ledger.Delta) bool { + return delta.Date >= slices.Min(datesMatched) + }) + deltas = deltas.Like(func(delta ledger.Delta) bool { + return delta.Date <= slices.Max(datesMatched) + }) + deltas = deltas.Like(func(delta ledger.Delta) bool { + return slices.Contains(namesMatched, delta.Name) + }) + + deltasSum := deltas.Group(ledger.GroupDate(""), ledger.GroupName("")).Balances()[""][ledger.USD] + csvSum := csvDeltas.Group(ledger.GroupDate(""), ledger.GroupName("")).Balances()[""][ledger.USD] + if deltasSum != csvSum { + log.Printf("csv sum %.2f but deltas sum %.2f", csvSum, deltasSum) + } + + for _, delta := range deltas { + matches, dates := matchies(csvDeltas, delta) + if len(matches) == 0 { + fmt.Printf("unique to ledger | %s %s %.2f\n", strings.Join(dates, "="), delta.Description, delta.Value) + } + } default: log.Fatalf("unknown command %q", positional[0]) }