hledger-forecast 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 410b448de275184312f25f5f5cc008fbaf945c27c40827c9f203fd3c14422729
4
- data.tar.gz: d9c9f019852246dee148758231bd8619f10e8246459dd2b719ede97f31ee8e05
3
+ metadata.gz: ac4e0a16f2b440612fb4596519b02c02d4a88b0d5f9cdb95c0988047a9892c3e
4
+ data.tar.gz: 6f9aed952f65c445d965a9b3dd7ccdb25fdb2c300bdee748ffab51b6a5a5056e
5
5
  SHA512:
6
- metadata.gz: 7700e2fc1778ad1e0548cd53ce3648b38ec2f56ce493204b5bb1efedff0d6621c3563ec09494ed2362f926aa3fe22c10f06394f551b5907cb8c9106c2642e3a1
7
- data.tar.gz: fba3f08d0c906fddaab46c31a236602b2843a0c6a5f56723cac9fdc89453e46b6871f129d0e8a684d369883f4dfa8a81cfde64e5997cf8ee4485e90e345ad772
6
+ metadata.gz: 40eb12f6238c4cc0f8fc37e85a74a6f4e88fbbd12917b07dfd2d305dd47f0cb09e1c999a343744d5766cb03c1484533e67ae6eb3e7ed6a7c3548f5f177eea55e
7
+ data.tar.gz: e09a771caf387bf62d9b09ca58ff59073589edeb0d9cbc3a9523246c31b7172b83869101fe6a020474d501a3b739dbe7a24ac6676a4baafb1380a5fe0c2a84b5
@@ -19,7 +19,7 @@ jobs:
19
19
  - name: Update packages
20
20
  run: sudo apt-get update
21
21
  - name: Install packages
22
- run: sudo apt-get -y install ledger hledger
22
+ run: sudo apt-get -y install hledger
23
23
  - name: Set up Ruby
24
24
  uses: ruby/setup-ruby@v1
25
25
  with:
data/README.md CHANGED
@@ -11,19 +11,30 @@
11
11
  <a href="https://github.com/olimorris/hledger-forecast/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/olimorris/hledger-forecast/ci.yml?branch=main&label=tests&style=for-the-badge"></a>
12
12
  </p>
13
13
 
