fava icon indicating copy to clipboard operation
fava copied to clipboard

Split Budget Entries

Open JP-Ellis opened this issue 11 months ago • 5 comments

Summary

I think it would be quite helpful to have some syntax to allow for split budget entries which combine into the one budget amount when it comes to calculations.

Motivation & Proposed Implementation

Let's consider a scenario where someone is budgeting for entertainment expenses as follows:

  • Netflix: $20 monthly
  • Disney: $35 monthly
  • Movies: $10 weekly
  • Musicals: $400 yearly
  • Games: $200 yearly
  • Other: $200 yearly

I can see two ways of implementing this currently, and I propose a new way.

Implementation 1 (Currently possible)

Create sub-accounts for each kind of entertainment expense and a single budget entry for each account:

** account.bean

2024-07-01 open Expenses:Entertainment:Netflix
2024-07-01 open Expenses:Entertainment:Disney
2024-07-01 open Expenses:Entertainment:Movies
2024-07-01 open Expenses:Entertainment:Musicals
2024-07-01 open Expenses:Entertainment:Games
2024-07-01 open Expenses:Entertainment:Other

** budget.bean

2024-07-01 custom "budget" Expenses:Entertainment:Netflix  "monthly" 20.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Disney   "monthly" 35.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Movies   "weekly"  10.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Musicals "yearly" 400.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Games    "yearly" 200.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Other    "yearly" 200.00 AUD

While this is fine, it is very verbose especially in the ledger itself where the user has to allocate each expense to the currect sub-account. It also does not scale well as more sub-accounts are added over time.

Implementation 2 (Currently possible)

Create a single entertainment account and a single budget entry for the account, and use comments to explain the split:

** account.bean

2024-07-01 open Expenses:Entertainment

** budget.bean

2024-07-01 custom "budget" Expenses:Entertainment  "monthly" (20.00 + 35.00 + 10.00 * 365/7 + 400.00/12 + 200.00/12 + 200.00/12) AUD
    ; Netflix: $20 monthly
    ; Disney: $35 monthly
    ; Movies: $10 weekly
    ; Musicals: $400 yearly
    ; Games: $200 yearly
    ; Other: $200 yearly

This is better when it comes to the ledger entries, but it is not ideal when it comes to the budget. It is prone to errors in calculation and lends itself to errors when the user eventually needs to update the budget.

Implementation 3 (Proposed)

Create a single entertainment account, and allow for budget sub-entries to be created which combine into the one budget at the nearest parent account:

** account.bean

2024-07-01 open Expenses:Entertainment

** budget.bean

2024-07-01 custom "budget" Expenses:Entertainment:Streaming:Netflix  "monthly" 20.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Streaming:Disney   "monthly" 35.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Movies   "weekly"  10.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Musicals "yearly" 400.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Games    "yearly" 200.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Other    "yearly" 200.00 AUD

Internally, I'm thinking this would work as follows:

graph TD
    A[Does the account exist?]
    A -->|Yes| B[Set budget for account]
    A -->|No| C[Find nearest parent account]
    C --> D[Add budget to parent account]

This would allow for the user to have a more concise ledger and set of accounts, while still being able to easily see the breakdown within the budget itself. The budget is typically changed less often than the ledger, and so this would be a more user-friendly way of managing the budget.

This proposed implementation would see no changes to the syntax of the budgets, but obviousy would require changes to the way the budgets are calculated. It would of course be possible to introduce some syntax to be more explicit about this, and I'm open to suggestions on that front.

Edge Case: Parent and Child Budgets

One edge case to consider with this implementation is what ought to happen if both a parent and child account have a budget set. For example:

** account.bean

2024-07-01 open Expenses:Entertainment

** budget.bean

2024-07-01 custom "budget" Expenses:Entertainment  "monthly" 100.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Netflix  "monthly" 20.00 AUD

I don't have a strong opinion on this, but I can imagine a few ways of handling this:

  1. It is an error to combine both methods. Either the account itself has a budget, or the account's budget is set from the child accounts; but not both.
  2. The child account's budget is added to the parent account's budget. This would result in an overall budget of $120.00 for Expenses:Entertainment.

Edge Case: Budget Changes

Another edge case to consider is what ought to happen if the user changes the budget for a child account. For example:

** account.bean

2024-07-01 open Expenses:Entertainment

** budget.bean

2023-07-01 custom "budget" Expenses:Entertainment:Movies "weekly"  10.00 AUD
2023-07-01 custom "budget" Expenses:Entertainment:Netflix  "monthly" 17.00 AUD
2024-01-01 custom "budget" Expenses:Entertainment:Movies "weekly"  15.00 AUD
2024-07-01 custom "budget" Expenses:Entertainment:Netflix  "monthly" 20.00 AUD

Provided the sub-account names match, I think it would be reasonable to have the budget for the parent account automatically update. In this case, the budget would have been $27.00 from 2023-07-01 to 2024-01-01, and then $32.00 until 2024-07-01 where it changes to $35.00.

Implementation 4 (unlikely)

One last option would be to allow for budgets to be repeated and combined. This would allow for the following:

** account.bean

2024-07-01 open Expenses:Entertainment

** budget.bean

2024-07-01 custom "budget" Expenses:Entertainment "monthly" 20.00 AUD  ; Netflix
2024-07-01 custom "budget" Expenses:Entertainment "monthly" 35.00 AUD  ; Disney
2024-07-01 custom "budget" Expenses:Entertainment "weekly"  10.00 AUD  ; Movies
2024-07-01 custom "budget" Expenses:Entertainment "yearly" 400.00 AUD  ; Musicals
2024-07-01 custom "budget" Expenses:Entertainment "yearly" 200.00 AUD  ; Games
2024-07-01 custom "budget" Expenses:Entertainment "yearly" 200.00 AUD  ; Other

