Account Validation
Overview
Account validation ensures the integrity of account lifecycles: accounts must be opened before use, must not be used after closing, and must follow naming conventions.
Account Lifecycle
open
[Not Exists] ─────────► [Open] ◄────────┐
│ │
│ close │ reopen
▼ │
[Closed] ─────────┘Validation Rules
Account Not Opened
An account MUST be opened before any posting references it. Using an unopened account produces a ValidationError.
Condition: Posting references an account with no prior open directive.
Example:
; ERROR: No open directive for Assets:Checking
2024-01-15 * "Deposit"
Assets:Checking 100 USD ; ValidationError
Income:SalaryFix:
2024-01-01 open Assets:Checking USD
2024-01-15 * "Deposit"
Assets:Checking 100 USD ; OK
Income:SalaryAccount Already Open
An account MUST NOT be opened twice without an intervening close. Opening an already-open account produces a ValidationError.
Condition: open directive for an account that is already open.
Example:
2020-01-01 open Assets:Checking USD
2021-01-01 open Assets:Checking USD ; ValidationError: Already openAccount Closed
An account MUST NOT be used in postings AFTER its close date.
UNDEFINED: Whether posting ON the close date is allowed is pending clarification. Tracked in: python-beancount.md conformance issues
Example:
2020-01-01 open Assets:OldAccount USD
2023-12-31 close Assets:OldAccount
; Posting AFTER close date is always an error
2024-01-15 * "Late transaction"
Assets:OldAccount 100 USD ; ERROR: Invalid reference to closed account
Income:SalaryAccount Close with Non-Zero Balance
UNDEFINED: Whether closing an account with non-zero balance produces an error is pending clarification. Tracked in: python-beancount.md conformance issues
Example:
2024-01-01 open Assets:Checking USD
2024-06-01 * "Deposit"
Assets:Checking 100 USD
Income:Gift
2024-12-31 close Assets:Checking ; OK: No error even with 100 USD balanceBest Practice: Zero the balance before closing for cleaner bookkeeping:
2024-12-30 * "Transfer out"
Assets:Checking -100 USD
Assets:NewAccount
2024-12-31 close Assets:Checking ; Balance is 0Note: The beancount.plugins.check_closing plugin can be used to enforce zero balance at close.
Invalid Account Name
Account names MUST follow naming conventions. Invalid names produce a ParserError.
Conditions:
- Does not start with valid root type
- Contains invalid characters
- Component doesn't start with uppercase letter or digit
Examples:
; Invalid root type
2024-01-01 open Savings:Emergency ; ParserError: "Savings" not valid root
; Lowercase component
2024-01-01 open Assets:checking ; ParserError: Must start with uppercase
; Invalid characters
2024-01-01 open Assets:My Account ; ParserError: Space not allowedAccount Name Rules
Root Types
Every account MUST begin with one of:
| Root | Purpose |
|---|---|
Assets | Things you own |
Liabilities | Things you owe |
Equity | Net worth, opening balances |
Income | Money received |
Expenses | Money spent |
Component Rules
account = root_type (":" component)+
root_type = "Assets" | "Liabilities" | "Equity" | "Income" | "Expenses"
component = capital_start (alphanumeric | "-")*
capital_start = [A-Z] | [0-9]
alphanumeric = [A-Za-z0-9]Valid Names
Assets:Checking
Assets:US:BofA:Checking
Liabilities:CreditCard:Chase-Sapphire
Expenses:Food:Groceries
Income:Salary:2024
Equity:Opening-Balances
Assets:401kInvalid Names
assets:Checking ; Lowercase root
Assets:checking ; Lowercase component start
Assets:My Checking ; Space in component
Assets::Checking ; Empty component
Savings:Account ; Invalid root
Assets ; No components after rootValidation Timing
Account validation occurs during the chronological scan:
- Parse all directives
- Sort by date
- Scan chronologically:
- Track open/close state per account
- Validate each posting against current state
- Validate each close against current state
Account State Tracking
class AccountState:
open_date: Optional[Date]
close_date: Optional[Date]
currencies: Optional[List[Currency]]
booking_method: Optional[str]
# Per-account state machine
account_states: Dict[Account, AccountState]Edge Cases
Reopening Accounts
Note: In Python beancount 3.x, accounts cannot be reopened after closing. A second open directive after a close produces a duplicate open error:
2020-01-01 open Assets:Checking USD
2022-12-31 close Assets:Checking
2024-01-01 open Assets:Checking USD ; ValidationError: Duplicate openIf you need to reuse an account after closing, you must use a new account name.
Same-Day Open and Use
Using an account on its open date is valid:
2024-01-15 open Assets:Checking USD
2024-01-15 * "First deposit"
Assets:Checking 100 USD ; OK: Same day as open
Income:GiftClose Date Boundary
UNDEFINED: Whether posting ON the close date is allowed is pending clarification.
Posting AFTER the close date is always an error:
2024-01-01 open Assets:Checking USD
2024-06-30 close Assets:Checking
; What should happen for posting ON close date?
2024-06-30 * "Same-day transaction"
Assets:Checking 100 USD ; UNDEFINED behavior
Income:Gift
; Posting AFTER close date is always an error
2024-07-01 * "After close"
Assets:Checking 100 USD ; ValidationError: Invalid reference to inactive account
Income:GiftNotes and Documents After Close
Notes and documents MAY reference closed accounts:
2024-06-30 close Assets:OldAccount
2024-07-15 note Assets:OldAccount "Records archived" ; OK
2024-07-15 document Assets:OldAccount "final-statement.pdf" ; OKConfiguration
Note: Python beancount 3.x does NOT support implicit account opening. All accounts must be explicitly opened before use.
The operating_currency option can be set to specify the main reporting currency, but this does not affect account validation requirements.
Implementation Notes
- Build account state map during parsing
- Sort directives chronologically before validation
- Track open date, close date, and constraints per account
- Validate postings against current account state
- Report all errors with account name and dates