1. 吉良野式支出管理
    1. コンセプト
    2. 登場人物
  2. 全体像
    1. Phase1. メモフェーズ
    2. Phase2. テキストデータ化
    3. Phase3. Excel 記入
      1. Excel フォーマット
      2. 変換(フォーマット)
      3. 変換(スクリプト)
      4. 書籍データの変換(フォーマット)
  3. TIPS
    1. その場でメモできない支出はどう記録するか
    2. 集計作業はどのように行うか
    3. カテゴリはどのように設計するか
  4. 参考

吉良野式支出管理

コンセプト

登場人物

全体像

Phase1. メモフェーズ

ガラケーの送信メールに以下フォーマットで記載。

[タイトル]
190510

[本文]
1839 3977*ガス代
1837 800*とんかつ
1803 120*電車
1437 265*トッボ
1308 120*コーヒー
1233 623*揚げ出し豆腐セット
0848 100*水
0809 124*電車

つまり一日ごとに「いつ、何に、何円使ったか」をメモしていく。

Phase2. テキストデータ化

ガラケーでメモしたデータは、一日の終わり(今日はもう支出しないというタイミングなど)に、Gmail に送信する。これを PC からアクセスしてコピペし、以下フォーマットで記録。

[ファイル名]
diary.dry

[中身]
*190510(Fri)
1839 3977*ガス代
1837 800*とんかつ
1803 120*電車
1437 265*トッボ
1308 120*コーヒー
1233 623*揚げ出し豆腐セット
0848 100*水
0809 124*電車

*190509(Thu)
...

ファイル名や記録方法は適当で良い(ここでは .dry ファイルをつくり、一日一見出しで記載している)。

Phase3. Excel 記入

このフェーズでは「テキストデータを Excel 用フォーマットに変換」した後、それを Excel にコピペする。

Excel フォーマット

以降では Excel は以下のようなフォーマットを想定する。

シート: 201905

日付 名前 カテゴリ 金額 flag 備考
2019/5/10 ガス代 公共料金 3977    
2019/5/10 とんかつ 夕食 900    
2019/5/10 電車 交通費 154 c  
2019/5/10 トッボ 間食 265 c  
2019/5/10 コーヒー 飲料 90 c  
2019/5/10 揚げ出し豆腐 昼食 623 c  
2019/5/10 飲料 100 c  
2019/5/10 電車 交通費 154 c  

シート:Category

朝食
昼食
夕食
飲料
間食
ゲーム
本:マンガラノベ
本:技術書
本:その他
交通費
日用品
清潔・身嗜み
病院・治療
PC関連
運動関連
家電・乗物
服
クレジットカード
収納
洗濯
公共料金
家賃

変換(フォーマット)

テキストデータを「Excel 上のフォーマット」に変換する。

Before:

1839 3977*ガス代
1837 800*とんかつ
1803 120*電車
1437 265*トッボ
1308 120*コーヒー
1233 623*揚げ出し豆腐セット
0848 100*水
0809 124*電車

After:

2019/05/13	ガス代	公共料金	3977
2019/05/13	とんかつ		800
2019/05/13	電車	交通費	120
2019/05/13	トッボ		265
2019/05/13	コーヒー	飲料	120
2019/05/13	揚げ出し豆腐セット		623
2019/05/13	水	飲料	100
2019/05/13	電車	交通費	124

変換(スクリプト)

変換スクリプトは Python 3 で以下のとおり。ただし一部自製ライブラリは割愛する。

# -*- coding: utf-8 -*-

import datetime
import os
import sys
from time import sleep

import pywintypes

from libclipboard import Clipboard

LINEBREAK = '\n'

def abort(msg):
    print(msg)
    exit(1)

def todaystr():
    return datetime.datetime.today().strftime('%Y/%m/%d')

def precise_clipget():
    c = 0
    cb = ''
    while True:
        try:
            cb = Clipboard.get()
            return cb
        except pywintypes.error:
            pass
        c += 1
        if c>100:
            raise RuntimeError('Clipboard.get() is failing...')
        sleep(0.05)

