lib: multiBalanceReport: Break calculateAccountChanges and acctChangesFromPostings separate functions.

This commit is contained in:
Stephen Morgan 2020-06-12 12:23:57 +10:00
parent a72c4f285b
commit 0dcfddd201
3 changed files with 62 additions and 36 deletions

View File

@ -22,8 +22,11 @@ module Hledger.Reports.MultiBalanceReport (
) )
where where
import Data.Foldable (toList)
import Data.List import Data.List
import Data.List.Extra (nubSort) import Data.List.Extra (nubSort)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Map (Map) import Data.Map (Map)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe import Data.Maybe
@ -125,12 +128,9 @@ multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle =
startbals = dbg' "startbals" $ startingBalances ropts q j' reportspan startbals = dbg' "startbals" $ startingBalances ropts q j' reportspan
-- The matched accounts with a starting balance. All of these should appear -- The matched accounts with a starting balance. All of these should appear
-- in the report even if they have no postings during the report period. -- in the report even if they have no postings during the report period.
startaccts = dbg'' "startaccts" $ map fst startbals startaccts = dbg'' "startaccts" $ HM.keys startbals
-- Helpers to look up an account's starting balance. -- Helpers to look up an account's starting balance.
startingBalanceFor a = fromMaybe nullmixedamt $ lookup a startbals startingBalanceFor a = HM.lookupDefault nullmixedamt a startbals
----------------------------------------------------------------------
-- 3. Gather postings for each column.
-- Postings matching the query within the report period. -- Postings matching the query within the report period.
ps :: [(Posting, Day)] = dbg'' "ps" $ getPostings ropts reportq j' ps :: [(Posting, Day)] = dbg'' "ps" $ getPostings ropts reportq j'
@ -142,21 +142,8 @@ multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle =
-- Group postings into their columns. -- Group postings into their columns.
colps = dbg'' "colps" $ calculateColumns colspans ps colps = dbg'' "colps" $ calculateColumns colspans ps
---------------------------------------------------------------------- -- Each account's balance changes across all columns.
-- 4. Calculate account balance changes in each column. acctchanges = dbg'' "acctchanges" $ calculateAccountChanges ropts q startbals colps
-- In each column, gather the accounts that have postings and their change amount.
acctChangesFromPostings :: [Posting] -> [(ClippedAccountName, MixedAmount)]
acctChangesFromPostings ps = [(aname a, (if tree_ ropts then aibalance else aebalance) a) | a <- as]
where
as = depthLimit $
(if tree_ ropts then id else filter ((>0).anumpostings)) $
drop 1 $ accountsFromPostings ps
depthLimit
| tree_ ropts = filter ((depthq `matchesAccount`).aname) -- exclude deeper balances
| otherwise = clipAccountsAndAggregate depth -- aggregate deeper balances at the depth limit
colacctchanges :: [[(ClippedAccountName, MixedAmount)]] =
dbg'' "colacctchanges" $ map (acctChangesFromPostings . snd) $ M.toList colps
---------------------------------------------------------------------- ----------------------------------------------------------------------
-- 5. Gather the account balance changes into a regular matrix including the accounts -- 5. Gather the account balance changes into a regular matrix including the accounts
@ -173,16 +160,6 @@ multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle =
where where
allpostedaccts :: [AccountName] = allpostedaccts :: [AccountName] =
dbg'' "allpostedaccts" . sort . accountNamesFromPostings $ map fst ps dbg'' "allpostedaccts" . sort . accountNamesFromPostings $ map fst ps
-- Each column's balance changes for each account, adding zeroes where needed.
colallacctchanges :: [[(ClippedAccountName, MixedAmount)]] =
dbg'' "colallacctchanges"
[ sortOn fst $ unionBy (\(a,_) (a',_) -> a == a') postedacctchanges zeroes
| postedacctchanges <- colacctchanges ]
where zeroes = [(a, nullmixedamt) | a <- displayaccts]
-- Transpose to get each account's balance changes across all columns.
acctchanges :: [(ClippedAccountName, [MixedAmount])] =
dbg'' "acctchanges"
[(a, map snd abs) | abs@((a,_):_) <- transpose colallacctchanges] -- never null, or used when null...
---------------------------------------------------------------------- ----------------------------------------------------------------------
-- 6. Build the report rows. -- 6. Build the report rows.
@ -191,7 +168,8 @@ multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle =
rows :: [MultiBalanceReportRow] = rows :: [MultiBalanceReportRow] =
dbg'' "rows" $ dbg'' "rows" $
[ PeriodicReportRow a (accountNameLevel a) valuedrowbals rowtot rowavg [ PeriodicReportRow a (accountNameLevel a) valuedrowbals rowtot rowavg
| (a,changes) <- dbg'' "acctchanges" acctchanges | (a,changesMap) <- HM.toList acctchanges
, let changes = toList changesMap
-- The row amounts to be displayed: per-period changes, -- The row amounts to be displayed: per-period changes,
-- zero-based cumulative totals, or -- zero-based cumulative totals, or
-- starting-balance-based historical balances. -- starting-balance-based historical balances.
@ -315,8 +293,8 @@ multiBalanceReportWith ropts@ReportOpts{..} q j priceoracle =
-- --
-- Balances at report start date, from all earlier postings which otherwise match the query. -- Balances at report start date, from all earlier postings which otherwise match the query.
-- These balances are unvalued except maybe converted to cost. -- These balances are unvalued except maybe converted to cost.
startingBalances :: ReportOpts -> Query -> Journal -> DateSpan -> [(AccountName, MixedAmount)] startingBalances :: ReportOpts -> Query -> Journal -> DateSpan -> HashMap AccountName MixedAmount
startingBalances ropts q j reportspan = map (\(a,_,_,b) -> (a,b)) startbalanceitems startingBalances ropts q j reportspan = HM.fromList $ map (\(a,_,_,b) -> (a,b)) startbalanceitems
where where
(startbalanceitems,_) = dbg'' "starting balance report" $ (startbalanceitems,_) = dbg'' "starting balance report" $
balanceReport ropts''{value_=Nothing, percent_=False} startbalq j balanceReport ropts''{value_=Nothing, percent_=False} startbalq j
@ -386,6 +364,37 @@ calculateColumns colspans = foldr addPosting emptyMap
addPosting (p, d) = maybe id (M.adjust (p:)) $ latestSpanContaining colspans d addPosting (p, d) = maybe id (M.adjust (p:)) $ latestSpanContaining colspans d
emptyMap = M.fromList . zip colspans $ repeat [] emptyMap = M.fromList . zip colspans $ repeat []
-- | Calculate account balance changes in each column.
--
-- In each column, gather the accounts that have postings and their change amount.
acctChangesFromPostings :: ReportOpts -> Query -> [Posting] -> HashMap ClippedAccountName MixedAmount
acctChangesFromPostings ropts q ps =
HM.fromList [(aname a, (if tree_ ropts then aibalance else aebalance) a) | a <- as]
where
as = depthLimit $
(if tree_ ropts then id else filter ((>0).anumpostings)) $
drop 1 $ accountsFromPostings ps
depthLimit
| tree_ ropts = filter ((depthq `matchesAccount`) . aname) -- exclude deeper balances
| otherwise = clipAccountsAndAggregate $ queryDepth depthq -- aggregate deeper balances at the depth limit
depthq = dbg1 "depthq" $ filterQuery queryIsDepth q
-- | Gather the account balance changes into a regular matrix including the accounts
-- from all columns
calculateAccountChanges :: ReportOpts -> Query
-> HashMap ClippedAccountName MixedAmount
-> Map DateSpan [Posting]
-> HashMap ClippedAccountName (Map DateSpan MixedAmount)
calculateAccountChanges ropts q startbals colps = acctchanges
where
-- Transpose to get each account's balance changes across all columns.
acctchanges = transposeMap colacctchanges <> (zeros <$ startbals)
colacctchanges :: Map DateSpan (HashMap ClippedAccountName MixedAmount) =
dbg'' "colacctchanges" $ fmap (acctChangesFromPostings ropts q) colps
zeros = nullmixedamt <$ colacctchanges
-- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport, -- | Generates a simple non-columnar BalanceReport, but using multiBalanceReport,
-- in order to support --historical. Does not support tree-mode boring parent eliding. -- in order to support --historical. Does not support tree-mode boring parent eliding.
@ -403,6 +412,20 @@ balanceReportFromMultiBalanceReport opts q j = (rows', total)
) | PeriodicReportRow a d amts _ _ <- rows] ) | PeriodicReportRow a d amts _ _ <- rows]
total = headDef nullmixedamt totals total = headDef nullmixedamt totals
-- | Transpose a Map of HashMaps to a HashMap of Maps.
transposeMap :: Map DateSpan (HashMap AccountName MixedAmount)
-> HashMap AccountName (Map DateSpan MixedAmount)
transposeMap xs = M.foldrWithKey addSpan mempty xs
where
addSpan span acctmap seen = HM.foldrWithKey (addAcctSpan span) seen acctmap
addAcctSpan span acct a = HM.alter f acct
where f = Just . M.insert span a . fromMaybe emptySpanMap
emptySpanMap = nullmixedamt <$ xs
-- Local debug helper -- Local debug helper
-- add a prefix to this function's debug output -- add a prefix to this function's debug output
dbg s = let p = "multiBalanceReport" in Hledger.Utils.dbg3 (p++" "++s) dbg s = let p = "multiBalanceReport" in Hledger.Utils.dbg3 (p++" "++s)
@ -411,7 +434,6 @@ dbg'' s = let p = "multiBalanceReport" in Hledger.Utils.dbg5 (p++" "++s)
-- dbg = const id -- exclude this function from debug output -- dbg = const id -- exclude this function from debug output
-- common rendering helper, XXX here for now -- common rendering helper, XXX here for now
tableAsText :: ReportOpts -> (a -> String) -> Table String String a -> String tableAsText :: ReportOpts -> (a -> String) -> Table String String a -> String
tableAsText (ReportOpts{pretty_tables_ = pretty}) showcell = tableAsText (ReportOpts{pretty_tables_ = pretty}) showcell =
unlines unlines