This would keep the existing syntax and not requiring finding the nearest parent account. The main (and I suspect insurmountable) issue with this is that it does not make it clear how one would update a single budget entry. One could create some logic that combines budgets with the same date, and subsequent budgets overwrite previous ones, but I can't see this being a very user-friendly way of managing budgets.

Implementation

This would be a somewhat significant change to the way budgets are calculated, but I think it would be a very helpful change for users. I expect there would be some discussion required as to how best to implement this and whether my proposed implementation is the best way to go about it. I'm happy to help with this if it is something that is considered to be a good idea.

JP-Ellis avatar Jan 05 '25 21:01 JP-Ellis

This is something I really would like to be seen/added, and if need be, I would be happy to spend a few hours on a PR to add this feature. But at the same time, I don't want any work I do to be wasted if this is a no-go from the start.

Could I please have someone weigh in on this? @yagebu, it appears you are the main contributor at the moment; what are your thoughts?

JP-Ellis avatar Jan 23 '25 23:01 JP-Ellis

I don't quite understand why anything needs to be changed here. To me the ledger described under "Implementation 1" should already work like desired. These properly declare what kind of budgets you want to have on which accounts. And if you don't actually care about these subaccounts, you can simply collapse (manually or via collapse-pattern) Expenses:Entertainment and look at the cumulative balance and budgets. What's missing?

yagebu avatar May 26 '25 18:05 yagebu

Let's take a concrete example:

*** Budgets
2024-07-01 custom "budget" Expenses:Entertainment:Netflix "monthly"   75 USD
2024-07-01 custom "budget" Expenses:Entertainment:Spotify "monthly"  120 USD
2024-07-01 custom "budget" Expenses:Entertainment:Disney  "monthly"   82 USD
2024-07-01 custom "budget" Expenses:Groceries             "monthly" 1000 USD

*** Accounts
2024-07-01 open Expenses:Entertainment
2024-07-01 open Expenses:Groceries
2024-07-01 open Assets:Savings

*** Transactions
2024-07-06 * "Netflix Subscription"
    Expenses:Entertainment 75 USD
    Assets:Savings

2024-07-15 * "Spotify Subscription"
    Expenses:Entertainment 110 USD
    Assets:Savings

2024-07-20 * "Disney Subscription"
    Expenses:Entertainment 80 USD
    Assets:Savings

2024-07-02 * "Groceries"
    Expenses:Groceries 1210 USD
    Assets:Savings

The resulting budget view for the expenses is

Image

which appears incorrect to me. Specifically:

  • The Expenses:Groceries show an overspend of 210 USD compared, which is correct.
  • The Expenses:Entertainment has no information, but should display an underspend of 12 USD compared to the budget
  • The Expenses shows an overspend of 198 USD, which is actually correct even though the Expenses:Entertainment underspend isn't displayed.

JP-Ellis avatar Jul 08 '25 23:07 JP-Ellis

I think the budget being declared on non-existent accounts might be part of the issue here. The Expenses:Entertainment in your screenshot shows the non-cumulative balance of that account, so it's also compared to the non-cumulative budget here. Declaring the accounts and hiding them by default produces the display you want:

Image
*** Budgets
2024-07-01 custom "budget" Expenses:Entertainment:Netflix "monthly"   75 USD
2024-07-01 custom "budget" Expenses:Entertainment:Spotify "monthly"  120 USD
2024-07-01 custom "budget" Expenses:Entertainment:Disney  "monthly"   82 USD
2024-07-01 custom "budget" Expenses:Groceries             "monthly" 1000 USD

2024-07-01 custom "fava-option" "collapse-pattern" "Expenses:Entertainment"

*** Accounts
2024-07-01 open Expenses:Entertainment:Netflix
2024-07-01 open Expenses:Entertainment:Spotify
2024-07-01 open Expenses:Entertainment:Disney
2024-07-01 open Expenses:Entertainment
2024-07-01 open Expenses:Groceries
2024-07-01 open Assets:Savings

*** Transactions
2024-07-06 * "Netflix Subscription"
    Expenses:Entertainment 75 USD
    Assets:Savings

2024-07-15 * "Spotify Subscription"
    Expenses:Entertainment 110 USD
    Assets:Savings

2024-07-20 * "Disney Subscription"
    Expenses:Entertainment 80 USD
    Assets:Savings

2024-07-02 * "Groceries"
    Expenses:Groceries 1210 USD
    Assets:Savings

yagebu avatar Jul 12 '25 08:07 yagebu

Thanks for that!

Would it be possible to adjust the default behaviour in Fava to allow budgets against non-declared accounts, similar to what your workaround achieves?

I'm finding it very useful to have fine-grained budget entries because it makes it very easy to identify their origin. Another benefit is fine-grained budgets like this is that they are very easy to update.

At the same time, I would like to avoid the introduction of the corresponding fine-grained accounts, because I specifically do not want costs allocated these fine-grained accounts. If they aren't declared, then Beancount produces an error whenever someone tries to use them.

The rationale for this is that the budget is not modified as frequently, and most of the time is spent sorting through transactions and allocating expenses. While doing this, the autocompletions are immensely useful, but if I have fine-grained accounts which match exactly the parent account, these suggestions all of a sudden become much less useful.

One workaround might be to place a null-balance assertion in the far future for these fine-grained accounts. This would ensure that an error is raised if a transaction is allocated against it, but doesn't help with the issue of suggestions.

JP-Ellis avatar Jul 23 '25 00:07 JP-Ellis