Having fun with money
Tags: clojure, finance
Published: 2023-06-20 15:05:45

I reached a significant financial milestone this month and am focusing on new goals.  Like solving a performance problem, you should always measure first.

After poking around at different account aggregation tools, I settled on [pocketsmith](https://www.pocketsmith.com/).  Their simple REST API was a main draw:

- [Docs](https://developers.pocketsmith.com/docs)
- [OpenAPI](https://github.com/pocketsmith/api/blob/master/openapi.json)

That task alone wasn't interesting enough, so I wrote a small Clojure library to talk to the API.  Ideally, it would leverage something like [martian](https://github.com/oliyh/martian), but my attempt was unsuccessful.  That could be a task for the future.

{% embed https://github.com/crinklywrappr/pocketsmith-api/ %}

`pocketsmith-api` uses Joda Time & Joda Money and has 99% code coverage.  Because it deals with money, I wanted to ensure it read amounts accurately in various currencies.

That said, Joda Money has some limitations.  I have listed some on the README.

Let's build something with it!

## Acorns...?

Acorns is a savings tool that, in the past, worked by sending users monthly reports of how much they could save if they rounded up all their charges to the nearest dollar and transferred the difference to a savings account.

They may do more now.

I always thought that idea was excellent but I wanted to avoid paying for it.  With Pocketsmith, we can accomplish that easily.

Here is the code.

{% embed https://gist.github.com/crinklywrappr/f2693daa046e762e687fd47f806eca7e %}

Call `(acorns)` to get the desired amount.

## Project setup

I prefer [deps-new](https://github.com/seancorfield/deps-new) these days.

```bash
clojure -Tnew lib :name <project-name>
```

Add `crinklywrappr/pocketsmith-api` to your `deps.edn`.  Clojure will pull in all of the other dependencies you need transitively.

```clojure
com.github.crinklywrappr/pocketsmith-api {:mvn/version "1.0.37"}
```

## Sanity checks

I like to be extra careful when dealing with money.  Please verify some elementary assertions before borrowing this code for any purpose.  I will demonstrate some tests on my account and list additional applicable checks.

The Clojure repl is unmatched for this.

### Am I looking at the correct accounts? ✓

I only want to consider credit card accounts.

```clojure
(into [] (r/map :name (cards (user))))
;=>
["Amex card" "Food card" "Amazon card" "Gas card" "Points card"]
```

### Am I requesting the correct transactions? ✓

Specifically, we only want to look at last month's debit transactions.  The current date is June 19, 2023.

```clojure
(last-month-query (user))
;=>
{:start_date "2023-05-01", :end_date "2023-05-31", :type "debit", :per_page 100}
```

### Am I receiving the correct transactions? ✓

Easy.  We can sort the transactions by date and inspect the first and last elements.

```clojure
(let [me (user)]
  (->> (last-month-charges me (cards me))
       (sort-by :date clj-time.core/before?)
       ((juxt first last))
       (mapv #(select-keys % [:date :payee]))))
;=>
[{:date
  #object[org.joda.time.LocalDateTime 0x5df45749 "2023-05-01T00:00:00.000"],
  :payee "SPOTIFY NEW YORK NY P22C7B5A33 XXXXXX1161"}
 {:date
  #object[org.joda.time.LocalDateTime 0x140ae942 "2023-05-31T00:00:00.000"],
  :payee "DOORDASH*LUNCH BOX CSAN FRANCIS NT_NZXE7CMB +XXXXXXX9470"}]
```

### Does the math make sense? ✓

We can do this by taking 10 of the transactions and eyeballing the numbers.

```clojure
(require '[clojurewerkz.money.format :as mf])
(let [me (user)]
  (clojure.pprint/print-table
   (->> (last-month-charges me (cards me))
        (map
         (fn [x]
           {:rounded (mf/format (round-up x))
            :amount (mf/format (:amount x))
            :difference (mf/format (difference x))}))
        (take 10))))

| :rounded | :amount | :difference |
|----------+---------+-------------|
|  $-55.00 | $-54.80 |       $0.20 |
|  $-26.00 | $-25.44 |       $0.56 |
|  $-29.00 | $-28.02 |       $0.98 |
|  $-23.00 | $-22.19 |       $0.81 |
|   $-3.00 |  $-2.99 |       $0.01 |
|  $-38.00 | $-37.94 |       $0.06 |
|  $-50.00 | $-49.81 |       $0.19 |
|  $-16.00 | $-15.44 |       $0.56 |
|  $-24.00 | $-23.90 |       $0.10 |
|   $-1.00 |  $-0.64 |       $0.36 |

```

`$54.80` rounds up to `$55.00`, and the difference is `$0.20`, and so forth.

### Additional checks

There are other assertions you may want to test, and I encourage you to do so.

- Are all the differences under `$1.00`?
- Does `difference` respond with `$0.00` when given an even dollar charge?
- Is the total amount less than the number of charges?
- Are all the charges truly debits (negative dollar amounts)?

## Conclusion

This project was a fun distraction while gearing up for my next financial goals.  Hopefully, this selfish exercise will help someone else.  😉

By the way, I need to squirrel away `$40.63` from last month.