View File

@ -1,10 +1,10 @@
cabal-version: 1.12 cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.33.0. -- This file has been generated from package.yaml by hpack version 0.33.1.
-- --
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
-- --
-- hash: c30491f8c77b1d38a1992455cc9c340cbcb17e95ec5c07085f9987b289747ba1 -- hash: dd7c200231996bc96dfb65f042843355e9f7db7002d68c953ada6e89cedd5cc5
name: hledger-lib name: hledger-lib
version: 1.18.99 version: 1.18.99
@ -149,6 +149,7 @@ library
, timeit , timeit
, transformers >=0.2 , transformers >=0.2
, uglymemo , uglymemo
, unordered-containers >=0.2
, utf8-string >=0.3.5 , utf8-string >=0.3.5
default-language: Haskell2010 default-language: Haskell2010
@ -202,6 +203,7 @@ test-suite doctest
, timeit , timeit
, transformers >=0.2 , transformers >=0.2
, uglymemo , uglymemo
, unordered-containers >=0.2
, utf8-string >=0.3.5 , utf8-string >=0.3.5
if (impl(ghc < 8.2)) if (impl(ghc < 8.2))
buildable: False buildable: False
@ -257,6 +259,7 @@ test-suite unittest
, timeit , timeit
, transformers >=0.2 , transformers >=0.2
, uglymemo , uglymemo
, unordered-containers >=0.2
, utf8-string >=0.3.5 , utf8-string >=0.3.5
buildable: True buildable: True
default-language: Haskell2010 default-language: Haskell2010

View File

@ -82,6 +82,7 @@ dependencies:
- time >=1.5 - time >=1.5
- timeit - timeit
- transformers >=0.2 - transformers >=0.2
- unordered-containers >=0.2
- uglymemo - uglymemo
- utf8-string >=0.3.5 - utf8-string >=0.3.5
- extra >=1.6.3 - extra >=1.6.3