Beancount.Report (beancount_ex v0.6.0)

Copy Markdown View Source

Higher-level reporting API built on top of Beancount.query_text/2.

Each function generates a canned BQL query and runs it through the configured engine, returning a neutral Beancount.Query.Result. Reports therefore work against any engine that implements Beancount.Engine.query/2.

A ledger argument may be either a list of directives (which is rendered first) or raw .bean text.

Examples below use Beancount.Engine.Elixir so they run without bean-query. Configure config :beancount_ex, engine: Beancount.Engine.Elixir to use these reports through Beancount.balances/1 and friends.

Summary

Functions

Balance sheet: balances of Assets, Liabilities and Equity accounts.

Balances for every account.

Holdings: unit and cost positions held in Asset accounts.

Income statement: balances of Income and Expenses accounts.

Journal of postings for a single account, ordered by date.

Types

ledger()

@type ledger() :: [Beancount.directive()] | binary()

result()

@type result() :: {:ok, Beancount.Query.Result.t()} | {:error, Beancount.Result.t()}

Functions

balance_sheet(ledger)

@spec balance_sheet(ledger()) :: result()

Balance sheet: balances of Assets, Liabilities and Equity accounts.

Examples

iex> Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
iex> {:ok, %Beancount.Query.Result{}} = Beancount.Report.balance_sheet(Beancount.Report.sample_ledger())

balances(ledger)

@spec balances(ledger()) :: result()

Balances for every account.

BQL: SELECT account, sum(position) AS balance GROUP BY account ORDER BY account.

Examples

iex> Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
iex> {:ok, result} = Beancount.Report.balances(Beancount.Report.sample_ledger())
iex> result.columns
["account", "balance"]

holdings(ledger)

@spec holdings(ledger()) :: result()

Holdings: unit and cost positions held in Asset accounts.

Examples

ledger = [
  Beancount.open(~D[2026-01-01], "Assets:Stocks", ["AAPL"], booking: "FIFO"),
  Beancount.open(~D[2026-01-01], "Assets:Cash", ["USD"]),
  Beancount.open(~D[2026-01-01], "Equity:Opening", ["USD"]),
  Beancount.transaction(~D[2026-01-02], "*", nil, "Buy", [
    Beancount.posting("Assets:Stocks", Decimal.new("10"), "AAPL",
      cost: %{amount: Decimal.new("150"), currency: "USD"}
    ),
    Beancount.posting("Assets:Cash", Decimal.new("-1500"), "USD")
  ])
]

Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
{:ok, %Beancount.Query.Result{columns: cols}} = Beancount.Report.holdings(ledger)
cols
# => ["account", "units", "cost"]

income_statement(ledger)

@spec income_statement(ledger()) :: result()

Income statement: balances of Income and Expenses accounts.

Examples

iex> Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
iex> {:ok, %Beancount.Query.Result{}} = Beancount.Report.income_statement(Beancount.Report.sample_ledger())

journal(ledger, account)

@spec journal(ledger(), String.t()) :: result()

Journal of postings for a single account, ordered by date.

Examples

iex> Application.put_env(:beancount_ex, :engine, Beancount.Engine.Elixir)
iex> {:ok, %Beancount.Query.Result{columns: cols}} =
...>   Beancount.Report.journal(Beancount.Report.sample_ledger(), "Assets:Bank")
iex> "date" in cols
true