def clipboard2lines():
    cb = precise_clipget()
    if cb=='':
        abort('Clipboard is empty.')
    lines = cb.split(LINEBREAK)
    return lines

def lines2clipboard(lines):
    ret = LINEBREAK.join(lines)
    Clipboard.set(ret)

def booksline2booklines(line, todaystr, category, total_price):
    # 3969*7冊、ナナマルサンバツ123、すうの空気攻略1、食戟のソーマ28、氷菓11、信長の忍び13
    SEP = '冊、'

    line = line.strip(' \t\n\r')

    if line.find(SEP) == -1:
        return None

    lines = []

    booksline = line.split(SEP)[1]
    booknames = booksline.split('、')
    bookcount = len(booknames)

    def get_number_pos(s):
        for idx,c in enumerate(s):
            if c in '123456789':
                return idx
        return -1

    def split_kansus(kansu_part):
        # 3969*7冊、ナナマルサンバツ1 2 3、すうの空気攻略1
        #                            ^ ^ ^
        # 1,2,3, ← こうする
        if kansu_part.find(' ')==-1:
            return kansu_part

        new_kansu_str = ','.join(kansu_part.split(' '))

        return new_kansu_str

    def determin_incremented_bookcount(booknames):
        ret = 0
        for bookname in booknames:
            number_pos = get_number_pos(bookname)
            kansu_part = bookname[number_pos:]
            # Q: -1 の根拠は?
            # 1,2  -> ["1","2"]
            # 1    -> ["1"]      これでもカウント1になっちゃうのでこの分は省く
            count = len(kansu_part.split(' ')) - 1
            ret += count
        return ret

    # 分母となる巻数系は先に計算する.
    # (先に全部走査しきらないと正しい値にならない)
    bookcount += determin_incremented_bookcount(booknames)

    for bookname in booknames:
        flag_dummy = ''
        
        number_pos = get_number_pos(bookname)
        bookname_only = bookname[:number_pos]
        kansu_part = bookname[number_pos:]
        kansu_str = split_kansus(kansu_part)

        itemname = bookname_only
        note = '{},'.format(kansu_str)
        price = '={}/{}'.format(total_price, bookcount)

        new_line = '{0}\t{1}\t{2}\t{3}\t{4}\t{5}'.format(todaystr, itemname, category, price, flag_dummy, note)
        lines.append(new_line)

    return lines

convertions="""
朝食               | パン
昼食               | 
夕食               |
飲料               | いろはす,ライフガード,コーヒー,水,エメマン,モニショ,カフェオレ,カフェラテ,綾鷹,ジュース
間食               | カロリーメイト,チョコ,ちょこ,ポテチ,ポテトチップス,じゃがりこ,間食,菓子
ゲーム             | maimai,チュウニ,舞,パンプ,ダンス,パセリ
本:マンガラノベ   | 冊
本:技術書         |
本:その他         |
交通費             | タクシー,JR,バス,駐輪,jr,電車,新幹線
日用品             | リップクリーム,ムヒ,歯ブラシ,歯磨き,洗顔,洗う,シャンプー,電池,石鹸,日用,調味,ティッシュ
清潔・身嗜み       | 散髪
病院・治療         | 病院
PC関連             |
運動関連           |
家電・乗物         |
服                 |
クレジットカード   | github,slack,biglobe
公共料金           | 電気,ガス,水道
"""

convertion_dict = {}
convertion_lines = convertions.split(LINEBREAK)
for line in convertion_lines:
    if len(line)==0:
        continue

    catename, triggerwords = [elm.strip() for elm in line.split('|')]
    if len(triggerwords)==0:
        continue

    for triggerword in triggerwords.split(','):
        convertion_dict[triggerword] = catename

