メインコンテンツへスキップ

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を追加すると、サポートされているすべてのコマンドが同じエンベロープで応答します:bea、target、data、truncated、そして境界付きリストの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つのコマンド(report、list、import)は結果の形状を維持するため、それらに対して書かれたjqパスは有効なままです。bea --json checkとbea --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が真の場合(1、true、yes、on)に暗黙的に指定されます。このモードでは、確認がない場合は終了コード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

読み取りはターミナルでは寛容で、他の場所では厳格です。台帳にローダーエラーがある場合、query、list、reportは--json、パイプされたstdout、真のCI、または--strictの下で終了コード1で終了します。代わりに部分的な回答を受け入れるには、コマンド自身の--allow-errorsを渡します。これにより、JSONでledger_valid: falseが設定され、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.2.0
      - run: bea --file main.bean check
      - run: bea --json --file main.bean report balance-sheet > balance-sheet.json

最初のエンジン対応コマンドは管理されたエンジンをダウンロードするため、ランナーにはネットワークアクセスが必要です。ジョブ間で再利用するには、ランナーOS、アーキテクチャ、Pythonバージョン、ピン留めされたbeaバージョンを含むキーで~/.local/share/bea/engineをキャッシュします。ジョブを再現可能にする必要がある場合はバージョンをピン留めし、リリースを追跡したい場合はピンを外します。CIはGitHub Actionsですでに真であるため、何も設定しなくてもプロンプトはオフになり、更新通知は無音です。ここには意図的にフォーマット手順はありません。スケジュールされたジョブは、しなくてもよいファイルを書き換えるべきではないため、pre-commitフックでbea format main.bean --checkを使用してください。これは何も触れず、ファイルがフォーマットを必要とする場合に終了コード1で終了します。

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

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

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

終了コード0は資格情報が解決され、エンベロープがそれが属するアカウントを指定することを意味します。error.categoryがauthである終了コード3は、それが解決されなかったことを意味し、メッセージは未設定の資格情報と拒否された資格情報を区別します。bea cloud logoutは、このように提供されたトークンに対して何も行いません。別のジョブがそれを共有する可能性があるため、取り消しも未設定もしません。漏洩したトークンはダッシュボードから取り消してください。ローカルコマンドには資格情報はまったく必要ありません。bea cloudとbea 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