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. Their simple REST API was a main draw:
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, but my attempt was unsuccessful. That could be a task for the future.
crinklywrappr / pocketsmith-api
small wrapper around the pocketsmith api for clojure developers
com.github.crinklywrappr/pocketsmith-api
Clojure library for interacting with the Pocketsmith REST API. Small, opinionated, and hand-crafted.
Coordinates
com.github.crinklywrappr/pocketsmith-api {:mvn/version "1.0.37"}
Usage
First, require the library and define your user key. You can generate a key on the dashboard from your pocketsmith account.
(require '[crinklywrappr.pocketsmith-api.core :as ps])
(def mykey "xxx")
Then, begin querying.
(def myuser (ps/authorized-user mykey :convert? true :minify? true))
;=> {:name "John Doe",
:login "my-username"
:email "my-address@email.com",
:week-start-day 0,
:id 1111111,
:base-currency-code #object[org.joda.money.CurrencyUnit 0x4d1b5405 "USD"],
:time-zone #object[org.joda.time.tz.CachedDateTimeZone 0x468ce331 "America/Chicago"]}
(def mycategories (into [] (ps/categories mykey myuser :flatten? true :normalize? true :convert? true :minify? true)))
;=> [{:id XXX, :title "Charity", :parents []}
{:id XXX, :title "Taxes", :parents []}
{:id XXX, :title "Bank",
…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.
Call (acorns)
to get the desired amount.
Project setup
I prefer deps-new these days.
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.
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.
(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.
(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.
(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.
(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.
Top comments (0)