cli: Add -% to compound balance commands
This commit introduces the commandline argument -%/--percent to show percentages of the column's total instead of the absolute amounts for each account in reports. The signs of the values are preserved. This option is especially useful for the balance and incomestatement commands. If there are multiple commodities involved in a report hledger bails with an error message. This can be avoided by using --cost. Also note that if one uses -% with the balance command the chances are high that all numbers are 0. This is due to the fact that by default balance sums up to zero. If one wants to use -% in a meaningful way with balance one has to add a query. In order to keep the implementation as simple as possible --tree has no influence over how the percentages are calculated, i.e., the percentages always represent the fraction of the columns total. If one wants to know the percentages relative to a parent account, one has to use a query to narrow down the accounts.
This commit is contained in:
parent
87b82b6839
commit
79ca4a767e
@ -51,6 +51,7 @@ module Hledger.Data.Amount (
|
||||
usd,
|
||||
eur,
|
||||
gbp,
|
||||
per,
|
||||
hrs,
|
||||
at,
|
||||
(@@),
|
||||
@ -181,6 +182,7 @@ hrs n = amount{acommodity="h", aquantity=n, astyle=amountstyle{aspreci
|
||||
usd n = amount{acommodity="$", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=2}}
|
||||
eur n = amount{acommodity="€", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=2}}
|
||||
gbp n = amount{acommodity="£", aquantity=roundTo 2 n, astyle=amountstyle{asprecision=2}}
|
||||
per n = amount{acommodity="%", aquantity=n, astyle=amountstyle{asprecision=1, ascommodityside=R, ascommodityspaced=True}}
|
||||
amt `at` priceamt = amt{aprice=Just $ UnitPrice priceamt}
|
||||
amt @@ priceamt = amt{aprice=Just $ TotalPrice priceamt}
|
||||
|
||||
|
||||
@ -12,6 +12,8 @@ module Hledger.Reports.BalanceReport (
|
||||
balanceReport,
|
||||
flatShowsExclusiveBalance,
|
||||
sortAccountItemsLike,
|
||||
unifyMixedAmount,
|
||||
perdivide,
|
||||
|
||||
-- * Tests
|
||||
tests_BalanceReport
|
||||
@ -66,7 +68,7 @@ flatShowsExclusiveBalance = True
|
||||
balanceReport :: ReportOpts -> Query -> Journal -> BalanceReport
|
||||
balanceReport ropts@ReportOpts{..} q j@Journal{..} =
|
||||
(if invert_ then brNegate else id) $
|
||||
(sorteditems, total)
|
||||
(mappedsorteditems, mappedtotal)
|
||||
where
|
||||
-- dbg1 = const id -- exclude from debug output
|
||||
dbg1 s = let p = "balanceReport" in Hledger.Utils.dbg1 (p++" "++s) -- add prefix in debug output
|
||||
@ -143,6 +145,14 @@ balanceReport ropts@ReportOpts{..} q j@Journal{..} =
|
||||
then sum $ map fourth4 items
|
||||
else sum $ map aebalance $ clipAccountsAndAggregate 1 displayaccts
|
||||
|
||||
-- Calculate percentages if needed.
|
||||
mappedtotal | percent_ = dbg1 "mappedtotal" $ total `perdivide` total
|
||||
| otherwise = total
|
||||
mappedsorteditems | percent_ =
|
||||
dbg1 "mappedsorteditems" $
|
||||
map (\(fname, sname, indent, amount) -> (fname, sname, indent, amount `perdivide` total)) sorteditems
|
||||
| otherwise = sorteditems
|
||||
|
||||
-- | A sorting helper: sort a list of things (eg report rows) keyed by account name
|
||||
-- to match the provided ordering of those same account names.
|
||||
sortAccountItemsLike :: [AccountName] -> [(AccountName, b)] -> [b]
|
||||
@ -185,6 +195,28 @@ brNegate (is, tot) = (map brItemNegate is, -tot)
|
||||
where
|
||||
brItemNegate (a, a', d, amt) = (a, a', d, -amt)
|
||||
|
||||
-- | Helper to unify a MixedAmount to a single commodity value.
|
||||
unifyMixedAmount :: MixedAmount -> Amount
|
||||
unifyMixedAmount mixedAmount = foldl combine (num 0) (amounts mixedAmount)
|
||||
where
|
||||
combine amount result =
|
||||
if isReallyZeroAmount amount
|
||||
then result
|
||||
else if isReallyZeroAmount result
|
||||
then amount
|
||||
else if acommodity amount == acommodity result
|
||||
then amount + result
|
||||
else error' "Cannot calculate percentages for accounts with multiple commodities. (Hint: Try --cost, -V or similar flags.)"
|
||||
|
||||
-- | Helper to calculate the percentage from two mixed. Keeps the sign of the first argument.
|
||||
-- Uses unifyMixedAmount to unify each argument and then divides them.
|
||||
perdivide :: MixedAmount -> MixedAmount -> MixedAmount
|
||||
perdivide a b =
|
||||
let a' = unifyMixedAmount a
|
||||
b' = unifyMixedAmount b
|
||||
in if isReallyZeroAmount a' || isReallyZeroAmount b' || acommodity a' == acommodity b'
|
||||
then mixed [per $ if aquantity b' == 0 then 0 else (aquantity a' / abs (aquantity b') * 100)]
|
||||
else error' "Cannot calculate percentages if accounts have different commodities. (Hint: Try --cost, -V or similar flags.)"
|
||||
|
||||
-- tests
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ multiBalanceReport ropts q j = multiBalanceReportWith ropts q j (journalPriceOra
|
||||
multiBalanceReportWith :: ReportOpts -> Query -> Journal -> PriceOracle -> MultiBalanceReport
|
||||
multiBalanceReportWith ropts@ReportOpts{..} q j@Journal{..} priceoracle =
|
||||
(if invert_ then mbrNegate else id) $
|
||||
MultiBalanceReport (colspans, sortedrows, totalsrow)
|
||||
MultiBalanceReport (colspans, mappedsortedrows, mappedtotalsrow)
|
||||
where
|
||||
dbg1 s = let p = "multiBalanceReport" in Hledger.Utils.dbg1 (p++" "++s) -- add prefix in this function's debug output
|
||||
-- dbg1 = const id -- exclude this function from debug output
|
||||
@ -162,7 +162,7 @@ multiBalanceReportWith ropts@ReportOpts{..} q j@Journal{..} priceoracle =
|
||||
-- These balances are unvalued except maybe converted to cost.
|
||||
startbals :: [(AccountName, MixedAmount)] = dbg1 "startbals" $ map (\(a,_,_,b) -> (a,b)) startbalanceitems
|
||||
where
|
||||
(startbalanceitems,_) = dbg1 "starting balance report" $ balanceReport ropts''{value_=Nothing} startbalq j'
|
||||
(startbalanceitems,_) = dbg1 "starting balance report" $ balanceReport ropts''{value_=Nothing, percent_=False} startbalq j'
|
||||
where
|
||||
ropts' | tree_ ropts = ropts{no_elide_=True}
|
||||
| otherwise = ropts{accountlistmode_=ALFlat}
|
||||
@ -344,6 +344,25 @@ multiBalanceReportWith ropts@ReportOpts{..} q j@Journal{..} priceoracle =
|
||||
totalsrow :: MultiBalanceReportTotals =
|
||||
dbg1 "totalsrow" (coltotals, grandtotal, grandaverage)
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- 9. Map the report rows to percentages if needed
|
||||
-- It is not correct to do this before step 6 due to the total and average columns.
|
||||
-- This is not done in step 6, since the report totals are calculated in 8.
|
||||
|
||||
-- Perform the divisions to obtain percentages
|
||||
mappedsortedrows :: [MultiBalanceReportRow] =
|
||||
if not percent_ then sortedrows
|
||||
else dbg1 "mappedsortedrows"
|
||||
[(aname, alname, alevel, zipWith perdivide rowvals coltotals, rowtotal `perdivide` grandtotal, rowavg `perdivide` grandaverage)
|
||||
| (aname, alname, alevel, rowvals, rowtotal, rowavg) <- sortedrows
|
||||
]
|
||||
mappedtotalsrow :: MultiBalanceReportTotals =
|
||||
if not percent_ then totalsrow
|
||||
else dbg1 "mappedtotalsrow" (
|
||||
map (\t -> perdivide t t) coltotals,
|
||||
perdivide grandtotal grandtotal,
|
||||
perdivide grandaverage grandaverage)
|
||||
|
||||
-- | Given a MultiBalanceReport and its normal balance sign,
|
||||
-- if it is known to be normally negative, convert it to normally positive.
|
||||
mbrNormaliseSign :: NormalSign -> MultiBalanceReport -> MultiBalanceReport
|
||||
|
||||
@ -113,6 +113,7 @@ data ReportOpts = ReportOpts {
|
||||
,no_total_ :: Bool
|
||||
,pretty_tables_ :: Bool
|
||||
,sort_amount_ :: Bool
|
||||
,percent_ :: Bool
|
||||
,invert_ :: Bool -- ^ if true, flip all amount signs in reports
|
||||
,normalbalance_ :: Maybe NormalSign
|
||||
-- ^ This can be set when running balance reports on a set of accounts
|
||||
@ -158,6 +159,7 @@ defreportopts = ReportOpts
|
||||
def
|
||||
def
|
||||
def
|
||||
def
|
||||
|
||||
rawOptsToReportOpts :: RawOpts -> IO ReportOpts
|
||||
rawOptsToReportOpts rawopts = checkReportOpts <$> do
|
||||
@ -186,6 +188,7 @@ rawOptsToReportOpts rawopts = checkReportOpts <$> do
|
||||
,row_total_ = boolopt "row-total" rawopts'
|
||||
,no_total_ = boolopt "no-total" rawopts'
|
||||
,sort_amount_ = boolopt "sort-amount" rawopts'
|
||||
,percent_ = boolopt "percent" rawopts'
|
||||
,invert_ = boolopt "invert" rawopts'
|
||||
,pretty_tables_ = boolopt "pretty-tables" rawopts'
|
||||
,color_ = color
|
||||
|
||||
@ -292,6 +292,7 @@ balancemode = hledgerCommandMode
|
||||
,flagNone ["budget"] (setboolopt "budget") "show performance compared to budget goals defined by periodic transactions"
|
||||
,flagNone ["invert"] (setboolopt "invert") "display all amounts with reversed sign"
|
||||
,flagNone ["transpose"] (setboolopt "transpose") "transpose rows and columns"
|
||||
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
||||
]
|
||||
++ outputflags
|
||||
)
|
||||
|
||||
@ -156,6 +156,36 @@ Flat-mode balance reports, which normally show exclusive balances, show inclusiv
|
||||
|
||||
<!-- $ for y in 2006 2007 2008 2009 2010; do echo; echo $y; hledger -f $y.journal balance ^expenses --depth 2; done -->
|
||||
|
||||
### Percentages
|
||||
|
||||
With `-%` or `--percent`, balance reports show each account's value expressed
|
||||
as a percentage of the column's total. This is useful to get an overview of
|
||||
the relative sizes of account balances. For example to obtain an overview of
|
||||
expenses:
|
||||
|
||||
```shell
|
||||
$ hledger balance expenses -%
|
||||
100.0 % expenses
|
||||
50.0 % food
|
||||
50.0 % supplies
|
||||
--------------------
|
||||
100.0 %
|
||||
```
|
||||
|
||||
Note that `--tree` does not have an effect on `-%`. The percentages are always
|
||||
relative to the total sum of each column, they are never relative to the parent
|
||||
account.
|
||||
|
||||
Since the percentages are relative to the columns sum, it is usually not useful
|
||||
to calculate percentages if the signs of the amounts are mixed. Although the
|
||||
results are technically correct, they are most likely useless. Especially in
|
||||
a balance report that sums up to zero (eg `hledger balance -B`) all percentage
|
||||
values will be zero.
|
||||
|
||||
This flag does not work if the report contains any mixed commodity accounts. If
|
||||
there are mixed commodity accounts in the report be sure to use `-V`
|
||||
or `-B` to coerce the report into using a single commodity.
|
||||
|
||||
### Multicolumn balance report
|
||||
|
||||
Multicolumn or tabular balance reports are a very useful hledger feature,
|
||||
|
||||
@ -40,6 +40,8 @@ you can alter the report mode with `--change`/`--cumulative`/`--historical`.
|
||||
Normally balancesheet shows historical ending balances, which is what
|
||||
you need for a balance sheet; note this means it ignores report begin
|
||||
dates (and `-T/--row-total`, since summing end balances generally does not make sense).
|
||||
Instead of absolute values [percentages](#percentages) can be displayed
|
||||
with `-%`.
|
||||
|
||||
This command also supports
|
||||
[output destination](hledger.html#output-destination) and
|
||||
|
||||
@ -31,6 +31,8 @@ will be shown, one for each report period.
|
||||
Normally cashflow shows changes in assets per period, though
|
||||
as with [multicolumn balance reports](#multicolumn-balance-reports)
|
||||
you can alter the report mode with `--change`/`--cumulative`/`--historical`.
|
||||
Instead of absolute values [percentages](#percentages) can be displayed
|
||||
with `-%`.
|
||||
|
||||
This command also supports
|
||||
[output destination](hledger.html#output-destination) and
|
||||
|
||||
@ -42,6 +42,8 @@ will be shown, one for each report period.
|
||||
Normally incomestatement shows revenues/expenses per period, though
|
||||
as with [multicolumn balance reports](#multicolumn-balance-reports)
|
||||
you can alter the report mode with `--change`/`--cumulative`/`--historical`.
|
||||
Instead of absolute values [percentages](#percentages) can be displayed
|
||||
with `-%`.
|
||||
|
||||
This command also supports
|
||||
[output destination](hledger.html#output-destination) and
|
||||
|
||||
@ -106,6 +106,7 @@ compoundBalanceCommandMode CompoundBalanceCommandSpec{..} =
|
||||
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
|
||||
,flagNone ["pretty-tables"] (setboolopt "pretty-tables") "use unicode when displaying tables"
|
||||
,flagNone ["sort-amount","S"] (setboolopt "sort-amount") "sort by amount instead of account code/name"
|
||||
,flagNone ["percent", "%"] (setboolopt "percent") "express values in percentage of each column's total"
|
||||
,outputFormatFlag
|
||||
,outputFileFlag
|
||||
]
|
||||
@ -135,13 +136,11 @@ compoundBalanceCommand CompoundBalanceCommandSpec{..} opts@CliOpts{reportopts_=r
|
||||
-- are used in single column mode, since in that situation we will be using
|
||||
-- balanceReportFromMultiBalanceReport which does not support eliding boring parents,
|
||||
-- and tree mode hides this.. or something.. XXX
|
||||
ropts'
|
||||
| not (flat_ ropts) &&
|
||||
interval_==NoInterval &&
|
||||
balancetype `elem` [CumulativeChange, HistoricalBalance]
|
||||
= ropts{balancetype_=balancetype, accountlistmode_=ALTree}
|
||||
| otherwise
|
||||
= ropts{balancetype_=balancetype}
|
||||
ropts' = ropts{
|
||||
balancetype_=balancetype,
|
||||
accountlistmode_=if not (flat_ ropts) && interval_==NoInterval && balancetype `elem` [CumulativeChange, HistoricalBalance] then ALTree else accountlistmode_,
|
||||
no_total_=if percent_ && length cbcqueries > 1 then True else no_total_
|
||||
}
|
||||
userq = queryFromOpts d ropts'
|
||||
format = outputFormatFromOpts opts
|
||||
|
||||
|
||||
42
tests/balance/percent.test
Normal file
42
tests/balance/percent.test
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env shelltest
|
||||
# 1. Single column percent
|
||||
hledger -f sample.journal balance expenses -%
|
||||
>>>
|
||||
100.0 % expenses
|
||||
50.0 % food
|
||||
50.0 % supplies
|
||||
--------------------
|
||||
100.0 %
|
||||
>>>= 0
|
||||
|
||||
# 2. Multi column percent
|
||||
hledger -f sample.journal balance expenses -% -p daily
|
||||
>>>
|
||||
Balance changes in 2008/06/03:
|
||||
|
||||
|| 2008/06/03
|
||||
===================++============
|
||||
expenses:food || 50.0 %
|
||||
expenses:supplies || 50.0 %
|
||||
-------------------++------------
|
||||
|| 100.0 %
|
||||
>>>= 0
|
||||
|
||||
# 3. In a balanced ledger everything should sum up to zero, therefore all percentages should be zero.
|
||||
hledger -f sample.journal balance -% -p quarterly
|
||||
>>>
|
||||
Balance changes in 2008:
|
||||
|
||||
|| 2008q1 2008q2 2008q3 2008q4
|
||||
======================++================================
|
||||
assets:bank:checking || 0 0 0 0
|
||||
assets:bank:saving || 0 0 0 0
|
||||
assets:cash || 0 0 0 0
|
||||
expenses:food || 0 0 0 0
|
||||
expenses:supplies || 0 0 0 0
|
||||
income:gifts || 0 0 0 0
|
||||
income:salary || 0 0 0 0
|
||||
liabilities:debts || 0 0 0 0
|
||||
----------------------++--------------------------------
|
||||
|| 0 0 0 0
|
||||
>>>= 0
|
||||
@ -283,3 +283,26 @@ Income Statement 2008/01/31,,2008/12/31 (Historical Ending Balances)
|
||||
===================++================================================================================================================================================
|
||||
Net: || $1 $1 $1 $1 $1 0 0 0 0 0 0 0
|
||||
>>>= 0
|
||||
|
||||
# 8. Percentage test
|
||||
hledger -f sample.journal incomestatement -p 'quarterly 2008' -T --average -%
|
||||
>>>
|
||||
Income Statement 2008
|
||||
|
||||
|| 2008q1 2008q2 2008q3 2008q4 Total Average
|
||||
===================++====================================================
|
||||
Revenues ||
|
||||
-------------------++----------------------------------------------------
|
||||
income:gifts || 0 100.0 % 0 0 50.0 % 50.0 %
|
||||
income:salary || 100.0 % 0 0 0 50.0 % 50.0 %
|
||||
-------------------++----------------------------------------------------
|
||||
|| 100.0 % 100.0 % 0 0 100.0 % 100.0 %
|
||||
===================++====================================================
|
||||
Expenses ||
|
||||
-------------------++----------------------------------------------------
|
||||
expenses:food || 0 50.0 % 0 0 50.0 % 50.0 %
|
||||
expenses:supplies || 0 50.0 % 0 0 50.0 % 50.0 %
|
||||
-------------------++----------------------------------------------------
|
||||
|| 0 100.0 % 0 0 100.0 % 100.0 %
|
||||
>>>= 0
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user