From 7f6cf1f849ff9115f90a93504adcb291e1746d88 Mon Sep 17 00:00:00 2001 From: Simon Michael Date: Sun, 19 Oct 2014 17:53:20 -0700 Subject: [PATCH] balance, register, register-csv: depth 0 shows summary items (#206) Previously, a depth:0 query produced an empty report (since there are no level zero accounts). Now, it aggregates all data into one summary item with account name "...". This makes it easier to see the kind of data Gwern was looking for from register-csv (net worth over time). Eg this shows one line per month summarising the total of assets and liabilities: hledger register-csv -- -MHE ^assets ^liabilities depth:0 Single and multi-column balance reports behave similarly. --- hledger-lib/Hledger/Data/Account.hs | 2 +- hledger-lib/Hledger/Data/AccountName.hs | 8 ++++++ hledger-lib/Hledger/Reports/BalanceReport.hs | 14 +++++++--- .../Hledger/Reports/MultiBalanceReports.hs | 8 +++--- hledger-lib/Hledger/Reports/PostingsReport.hs | 16 ++++++----- tests/balance/depth.test | 21 ++++++++++++++- tests/register/depth.test | 27 +++++++++++++++++-- 7 files changed, 79 insertions(+), 17 deletions(-) diff --git a/hledger-lib/Hledger/Data/Account.hs b/hledger-lib/Hledger/Data/Account.hs index c98a75904..970439916 100644 --- a/hledger-lib/Hledger/Data/Account.hs +++ b/hledger-lib/Hledger/Data/Account.hs @@ -125,7 +125,7 @@ clipAccounts d a = a{asubs=subs} clipAccountsAndAggregate :: Int -> [Account] -> [Account] clipAccountsAndAggregate d as = combined where - clipped = [a{aname=clipAccountName d $ aname a} | a <- as] + clipped = [a{aname=clipOrEllipsifyAccountName d $ aname a} | a <- as] combined = [a{aebalance=sum (map aebalance same)} | same@(a:_) <- groupBy (\a1 a2 -> aname a1 == aname a2) clipped] {- diff --git a/hledger-lib/Hledger/Data/AccountName.hs b/hledger-lib/Hledger/Data/AccountName.hs index 2fa7c166c..ea61ca2c1 100644 --- a/hledger-lib/Hledger/Data/AccountName.hs +++ b/hledger-lib/Hledger/Data/AccountName.hs @@ -108,9 +108,17 @@ elideAccountName width s = | length ss > 1 = elideparts width (done++[take 2 $ head ss]) (tail ss) | otherwise = done++ss +-- | Keep only the first n components of an account name, where n +-- is a positive integer. If n is 0, returns the empty string. clipAccountName :: Int -> AccountName -> AccountName clipAccountName n = accountNameFromComponents . take n . accountNameComponents +-- | Keep only the first n components of an account name, where n +-- is a positive integer. If n is 0, returns "...". +clipOrEllipsifyAccountName :: Int -> AccountName -> AccountName +clipOrEllipsifyAccountName 0 = const "..." +clipOrEllipsifyAccountName n = accountNameFromComponents . take n . accountNameComponents + -- | Convert an account name to a regular expression matching it and its subaccounts. accountNameToAccountRegex :: String -> String accountNameToAccountRegex "" = "" diff --git a/hledger-lib/Hledger/Reports/BalanceReport.hs b/hledger-lib/Hledger/Reports/BalanceReport.hs index 6492379f3..020420984 100644 --- a/hledger-lib/Hledger/Reports/BalanceReport.hs +++ b/hledger-lib/Hledger/Reports/BalanceReport.hs @@ -67,6 +67,9 @@ balanceReport opts q j = (items, total) accts = ledgerRootAccount $ ledgerFromJournal q $ journalSelectingAmountFromOpts opts j accts' :: [Account] + | queryDepth q == 0 = + dbg "accts" $ + take 1 $ clipAccountsAndAggregate (queryDepth q) $ flattenAccounts accts | flat_ opts = dbg "accts" $ filterzeros $ filterempty $ @@ -75,7 +78,8 @@ balanceReport opts q j = (items, total) filter (not.aboring) $ drop 1 $ flattenAccounts $ markboring $ - prunezeros $ clipAccounts (queryDepth q) accts + prunezeros $ + clipAccounts (queryDepth q) accts where balance = if flat_ opts then aebalance else aibalance filterzeros = if empty_ opts then id else filter (not . isZeroMixedAmount . balance) @@ -99,14 +103,18 @@ markBoringParentAccounts = tieAccountParents . mapAccounts mark | otherwise = a balanceReportItem :: ReportOpts -> Query -> Account -> BalanceReportItem -balanceReportItem opts _ a@Account{aname=name} +balanceReportItem opts q a | flat_ opts = ((name, name, 0), (if flatShowsExclusiveBalance then aebalance else aibalance) a) | otherwise = ((name, elidedname, indent), aibalance a) where + name | queryDepth q > 0 = aname a + | otherwise = "..." elidedname = accountNameFromComponents (adjacentboringparentnames ++ [accountLeafName name]) adjacentboringparentnames = reverse $ map (accountLeafName.aname) $ takeWhile aboring $ parents indent = length $ filter (not.aboring) parents - parents = init $ parentAccounts a + -- parents exclude the tree's root node + parents = case parentAccounts a of [] -> [] + as -> init as -- -- the above using the newer multi balance report code: -- balanceReport' opts q j = (items, total) diff --git a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs index 9af2116d6..44fbaf7d0 100644 --- a/hledger-lib/Hledger/Reports/MultiBalanceReports.hs +++ b/hledger-lib/Hledger/Reports/MultiBalanceReports.hs @@ -129,7 +129,7 @@ multiBalanceReport opts q j = MultiBalanceReport (displayspans, items, totals) displayedAccts :: [ClippedAccountName] = dbg "displayedAccts" $ (if tree_ opts then expandAccountNames else id) $ - nub $ map (clipAccountName depth) $ + nub $ map (clipOrEllipsifyAccountName depth) $ if empty_ opts then nub $ sort $ startAccts ++ postedAccts else postedAccts acctBalChangesPerSpan :: [[(ClippedAccountName, MixedAmount)]] = @@ -150,7 +150,7 @@ multiBalanceReport opts q j = MultiBalanceReport (displayspans, items, totals) HistoricalBalance -> drop 1 $ scanl (+) (startingBalanceFor a) changes CumulativeBalance -> drop 1 $ scanl (+) nullmixedamt changes _ -> changes - , empty_ opts || any (not . isZeroMixedAmount) displayedBals + , empty_ opts || depth == 0 || any (not . isZeroMixedAmount) displayedBals ] totals :: [MixedAmount] = @@ -162,6 +162,6 @@ multiBalanceReport opts q j = MultiBalanceReport (displayspans, items, totals) dbg "highestlevelaccts" $ [a | a <- displayedAccts, not $ any (`elem` displayedAccts) $ init $ expandAccountName a] - dbg s = let p = "multiBalanceReport" in Hledger.Utils.dbg (p++" "++s) -- add prefix in debug output - -- dbg = const id -- exclude from debug output + dbg s = let p = "multiBalanceReport" in Hledger.Utils.dbg (p++" "++s) -- add prefix in this function's debug output + -- dbg = const id -- exclude this function from debug output diff --git a/hledger-lib/Hledger/Reports/PostingsReport.hs b/hledger-lib/Hledger/Reports/PostingsReport.hs index 3e657e7b7..cecb73826 100644 --- a/hledger-lib/Hledger/Reports/PostingsReport.hs +++ b/hledger-lib/Hledger/Reports/PostingsReport.hs @@ -85,7 +85,7 @@ postingsReport opts q j = (totallabel, items) whichdate = whichDateFromOpts opts itemps | interval == NoInterval = map (,Nothing) reportps | otherwise = summarisePostingsByInterval interval whichdate depth showempty reportspan reportps - items = postingsReportItems itemps (nullposting,Nothing) whichdate depth startbal runningcalc 1 + items = dbg "items" $ postingsReportItems itemps (nullposting,Nothing) whichdate depth startbal runningcalc 1 where startbal = if balancetype_ opts == HistoricalBalance then sumPostings precedingps else 0 runningcalc | average_ opts = \i avg amt -> avg + (amt - avg) `divideMixedAmount` (fromIntegral i) -- running average @@ -108,7 +108,7 @@ postingsReportItems ((p,menddate):ps) (pprev,menddateprev) wd d b runningcalcfn isfirstintxn = ptransaction p /= ptransaction pprev isdifferentdate = case wd of PrimaryDate -> postingDate p /= postingDate pprev SecondaryDate -> postingDate2 p /= postingDate2 pprev - p' = p{paccount=clipAccountName d $ paccount p} + p' = p{paccount= clipOrEllipsifyAccountName d $ paccount p} b' = runningcalcfn itemnum b (pamount p) -- | Generate one postings report line item, containing the posting, @@ -150,8 +150,10 @@ type SummaryPosting = (Posting, Maybe Day) -- postings within it, aggregate the postings into one summary posting per -- account. -- --- When a depth argument is present, postings to accounts of greater depth are --- also aggregated where possible. +-- When a depth argument is present, postings to accounts of greater +-- depth are also aggregated where possible. If the depth is 0, all +-- postings in the span are aggregated into a single posting with +-- account name "...". -- -- The showempty flag includes spans with no postings and also postings -- with 0 amount. @@ -166,8 +168,10 @@ summarisePostingsInDateSpan (DateSpan b e) wd depth showempty ps b' = fromMaybe (maybe nulldate postingdate $ headMay ps) b e' = fromMaybe (maybe (addDays 1 nulldate) postingdate $ lastMay ps) e summaryp = nullposting{pdate=Just b'} - clippedanames = nub $ map (clipAccountName depth) anames - summaryps = [summaryp{paccount=a,pamount=balance a} | a <- clippedanames] + clippedanames | depth > 0 = nub $ map (clipAccountName depth) anames + | otherwise = ["..."] + summaryps | depth > 0 = [summaryp{paccount=a,pamount=balance a} | a <- clippedanames] + | otherwise = [summaryp{paccount="...",pamount=sum $ map pamount ps}] summarypes = map (, Just e') $ (if showempty then id else filter (not . isZeroMixedAmount . pamount)) summaryps anames = sort $ nub $ map paccount ps -- aggregate balances by account, like ledgerFromJournal, then do depth-clipping diff --git a/tests/balance/depth.test b/tests/balance/depth.test index 7b970c498..e2f196c43 100644 --- a/tests/balance/depth.test +++ b/tests/balance/depth.test @@ -1,4 +1,4 @@ -# 1 +# 1. hledgerdev -f sample.journal balance --no-total --depth 1 >>> $-1 assets @@ -7,3 +7,22 @@ hledgerdev -f sample.journal balance --no-total --depth 1 $1 liabilities >>>=0 +# 2. Depth 0 aggregates everything into one line +hledgerdev -f sample.journal balance --no-total --depth 0 +>>> + 0 ... +>>>=0 + +# 3. Ditto in a multi-column balance report. +hledgerdev -f sample.journal balance -M -e 2008/4 --depth 0 +>>> +Balance changes in 2008/01: + + || 2008/01 +=====++========== + ... || 0 +-----++---------- + || 0 + +>>>=0 + diff --git a/tests/register/depth.test b/tests/register/depth.test index 19ea80c51..78dcd72cb 100644 --- a/tests/register/depth.test +++ b/tests/register/depth.test @@ -8,7 +8,7 @@ hledgerdev -f - register aa --depth 1 2010/01/01 x a 1 1 >>>=0 -# 2. similar to above, postings with same clipped account name are not aggregated +# 2. separate postings remain separate hledgerdev -f - register aa --depth 2 <<< 2010/1/1 x @@ -28,7 +28,7 @@ hledgerdev -f - register aa --depth 2 2010/01/02 z a:aa 1 3 >>>=0 -# 3. as above, but with a reporting interval causing postings to be aggregated +# 3. with a reporting interval, all postings are aggregated under each (clipped) account hledgerdev -f - register aa --depth 1 --daily <<< 2010/1/1 x @@ -56,3 +56,26 @@ hledgerdev -f - register a --depth 1 --cleared 2012/01/01 (a) 1 1 >>>2 >>>=0 + +# 5. depth 0 aggregates everything into a single line +hledgerdev -f - register --depth 0 --daily a b +<<< +2010/1/1 x + a:aa 1 + b:bb 2 + c:cc + +2010/1/1 y + a:aa 1 + b:bb 2 + c:cc + +2010/1/2 z + a:aa 1 + b:bb 2 + c:cc +>>> +2010/01/01d ... 6 6 +2010/01/02d ... 3 9 +>>>=0 +