-
-
Notifications
You must be signed in to change notification settings - Fork 326
Introduce multi-period Account data type and use it for MultiBalanceReport and BudgetReport. #2360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
This upgrades Account to enable it to do the hard work in MultiBalanceReport, but does not use the new functionality just yet. It continues to function as before by only using the "abhistorical" value.
For use in budget reports.
Ensure that implied accounts with no postings are not displayed, but accounts with zero balance and actual postings are.
Rephrase everything in terms of boringness to make for a clearer logical flow.
This removes the type alias Account, and replaces it with the fully-qualified name Account AccountBalance. This breaks some backwards compatibility, but that was already broken by the change of Account type constructor in any case. This simplifies the interface.
82a01da
to
6745f0a
Compare
mergeWithKey can create corrupt output if its inputs don't satisfy certain conditions. We restrict the domain here to only those cases where it is guaranteed safe. This still covers all the cases that we need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Initial comments.
|
||
-- tests | ||
|
||
tests_AccountBalance = testGroup "AccountBalance" [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep these separate for clarity if you like, but I suggest grouping them under tests_AccountBalance for export to avoid breaking the consistent pattern (every module exports one similarly named test group)
@@ -9,7 +9,6 @@ version: 1.42.99 | |||
synopsis: Terminal interface for the hledger accounting system | |||
description: A simple terminal user interface for the hledger accounting system. | |||
It can be a more convenient way to browse your accounts than the CLI. | |||
This package currently does not support Microsoft Windows, except in WSL. | |||
. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is in master and will disappear after rebase I think.
account a:ab | ||
account a:ac:aca | ||
account b | ||
# ** 11. In flat mode we can see that undeclared accounts are excluded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Text trivia:
I have been trying to move in the direction of "list" actually as the preferred term actually (for the -l short flag, eg. Possibly should go all the way and add --list long flag ?)
But your change matches the flag used in the command, so maybe it's better..
I feel maybe the old test description here was bad.. I think --declared includes declared accounts, rather than excluding undeclared ones. Perhaps we should drop that text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ok with the behaviour change here personally. I'd put a ! (= "breaking change") after the first commit message prefix which helps me notice these at changelog/relnotes time.
ref: is a new prefix meaning "refactoring" I guess. Ok, but perhaps it isn't the right one here since this is not strictly a refactoring. In such cases I often go with a generic dev:.
|
||
# ** 16. balance --flat --empty does not display accounts which have not been | ||
# seen, even if they're implied, but does show accounts that have been seen | ||
# with 0 balance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this means in userese ?
@@ -50,7 +50,6 @@ Budget performance in 2016-12-01..2016-12-03: | |||
|| 2016-12-01 2016-12-02 2016-12-03 | |||
==================++============================================================== | |||
assets:cash || $-10 [ 40% of $-25] $-14 [56% of $-25] $-51 [204% of $-25] | |||
expenses || $10 [ 40% of $25] $14 [56% of $25] $51 [204% of $25] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another behaviour change, worth noting with a ! in message.
The parent "expenses" account is not shown, because there's no explicit budget goal for it, and because we're in list mode ? So if we want to see aggregated budget performance, tree mode will be needed. Ok I guess.
Why is it still shown in the previous test ?
@@ -747,8 +745,7 @@ Budget performance in 2023-08: | |||
==============++================= | |||
income || 0 [0% of $-200] | |||
employment || 0 [0% of $-100] | |||
gifts || 0 | |||
cash || 0 [0% of $-100] | |||
gifts:cash || 0 [0% of $-100] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a consistency fix, squashing these like a normal balance report would. I think I tried and failed to fix this recently. 👍🏻
@@ -296,6 +296,7 @@ Income Statement .. | |||
=================++==== | |||
Revenues || | |||
-----------------++---- | |||
revenues || 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should drop "leaf" from this test's description now I guess.
data AccountBalances a = AccountBalances { | ||
abhistorical :: a -- ^ historical balance information | ||
,abdatemap :: IM.IntMap a -- ^ balance information associated to a start day | ||
} deriving (Eq, Functor, Generic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for using a type parameter ? Do we truly need it ?
How does the "start day" map, with Int keys I assume, work here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for using a type parameter ? Do we truly need it ?
For budget report, I guess. It hurts code comprehensability a bit.
@@ -733,18 +735,31 @@ nullaccountdeclarationinfo = AccountDeclarationInfo { | |||
|
|||
-- | An account, with its balances, parent/subaccount relationships, etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should change this to "An account within a hierarchy", or something. Representing/navigating a hierarchy of account balances is Account's purpose.
Could we quantify that a little more - eg "balance reports are 1% faster with 1k txns, 5% faster with 10k txns" ? |
Also I wonder if there's any memory impact, |
instance Monoid a => Monoid (AccountBalances a) where | ||
mempty = AccountBalances mempty mempty | ||
|
||
-- | Construct an 'AccountBalance' from a list. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Construct an 'AccountBalances' from a list
accountBalancesFromList :: a -> [(Day, a)] -> AccountBalances a | ||
accountBalancesFromList h = AccountBalances h . IM.fromList . map (\(d, a) -> (fromInteger $ toModifiedJulianDay d, a)) | ||
|
||
-- | Get the 'AccountBalance' associated to the period containing a given 'Day'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More general doc: Get the account balance information associated ... ?
lookupAccountBalance d (AccountBalances h as) = | ||
maybe h snd $ IM.lookupLE (fromInteger $ toModifiedJulianDay d) as | ||
|
||
-- | Add the 'AccountBalance' to the appropriate location in 'AccountBalances'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add balance information ...
{-| | ||
|
||
|
||
An 'AccountBalance' consists of a historical balance, along with balances |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe clarify that this module contains operations for two distinct types: AccountBalances and AccountBalance. (Easy for a reader to mix up). Almost worth separate modules ?
Nothing -> balances{abhistorical = abhistorical balances <> b} | ||
Just day -> balances{abdatemap = IM.insertWith (<>) (fromInteger $ toModifiedJulianDay day) b $ abdatemap balances} | ||
|
||
-- | Performs an operation on the contents of two 'AccountBalances'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merges two AccountBalances, using the given operation to combine their balance informations. ?
|
||
-- | Performs an operation on the contents of two 'AccountBalances', with | ||
-- separate treatments for those only in the first, only in the second, or in | ||
-- both 'AccountBalance's. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merges two AccountBalances, with separate operations for balance informations only in ... ?
applyAccountBalance :: (MixedAmount -> MixedAmount) -> AccountBalance -> AccountBalance | ||
applyAccountBalance f a = a{abebalance = f $ abebalance a, abibalance = f $ abibalance a} | ||
|
||
-- | Perform an operation on the 'MixedAmount' in two 'AccountBalance'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge two AccountBalance, using the given operation to combine their amounts ?
,aebalance :: MixedAmount -- ^ this account's balance, excluding subaccounts | ||
,aibalance :: MixedAmount -- ^ this account's balance, including subaccounts | ||
} deriving (Generic) | ||
,abalances :: AccountBalances a -- ^ historical and date-associated account balances |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to be clear on this everywhere, but maybe here mention both "end balances" (on a date) and "balance changes" (in a period) (consistent with terminology elsewhere) are/may be being represented by "AccountBalance(s)".
aname=a | ||
,asubs=map (uncurry accountTree') $ M.assocs m' | ||
} | ||
(accountFromBalances a bals){ asubs=map (uncurry accountTree') $ M.assocs m' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc and function is a bit hard to understand, any clarification welcome
mapAccounts f a = f a{asubs = map (mapAccounts f) $ asubs a} | ||
|
||
-- | Apply a function to all 'AccountBalances' within this and sub accounts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar functions in AccountBalances.hs are named "apply" - maybe "map" is best, like this one ?
allAccounts p a | ||
| not (p a) = False | ||
| otherwise = all (allAccounts p) $ asubs a | ||
|
||
-- | Add subaccount-inclusive balances to an account tree. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're here, we could make this one clearer:
Recalculate all the subaccount-inclusive balances in this account tree.
subtotals = foldMap abalances subs | ||
|
||
setImplicitBalances :: AccountBalances AccountBalance -> AccountBalances AccountBalance | ||
setImplicitBalances = mergeAccountBalances onlyChildren noChildren combineChildren subtotals |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean setInclusiveBalances ?
-- sort first, and vice versa. | ||
sortAccountTreeByAmount :: NormalSign -> Account -> Account | ||
sortAccountTreeByAmount normalsign = mapAccounts $ \a -> a{asubs=sortSubs $ asubs a} | ||
-- | Merge two accounts and their subaccounts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge two account trees ?
-- | Derive 1. an account tree and 2. each account's total exclusive | ||
-- and inclusive changes from a list of postings. | ||
-- This is the core of the balance command (and of *ledger). | ||
-- The accounts are returned as tree. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a tree
@@ -85,33 +108,47 @@ nullacct = Account | |||
-- The accounts are returned as a list in flattened tree order, | |||
-- and also reference each other as a tree. | |||
-- (The first account is the root of the tree.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this (and maybe the next), let's explain a bit more that this calculates multiple inclusive and exclusive change amounts for each account, associated with the dates produced by the given date-bucketing function (usually representing the end dates of report subperiods)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(It looks like this will enable non-contiguous report periods in future, which will be cool)
Thanks for the detailed comments. I'm on holiday at the moment, so it'll be
about 3 weeks before I can make the requested changes, but I will when I
get back.
|
This rejigs the
MultiBalanceReport
internals to use an enhancedAccount
data type to save the values. This has a few effects:Account
means thatBudgetReport
can be simplified.There are some small changes in behaviour with respect to budget reports, where it looked like some behaviour was implemented to work around needing to get the budget and actuals into the same shape so they could be merged. This is no longer necessary, but may still be desired for other reasons.
Let me know your thoughts.