paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

マネートラッキングをFireflyに一本化する API投入編

前回Firefly IIIのAPIを確認したところでクレジット支払いのcsvファイルを変換して投入したいと思います。

目的のおさらい

クレジットの明細csvファイルをPOST用のフォーマットに変換してAPI経由で投入する

カテゴリー整理

クレジットカードは翌月の支払いになるのでいつの支払い扱いにすればいいか迷いますが、 マネートラッキングということでシンプルに支払いが来た月の支出にします。

私はほとんどキャッシュレス生活なので支払いのカテゴリを下記の形に分けます。

  • スーパー
  • サブスク
  • 買い物(食材以外)
  • 交際費(飲み屋系)
  • Suica払い
  • その他突発的は払いは分類しない

POSTフォーマット

transactionsが資金移動の項目です。

公式ドキュメントにはフルセット書いてありますが、issueを探したら以下の形でOKでした。
リクエストの必須パラメータが書いてないので分かりずらいですね。
No parametersなので何でもいいのかな。

{
  "transactions": [
    {
      "type": "withdrawal",
      "date": "2021-03-26T00:00:00+09:00", # 支払日
      "amount": "xxxx", # 金額
      "description": "xxxx", # 支払い項目
      "currency_id": 16, # JPY
      "category_id": xxxx, # カテゴリ
      "source_id": 5, # トラッキング用アカウント 
      "destination_id": 36 # クレジット払いのアカウント
    }
  ]
}

csvの変換コード

4月分の請求はまだ来ていないので3月分で作ってみます。

csvの内容

クレジットの明細は以下の形です。

2021/01/31,インターネットイニシアティブ,1829,,,,
2021/01/31,ヤフージャパン,508,,,,
2021/02/01,J:COM サービス利用料,5729,,,,
2021/02/03,JR東日本モバイルSuica,2000,,,,
2021/02/04,イトーヨーカドー 食品,3237,,,,イトーヨーカドーXXX店 食料品
2021/02/05,JR東日本モバイルSuica,2000,,,,
2021/02/06,イオンリテール,1282,,,,
2021/02/06,イオンリテール,4121,,,,
2021/02/10,JR東日本モバイルSuica,3000,,,,
…
pythonで変換

何で作るか迷いましたがとりあえず慣れているpythonで作ります。
csvから読み込んで出力は何度もやっているので私としてはおなじみの形です。
テンプレに明細、金額、カテゴリを渡しているだけです。

分類分けが長いので抜粋版です。

import csv

with open('202103.csv', 'r') as f: 
    reader = csv.reader(f)
    data = [x for x in reader]

text = '"{{ "transactions": [ {{ "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "{}", \
"description": "{}", "currency_id": 16, "category_id": {}, "source_id": 5, "destination_id": 36 }} ] }}"'

for a in data[1:-3]: # data内のヘッダ、フッタは渡さない
  if 'ヤフーかんたん決済' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),9))
  elif 'JR東日本モバイルSuica' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),7))
  elif 'イオンリテール' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),6))
  elif 'イトーヨーカドー' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),6))
  elif 'インターネットイニシアティブ' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),8))
  elif 'AMAZON.CO.JP' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),9))
  elif 'DIGITALOCEAN.COM' in a[1]:
      print(text.format(a[2],' '.join([a[0],a[1]]),8))
  elif 'NETFLIX.COM' in a[1]:

出力する

ワンライナーで出力されて記事内だと分かりづらいので改行してます。

masashi@DESKTOP-986MNSO:/mnt/c/Users/tsuru/tools/expenses$ python3 transactionapi.py
"{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "1829", \
"description": "2021/01/31 インターネットイニシアティブ", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }"
"{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "508", \
"description": "2021/01/31 ヤフージャパン", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }"
"{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "5729", ¥
"description": "2021/02/01 J:COM サービス利用料", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }"

いいですね。

API用のコマンドと結合

POST用にcurlコマンドと結合します。
後々何で作るかは考えますが、とりあえずはshellで回して作成します。

masashi@DESKTOP-986MNSO:/mnt/c/Users/tsuru/tools/expenses$ python3 transactionapi.py | while read line ; do echo "curl -X POST http://192.168.0.10/api/v1/transactions -H \"accept: application/json\" -H \"Authorization: Bearer \$(cat 'firefl
y token.txt')\" -H \"Content-Type: application/json\" -d ${line}" ; done
curl -X POST http://192.168.0.10/api/v1/transactions -H "accept: application/json" -H "Authorization: Bearer $(cat 'firefly token.txt')" -H "Content-Type: application/json" -d "{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "1829", "description": "2021/01/31 インターネットイニシアティブ", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }"
curl -X POST http://192.168.0.10/api/v1/transactions -H "accept: application/json" -H "Authorization: Bearer $(cat 'firefly token.txt')" -H "Content-Type: application/json" -d "{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "508", "description": "2021/01/31 ヤフージャパン", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }"

OK。 いい感じです。
そのまま流すのは怖いのでいったんテキストに吐いてから流していきます。

サンプルとして1つ張ります。

masashi@DESKTOP-986MNSO:/mnt/c/Users/tsuru/tools/expenses$ curl -X POST http://192.168.0.10/api/v1/transactions -H "accept: application/json" -H "Authorization: Bearer $(cat 'firefly token.txt')" \
-H "Content-Type: application/json" -d '{ "transactions": [ { "type": "withdrawal", "date": "2021-03-26T00:00:00+09:00", "amount": "508", \
"description": "2021/01/31 ヤフージャパン", "currency_id": 16, "category_id": 8, "source_id": 5, "destination_id": 36 } ] }'
{"data":{"type":"transactions",
"id":"103",
"attributes":{"created_at":"2021-04-24T18:55:18+09:00",
"updated_at":"2021-04-24T18:55:18+09:00",
"user":1,
"group_title":null,
"transactions":[{"user":1,
"transaction_journal_id":103,
"type":"withdrawal",
"date":"2021-03-26T00:00:00+09:00",
"order":0,
"currency_id":16,
"currency_code":"JPY",
"currency_name":"Japanese yen",
…

レスポンスbodyが返ってきてますのでOKそうです。(改行編集済み)

というワケで一通り流した後の画面がこちら。

f:id:paloma69:20210425122836p:plain

編集してますが、Firefly iiiの画面です。
項目を書くだけで集計してくれて非常に便利です。
デザインもシンプルながらカッコいいです。

今回流したものはちゃんとページ下部に追加されてます。

まとめ

というわけでクレジットの明細をFirefly iiiに一本化することができました。
やってることはpythonでのテキスト変換とshellでのPOSTと簡単なことですが、
あとはFireflyがやってくれるので非常に楽ちんです。
ツールはしばらくshellとpythonでやりますが、不便が出てきたらまた考えます。

月次で回せる様変換コードに日付の自動挿入をつくらないとですね。
Suicaはチャージ分しか明細に乗ってこないので、ここの詳細化もしたいところです。

また、改行はしましたが全体的にコマンドが長くて読みにくくなってしまいました。
すいません。

おまけ

投入データのテンプレートを書くときに遭遇したのですが、波括弧を含んだfornatは括弧を2重にしないとエラーになってしまう様です。

>>> print('{ "price": {} }'.format(100))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ' "price"'
>>>
>>> print('{{ "price": {} }}'.format(100))
{ "price": 100 }