Skip to content

テーブル定義書

関連: 月次予算 / 支出の保存 / 収入 / PostgreSQL

正は sql/ のマイグレーションである。 Bot 起動時に src/finance_manager/db.py の SCHEMA_FILES 順で適用する。 金額の初期値はスキーマとは別で、sql/budget_seed.example.sql を手動実行する。

項目 値
DB 名 finance_manager
書込ユーザー finance_app(Bot)
読取ユーザー finance_readonly(Grafana)
分類の正 テーブルではなく src/finance_manager/taxonomy.py

全体像

incomes (給与) ──► budget_months.ceiling_yen
budget_settings / category_budgets ──► 初回 draft のフォールバック
fixed_cost_items ──► budget_months.fixed_costs_yen / variable_pool_yen
budget_month_categories ──► 変動カテゴリ枠
expenses ──► 変動消化(固定費カテゴリは除外)
surplus_ledger ◄── 未割当 / 月末実残

expenses ◄── notification_raw.expense_id(任意)
wishlist_items / planned_expenses … 固定貯金、別枠とは別

外部キーとして存在する線は、月ヘッダと変動枠、通知と支出の2本だけである。 給与、固定費マスタ、seed、別枠はアプリが読んで計算する。

物理 ER

erDiagram
  budget_months ||--|{ budget_month_categories : "year_month"
  expenses ||--o{ notification_raw : "expense_id"

  expenses {
    bigint id PK
    int amount_yen
    text category
    timestamptz paid_at
  }
  incomes {
    bigint id PK
    int amount_yen
    text category
    timestamptz received_at
  }
  budget_months {
    date year_month PK
    int ceiling_yen
    int savings_yen
    int fixed_costs_yen
    int variable_pool_yen
    text status
  }
  budget_month_categories {
    date year_month PK
    text category PK
    int monthly_limit_yen
  }
  fixed_cost_items {
    bigint id PK
    text name UK
    int monthly_yen
    text expense_category
    boolean active
  }
  surplus_ledger {
    bigint id PK
    date year_month
    int amount_yen
    text reason
  }
  budget_settings {
    int id PK
    int monthly_income_yen
    int monthly_savings_yen
  }
  category_budgets {
    text category PK
    int monthly_limit_yen
  }
  notification_raw {
    bigint id PK
    bigint expense_id FK
  }
  wishlist_items {
    bigint id PK
    text name
    int target_yen
  }
  planned_expenses {
    bigint id PK
    text name
    int target_yen
    date deadline
  }

wishlist_items と planned_expenses は他テーブルとつながっていない。 固定貯金、別枠貯金とも別の袋である。

予算の流れ

草案を作るとき、本来の値(前月給与、前月の変動枠)が無ければ seed を使う。 点線がフォールバックである。

flowchart LR
  subgraph 本来の正
    I["incomes 前月の給与"] --> BM["budget_months 今月"]
    FC["fixed_cost_items"] --> BM
    BMC["budget_month_categories"] --> BM
  end

  subgraph フォールバック
    BS["budget_settings"] -.->|給与が無いとき天井| BM
    CB["category_budgets"] -.->|初月だけ枠のひな型| BMC
  end

  E["expenses 変動カテゴリ"] --> SP["変動の消化"]
  BM --> U["未割当 / 月末実残"]
  U --> SL["surplus_ledger 別枠"]

適用順(SCHEMA_FILES)

ファイル 内容
001〜004 expenses 本体、場面、内訳、小分類
005 budget_settings / category_budgets(フォールバック)
006 incomes
007 budget_months / budget_month_categories
008 wishlist_items / planned_expenses
009 notification_raw
010 旧大分類の読み替え(娯楽、衣服、交通 → 趣味 など)
011 fixed_cost_items / surplus_ledger / 月ヘッダ拡張

sql/grafana_readonly_grants.sql は Grafana 用 GRANT の手作業ファイルで、起動時には走らない。


expenses(支出)

1件の支出を置く。 Discord 手動が本線である。

列 型 制約 意味
id BIGSERIAL PK 支出 ID
source TEXT NOT NULL, default discord 取込元
discord_user_id TEXT 投稿者
amount_yen INTEGER NOT NULL, > 0 金額(円)
merchant TEXT 店名など
category TEXT 大分類(taxonomy.py)
memo TEXT メモ
paid_at TIMESTAMPTZ 利用日時(UTC で保存)。不明なら null(集計は created_at にフォールバック)。Gemini が返す日時はオフセットの有無に関わらず 日本時間の壁時計 として解釈する(Z / +00:00 付きでも時計の数字を JST にする)。今日/月の集計は (paid_at AT TIME ZONE 'Asia/Tokyo') と JST 壁時計の naive 境界を比較する(UTC に直した境界を渡さない)。境界は db.jst_day_window / db.jst_range_window / db.month_bounds だけで組む
raw_text TEXT NOT NULL 元メッセージ
occasion TEXT 場面(朝食 / 夕食 / 遊び など)
occasion_reason TEXT 場面推定の短い根拠
line_items JSONB レシート内訳。読めなければ null
subcategories TEXT[] NOT NULL, default {} 小分類(最大5目安)
created_at TIMESTAMPTZ NOT NULL, now() 記録日時

インデックス: paid_at, created_at, subcategories(GIN)

大分類の現行値は 支出の保存 / 月次予算 を参照する。


incomes(収入)

支出とは別テーブルである。 ID も別採番し、返事では income_id= と出す。

列 型 制約 意味
id BIGSERIAL PK 収入 ID
source TEXT NOT NULL, default discord 取込元
discord_user_id TEXT 投稿者
amount_yen INTEGER NOT NULL, > 0 金額
category TEXT 給与 / ボーナス / 臨時 / 立替精算 / 給付 / その他
counterparty TEXT 支払元
memo TEXT メモ
received_at TIMESTAMPTZ 入金日時。不明なら created_at
raw_text TEXT NOT NULL 元メッセージ
created_at TIMESTAMPTZ NOT NULL, now() 記録日時

予算上限に使うのは category = '給与' の前月合計だけである。 ボーナスは記録するが、上限には入れない。


budget_settings / category_budgets(フォールバック)

行はほぼ seed 用である。 月次の正は budget_months 側にある。

budget_settings

1行だけ置く(id = 1)。

列 型 制約 意味
id INTEGER PK, = 1 固定
monthly_income_yen INTEGER NOT NULL, > 0 前月給与が無いときの天井フォールバック
monthly_savings_yen INTEGER NOT NULL, default 50000, >= 0 固定貯金の初期値
updated_at TIMESTAMPTZ NOT NULL

category_budgets

列 型 制約 意味
category TEXT PK 変動大分類
monthly_limit_yen INTEGER NOT NULL, >= 0 月次枠
note TEXT

当月行が無いとき、ここから budget_month_categories へコピーする。 その他 と固定費カテゴリは置かない。


budget_months(月ヘッダ)

year_month はその月の1日である。

列 型 制約 意味
year_month DATE PK 対象月(1日)
ceiling_yen INTEGER NOT NULL, >= 0 上限=前月の給与合計
savings_yen INTEGER NOT NULL, default 50000, >= 0 固定貯金
fixed_costs_yen INTEGER NOT NULL, default 0, >= 0 確定時スナップショットの固定費合計
variable_pool_yen INTEGER NOT NULL, default 0 ceiling − savings − fixed_costs
unallocated_to_surplus_yen INTEGER NOT NULL, default 0 月初確定で別枠へ入れた未割当
month_end_surplus_yen INTEGER 月末締めで別枠へ入れた実残
status TEXT draft / confirmed / closed 未確定 / 確定 / 月末締め済み
created_at TIMESTAMPTZ NOT NULL
updated_at TIMESTAMPTZ NOT NULL

draft 中は、前月給与と固定費マスタから天井と変動枠を取り直す。


budget_month_categories(変動カテゴリ枠)

列 型 制約 意味
year_month DATE PK, FK → budget_months 対象月
category TEXT PK, <> 'その他' 変動大分類
monthly_limit_yen INTEGER NOT NULL, >= 0 その月の枠
note TEXT

親行削除で CASCADE する。 固定費名(家賃、通信など)はここに入れない。


fixed_cost_items(固定費マスタ)

毎月引く定額リストである。 実績支出とは別に「引く額」を持つ。

列 型 制約 意味
id BIGSERIAL PK
name TEXT NOT NULL, UNIQUE 項目名(家賃+駐車場 など)
monthly_yen INTEGER NOT NULL, >= 0 月額
expense_category TEXT 実績集計用の expenses.category(任意)
active BOOLEAN NOT NULL, default TRUE FALSE なら合計から除外
note TEXT
updated_at TIMESTAMPTZ NOT NULL

expense_category が付いている支出は、変動枠の消化に入れない。 未設定(例: 散髪)は引くだけである。


surplus_ledger(別枠貯金)

固定貯金とは別の袋である。 正は入金、負は出金とする。

列 型 制約 意味
id BIGSERIAL PK
year_month DATE 対象月(手動は null 可)
amount_yen INTEGER NOT NULL 金額
reason TEXT month_unallocated / month_end_remainder / manual 理由
note TEXT
created_at TIMESTAMPTZ NOT NULL
reason いつ増えるか
month_unallocated Discord「このままで」(変動枠のうち未割当)
month_end_remainder Discord「月末締め」(変動の実残)
manual 手入力の入出金(取り崩し UI は未実装)

同じ月、同じ reason は二重に積まない。


wishlist_items / planned_expenses

固定貯金、別枠貯金とはさらに別である。

wishlist_items

列 型 制約 意味
id BIGSERIAL PK
name TEXT NOT NULL 名前
target_yen INTEGER NOT NULL, > 0 目標額
saved_yen INTEGER NOT NULL, default 0, >= 0 進捗
status TEXT open / bought / cancelled
discord_user_id TEXT
created_at / updated_at TIMESTAMPTZ NOT NULL

planned_expenses

wishlist に近い。 違うのは deadline(DATE)と status open / done / cancelled である。


notification_raw(通知の生データ)

PayPay 等の Android 通知を置く。 自動取込は後段である。

列 型 制約 意味
id BIGSERIAL PK
source TEXT default android_notification
app / title / body_text TEXT 通知の見た目
received_at TIMESTAMPTZ
payload JSONB NOT NULL 生 JSON
dedupe_key TEXT UNIQUE(NULL 以外) 二重取込防止
expense_id BIGINT FK → expenses, ON DELETE SET NULL 支出化できたとき
skipped_reason TEXT 支出にしなかった理由
created_at TIMESTAMPTZ NOT NULL

権限

Grafana は SELECT のみである。 finance_readonly への GRANT は各 sql/*.sql の末尾、または sql/grafana_readonly_grants.sql にある。