メインコンテンツへスキップ
Beancount.io Logo

beaによる簿記の自動化

beaでBeancountの台帳をスクリプト化する: 台帳を明示的に解決し、jqでJSONエンベロープを解析し、終了コードで分岐し、無人で実行し、夜間チェックをスケジュールする。

スクリプトはbeaを4つの決定で駆動します: 読み取る台帳、機械可読な出力のための--json、必要な値のためのjq、そして分岐する終了コードです。このガイドでは、これら4つの決定を最初から最後まで説明し、それからスケジュールします。

ジョブを実行するマシンにbeaがあり、到達可能な台帳が必要です。新しい帳簿を開始する場合は、まずCLIクイックスタートに従ってください。フラグ、エンベロープキー、終了コードに関するすべての事実は、Beancount CLIリファレンスで調べられ、ここでは繰り返しません。

台帳を明示的に選択する

ファイルを指定します。ローカルコマンドは、--file、次に$BEA_FILE、次に作業ディレクトリ内の./main.beanの順にターゲットを解決します。スケジュールされたジョブは、あなたが思う場所ではめったに実行されません。

bea --file ~/books/main.bean check
BEA_FILE=~/books/main.bean bea check
cd ~/books && bea check

グローバルオプションは、bea --file main.bean checkのように、コマンドの前に置きます。解決されたファイルが存在しない場合、コマンドは終了コード2で終了し、3つのソースすべてを名前で示すため、cronエントリのタイプミスは、間違った帳簿を検証する代わりに、大きな問題として失敗します。--ledgerフラグによるホスト型ターゲティングはまだ存在しません。beaはローカルファイルを暗黙的にアップロードすることはありません。

JSONエンベロープを読む

グローバルな--jsonを追加すると、サポートされているすべてのコマンドが同じエンベロープで応答します: beatargetdatatruncated、そして制限付きリストのlimitです。金額は10進数の文字列で、日付はISO YYYY-MM-DD形式であるため、値はパイプラインに浮動小数点数が入ることなく比較しても安全です。エンベロープのキーは、JSONおよび終了コードリファレンスに表としてまとめられています。

bea --json --file main.bean report income-statement | jq .data.net_profit
bea --json --file main.bean list transaction --limit 2 | jq '.data[0].postings[0].units'
bea --json --file main.bean import statement.csv --csv date=Date,amount=Amount,payee=Payee \
  --account Assets:Checking --apply --duplicates skip | jq '.data | {written, ready, duplicates}'

これら3つのコマンド(reportlistimport)は、結果の形状を維持するため、それらに対して書かれたjqパスは有効なままです。bea --json checkbea --json queryも今日はエンベロープを出力しますが、これらはネイティブのBeancount実行ファイルに引き渡されるコマンドであるため、スクリプトはその出力形状ではなくcheckの終了ステータスに基づいてキーを設定する必要があります。重複ポリシーは意図的に選択してください: インポートで決定が必要な場合は、インポートウォークスルーで説明されているように、--duplicatesが引き続き必要です。

正しい終了コードで停止する

ステータスで分岐し、書き込みを行うものを再試行する前にエラーオブジェクトを読み取ります。--jsonモードでは、失敗時にはstdoutには何も書き込まれず、stderrに正確に1つのオブジェクトが書き込まれ、そのerror.categoryがクラスを指定します: validation(1)、usage(2)、auth(3)、conflict(4)。

#!/usr/bin/env bash
set -euo pipefail
 
out=$(mktemp)
err=$(mktemp)
status=0
 
bea --json --file main.bean report income-statement >"$out" 2>"$err" || status=$?
 
case "$status" in
  0) jq -r '.data.net_profit | to_entries[] | "net profit: \(.value) \(.key)"' "$out" ;;
  4) echo "conflict — inspect the ledger before retrying" >&2
     jq -r '.error.message' "$err" >&2
     exit 4 ;;
  *) jq -r '.error | "\(.category) (exit \(.exit_code)): \(.message)"' "$err" >&2
     exit "$status" ;;
esac

終了コード4は、スクリプトが決して盲目的に再試行してはならないものです。これは、結果が競合または不明であることを意味します。たとえば、書き込み中に外部編集が到着したり、initターゲットが既に存在したりする場合です。台帳を検査し、それから新しい読み取りから再試行します。終了コード1は、検証エラーおよびその他の実行時エラーをカバーします。error.detailsには個々の台帳エラーが含まれ、error.resultには部分的な書き込みが実際に何を行ったかが含まれます。ゼロ以外の終了は、何も変更されなかったことを保証するものではありません。