14
- **"Improved", you say?** Using a _yaml_ file, forecasts can be quickly generated into a _journal_ file ready to be fed into [hledger](https://github.com/simonmichael/hledger). Forecasts can be easily constrained between dates, inflated by modifiers, tracked until they appear in your bank statements and summarized into your own daily/weekly/monthly/yearly personal forecast income and expenditure statement.
14
+ **"Improved", you say?** Using a _CSV_ (or _YML_) file, forecasts can be quickly generated into a _journal_ file ready to be fed into [hledger](https://github.com/simonmichael/hledger). **A 15 line [CSV file](https://github.com/olimorris/hledger-forecast/blob/main/example.csv) can generate a 42 line hledger [forecast file](https://github.com/olimorris/hledger-forecast/blob/main/example.journal)!**
15
15
 
16
- I **strongly** recommend you read the [rationale](#paintbrush-rationale) section to see if this app might be useful to you.
16
+ Forecasts can also be constrained between dates, inflated by modifiers, tracked until they appear in your bank statements and summarized into your own daily/weekly/monthly/yearly personal forecast income and expenditure statement.
17
17
 
18
18
  ## :sparkles: Features
19
19
 
20
- - :book: Uses a simple yaml file to generate forecasts which can be used with hledger
20
+ - :muscle: Uses a simple CSV (or YML) file to generate forecasts which can be used with hledger
21
21
  - :date: Can smartly track forecasts against your bank statement
22
22
  - :moneybag: Can automatically apply modifiers such as inflation/deflation to forecasts
23
23
  - :abacus: Enables the use of maths in your forecasts (for amounts and dates)
24
24
  - :chart_with_upwards_trend: Display your forecasts as income and expenditure reports (e.g. daily, weekly, monthly)
25
25
  - :computer: Simple and easy to use CLI
26
26
 
27
+ ## :camera_flash: Screenshots
28
+
29
+ **CSV forecast and corresponding journal output**
30
+
31
+ <img src="https://github.com/olimorris/hledger-forecast/assets/9512444/430503b5-f447-4972-b122-b48f8628aff9" alt="Hledger-Forecast" />
32
+
33
+ **Output from the `summarize` command**
34
+
35
+ <img src="https://github.com/olimorris/hledger-forecast/assets/9512444/f5017ea2-9606-46ec-8b38-8840dc175e7b" alt="Summarize command" />
36
+
37
+
27
38
  ## :package: Installation
28
39
 
29
40
  Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) installed on your system, simply run:
@@ -50,13 +61,13 @@ The available options are:
50
61
 
51
62
  ### Generate command
52
63
 
53
- The `hledger-forecast generate` command will generate a forecast _from_ a `yaml` file _to_ a journal file. You can see the output of this command in the [example.journal](https://github.com/olimorris/hledger-forecast/blob/main/example.journal) file.
64
+ The `hledger-forecast generate` command will generate a forecast _from_ a `CSV` or `YML` file _to_ a journal file. You can see the output of this command in the [example.journal](https://github.com/olimorris/hledger-forecast/blob/main/example.journal) file.
54
65
 
55
66
  The available options are:
56
67
 
57
68
  Usage: hledger-forecast generate [options]
58
69
 
59
- -f, --forecast FILE The path to the FORECAST yaml file to generate from
70
+ -f, --forecast FILE The path to the FORECAST CSV/YML file to generate from
60
71
  -o, --output-file FILE The path to the OUTPUT file to create
61
72
  -t, --transaction FILE The path to the TRANSACTION journal file
62
73
  --force Force an overwrite of the output file
@@ -73,159 +84,175 @@ To work with hledger, include the forecast file and use the `--forecast` flag:
73
84
 
74
85
  hledger -f bank_transactions.journal -f forecast.journal --forecast bal assets -e 2024-02
75
86
 
76
- The command will generate a forecast up to the end of Feb 2024, showing the balance for any asset accounts, overlaying some bank transactions with the forecast journal file. Of course, refer to the [hledger](https://hledger.org/dev/hledger.html) documentation for more information on how to query your finances.
77
-
78
- > **Note**: To apply any modifiers, use the `--auto` flag at the end of your command.
87
+ The command will generate a forecast up to the end of Feb 2024, showing the balance for any asset accounts, overlaying some bank transactions with the forecast journal file. Forecasting in hledger can be complicated so be sure to refer to the [documentation](https://hledger.org/dev/hledger.html) or start a [discussion](https://github.com/olimorris/hledger-forecast/discussions/new?category=q-a).
79
88
 
80
89
  ### Summarize command
81
90
 
82
- As your `yaml` configuration file grows, it can be helpful to sum up the total amounts and output them to the CLI.
91
+ As your configuration file grows, it can be helpful to sum up the total amounts and output them to the CLI.
83
92
  Furthermore, being able to see your monthly profit and loss statement _if_ you were to purchase that new item may
84
93
  influence your buying decision. In hledger-forecast, this can be achieved by:
85
94
 
86
- hledger-forecast summarize -f my_forecast.yml
95
+ hledger-forecast summarize -f my_forecast.csv
87
96
 
88
97
  The available options are:
89
98
 
90
99
  Usage: hledger-forecast summarize [options]
91
100
 
92
- -f, --forecast FILE The path to the FORECAST yaml file to summarize
101
+ -f, --forecast FILE The path to the FORECAST CSV/YML file to summarize
93
102
  -r, --roll-up PERIOD The period to roll-up your forecasts into. One of:
94
103
  [yearly], [half-yearly], [quarterly], [monthly], [weekly], [daily]
95
104
  -v, --verbose Show additional information in the summary
96
105
  -h, --help Show this help message
97
106
 
98
- ## :gear: Configuration
99
-
100
- ### The yaml file
101
-
102
- > **Note**: See the [example.yml](https://github.com/olimorris/hledger-forecast/blob/main/example.yml) file for an example config and its corresponding [output](https://github.com/olimorris/hledger-forecast/blob/main/example.journal)
103
-
104
- Firstly, create a `yaml` file which will contain the transactions you'd like to forecast:
105
-
106
- ```yaml
107
- # forecast.yml
108
- monthly:
109
- - account: "Assets:Bank"
110
- from: "2023-03-01"
111
- transactions:
112
- - amount: 2000
113
- category: "Expenses:Mortgage"
114
- description: Mortgage
115
- - amount: 500
116
- category: "Expenses:Food"
117
- description: Food
118
-
119
- settings:
120
- currency: GBP
107
+ ## :gear: Creating your forecast
108
+
109
+ The app makes it easy to generate a comprehensive _journal_ file with very few lines of code, making it much easier to stay on top of your forecasting from month to month.
110
+
111
+ ### Columns
112
+
113
+ The _CSV_ file _should_ contain a header row with the following columns:
114
+
115
+ - `type` - (string) - The type of forecast entry. One of `monthly`, `quarterly`, `half-yearly`, `yearly`, `once` or `custom`
116
+ - `frequency` - (string) - The frequency that the type repeats with (only if `custom`). As per hledger's [periodic rule syntax](https://hledger.org/dev/hledger.html#periodic-transactions)
117
+ - `account` - (string) - The account the transaction applies to e.g. _Expenses:Food_
118
+ - `from` - (date) - The date the transaction should start from e.g. _2023-06-01_
119
+ - `to` - (date) _(optional)_ - The date the transaction should end on e.g. _2023-12-31_
120
+ - `description` - (string) - A description of the transaction
121
+ - `category` - (string) - The classification or category of the transaction
122
+ - `amount` - (integer/float) - The amount of the transaction
123
+ - `roll-up` - (integer/float) _(optional)_ - For use with the summarizer, the value you need to multiply the amount by to get it into a yearly amount
124
+ - `summary_exclude` - (boolean) _(optional)_ - Exclude the transaction from the summarizer?
125
+ - `track` - (boolean) _(optional)_ - Track the transaction against your confirmed transactions?
126
+
127
+ ### An example CSV forecast
128
+
129
+ Putting it together, we end up with a CSV file like:
130
+
131
+ ```csv
132
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
133
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
134
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Mortgage,Expenses:Mortgage,2000,,,
135
+ monthly,,Assets:Bank,01/03/2023,,Bills,Expenses:Bills,175,,,
136
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
137
+ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
138
+ monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
139
+ monthly,,Assets:Bank,01/03/2023,01/03/2025,Rainy day fund,Assets:Savings,300,,,
140
+ monthly,,Assets:Pension,01/01/2024,,Pension draw down,Income:Pension,-500,,,
141
+ quarterly,,Assets:Bank,01/04/2023,,Quarterly bonus,Income:Bonus,-1000,,,
142
+ half-yearly,,Assets:Bank,01/04/2023,,Top up holiday funds,Expenses:Holiday,500,,,
143
+ yearly,,Assets:Bank,01/04/2023,,Annual bonus,Income:Bonus,-2000,,,
144
+ once,,Assets:Bank,05/03/2023,,Refund for that damn laptop,Expenses:Shopping,-3000,,TRUE,TRUE
145
+ custom,every 2 weeks,Assets:Bank,01/03/2023,,Hair and beauty,Expenses:Personal Care,80,26,,
146
+ custom,every 5 weeks,Assets:Bank,01/03/2023,,Misc expenses,Expenses:General Expenses,30,10.4,,
147
+ settings,currency,USD,,,,,,,,
121
148
  ```
122
149
 
123
- Let's examine what's going on in this config file:
124
-
125
- - We're telling the app to create two monthly transactions and repeat them, forever, starting from March 2023
126
- - We're telling the app to link them both to the `Assets:Bank` account
127
- - We've added descriptions to make it easy to follow in our output file
128
- - Finally, we're telling the app to use the `GBP` currency; the default (`USD`) will be used if this is not specified
129
-
130
- ### Periods
150
+ ### Additional features
131
151
 
132
- Besides monthly recurring transactions, the app also supports the following periods:
152
+ From the example above, there are a few additional features that may be useful.
133
153
 
134
- - `quarterly` - For transactions every _3 months_
135
- - `half-yearly` - For transactions every _6 months_
136
- - `yearly` - Generate transactions _once a year_
137
- - `once` - Generate _one-time_ transactions on a specified date
138
- - `custom` - Generate transactions every _n days/weeks/months_
154
+ #### Calculated amounts
139
155
 
140
- These will write periodic transactions such as `~ every 3 months` or `~ every year` in the output journal file.
141
-
142
- #### Custom period
156
+ > **Note**: Calculations will be determined up to two decimal places
143
157
 
144
- When you need a bespoke time bound forecast, a custom period may be useful. Custom periods allow you to specify a periodic rule as per hledger's [periodic rule syntax](https://hledger.org/dev/hledger.html#periodic-transactions):
158
+ It may be helpful to let the app calculate the forecasted amount in your transactions on your behalf. This can be especially useful if you're spreading a payment out over a number of months:
145
159
 
146
- ```yaml
147
- custom:
148
- - frequency: "every 2 weeks"
149
- account: "Assets:Bank"
150
- from: "2023-03-01"
151
- transactions:
152
- - amount: 80
153
- category: "Expenses:Personal Care"
154
- description: Hair and beauty
160
+ ```csv
161
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
162
+ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
155
163
  ```
156
164
 
157
- ### Dates
165
+ Simply start the `amount` column with a `=` sign and use standard Excel based math formatting.
158
166
 
159
- The core of any solid forecast is predicting the correct periods that costs will fall into. When running the app from the CLI, you can specify specific dates to generate transactions over (see the [usage](#rocket-usage) section).
167
+ #### Calculated dates
160
168
 
161
- You can further control the dates at a period/top-level as well as at a transaction level:
169
+ It may also be helpful for `to` dates to be calculated:
162
170
 
163
- #### Top level
171
+ ```csv
172
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
173
+ monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
174
+ ```
164
175
 
165
- In the example below, all transactions in the `monthly` block will be constrained by the `to` date:
176
+ ### Settings
166
177
 
167
- ```yaml
168
- monthly:
169
- - account: "Assets:Bank"
170
- from: "2023-03-01"
171
- to: "2025-01-01"
172
- transactions:
173
- # details omitted for brevity
178
+ There are also additional settings that can be applied in the forecast. The defaults are:
179
+
180
+ ```csv
181
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
182
+ settings,currency,USD,,,,,,,,
183
+ settings,show_symbol,true,,,,,,,,
184
+ settings,thousands_separator,true,,,,,,,,
174
185
  ```
175
186
 
176
- #### Transaction level
187
+ ### An example YML forecast
188
+
189
+ > **Note**: The app uses `yml` in place of `yaml` by default
177
190
 
178
- In the example below, only the single transaction will be constrained by the `to` date:
191
+ Taking the _CSV_ example above and applying it to a _YML_ file:
179
192
 
180
- ```yaml
193
+ ```yml
181
194
  monthly:
182
195
  - account: "Assets:Bank"
183
196
  from: "2023-03-01"
184
197
  transactions:
198
+ - amount: -3500
199
+ category: "Income:Salary"
200
+ description: Salary
185
201
  - amount: 2000
186
202
  category: "Expenses:Mortgage"
187
203
  description: Mortgage
188
204
  to: "2025-01-01"
189
- ```
190
-
191
- It can also be useful to compute a `to` date by adding on a number of months to the `from` date. Extending the example above:
192
-
193
- ```yaml
194
- - amount: 125
195
- category: "Expenses:Holiday"
196
- description: Holiday
197
- to: "=12"
198
- ```
199
-
200
- This will take the `to` date to _2024-02-29_. This can be useful if you know a payment is due to end in _n_ months time and don't wish to use one of the many date calculators on the internet.
201
-
202
- ### Calculated amounts
203
-
204
- > **Note**: Calculations will be determined up to two decimal places
205
-
206
- It may be helpful to let the app calculate the forecasted amount in your transactions on your behalf. This can be especially useful if you're spreading a payment out over a number of months:
207
-
208
- ```yaml
209
- monthly:
210
- - account: "Liabilities:Amex"
211
- from: "2023-05-01"
212
- transactions:
205
+ - amount: 175
206
+ category: "Expenses:Bills"
207
+ description: Bills
208
+ - amount: 500
209
+ category: "Expenses:Food"
210
+ description: Food
213
211
  - amount: "=5000/24"
214
212
  category: "Expenses:House"
215
213
  description: New Kitchen
216
- ```
217
-
218
- Simply ensure that the amount starts with an `=` sign, is enclosed in quotation marks and uses standard mathematical notations. Of course, it may make sense to restrict this transaction with a `to` date in months, as per the [transaction level dates](#transaction-level) section.
219
-
220
- ### Tracking transactions
214
+ - amount: 125
215
+ category: "Expenses:Holiday"
216
+ description: Holiday
217
+ to: "=12"
218
+ - account: "Assets:Bank"
219
+ from: "2023-03-01"
220
+ to: "2025-01-01"
221
+ transactions:
222
+ - amount: 300
223
+ category: "Assets:Savings"
224
+ description: "Rainy day fund"
225
+ - account: "Assets:Pension"
226
+ from: "2024-01-01"
227
+ transactions:
228
+ - amount: -500
229
+ category: "Income:Pension"
230
+ description: Pension draw down
221
231
 
222
- > **Note**: Marking a transaction for tracking will ensure that it is only written into the forecast if it isn't found within a specified transaction file
232
+ quarterly:
233
+ - account: "Assets:Bank"
234
+ from: "2023-04-01"
235
+ transactions:
236
+ - amount: -1000.00
237
+ category: "Income:Bonus"
238
+ description: Quarterly bonus
223
239
 
224
- Sometimes it can be useful to track and monitor forecasted transactions to ensure that they are accounted for in any financial projections. If they are present, then these should be discarded from your forecast as this will create a double count against your actuals. However, if they can't be found then they should be carried forward into a future period to ensure accurate recording.
240
+ half-yearly:
241
+ - account: "Assets:Bank"
242
+ from: "2023-04-01"
243
+ transactions:
244
+ - amount: 500
245
+ category: "Expenses:Holiday"
246
+ description: Top up holiday funds
225
247
 
226
- To mark transactions as available for tracking you may use the `track` option in your config file:
248
+ yearly:
249
+ - account: "Assets:Bank"
250
+ from: "2023-04-01"
251
+ transactions:
252
+ - amount: -2000.00
253
+ category: "Income:Bonus"
254
+ description: Annual Bonus
227
255
 
228
- ```yaml
229
256
  once:
230
257
  - account: "Assets:Bank"
231
258
  from: "2023-03-05"
@@ -233,94 +260,97 @@ once:
233
260
  - amount: -3000
234
261
  category: "Expenses:Shopping"
235
262
  description: Refund for that damn laptop
263
+ summary_exclude: true
236
264
  track: true
237
- ```
238
-
239
- > **Note**: This feature has been designed to work with one-off transactions only
240
-
241
- To use this feature, ensure you pass a filepath to the `-t` flag, such as:
242
265
 
243
- hledger-forecast generate -t [journal_file_to_search] -f [path_to_yaml_file] -o [path_to_output_journal]
266
+ custom:
267
+ - account: "Assets:Bank"
268
+ from: "2023-03-01"
269
+ transactions:
270
+ - amount: 80
271
+ category: "Expenses:Personal Care"
272
+ description: Hair and beauty
273
+ frequency: "every 2 weeks"
274
+ roll-up: 26
275
+ - amount: 30
276
+ category: "Expenses:General Expenses"
277
+ description: Misc expenses
278
+ frequency: "every 5 weeks"
279
+ roll-up: 10.4
244
280
 
245
- The app will use a hledger query to determine if the combination of category and amount is present in the periods between the `from` key and the current date in the journal file you've specified. If not, then the app will include it as a forecast transaction in the output file.
281
+ settings:
282
+ currency: USD
283
+ ```
246
284
 
247
- ### Modifiers
285
+ #### Modifiers
248
286
 
249
287
  > **Note**: For modifiers to be included in your hledger reporting, use the `--auto` flag
250
288
 
251
- Within your forecasts, it can be useful to reflect future increases/decreases in certain categories. For example, next year, I expect the cost of groceries to increase by 2%:
289
+ Currently, a YML forecast allows a user to include forecasted % uplifts or downshifts:
252
290
 
253
- ```yaml
291
+ ```yml
254
292
  monthly:
255
- account: "Assets:Bank"
256
- from: "2023-03-05"
257
- transactions:
258
- - amount: 450
259
- category: "Expenses:Groceries"
260
- description: Food shopping
261
- modifiers:
262
- - amount: 0.02
263
- description: "Inflation"
264
- from: "2024-01-01"
265
- to: "2024-12-31"
293
+ - account: "Assets:Bank"
294
+ from: "2023-03-01"
295
+ transactions:
296
+ - amount: 500
297
+ category: "Expenses:Food"
298
+ description: Food
299
+ modifiers:
300
+ - amount: 0.02
301
+ description: "Inflation"
302
+ from: "2024-01-01"
303
+ to: "2024-12-31"
304
+ - amount: 0.05
305
+ description: "Inflation"
306
+ from: "2025-01-01"
307
+ to: "2025-12-31"
266
308
  ```
267
309
 
268
- This will generate an [auto-posting](https://hledger.org/dev/hledger.html#auto-postings) in your forecast which will
269
- uplift any transaction with an `Expenses:Groceries` category.
310
+ This will generate an [auto-posting](https://hledger.org/dev/hledger.html#auto-postings) in your forecast which will uplift any transaction with an `Expenses:Food` category. In the first year the uplift with be 2% and in the following year, 5%.
270
311
 
271
- Of course you may wish to apply 2% for next year and another 3% for the year after:
312
+ #### Additional YML features
272
313
 
273
- ```yaml
274
- # details above omitted for brevity
275
- modifiers:
276
- - amount: 0.02
277
- description: "Inflation"
278
- from: "2024-01-01"
279
- to: "2024-12-31"
280
- - amount: 0.05
281
- description: "Inflation"
282
- from: "2025-01-01"
283
- to: "2025-12-31"
284
- ```
314
+ Dates in a YML file can be constrained by the `to` date in two ways:
285
315
 
286
- ### Roll-ups
287
-
288
- As part of the summarize command, it can be useful to sum-up all of the transactions in your `yaml` file and see what your income and expenditure is over a given period (e.g. "how much profit do I _actually_ make every year when all of my costs are taken into account?").
316
+ ```yml
317
+ monthly:
318
+ - account: "Assets:Bank"
319
+ from: "2023-03-01"
320
+ to: "2025-01-01"
321
+ transactions:
322
+ # details omitted for brevity
323
+ ```
289
324
 
290
- In order to do this, custom forecasts need to have the `roll-up` key defined. That is, given the custom period you've specified, what number do you need to multiply the amount by in order to "roll it up" into an annualised figure. Let's look at the example below:
325
+ or:
291
326
 
292
- ```yaml
293
- custom:
294
- - frequency: "every 2 weeks"
295
- account: "Assets:Bank"
327
+ ```yml
328
+ monthly:
329
+ - account: "Assets:Bank"
296
330
  from: "2023-03-01"
297
- roll-up: 26
298
331
  transactions:
299
- - amount: 80
300
- category: "Expenses:Personal Care"
301
- description: Hair and beauty
332
+ - amount: 2000
333
+ category: "Expenses:Mortgage"
334
+ description: Mortgage
335
+ to: "2025-01-01"
302
336
  ```
303
337
 
304
- Every 2 weeks a planned expense of £80 is made. So over the course of a year, we'd need to multiply that amount by 26 to get to an annualised figure. Of course for periods like `monthly` and `quarterly` it's easy for hledger-forecast to annualise those amounts so no `roll-up` is required.
338
+ ### Tracking
305
339
 
306
- To see the monthly summary of your `yaml` file, the following command can be used:
307
-
308
- hledger-forecast summarize -f my_forecast.yml -r monthly
340
+ > **Note**: Marking a transaction for tracking will ensure that it is only written into the forecast if it isn't found within a specified transaction file
309
341
 
310
- You can also roll-up with the following periods:
342
+ Sometimes it can be useful to track and monitor forecasted transactions to ensure that they are accounted for in any financial projections. If they are present, then these should be discarded from your forecast as this will create a double count against your actuals. However, if they can't be found then they should be carried forward into a future period to ensure accurate recording.
311
343
 
312
- - daily
313
- - weekly
314
- - monthly
315
- - quarterly
316
- - half-yearly
317
- - yearly
344
+ To mark transactions as available for tracking you may use the `track` option in your config file:
318
345
 
319
- ### Summary exclusions
346
+ ```csv
347
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
348
+ once,,Assets:Bank,2023-03-05,,Refund for that damn laptop,Expenses:Shopping,-3000,,,TRUE
349
+ ```
320
350
 
321
- It can be useful to exclude certain items from your summary, like one-off items. This can be achieved by specifying `summary_exclude: true` next to a transaction:
351
+ Or:
322
352
 
323
- ```yaml
353
+ ```yml
324
354
  once:
325
355
  - account: "Assets:Bank"
326
356
  from: "2023-03-05"
@@ -328,37 +358,18 @@ once:
328
358
  - amount: -3000
329
359
  category: "Expenses:Shopping"
330
360
  description: Refund for that damn laptop
331
- summary_exclude: true
332
361
  track: true
333
362
  ```
334
363
 
335
- ### Additional config settings
336
-
337
- Additional settings in the config file to consider:
364
+ > **Note**: This feature has been designed to work with `once` transaction types only
338
365
 
339
- ```yaml
340
- settings:
341
- currency: GBP # Specify the currency to use
342
- show_symbol: true # Show the currency symbol?
343
- thousands_separator: true # Separate thousands with a comma?
344
- ```
345
-
346
- ## :camera_flash: Screenshots
347
-
348
- **Yaml config file and output**
349
-
350
- <img src="https://github.com/olimorris/hledger-forecast/assets/9512444/c3c3222e-f797-4643-bebd-9c94134bee92" alt="Hledger-Forecast" />
351
-
352
- **Summarize command**
353
-
354
- <img src="https://github.com/olimorris/hledger-forecast/assets/9512444/f5017ea2-9606-46ec-8b38-8840dc175e7b" alt="Summarize command" />
366
+ To use this feature, ensure you pass a filepath to the `-t` flag, such as:
355
367
 
356
- ## :paintbrush: Rationale
368
+ hledger-forecast generate -t [journal_file_to_search] -f [path_to_yaml_file] -o [path_to_output_journal]
357
369
 
358
- I moved to hledger from my trusty Excel macro workbook. This thing had been with me for 5+ years. I used it to workout whether I could afford that new gadget and when I'd be in a position to buy a house. I used it to see if I was on track to have £X in my savings accounts by a given date as well as see how much money I could save on a monthly basis. That time I accidentally double counted my bonus or thought I'd accounted for my credit card bill? Painful! Set me back a few months in terms of my savings plans. In summary, I relied _heavily_ on having a detailed and accurate forecast.
370
+ The app will use a hledger query to determine if the combination of category and amount is present in the periods between the `from` key and the current date in the journal file you've specified. If not, then the app will include it as a forecast transaction in the output file.
359
371
 
360
- I love hledger. Switching from Excel has been a breath of fresh air. There's only so many bank transactions a workbook can take before it starts groaning (yes, even on an M1 Mac). However there were a few forecasting features that I missed. The sort of features that in Excel terms mean I'd just copy a bunch of cells and paste them into columns which represented future dates or apply a neat little formula to divide a big number by 12 to get to a monthly repayment. Because I like to plan 3-5 years out at a time, I wanted to crudely account for future price and salary increases. Sure, I can add some auto-postings to the end of my journal file but I bet a lot of users didn't know about this or even know how to constrain them between two dates.
372
+ ## :pencil2: Contributing
361
373
 
362
- I also made an assumption that a lot of users probably think of their finances in terms of their monthly costs (e.g. car payments, mortgage, food), half-yearly costs (e.g. service charge if you have an apartment in the UK) and yearly costs (e.g. holidays, gifts) etc. But likely never do the math to add them all together and workout how much money they have left over by the end of it all. Well I built that into this app and my daily profit figure hit me hard :rofl:. Give it a try!
374
+ I am open to any pull requests that fix bugs but would ask that any new functionality is discussed before it could be accepted.
363
375
 
364
- So I thought I'd share this little Ruby gem in the hope that people find it useful. Perhaps for those who are moving from an Excel based approach to [plain text accounting](https://plaintextaccounting.org), or for those who want a little bit of improvement to the existing capabilities within hledger.
data/example.csv ADDED
@@ -0,0 +1,16 @@
1
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
2
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
3
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Mortgage,Expenses:Mortgage,2000,,,
4
+ monthly,,Assets:Bank,01/03/2023,,Bills,Expenses:Bills,175,,,
5
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
6
+ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
7
+ monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
8
+ monthly,,Assets:Bank,01/03/2023,01/03/2025,Rainy day fund,Assets:Savings,300,,,
9
+ monthly,,Assets:Pension,01/01/2024,,Pension draw down,Income:Pension,-500,,,
10
+ quarterly,,Assets:Bank,01/04/2023,,Quarterly bonus,Income:Bonus,-1000,,,
11
+ half-yearly,,Assets:Bank,01/04/2023,,Top up holiday funds,Expenses:Holiday,500,,,
12
+ yearly,,Assets:Bank,01/04/2023,,Annual bonus,Income:Bonus,-2000,,,
13
+ once,,Assets:Bank,05/03/2023,,Refund for that damn laptop,Expenses:Shopping,-3000,,TRUE,TRUE
14
+ custom,every 2 weeks,Assets:Bank,01/03/2023,,Hair and beauty,Expenses:Personal Care,80,26,,
15
+ custom,every 5 weeks,Assets:Bank,01/03/2023,,Misc expenses,Expenses:General Expenses,30,10.4,,
16
+ settings,currency,USD,,,,,,,,
data/example.journal CHANGED
@@ -1,51 +1,47 @@
1
1
  ~ monthly from 2023-03-01 * Salary, Bills, Food, New Kitchen
2
- Income:Salary $-3,500.00; Salary
3
- Expenses:Bills $175.00 ; Bills
4
- Expenses:Food $500.00 ; Food
5
- Expenses:House $208.33 ; New Kitchen
2
+ Income:Salary $-3,500.00; Salary
3
+ Expenses:Bills $175.00 ; Bills
4
+ Expenses:Food $500.00 ; Food
5
+ Expenses:House $208.33 ; New Kitchen
6
6
  Assets:Bank
7
7
 
8
8
  ~ monthly from 2023-03-01 to 2025-01-01 * Mortgage
9
- Expenses:Mortgage $2,000.00 ; Mortgage
9
+ Expenses:Mortgage $2,000.00 ; Mortgage
10
10
  Assets:Bank
11
11
 
12
12
  ~ monthly from 2023-03-01 to 2024-02-29 * Holiday
13
- Expenses:Holiday $125.00 ; Holiday
13
+ Expenses:Holiday $125.00 ; Holiday
14
14
  Assets:Bank
15
15
 
16
- ~ monthly from 2023-03-01 to 2025-01-01 * Rainy day fund
17
- Assets:Savings $300.00 ; Rainy day fund
16
+ ~ monthly from 2023-03-01 to 2025-03-01 * Rainy day fund
17
+ Assets:Savings $300.00 ; Rainy day fund
18
18
  Assets:Bank
19
19
 
20
20
  ~ monthly from 2024-01-01 * Pension draw down
21
- Income:Pension $-500.00 ; Pension draw down
21
+ Income:Pension $-500.00 ; Pension draw down
22
22
  Assets:Pension
23
23
 
24
24
  ~ every 3 months from 2023-04-01 * Quarterly bonus
25
- Income:Bonus $-1,000.00; Quarterly bonus
25
+ Income:Bonus $-1,000.00; Quarterly bonus
26
26
  Assets:Bank
27
27
 
28
28
  ~ every 6 months from 2023-04-01 * Top up holiday funds
29
- Expenses:Holiday $500.00 ; Top up holiday funds
29
+ Expenses:Holiday $500.00 ; Top up holiday funds
30
30
  Assets:Bank
31
31
 
32
- ~ yearly from 2023-04-01 * Annual Bonus
33
- Income:Bonus $-2,000.00; Annual Bonus
32
+ ~ yearly from 2023-04-01 * Annual bonus
33
+ Income:Bonus $-2,000.00; Annual bonus
34
34
  Assets:Bank
35
35
 
36
36
  ~ every 2 weeks from 2023-03-01 * Hair and beauty
37
- Expenses:Personal Care $80.00 ; Hair and beauty
37
+ Expenses:Personal Care $80.00 ; Hair and beauty
38
38
  Assets:Bank
39
39
 
40
- ~ 2023-06-01 * [TRACKED] Refund for that damn laptop
41
- Expenses:Shopping $-3,000.00; Refund for that damn laptop
40
+ ~ every 5 weeks from 2023-03-01 * Misc expenses
41
+ Expenses:General Expenses $30.00 ; Misc expenses
42
42
  Assets:Bank
43
43
 
44
- = Expenses:Food date:2024-01-01..2024-12-31
45
- Expenses:Food *0.02 ; Food - Inflation
46
- Assets:Bank *-0.02
47
-
48
- = Expenses:Food date:2025-01-01..2025-12-31
49
- Expenses:Food *0.05 ; Food - Inflation
50
- Assets:Bank *-0.05
44
+ ~ 2023-07-01 * [TRACKED] Refund for that damn laptop
45
+ Expenses:Shopping $-3,000.00; Refund for that damn laptop
46
+ Assets:Bank
51
47
 
data/example.yml CHANGED
@@ -80,14 +80,19 @@ once:
80
80
  track: true
81
81
 
82
82
  custom:
83
- - frequency: "every 2 weeks"
84
- account: "Assets:Bank"
83
+ - account: "Assets:Bank"
85
84
  from: "2023-03-01"
86
- roll-up: 26
87
85
  transactions:
88
86
  - amount: 80
89
87
  category: "Expenses:Personal Care"
90
88
  description: Hair and beauty
89
+ frequency: "every 2 weeks"
90
+ roll-up: 26
91
+ - amount: 30
92
+ category: "Expenses:General Expenses"
93
+ description: Misc expenses
94
+ frequency: "every 5 weeks"
95
+ roll-up: 10.4
91
96
 
92
97
  settings:
93
98
  currency: USD
@@ -6,7 +6,7 @@ module HledgerForecast
6
6
  end
7
7
 
8
8
  def evaluate(amount)
9
- return amount unless amount.is_a?(String)
9
+ return amount.to_f unless amount.is_a?(String)
10
10
 
11
11
  @calculator.evaluate(amount.slice(1..-1))
12
12
  end
@@ -69,9 +69,16 @@ module HledgerForecast
69
69
  opts.separator ""
70
70
 
71
71
  opts.on("-f", "--forecast FILE",
72
- "The path to the FORECAST yaml file to generate from") do |file|
72
+ "The path to the FORECAST csv/yml file to generate from") do |file|
73
73
  options[:forecast_file] = file
74
- options[:output_file] ||= file.sub(/\.yml$/, '.journal')
74
+
75
+ options[:file_type] = if File.extname(file) == '.csv'
76
+ "csv"
77
+ else
78
+ "yml"
79
+ end
80
+
81
+ options[:output_file] ||= file.sub(options[:file_type], 'journal')
75
82
  end
76
83
 
77
84
  opts.on("-o", "--output-file FILE",
@@ -100,7 +107,8 @@ module HledgerForecast
100
107
  end
101
108
  end.parse!(args)
102
109
 
103
- options[:forecast_file] = "forecast.yml" unless options[:forecast_file]
110
+ options[:forecast_file] = "forecast.csv" unless options[:forecast_file]
111
+ options[:file_type] = "csv" unless options[:file_type]
104
112
  options[:output_file] = "forecast.journal" unless options[:output_file]
105
113
 
106
114
  options
@@ -114,7 +122,12 @@ module HledgerForecast
114
122
  opts.separator ""
115
123
 
116
124
  opts.on("-f", "--forecast FILE",
117
- "The path to the FORECAST yaml file to summarize") do |file|
125
+ "The path to the FORECAST csv/yml file to summarize") do |file|
126
+ options[:file_type] = if File.extname(file) == '.csv'
127
+ "csv"
128
+ else
129
+ "yml"
130
+ end
118
131
  options[:forecast_file] = file
119
132
  end
120
133
 
@@ -159,6 +172,7 @@ module HledgerForecast
159
172
  forecast = File.read(options[:forecast_file])
160
173
 
161
174
  begin
175
+ forecast = HledgerForecast::CSVParser.parse(forecast) if options[:file_type] == "csv"
162
176
  transactions = Generator.generate(forecast, options)
163
177
  rescue StandardError => e
164
178
  puts "An error occurred while generating transactions: #{e.message}"
@@ -166,6 +180,7 @@ module HledgerForecast
166
180
  end
167
181
 
168
182
  output_file = options[:output_file]
183
+
169
184
  if File.exist?(output_file) && !options[:force]
170
185
  print "\nFile '#{output_file}' already exists. Overwrite? (y/n): "
171
186
  overwrite = gets.chomp.downcase
@@ -184,6 +199,8 @@ module HledgerForecast
184
199
 
185
200
  def self.summarize(options)
186
201
  config = File.read(options[:forecast_file])
202
+ config = HledgerForecast::CSVParser.parse(config) if options[:file_type] == "csv"
203
+
187
204
  summarizer = Summarizer.summarize(config, options)
188
205
 
189
206
  puts SummarizerFormatter.format(summarizer[:output], summarizer[:settings])
@@ -0,0 +1,106 @@
1
+ module HledgerForecast
2
+ # Formats various items used throughout the application
3
+ class CSVParser
4
+ def self.parse(csv_data, cli_options = nil)
5
+ new.parse(csv_data, cli_options)
6
+ end
7
+
8
+ def parse(csv_data, _cli_options)
9
+ csv_data = CSV.parse(csv_data, headers: true)
10
+ yaml_data = {}
11
+ group_by_type(csv_data, yaml_data)
12
+ yaml_data.to_yaml
13
+ end
14
+
15
+ private
16
+
17
+ def group_by_type(csv_data, yaml_data)
18
+ csv_data.group_by { |row| row['type'] }.each do |type, rows|
19
+ if type == 'settings'
20
+ handle_settings(rows, yaml_data)
21
+ else
22
+ yaml_data[type] ||= []
23
+ group_by_account_and_from(rows, yaml_data[type], type)
24
+ end
25
+ end
26
+ end
27
+
28
+ def handle_settings(rows, yaml_data)
29
+ yaml_data['settings'] ||= {}
30
+ rows.each do |row|
31
+ yaml_data['settings'][row['frequency']] = cast_to_proper_type(row['account'])
32
+ end
33
+ end
34
+
35
+ def group_by_account_and_from(rows, yaml_rows, type)
36
+ rows.group_by { |row| [row['account'], row['from']] }.each do |(account, from), transactions|
37
+ yaml_rows << if type == 'custom'
38
+ build_custom_transaction(account, from, transactions)
39
+ else
40
+ build_transaction(account, from, transactions)
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_transaction(account, from, transactions)
46
+ transaction = {
47
+ 'account' => account,
48
+ 'from' => Date.parse(from).strftime('%Y-%m-%d'),
49
+ 'transactions' => []
50
+ }
51
+
52
+ transactions.each do |row|
53
+ transaction['transactions'] << build_transaction_data(row)
54
+ end
55
+
56
+ transaction
57
+ end
58
+
59
+ def build_custom_transaction(account, from, transactions)
60
+ transaction = {
61
+ 'account' => account,
62
+ 'from' => Date.parse(from).strftime('%Y-%m-%d'),
63
+ 'transactions' => []
64
+ }
65
+
66
+ transactions.each do |row|
67
+ transaction_data = build_transaction_data(row)
68
+ transaction_data['frequency'] = row['frequency']
69
+ transaction_data['roll-up'] = row['roll-up'].to_f if row['roll-up']
70
+ transaction['transactions'] << transaction_data
71
+ end
72
+
73
+ transaction
74
+ end
75
+
76
+ def build_transaction_data(row)
77
+ transaction_data = {
78
+ 'amount' => row['amount'].start_with?("=") ? row['amount'].to_s : row['amount'].to_f,
79
+ 'category' => row['category'],
80
+ 'description' => row['description']
81
+ }
82
+
83
+ if row['to']
84
+ transaction_data['to'] = if row['to'].start_with?("=")
85
+ row['to']
86
+ else
87
+ Date.parse(row['to']).strftime('%Y-%m-%d')
88
+ end
89
+ end
90
+
91
+ transaction_data['summary_exclude'] = true if row['summary_exclude'] && row['summary_exclude'].downcase == "true"
92
+ transaction_data['track'] = true if row['track'] && row['track'].downcase == "true"
93
+
94
+ transaction_data
95
+ end
96
+
97
+ def cast_to_proper_type(str)
98
+ case str.downcase
99
+ when 'true', 'false'
100
+ str.downcase == 'true'
101
+ else
102
+ str
103
+ end
104
+ end
105
+ end
106
+ end
@@ -55,7 +55,8 @@ module HledgerForecast
55
55
  description: t['description'],
56
56
  to: t['to'] ? Calculator.new.evaluate_date(Date.parse(block['from']), t['to']) : nil,
57
57
  modifiers: t['modifiers'] ? Transactions::Modifiers.get_modifiers(t, block) : [],
58
- track: Transactions::Trackers.track?(t, block, @settings) ? true : false
58
+ track: Transactions::Trackers.track?(t, block, @settings) ? true : false,
59
+ frequency: t['frequency'] || nil
59
60
  }
60
61
  end
61
62
 
@@ -58,7 +58,7 @@ module HledgerForecast
58
58
 
59
59
  output.last[:transactions] << {
60
60
  amount: amount,
61
- annualised_amount: amount * (block['roll-up'] || annualise(period)),
61
+ annualised_amount: amount * (t['roll-up'] || annualise(period)),
62
62
  rolled_up_amount: 0,
63
63
  category: t['category'],
64
64
  exclude: t['summary_exclude'],
@@ -13,7 +13,11 @@ module HledgerForecast
13
13
  def generate
14
14
  data.each_value do |blocks|
15
15
  blocks.each do |block|
16
- process_block(block)
16
+ if block[:type] == "custom"
17
+ process_custom_block(block)
18
+ else
19
+ process_block(block)
20
+ end
17
21
  end
18
22
  end
19
23
 
@@ -30,10 +34,23 @@ module HledgerForecast
30
34
  @output = []
31
35
  end
32
36
 
37
+ def process_custom_block(block)
38
+ block[:transactions].each do |to, transactions|
39
+ to = get_header(block[:to], to)
40
+
41
+ transactions.each do |t|
42
+ header = "~ #{t[:frequency]} from #{block[:from]}#{to} * #{t[:description]}\n"
43
+ footer = " #{block[:account]}\n\n"
44
+ output << { header: header, transactions: write_transactions([t]), footer: footer }
45
+ end
46
+ end
47
+ end
48
+
33
49
  def process_block(block)
34
50
  block[:transactions].each do |to, transactions|
35
51
  to = get_header(block[:to], to)
36
52
  block[:descriptions] = get_descriptions(transactions)
53
+
37
54
  frequency = get_periodic_rules(block[:type], block[:frequency])
38
55
 
39
56
  header = "#{frequency} #{block[:from]}#{to} * #{block[:descriptions]}\n"
@@ -32,6 +32,13 @@ module HledgerForecast
32
32
  end
33
33
 
34
34
  def self.exists?(transaction, account, from, to, options)
35
+
36
+ if !options[:transaction_file]
37
+ puts "\nWarning: ".bold.yellow + "For tracked transactions, please specify a file with the `-t` flag"
38
+ puts "ERROR: ".bold.red + "Tracked transactions ignored for now"
39
+ return
40
+ end
41
+
35
42
  # Format the money
36
43
  amount = Formatter.format_money(transaction['amount'], options)
37
44
  inverse_amount = Formatter.format_money(transaction['amount'] * -1, options)
@@ -1,3 +1,3 @@
1
1
  module HledgerForecast
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'colorize'
4
+ require 'csv'
4
5
  require 'date'
5
6
  require 'dentaku'
6
7
  require 'highline'
@@ -14,6 +15,7 @@ Money.rounding_mode = BigDecimal::ROUND_HALF_UP
14
15
 
15
16
  require_relative 'hledger_forecast/calculator'
16
17
  require_relative 'hledger_forecast/cli'
18
+ require_relative 'hledger_forecast/csv_parser'
17
19
  require_relative 'hledger_forecast/formatter'
18
20
  require_relative 'hledger_forecast/generator'
19
21
  require_relative 'hledger_forecast/settings'
@@ -24,4 +26,3 @@ require_relative 'hledger_forecast/version'
24
26
  require_relative 'hledger_forecast/transactions/default'
25
27
  require_relative 'hledger_forecast/transactions/modifiers'
26
28
  require_relative 'hledger_forecast/transactions/trackers'
27
-
data/spec/command_spec.rb CHANGED
@@ -14,7 +14,6 @@ JOURNAL
14
14
 
15
15
  RSpec.describe 'command' do
16
16
  it 'uses the CLI to generate an output' do
17
- # Delete the file if it exists
18
17
  generated_journal = './test_output.journal'
19
18
  File.delete(generated_journal) if File.exist?(generated_journal)
20
19
 
@@ -22,4 +21,13 @@ RSpec.describe 'command' do
22
21
 
23
22
  expect(File.read(generated_journal)).to eq(output)
24
23
  end
24
+
25
+ it 'uses the CLI to generate an output with a CSV config file' do
26
+ generated_journal = './test_output.journal'
27
+ File.delete(generated_journal) if File.exist?(generated_journal)
28
+
29
+ system("./bin/hledger-forecast generate -f ./spec/stubs/forecast.csv -o ./test_output.journal --force")
30
+
31
+ expect(File.read(generated_journal)).to eq(output)
32
+ end
25
33
  end
@@ -0,0 +1,32 @@
1
+ require_relative '../lib/hledger_forecast'
2
+
3
+ RSpec.describe 'CSV and yml outputs' do
4
+ it 'should return the same value when ran through hledger' do
5
+ generated_journal = './test_output.journal'
6
+ File.delete(generated_journal) if File.exist?(generated_journal)
7
+
8
+ system("./bin/hledger-forecast generate -f example.csv -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
9
+ csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
10
+
11
+ system("./bin/hledger-forecast generate -f example.yml -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
12
+ yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
13
+
14
+ expect(csv_output).to eq(yml_output)
15
+ end
16
+
17
+ # it 'check that it can fail!' do
18
+ # generated_journal = './test_output.journal'
19
+ # File.delete(generated_journal) if File.exist?(generated_journal)
20
+ #
21
+ # system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.csv -o ./test_output.journal --force > /dev/null 2>&1")
22
+ #
23
+ # ### CHANGE DATE!!!!!!!!!!!!!!!!
24
+ # csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-03`
25
+ # ### CHANGE DATE!!!!!!!!!!!!!!!!
26
+ #
27
+ # system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.yml -o ./test_output.journal --force > /dev/null 2>&1")
28
+ # yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
29
+ #
30
+ # expect(csv_output).not_to eq(yml_output)
31
+ # end
32
+ end
@@ -0,0 +1,110 @@
1
+ require_relative '../lib/hledger_forecast'
2
+
3
+ input = <<~CSV
4
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
5
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
6
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Mortgage,Expenses:Mortgage,2000,,,
7
+ monthly,,Assets:Bank,01/03/2023,,Bills,Expenses:Bills,175,,,
8
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
9
+ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
10
+ monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
11
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Rainy day fund,Assets:Savings,300,,,
12
+ monthly,,Assets:Pension,01/01/2024,,Pension draw down,Income:Pension,-500,,,
13
+ quarterly,,Assets:Bank,01/04/2023,,Quarterly bonus,Income:Bonus,-1000,,,
14
+ half-yearly,,Assets:Bank,01/04/2023,,Top up holiday funds,Expenses:Holiday,500,,,
15
+ yearly,,Assets:Bank,01/04/2023,,Annual bonus,Income:Bonus,-2000,,,
16
+ once,,Assets:Bank,05/03/2023,,Refund for that damn laptop,Expenses:Shopping,-3000,,TRUE,TRUE
17
+ custom,every 2 weeks,Assets:Bank,01/03/2023,,Hair and beauty,Expenses:Personal Care,80,26,,
18
+ settings,currency,USD,,,,,,,,
19
+ settings,show_symbol,TRUE,,,,,,,,
20
+ settings,thousands_separator,TRUE,,,,,,,,
21
+ CSV
22
+
23
+ output = <<~YAML
24
+ ---
25
+ monthly:
26
+ - account: Assets:Bank
27
+ from: '2023-03-01'
28
+ transactions:
29
+ - amount: -3500.0
30
+ category: Income:Salary
31
+ description: Salary
32
+ - amount: 2000.0
33
+ category: Expenses:Mortgage
34
+ description: Mortgage
35
+ to: '2025-01-01'
36
+ - amount: 175.0
37
+ category: Expenses:Bills
38
+ description: Bills
39
+ - amount: 500.0
40
+ category: Expenses:Food
41
+ description: Food
42
+ - amount: "=5000/24"
43
+ category: Expenses:House
44
+ description: New Kitchen
45
+ - amount: 125.0
46
+ category: Expenses:Holiday
47
+ description: Holiday
48
+ to: "=12"
49
+ - amount: 300.0
50
+ category: Assets:Savings
51
+ description: Rainy day fund
52
+ to: '2025-01-01'
53
+ - account: Assets:Pension
54
+ from: '2024-01-01'
55
+ transactions:
56
+ - amount: -500.0
57
+ category: Income:Pension
58
+ description: Pension draw down
59
+ quarterly:
60
+ - account: Assets:Bank
61
+ from: '2023-04-01'
62
+ transactions:
63
+ - amount: -1000.0
64
+ category: Income:Bonus
65
+ description: Quarterly bonus
66
+ half-yearly:
67
+ - account: Assets:Bank
68
+ from: '2023-04-01'
69
+ transactions:
70
+ - amount: 500.0
71
+ category: Expenses:Holiday
72
+ description: Top up holiday funds
73
+ yearly:
74
+ - account: Assets:Bank
75
+ from: '2023-04-01'
76
+ transactions:
77
+ - amount: -2000.0
78
+ category: Income:Bonus
79
+ description: Annual bonus
80
+ once:
81
+ - account: Assets:Bank
82
+ from: '2023-03-05'
83
+ transactions:
84
+ - amount: -3000.0
85
+ category: Expenses:Shopping
86
+ description: Refund for that damn laptop
87
+ summary_exclude: true
88
+ track: true
89
+ custom:
90
+ - account: Assets:Bank
91
+ from: '2023-03-01'
92
+ transactions:
93
+ - amount: 80.0
94
+ category: Expenses:Personal Care
95
+ description: Hair and beauty
96
+ frequency: every 2 weeks
97
+ roll-up: 26.0
98
+ settings:
99
+ currency: USD
100
+ show_symbol: true
101
+ thousands_separator: true
102
+ YAML
103
+
104
+ RSpec.describe 'CSV parser' do
105
+ it 'converts a CSV file to the YML output needed for the plugin' do
106
+ computed_yaml = HledgerForecast::CSVParser.parse(input)
107
+
108
+ expect(computed_yaml).to eq(output)
109
+ end
110
+ end
data/spec/custom_spec.rb CHANGED
@@ -2,20 +2,17 @@ require_relative '../lib/hledger_forecast'
2
2
 
3
3
  base_config = <<~YAML
4
4
  custom:
5
- - frequency: "every 2 weeks"
5
+ - account: "[Assets:Bank]"
6
6
  from: "2023-05-01"
7
- account: "[Assets:Bank]"
8
7
  transactions:
9
8
  - amount: 80
10
9
  category: "[Expenses:Personal Care]"
11
10
  description: Hair and beauty
12
- - frequency: "every 5 days"
13
- from: "2023-05-01"
14
- account: "[Assets:Checking]"
15
- transactions:
11
+ frequency: "every 2 weeks"
16
12
  - amount: 50
17
13
  category: "[Expenses:Groceries]"
18
14
  description: Gotta feed that stomach
15
+ frequency: "every 5 days"
19
16
 
20
17
  settings:
21
18
  currency: GBP
@@ -28,19 +25,19 @@ base_output = <<~JOURNAL
28
25
 
29
26
  ~ every 5 days from 2023-05-01 * Gotta feed that stomach
30
27
  [Expenses:Groceries] £50.00; Gotta feed that stomach
31
- [Assets:Checking]
28
+ [Assets:Bank]
32
29
 
33
30
  JOURNAL
34
31
 
35
32
  calculated_config = <<~YAML
36
33
  custom:
37
- - frequency: "every 2 weeks"
34
+ - account: "[Assets:Bank]"
38
35
  from: "2023-05-01"
39
- account: "[Assets:Bank]"
40
36
  transactions:
41
37
  - amount: 80
42
38
  category: "[Expenses:Personal Care]"
43
39
  description: Hair and beauty
40
+ frequency: "every 2 weeks"
44
41
  to: "=6"
45
42
 
46
43
  settings:
@@ -0,0 +1,5 @@
1
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
2
+ monthly,,Assets:Bank,01/03/2023,,Mortgage,Expenses:Mortgage,2000.55,,,
3
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,100,,,
4
+ monthly,,Assets:Savings,01/03/2023,,Savings,Assets:Bank,-1000,,,
5
+ settings,currency,GBP,,,,,,,,
@@ -20,22 +20,22 @@ config = <<~YAML
20
20
  description: Savings
21
21
 
22
22
  custom:
23
- - frequency: "every 2 weeks"
23
+ - account: "[Assets:Bank]"
24
24
  from: "2023-05-01"
25
- account: "[Assets:Bank]"
26
- roll-up: 26
27
25
  transactions:
28
26
  - amount: 80
29
27
  category: "[Expenses:Personal Care]"
30
28
  description: Hair and beauty
31
- - frequency: "every 5 days"
29
+ frequency: "every 2 weeks"
30
+ roll-up: 26
31
+ - account: "[Assets:Checking]"
32
32
  from: "2023-05-01"
33
- account: "[Assets:Checking]"
34
- roll-up: 73
35
33
  transactions:
36
34
  - amount: 50
37
35
  category: "[Expenses:Groceries]"
38
36
  description: Gotta feed that stomach
37
+ frequency: "every 5 days"
38
+ roll-up: 73
39
39
 
40
40
  settings:
41
41
  currency: GBP
@@ -57,7 +57,7 @@ RSpec.describe HledgerForecast::Summarizer do
57
57
 
58
58
  expect(output.first).to include(:account, :from, :to, :type, :frequency)
59
59
  expect(output.first[:amount]).to eq(2000.55)
60
- expect(output.last[:rolled_up_amount]).to eq(304) # ((50 * 73) / 12)
60
+ expect(output.last[:rolled_up_amount]).to eq((50.0 * 73.0) / 12.0) # ((50 * 73) / 12)
61
61
  expect(output.length).to eq(5)
62
62
  end
63
63
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hledger-forecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oli Morris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-29 00:00:00.000000000 Z
11
+ date: 2023-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -109,12 +109,14 @@ files:
109
109
  - LICENSE
110
110
  - README.md
111
111
  - bin/hledger-forecast
112
+ - example.csv
112
113
  - example.journal
113
114
  - example.yml
114
115
  - hledger-forecast.gemspec
115
116
  - lib/hledger_forecast.rb
116
117
  - lib/hledger_forecast/calculator.rb
117
118
  - lib/hledger_forecast/cli.rb
119
+ - lib/hledger_forecast/csv_parser.rb
118
120
  - lib/hledger_forecast/formatter.rb
119
121
  - lib/hledger_forecast/generator.rb
120
122
  - lib/hledger_forecast/settings.rb
@@ -126,6 +128,8 @@ files:
126
128
  - lib/hledger_forecast/version.rb
127
129
  - spec/command_spec.rb
128
130
  - spec/computed_amounts_spec.rb
131
+ - spec/csv_and_yml_comparison_spec.rb
132
+ - spec/csv_parser_spec.rb
129
133
  - spec/custom_spec.rb
130
134
  - spec/half-yearly_spec.rb
131
135
  - spec/modifier_spec.rb
@@ -134,6 +138,7 @@ files:
134
138
  - spec/monthly_spec.rb
135
139
  - spec/once_spec.rb
136
140
  - spec/quarterly_spec.rb
141
+ - spec/stubs/forecast.csv
137
142
  - spec/stubs/forecast.yml
138
143
  - spec/stubs/transactions_found.journal
139
144
  - spec/stubs/transactions_found_inverse.journal
@@ -160,13 +165,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
160
165
  - !ruby/object:Gem::Version
161
166
  version: '0'
162
167
  requirements: []
163
- rubygems_version: 3.2.3
168
+ rubygems_version: 3.4.13
164
169
  signing_key:
165
170
  specification_version: 4
166
171
  summary: An extended wrapper around hledger's forecasting functionality
167
172
  test_files:
168
173
  - spec/command_spec.rb
169
174
  - spec/computed_amounts_spec.rb
175
+ - spec/csv_and_yml_comparison_spec.rb
176
+ - spec/csv_parser_spec.rb
170
177
  - spec/custom_spec.rb
171
178
  - spec/half-yearly_spec.rb
172
179
  - spec/modifier_spec.rb
@@ -175,6 +182,7 @@ test_files:
175
182
  - spec/monthly_spec.rb
176
183
  - spec/once_spec.rb
177
184
  - spec/quarterly_spec.rb
185
+ - spec/stubs/forecast.csv
178
186
  - spec/stubs/forecast.yml
179
187
  - spec/stubs/transactions_found.journal
180
188
  - spec/stubs/transactions_found_inverse.journal