ret_lines = []
lines = clipboard2lines()
for line in lines:
    uline = line
    if uline.find('*')==-1:
        continue

    body = uline[5:]
    price, itemname = body.split('*')
    itemname = itemname[:-1] # なぜか改行文字が入っているので...
    itemname = itemname.split('。', 1)[0]
    price = int(price)
    today = todaystr()

    category = '' # @todo CSV前提にするなら空値より"★"とかの方が「要編集」だとわかりやすい?
    for key in convertion_dict:
        if itemname.find(key)!=-1:
            category = convertion_dict[key]
            break

    booklines_candidate = booksline2booklines(line, todaystr(), category, price)
    if booklines_candidate != None:
        booklines = booklines_candidate
        ret_lines.extend(booklines)
        continue

    ret = '{0}\t{1}\t{2}\t{3}'.format(today, itemname, category, price)
    ret_lines.append(ret)

lines2clipboard(ret_lines)

長々としているが、要点をまとめると以下のとおり。

要するに手作業で Before から After に直すのが面倒だから自動化している。方法は何でも良いし、自動化が難しいなら無理にしなくてもよい。

書籍データの変換(フォーマット)

この変換は必須ではないが、個人的によくマンガ・ライトノベルについては、何のタイトルの何の巻を買ったかを網羅しておきたかったため、少し力を入れて整えた。

Before:

3969*7冊、ナナマルサンバツ123、すうの空気攻略1、食戟のソーマ28、氷菓11、信長の忍び13

After:

2019/05/13	ナナマルサンバツ	本:マンガラノベ	=3969/5		123,
2019/05/13	すうの空気攻略	本:マンガラノベ	=3969/5		1,
2019/05/13	食戟のソーマ	本:マンガラノベ	=3969/5		28,
2019/05/13	氷菓	本:マンガラノベ	=3969/5		11,
2019/05/13	信長の忍び	本:マンガラノベ	=3969/5		13,

TIPS

その場でメモできない支出はどう記録するか

たとえばクレジットカードによる引き落としは月一で紙明細が届くか、Web で確認するようになっており、Phase1 のメモフェーズで記録するタイミングがない。このような支出はどうするか。

以下のようにする。

ルーチンタスク化については、たとえば私は以下のようにしている。

3 2019/05/15 Wed             zk 1 Biglobe メアド確認 rep:30
3 2019/05/15 Wed             zk 1 Biglobe ログインして請求確認(毎月13日に前月分出る)&当月分kakeibo記入 rep:30

これは Biglobe に絡む支出記入を、毎月 13 日の後に行うルーチンタスクである。 Tritask というタスク管理ツールを使っている。しかし手段は何でも良い。最も簡単なのは、デジタルカレンダーでリマインドを設定しておくことだろう。

集計作業はどのように行うか

月ごとに作ったシート(内のデータ)を一つのシートにまとめるのが良い。その際、月ごとや年ごとの集計が行えるよう、別途列を設けるのが望ましい。以下に例を示す。

日付 名前 カテゴリ 金額 Flag 備考
2013/3/10 オーザックうす塩 間食 128     3 2013
2013/3/10 弁当 唐揚げととん汁 昼食 580   おごり 3 2013

このようなシートをつくった後は、ピボットテーブル機能などで集計を行う。

カテゴリはどのように設計するか

試行錯誤するしかないが、自分の生活と照らし合わせると良い。

たとえば私の場合、食事がほぼ外食(自炊しない)であることと、各食事にかかった費用はちゃんと記録しておきたいことから「朝食」「昼食」「夕食」「飲料」「間食」というカテゴリをつくっている。もし私とは違い、自炊する派であれば、単に「食費」だけで済むだろう。あるいは「食費」「外食費」あたりだろうか。

また本については、私は「本:マンガラノベ」「本:技術書」「本:その他」としている。これも人によっては「本」一つで良かったり、逆に「本:小説」「本:マンガ」「本:ラノベ」「本:実用書」「本:図鑑」のような細分化もありえる。ちなみに、表示順でまとめやすくするために「本:」のような接頭辞は極力設けるのが望ましい。

参考

Back to the toppage.