hledger-forecast 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/README.md +207 -196
- data/example.csv +16 -0
- data/example.journal +19 -23
- data/example.yml +8 -3
- data/lib/hledger_forecast/calculator.rb +1 -1
- data/lib/hledger_forecast/cli.rb +21 -4
- data/lib/hledger_forecast/csv_parser.rb +106 -0
- data/lib/hledger_forecast/generator.rb +2 -1
- data/lib/hledger_forecast/summarizer.rb +1 -1
- data/lib/hledger_forecast/transactions/default.rb +18 -1
- data/lib/hledger_forecast/transactions/trackers.rb +7 -0
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +2 -1
- data/spec/command_spec.rb +9 -1
- data/spec/csv_and_yml_comparison_spec.rb +32 -0
- data/spec/csv_parser_spec.rb +110 -0
- data/spec/custom_spec.rb +6 -9
- data/spec/stubs/forecast.csv +5 -0
- data/spec/summarizer_spec.rb +7 -7
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac4e0a16f2b440612fb4596519b02c02d4a88b0d5f9cdb95c0988047a9892c3e
|
4
|
+
data.tar.gz: 6f9aed952f65c445d965a9b3dd7ccdb25fdb2c300bdee748ffab51b6a5a5056e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40eb12f6238c4cc0f8fc37e85a74a6f4e88fbbd12917b07dfd2d305dd47f0cb09e1c999a343744d5766cb03c1484533e67ae6eb3e7ed6a7c3548f5f177eea55e
|
7
|
+
data.tar.gz: e09a771caf387bf62d9b09ca58ff59073589edeb0d9cbc3a9523246c31b7172b83869101fe6a020474d501a3b739dbe7a24ac6676a4baafb1380a5fe0c2a84b5
|
data/.github/workflows/ci.yml
CHANGED
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
|
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
|
-
|
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
|
-
- :
|
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 `
|
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
|
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.
|
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
|
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.
|
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
|
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:
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
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
|
-
|
152
|
+
From the example above, there are a few additional features that may be useful.
|
133
153
|
|
134
|
-
|
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
|
-
|
141
|
-
|
142
|
-
#### Custom period
|
156
|
+
> **Note**: Calculations will be determined up to two decimal places
|
143
157
|
|
144
|
-
|
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
|
-
```
|
147
|
-
|
148
|
-
|
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
|
-
|
165
|
+
Simply start the `amount` column with a `=` sign and use standard Excel based math formatting.
|
158
166
|
|
159
|
-
|
167
|
+
#### Calculated dates
|
160
168
|
|
161
|
-
|
169
|
+
It may also be helpful for `to` dates to be calculated:
|
162
170
|
|
163
|
-
|
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
|
-
|
176
|
+
### Settings
|
166
177
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
|
187
|
+
### An example YML forecast
|
188
|
+
|
189
|
+
> **Note**: The app uses `yml` in place of `yaml` by default
|
177
190
|
|
178
|
-
|
191
|
+
Taking the _CSV_ example above and applying it to a _YML_ file:
|
179
192
|
|
180
|
-
```
|
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
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
281
|
+
settings:
|
282
|
+
currency: USD
|
283
|
+
```
|
246
284
|
|
247
|
-
|
285
|
+
#### Modifiers
|
248
286
|
|
249
287
|
> **Note**: For modifiers to be included in your hledger reporting, use the `--auto` flag
|
250
288
|
|
251
|
-
|
289
|
+
Currently, a YML forecast allows a user to include forecasted % uplifts or downshifts:
|
252
290
|
|
253
|
-
```
|
291
|
+
```yml
|
254
292
|
monthly:
|
255
|
-
account: "Assets:Bank"
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
312
|
+
#### Additional YML features
|
272
313
|
|
273
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
325
|
+
or:
|
291
326
|
|
292
|
-
```
|
293
|
-
|
294
|
-
-
|
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:
|
300
|
-
category: "Expenses:
|
301
|
-
description:
|
332
|
+
- amount: 2000
|
333
|
+
category: "Expenses:Mortgage"
|
334
|
+
description: Mortgage
|
335
|
+
to: "2025-01-01"
|
302
336
|
```
|
303
337
|
|
304
|
-
|
338
|
+
### Tracking
|
305
339
|
|
306
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
351
|
+
Or:
|
322
352
|
|
323
|
-
```
|
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
|
-
|
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
|
-
|
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
|
-
|
368
|
+
hledger-forecast generate -t [journal_file_to_search] -f [path_to_yaml_file] -o [path_to_output_journal]
|
357
369
|
|
358
|
-
|
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
|
-
|
372
|
+
## :pencil2: Contributing
|
361
373
|
|
362
|
-
I
|
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
|
-
Expenses:Bills
|
4
|
-
Expenses:Food
|
5
|
-
Expenses:House
|
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
|
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
|
13
|
+
Expenses:Holiday $125.00 ; Holiday
|
14
14
|
Assets:Bank
|
15
15
|
|
16
|
-
~ monthly from 2023-03-01 to 2025-
|
17
|
-
Assets:Savings
|
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
|
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
|
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
|
29
|
+
Expenses:Holiday $500.00 ; Top up holiday funds
|
30
30
|
Assets:Bank
|
31
31
|
|
32
|
-
~ yearly from 2023-04-01 * Annual
|
33
|
-
Income: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
|
37
|
+
Expenses:Personal Care $80.00 ; Hair and beauty
|
38
38
|
Assets:Bank
|
39
39
|
|
40
|
-
~ 2023-
|
41
|
-
Expenses:
|
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
|
-
|
45
|
-
Expenses:
|
46
|
-
Assets:Bank
|
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
|
-
-
|
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
|
data/lib/hledger_forecast/cli.rb
CHANGED
@@ -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
|
72
|
+
"The path to the FORECAST csv/yml file to generate from") do |file|
|
73
73
|
options[:forecast_file] = file
|
74
|
-
|
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.
|
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
|
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 * (
|
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
|
-
|
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)
|
data/lib/hledger_forecast.rb
CHANGED
@@ -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
|
-
-
|
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
|
-
|
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:
|
28
|
+
[Assets:Bank]
|
32
29
|
|
33
30
|
JOURNAL
|
34
31
|
|
35
32
|
calculated_config = <<~YAML
|
36
33
|
custom:
|
37
|
-
-
|
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,,,,,,,,
|
data/spec/summarizer_spec.rb
CHANGED
@@ -20,22 +20,22 @@ config = <<~YAML
|
|
20
20
|
description: Savings
|
21
21
|
|
22
22
|
custom:
|
23
|
-
-
|
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
|
-
|
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(
|
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.
|
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-
|
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.
|
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
|