ターミナルなしで実行する

beaはそれ自身でプロンプトを停止します。--no-inputは、stdinがターミナルでない場合、--jsonが設定されている場合、およびCIが真値(1trueyeson)である場合に暗黙的に指定されます。このモードでは、確認が欠落している場合、永久に待機する代わりに終了コード2で失敗します。

CI=true BEA_NO_UPDATE_NOTIFIER=1 bea --json --file main.bean report balance-sheet
bea --json --file main.bean list transaction --limit 100 --sort oldest

読み取りはターミナルでは寛容で、それ以外の場所では厳格です。台帳にローダーエラーがある場合、querylistreportは、--json下、パイプされたstdout下、真値のCI下、または--strict付きで終了コード1で終了します。代わりに部分的な回答を受け入れるには、コマンド自身の--allow-errorsを渡します。これにより、JSONのledger_validfalseに設定され、ledger_errorsが埋められます。--strictはその鏡像です。ターミナルでも部分的な回答を拒否します。これは、人間が同じスクリプトを手動で実行する場合に必要なものです。bea checkには--allow-errorsはありません。エラーの報告がその仕事のすべてであり、エラーを検出すると常に終了コード1で終了します。BEA_NO_UPDATE_NOTIFIER=1を設定して受動的な更新通知を抑制します。真値のCIはすでにそれを実行します。

チェックをスケジュールする

毎晩検証を実行し、終了コードをアラートにします。以下の両方のブロックはテンプレートです。パス、スケジュール、ランナーはあなたのものです。

# crontab -e — 07:15 daily; cron mails you only when bea exits nonzero
15 7 * * * BEA_NO_UPDATE_NOTIFIER=1 /opt/homebrew/bin/bea --file /home/alice/books/main.bean check
name: ledger
on:
  schedule:
    - cron: "15 7 * * *"
  push:
jobs:
  check:
    runs-on: ubuntu-latest
    env:
      BEA_NO_UPDATE_NOTIFIER: "1"
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uv tool install beancount-io==0.1.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

ジョブを再現可能にする必要がある場合はバージョンを固定し、リリースを追跡したい場合は固定を解除します。CIはGitHub Actionsで既に真値であるため、何も設定する前にプロンプトはオフになり、更新通知は静かになります。ここでは意図的にフォーマット手順はありません。スケジュールされたジョブは、必要のないファイルを書き換えるべきではありません。そのため、pre-commitフックでbea format --checkを使用してください。これは何も変更せず、ファイルにフォーマットが必要な場合は終了コード1で終了します。

ジョブでホスト型資格情報を使用する

CIプロバイダーのシークレットストアからBEA_TOKENを設定し、ブラウザーのサインインを完全にスキップします。トークンは環境から読み取られ、ディスクに書き込まれることはないため、ランナーのホームディレクトリに次のジョブが見つけるためのものが残ることはありません。

export BEA_TOKEN="$YOUR_CI_SECRET"
bea --json cloud status

終了コード0は、資格情報が解決され、エンベロープがそれが属するアカウントを指定することを意味します。終了コード3error.categoryauthは、そうでないことを意味し、メッセージは未設定の資格情報と拒否された資格情報を区別します。bea cloud logoutは、この方法で提供されたトークンには何も実行しません。別のジョブがそれを共有する可能性があるため、取り消しも未設定も行いません。漏洩したトークンは、代わりにダッシュボードから取り消してください。ローカルコマンドには資格情報はまったく必要ありません。ホスト型サービスに到達するのはbea cloudbea askだけです。完全な変数リストは設定リファレンスにあります。

すべてがJSONで応答するわけではありません。bea askはJSONモードを完全に拒否し、bea cloud loginは人間を必要とし、成功したbea cloud logoutまたはbea cloud ledger cloneはJSONの成功オブジェクトを返しません。代わりにそれらの終了ステータスを読み取ってください。ヘルプ、バージョン、シェル補完の出力はテキストのままです。

次のステップ

出典: https://beancount.io/ja/docs/Solutions/automate-bookkeeping-with-bea