mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-10 05:39:31 +03:00
gather all balance report docs, rewrite, check with doctest
This commit is contained in:
parent
1110bcaf4e
commit
aaf8a0caf6
@ -1,88 +1,84 @@
|
||||
{-|
|
||||
|
||||
A ledger-compatible @balance@ command. Here's how it should work:
|
||||
A ledger-compatible @balance@ command.
|
||||
|
||||
A sample account tree (as in the sample.ledger file):
|
||||
ledger's balance command is easy to use but hard to describe precisely.
|
||||
Here are some attempts.
|
||||
|
||||
|
||||
I. high level description with examples
|
||||
---------------------------------------
|
||||
|
||||
We'll use sample.ledger, which has the following account tree:
|
||||
|
||||
@
|
||||
assets
|
||||
cash
|
||||
checking
|
||||
saving
|
||||
bank
|
||||
checking
|
||||
saving
|
||||
cash
|
||||
expenses
|
||||
food
|
||||
supplies
|
||||
food
|
||||
supplies
|
||||
income
|
||||
gifts
|
||||
salary
|
||||
gifts
|
||||
salary
|
||||
liabilities
|
||||
debts
|
||||
debts
|
||||
@
|
||||
|
||||
The balance command shows top-level accounts by default:
|
||||
The balance command shows accounts with their aggregate balances.
|
||||
Subaccounts are displayed with more indentation. Each balance is the sum
|
||||
of any transactions in that account plus any balances from subaccounts:
|
||||
|
||||
@
|
||||
\> ledger balance
|
||||
$-1 assets
|
||||
$2 expenses
|
||||
$-2 income
|
||||
$1 liabilities
|
||||
$ hledger -f sample.ledger balance
|
||||
$-1 assets
|
||||
$1 bank:saving
|
||||
$-2 cash
|
||||
$2 expenses
|
||||
$1 food
|
||||
$1 supplies
|
||||
$-2 income
|
||||
$-1 gifts
|
||||
$-1 salary
|
||||
$1 liabilities:debts
|
||||
@
|
||||
|
||||
With -s (--subtotal), also show the subaccounts:
|
||||
Usually, the non-interesting accounts are elided or omitted. To be
|
||||
precise, an interesting account is one with: a non-zero balance, or a
|
||||
balance different from its single subaccount, or two or more interesting
|
||||
subaccounts. (More subtleties to be filled in here.)
|
||||
|
||||
So, above, @checking@ is omitted because it has no interesting subaccounts
|
||||
and a zero balance. @bank@ is elided because it has only a single
|
||||
interesting subaccount (saving) and it would be showing the same balance
|
||||
($1). Ditto for @liabilities@.
|
||||
|
||||
With one or more account pattern arguments, the balance command shows
|
||||
accounts whose name matches one of the patterns, plus their parents
|
||||
(elided) and subaccounts. So with the pattern o we get:
|
||||
|
||||
@
|
||||
$-1 assets
|
||||
$-2 cash
|
||||
$1 saving
|
||||
$2 expenses
|
||||
$1 food
|
||||
$1 supplies
|
||||
$-2 income
|
||||
$-1 gifts
|
||||
$-1 salary
|
||||
$1 liabilities:debts
|
||||
@
|
||||
|
||||
- @checking@ is not shown because it has a zero balance and no interesting
|
||||
subaccounts.
|
||||
|
||||
- @liabilities@ is displayed only as a prefix because it has the same balance
|
||||
as its single subaccount.
|
||||
|
||||
With an account pattern, show only the accounts with matching names:
|
||||
|
||||
@
|
||||
\> ledger balance o
|
||||
$1 expenses:food
|
||||
$-2 income
|
||||
$ hledger -f sample.ledger balance o
|
||||
$1 expenses:food
|
||||
$-2 income
|
||||
$-1 gifts
|
||||
$-1 salary
|
||||
--------------------
|
||||
$-1
|
||||
$-1
|
||||
@
|
||||
|
||||
- The o matched @food@ and @income@, so they are shown.
|
||||
The o pattern matched @food@ and @income@, so they are shown. Unmatched
|
||||
parents of matched accounts are also shown (elided) for context (@expenses@).
|
||||
|
||||
- Parents of matched accounts are also shown for context (@expenses@).
|
||||
Also, the balance report shows the total of all displayed accounts, when
|
||||
that is non-zero. Here, it is displayed because the accounts shown add up
|
||||
to $-1.
|
||||
|
||||
- This time the grand total is also shown, because it is not zero.
|
||||
|
||||
Again, -s adds the subaccounts:
|
||||
|
||||
@
|
||||
\> ledger -s balance o
|
||||
$1 expenses:food
|
||||
$-2 income
|
||||
$-1 gifts
|
||||
$-1 salary
|
||||
--------------------
|
||||
$-1
|
||||
@
|
||||
|
||||
- @food@ has no subaccounts. @income@ has two, so they are shown.
|
||||
|
||||
- We do not add the subaccounts of parents included for context (@expenses@).
|
||||
|
||||
Some notes for the implementation:
|
||||
II. Some notes for the implementation
|
||||
-------------------------------------
|
||||
|
||||
- a simple balance report shows top-level accounts
|
||||
|
||||
@ -100,6 +96,218 @@ Some notes for the implementation:
|
||||
|
||||
- the sum of the balances shown is displayed at the end, if it is non-zero
|
||||
|
||||
|
||||
III. John's description 2009/02
|
||||
-------------------------------
|
||||
|
||||
johnw: \"Since you've been re-implementing the balance report in Haskell, I thought
|
||||
I'd share with you in pseudocode how I rewrote it for Ledger 3.0, since
|
||||
the old method never stopped with the bugs. The new scheme uses a 5 stage
|
||||
algorithm, with each stage gathering information for the next:
|
||||
|
||||
STEP 1
|
||||
|
||||
Based on the user's query, walk through all the transactions in their
|
||||
journal, finding which ones to include in the account subtotals. For each
|
||||
transaction that matches, mark the account as VISITED.
|
||||
|
||||
STEP 2
|
||||
|
||||
Recursively walk the accounts tree, depth-first, computing aggregate
|
||||
totals and aggregate \"counts\" (number of transactions contributing to the
|
||||
aggregate total).
|
||||
|
||||
STEP 3
|
||||
|
||||
Walk the account tree again, breadth-first, and for every VISITED account,
|
||||
check whether it matches the user's \"display predicate\". If so, mark the
|
||||
account as MATCHING.
|
||||
|
||||
STEP 4
|
||||
|
||||
Do an in-order traversal of the account tree. Except for the top-most
|
||||
account (which serves strictly as a container for the other accounts):
|
||||
|
||||
a. If the account was MATCHING, or two or more of its children are
|
||||
MATCHING or had descendents who were MATCHING, display the account.
|
||||
|
||||
b. Otherwise, if the account had *any* children or descendants who
|
||||
were VISITED and *no* children or descendants who were MATCHING,
|
||||
then apply the display predicate from STEP 3 to the account. If
|
||||
it matches, also print this account. (This step allows -E to
|
||||
report empty accounts which otherwise did match the original
|
||||
query).
|
||||
|
||||
STEP 5
|
||||
|
||||
When printing an account, display a \"depth spacer\" followed by the \"partial name\".
|
||||
tal
|
||||
The partial name is found by taking the base account's name, then
|
||||
prepending to it every non-MATCHING parent until a MATCHING parent is
|
||||
found.
|
||||
|
||||
The depth spacer is found by outputting two spaces for every MATCHING parent.
|
||||
|
||||
This way, \"Assets:Bank:Checking\" might be reported as:
|
||||
|
||||
Assets
|
||||
Bank
|
||||
Checking
|
||||
|
||||
or
|
||||
|
||||
Assets
|
||||
Bank:Checking
|
||||
|
||||
or
|
||||
|
||||
Assets:Bank:Checking
|
||||
|
||||
Depending on whether the parents were or were not reported for their own reasons.
|
||||
\"
|
||||
|
||||
\"I just had to add one more set of tree traversals, to correctly determine
|
||||
whether a final balance should be displayed
|
||||
|
||||
without --flat, sort at each level in the hierarchy
|
||||
with --flat, sort across all accounts\"
|
||||
|
||||
IV. A functional description
|
||||
-----------------------------
|
||||
|
||||
1. filter the transactions, keeping only those included in the calculation.
|
||||
Remember the subset of accounts involved. (VISITED)
|
||||
|
||||
2. generate a full account & balance tree from all transactions
|
||||
|
||||
3. Remember the subset of VISITED accounts which are matched for display.
|
||||
(MATCHING)
|
||||
|
||||
4. walk through the account tree:
|
||||
|
||||
a. If the account is in MATCHING, or two or more of its children are or
|
||||
have descendants who are, display it.
|
||||
|
||||
b. Otherwise, if the account has any children or descendants in VISITED
|
||||
but none in MATCHING, and it is matched for display, display it.
|
||||
(allows -E to report empty accounts which otherwise did match the
|
||||
original query).
|
||||
|
||||
5. when printing an account, display a \"depth spacer\" followed by the
|
||||
\"partial name\". The partial name is found by taking the base account's
|
||||
name, then prepending to it every non-MATCHING parent until a MATCHING
|
||||
parent is found. The depth spacer is two spaces per MATCHING parent.
|
||||
|
||||
6. I just had to add one more set of tree traversals, to correctly
|
||||
determine whether a final balance should be displayed
|
||||
|
||||
7. without --flat, sort at each level in the hierarchy
|
||||
with --flat, sort across all accounts
|
||||
|
||||
V. Another functional description with new terminology
|
||||
------------------------------------------------------
|
||||
|
||||
- included transactions are those included in the calculation, specified
|
||||
by -b, -e, -p, -C, -R, account patterns and description patterns.
|
||||
|
||||
- included accounts are the accounts referenced by included transactions.
|
||||
|
||||
- matched transactions are the included transactions which match the
|
||||
display predicate, specified by -d.
|
||||
|
||||
- matched accounts are the included accounts which match the display
|
||||
predicate, specified by -d, --depth, -E, -s
|
||||
|
||||
- an account name tree is the full hierarchy of account names implied by a
|
||||
set of transactions
|
||||
|
||||
- an account tree is an account name tree augmented with the aggregate
|
||||
balances and transaction counts for each named account
|
||||
|
||||
- the included account tree is the account tree for the included transactions
|
||||
|
||||
- a matched account tree contains one or more matched accounts
|
||||
|
||||
- to generate the balance report, walk through the included account tree
|
||||
and display each account if
|
||||
|
||||
- it is matching
|
||||
|
||||
- or it has two more more matching subtrees
|
||||
|
||||
- or it has included offspring but no matching offspring
|
||||
|
||||
- to display an account, display an indent then the \"partial name\". The
|
||||
partial name is the account's name, prefixed by each unmatched parent
|
||||
until a matched parent is found. The indent is two spaces per matched
|
||||
parent.
|
||||
|
||||
|
||||
VI. John's description 2009/03/11
|
||||
---------------------------------
|
||||
|
||||
johnw: \"Well, I had to rewrite the balance reporting code yet again,
|
||||
because it wouldn't work with --depth correctly. Here's the new algorithm.
|
||||
|
||||
STEP 1: Walk all postings, looking for those that match the user's query.
|
||||
As a match is found, mark its account VISITED.
|
||||
|
||||
STEP 2: Do a traversal of all accounts, sorting as need be, and collect
|
||||
them all into an ordered list.
|
||||
|
||||
STEP 3: Keeping that list on the side, do a *depth-first* traversal of
|
||||
the account tree.
|
||||
|
||||
visited = 0
|
||||
to_display = 0
|
||||
|
||||
(visited, to_display) += <recurse for all immediate children>
|
||||
|
||||
if account is VISITED or (no --flat and visited > 0):
|
||||
if account matches display predicate and
|
||||
(--flat or to_display != 1):
|
||||
mark account as TO_DISPLAY
|
||||
to_display = 1
|
||||
visited = 1
|
||||
|
||||
return (visited, to_display)
|
||||
|
||||
STEP 4: top_displayed = 0
|
||||
|
||||
for every account in the ordered list:
|
||||
if account has been marked TO_DISPLAY:
|
||||
mark account as DISPLAYED
|
||||
format the account and print
|
||||
|
||||
if --flat and account is DISPLAYED:
|
||||
top_displayed += 1
|
||||
|
||||
if no --flat:
|
||||
for every top-most account:
|
||||
if account is DISPLAYED or any children or DISPLAYED:
|
||||
top_displayed += 1
|
||||
|
||||
if no --no-total and top_displayed > 1 and
|
||||
top-most accounts sum to a non-zero balance:
|
||||
output separator
|
||||
output balance sum account as DISPLAYED
|
||||
format the account and print
|
||||
|
||||
if --flat and account is DISPLAYED:
|
||||
top_displayed += 1
|
||||
|
||||
if no --flat:
|
||||
for every top-most account:
|
||||
if account is DISPLAYED or any children or DISPLAYED:
|
||||
top_displayed += 1
|
||||
|
||||
if no --no-total and top_displayed > 1 and
|
||||
top-most accounts sum to a non-zero balance:
|
||||
output separator
|
||||
output balance sum
|
||||
\"
|
||||
|
||||
|
||||
-}
|
||||
|
||||
module BalanceCommand
|
||||
|
Loading…
Reference in New Issue
Block a user